Skip to content

🔥更新:2024-12-09📝字数: 0 字⏱时长: 0 分钟

第一章:React 中的高阶组件(HOC)

1.1 概述

  • 维基百科对高阶函数的定义:高阶函数至少满足以下条件之一
    • ① 接收一个或多个函数作为输入。
    • ② 输出一个函数。
js
function foo(callback) {  
    if(callback){
        callback()
    }
    console.log('foo 函数')
}

foo(() => {
    console.log('我是另一个函数')
})

注意⚠️:

  • 高阶函数可以将函数作为数据进行操作和传递,从而实现更灵活的编程。
  • 高阶函数可以用来实现函数的组合柯里化延迟执行等功能。
  • JavaScript 中数组的 map()forEach()filter()reduce() 等都是高阶函数。

1.2 高阶组件的定义和使用(⭐)

  • 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。
  • HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
  • 具体而言,高阶组件是参数为组件,返回值为新组件的函数。
jsx
const EnhancedComponent = higherOrderComponent(WrappedComponent);

注意⚠️:

  • 高阶组件不是组件,而是函数。
  • 高阶组件通常在一些 React 第三方库中非常常见!!!
  • 项目结构:

image-20231225091919502

  • 示例:
jsx
import React, {PureComponent} from 'react'

// 定义一个高阶组件
function hoc(WrappedComponent) { 
  // ① 定义组件:类组件
  class HocComponent extends PureComponent {
    render() {
      return <WrappedComponent {...this.props} />
    }
  }
  // ① 定义组件:函数组件,略。
  
  
  // ② 给组件添加 displayName
  HocComponent.displayName = `Hoc(${WrappedComponent.displayName || WrappedComponent.name})`
  // ③ 返回组件
  return HocComponent
} 

class App extends PureComponent{
  
  state = {
    message: 'Hello React'
  }
  
  render() {
    return (
      <div>
        <h2>{this.state.message}</h2>
      </div>
    )
  }
}

// 使用高阶组件
export default hoc(App)

1.3 高阶组件的应用场景(⭐)

1.3.1 场景一

  • 应用场景一:给一些需要特殊数据的组件,注入 props 。

  • 需求:给 Home 组件、Profile 组件注入自定义的 props ,但是 About 组件不需要。

image-20231225100205181

  • 项目结构:

image-20231225100237173

  • 示例:
  • 其中,App.jsx
jsx
import React, {PureComponent} from 'react'
import Profile from "@/components/Profile"
import Home from "@/components/Home"
import About from "@/components/About"

class App extends PureComponent {
  
  state = {
    message: '我是 App 组件'
  }
  
  render() {
    const {message} = this.state;
    return (
      <div style={{backgroundColor: 'pink',padding: '10px',width: '500px'}}>
        <h2>{message}</h2>
        <Home name={"home"}/>
        <Profile name={"profile"}/>
        <About/>
      </div>
    )
  }
}

// 使用高阶组件
export default App
  • 其中,withConfig.js
js
import {PureComponent} from "react"

// 给一些需要特殊数据的组件,注入 props
export function withConfig(WrappedComponent, config) {
  class HocComponent extends PureComponent {
    render() {
      return <WrappedComponent {...this.props} {...config}/>
    }
  }
  
  HocComponent.displayName = `Hoc(${WrappedComponent.displayName || WrappedComponent.name})`
  
  return HocComponent
  
}
  • 其中,Home.jsx
jsx
import {useState} from "react";
import {withConfig} from "@/hoc/withConfig";

function Home(props) {
  const [state] = useState("我是 Home 组件");
  return (
    <div style={{backgroundColor: 'skyblue', padding: '5px'}}>
      <h2>{state}</h2>
      <div>
        <h3>名称:{props.name}</h3>
        <h3>年龄:{props.age}</h3>
      </div>
    </div>
  )
}


export default withConfig(Home, {age: 18})
  • 其中,Profile.jsx
jsx
import {useState} from "react";
import {withConfig} from "@/hoc/withConfig";

function Profile(props) {
  const [state] = useState("我是 Profile 组件");
  return (
    <div style={{backgroundColor: 'saddlebrown', padding: '5px'}}>
      <h2>{state}</h2>
      <div>
        <h3>名称:{props.name}</h3>
        <h3>年龄:{props.age}</h3>
      </div>
    </div>
  )
}

export default withConfig(Profile, { age: 18})
  • 其中,About.jsx
jsx
import {useState} from "react";

function About() {
  const [state] = useState("我是 About 组件");
  return (
    <div style={{backgroundColor: 'slateblue', padding: '5px'}}>
      <h2>{state}</h2>
    </div>
  )
}

export default About

1.3.2 场景二

  • 应用场景二:登录鉴权。

  • 需求:如果登录成功,则显示购物车等组件;否则,则提示需要登录。

注意⚠️:实际开发中,一般需要配合 localStorage 实现登录鉴权功能。

  • 项目结构:

image-20231225131020014

  • 示例:
  • 其中,App.jsx
jsx
import React, {PureComponent} from 'react'
import Cart from "@/components/Cart"

class App extends PureComponent {
  
  state = {
    message: '我是 App 组件',
    isLogin: false
  }
  
  btnLogin() {
    localStorage.setItem('token', 'token')
    this.setState({
      isLogin: true
    })
  }
  
  btnLogout() {
    localStorage.removeItem('token')
    this.setState({
      isLogin: false
    })
  }
  
  render() {
    const {isLogin} = this.state
    return (
      <div style={{backgroundColor: 'pink', padding: '10px', width: '500px'}}>
        <h2>{this.state.message}</h2>
        {!isLogin ? <button onClick={() => this.btnLogin()}>登录</button> :
          <button onClick={() => this.btnLogout()}>注销</button>}
        <Cart/>
      </div>
    )
  }
}

// 使用高阶组件
export default App
  • 其中,withLogin.js
js
import Login from "@/components/Login"

export function withLogin(WrappedComponent){
  const HocComponent = (props) => {
    // 从 localStorage 中获取 token 
    const token = localStorage.getItem("token")
    if(token){
      return <WrappedComponent {...props}/>
    }else{
      return <Login/>
    }
  }
  
  HocComponent.displayName = `Hoc(${WrappedComponent.displayName || WrappedComponent.name})`
  
  return HocComponent
  
}
  • 其中,Cart.jsx
jsx
import React, {Component} from 'react'
import {withLogin} from "@/hoc/withLogin"

class Cart extends Component {
  
  state = {
    message: '我是 Cart 组件',
  }
  
  render() {
    return (
      <div style={{backgroundColor: 'skyblue', padding: '10px'}}>
        <h2>购物车</h2>
      </div>
    )
  }
}

export default withLogin(Cart)
  • 其中,Login.jsx
jsx
import React, {Component} from 'react'

class Login extends Component {
  
  state = {
    message: '我是 Login 组件',
  }
  render() {
    return (
      <div style={{backgroundColor: 'saddlebrown', padding: '10px'}}>
        <h2>请登录</h2>
      </div>
    )
  }
}

export default Login

1.3.3 场景三

  • 应用场景三:计算 React 组件挂载阶段的渲染时间。

  • 需求:在控制台显示 React 组件挂载阶段的渲染时间。

  • 项目结构:

image-20231225133332145

  • 示例:
  • 其中,App.jsx
jsx
import React, {PureComponent} from 'react'
import Cart from "@/components/Cart"

class App extends PureComponent {
  
  state = {
    message: '我是 App 组件',
  }
  render() {
    return (
      <div style={{backgroundColor: 'pink', padding: '10px', width: '500px'}}>
        <h2>{this.state.message}</h2>
        <Cart/>
      </div>
    )
  }
}

// 使用高阶组件
export default App
  • 其中,withComputeTime.js
js
import {PureComponent} from "react"

export function withComputeTime(WrappedComponent) {
  
  class HocComponent extends PureComponent {
    
    constructor(props) {
      super(props)
      this.beingTime = new Date().getTime()
    }
    
    componentDidMount() {
      const componentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
      let renderTime = new Date().getTime() - this.beingTime
      console.log('渲染时间:', `${componentName} => ${renderTime} 毫秒`)
    }
    
    render() {
      return <WrappedComponent {...this.props} />
    }
  }
  
  HocComponent.displayName = `Hoc(${WrappedComponent.displayName || WrappedComponent.name})`
  
  return HocComponent
  
}
  • 其中,Cart.jsx
jsx
import React, {PureComponent} from 'react'
import {withComputeTime} from "@/hoc/withComputeTime"

class Cart extends PureComponent {
  
  state = {
    message: '我是 Cart 组件',
    list: [1, 2, 3, 4, 5, 6, 7],
  }
  
  render() {
    return (
      <div style={{backgroundColor: 'skyblue', padding: '10px'}}>
        <h2>购物车</h2>
        <ul>
          {
            this.state.list.map((item, index) => {
              return <li key={index}>{item}</li>
            })
          }
        </ul>
      </div>
    )
  }
}

export default withComputeTime(Cart)

1.4 高阶组件的实际应用 -- 主题切换(⭐)

1.4.1 基本实现

  • 需求:完成主题的切换。

  • 分析:

image-20231225103941974

  • 项目结构:

image-20231225105337139

  • 示例:
  • 其中,ThemeContext.js
js
import {createContext} from "react"
// 主题
export const themes = {
  light: { // 明亮 
    background: 'white',
  },
  dark: { // 暗黑
    background: 'gray',
  },
};
// 创建主题 Context 
const ThemeContext = createContext(themes.light)
ThemeContext.displayName = "ThemeContext"
// 导出主题 Context 
export default ThemeContext
  • 其中,App.jsx
jsx
import React, {PureComponent} from 'react'
import Home from "@/components/Home"
import ThemeContext, {themes} from "@/context/themeContext"

class App extends PureComponent {
  
  state = {
    message: '我是 App 组件',
    theme: themes.light
  }
  
  changeTheme() {
    this.setState(state => ({
      theme: state.theme === themes.dark ? themes.light : themes.dark,
    }));
  }
  
  render() {
    return (
      <div style={{backgroundColor: 'pink', padding: '10px', width: '500px'}}>
        <h2>{this.state.message}</h2>
        <button onClick={() => this.changeTheme()}>切换主题</button> 
        <ThemeContext.Provider value={this.state.theme}> 
          <Home/> 
        </ThemeContext.Provider> 
      </div>
    )
  }
}

// 使用高阶组件
export default App
  • 其中,Home.jsx
jsx
import {useState} from "react"
import ThemeContext from "@/context/themeContext"

function Home() {
  const [state] = useState("我是 Home 组件");
  return (
    <ThemeContext.Consumer> 
      { 
        value => { 
          return ( 
            <div style={{backgroundColor: value.background, padding: '5px'}}> 
              <h2>{state}</h2> 
            </div> 
          ) 
        } 
      } 
    </ThemeContext.Consumer> 
  
  )
}


export default Home

1.4.2 优化

  • 优化:上述的方案太繁琐了,难道每个子组件都需要这么写?可以使用 hoc 来进行优化:

  • 示例:

  • 其中,withTheme.js

js
import {PureComponent} from "react"
import ThemeContext from "@/context/themeContext"

export function withTheme(WrappedComponent) {
  class HocComponent extends PureComponent {
    render() {
      return (
        <ThemeContext.Consumer > 
          {value => <WrappedComponent {...this.props} {...value}/>} 
        </ThemeContext.Consumer> 
      )
    }
  }
  
  HocComponent.displayName = `Hoc(${WrappedComponent.displayName || WrappedComponent.name})`
  
  return HocComponent
  
}
  • 其中,Home.jsx
jsx
import {useState} from "react"
import {withTheme} from "@/hoc/withTheme"

function Home(props) {
  const [state] = useState("我是 Home 组件");
  return (
    <div style={{backgroundColor: props.background, padding: '5px'}}>
      <h2>{state}</h2>
    </div>
  
  )
}
export default withTheme(Home)

1.4.3 优化

  • 优化:不觉得 withTheme 的实现太繁琐了吗?为什么不使用函数组件来实现?

  • 示例:withTheme.js

js
import ThemeContext from "@/context/themeContext"

export function withTheme(WrappedComponent) {
  function HocComponent (props){
    return (
      <ThemeContext.Consumer >
        {value => <WrappedComponent {...props} {...value}/>}
      </ThemeContext.Consumer>
    )
  }
  
  HocComponent.displayName = `Hoc(${WrappedComponent.displayName || WrappedComponent.name})`
  
  return HocComponent
  
}

1.4.4 优化

  • 优化:既然都使用了函数组件,为什么不使用箭头函数来实现?

  • 示例:withTheme.js

js
import ThemeContext from "@/context/themeContext"

export function withTheme(WrappedComponent) {
  const HocComponent = (props) => {
    return (
      <ThemeContext.Consumer >
        {value => <WrappedComponent {...props} {...value}/>}
      </ThemeContext.Consumer>
    )
  }
  
  HocComponent.displayName = `Hoc(${WrappedComponent.displayName || WrappedComponent.name})`
  
  return HocComponent
  
}

1.5 高阶组件的意义(⭐)

  • 我们会发现利用高阶组件可以针对某些 React 代码进行更加优雅的处理;但是,HOC 也有自己的缺点:
    • ① HOC 需要在原组件上进行包裹或者嵌套,如果大量的使用 HOC,将会产生非常多的嵌套,会让调试变得非常困难。
    • ② HOC 可以劫持 props在不遵守决定的情况下可能会造成冲突

注意⚠️:之前学习的 React.memo()React.forwardRef() 就是高阶组件。

  • Hooks 的出现,是开创性的,解决了很多 React 之前存在的问题,如:this 指向问题,hoc 的嵌套复杂度问题,后续讲解!!!

第二章:Portal(⭐)

2.1 概述

  • Portal 在英文中的含义是 传送门 的意思。
  • 在 React 中就是提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀方法:
jsx
ReactDOM.createPortal(child, container)

注意⚠️:其实,和 Vue3.x 中的 Teleport 内置组件没什么区别。Teleport 的英文含义是心灵传输的意思。

  • 简单理解:常规的组件渲染都是在 public 目录下 index.html 文件中的<div id="root"></div>节点下面;但是,通过 Portal 我们可以将组件渲染到任何节点,如:body 节点等。

2.2 案例

  • 需求:动态显示模态框

  • 项目结构:

image-20231225150943335

  • 示例:
  • 其中,App.jsx
jsx
import React, {PureComponent} from 'react'
import Modal from "@/components/Modal"
import '@/App.css'

class App extends PureComponent {
  
  state = {
    message: '我是 App 组件',
    isShowModal: false,
  }
  
  showModal() {
    this.setState({
      isShowModal: true,
    })
  }
  
  closeModal() {
    this.setState({
      isShowModal: false,
    })
  }
  
  render() {
    const {isShowModal} = this.state
    return (
      <div >
        <h2>{this.state.message}</h2>
        <button onClick={() => this.showModal()}>点我,显示模态框</button>
        <div className={`${isShowModal ? 'modal-open' : ''}`}>
          {
            isShowModal && (<Modal>
              <div>
                <div>
                  <label htmlFor="username">
                    用户名:<input type="text" id="username" defaultValue={""}/>
                  </label>
                </div>
                <div>
                  <label htmlFor="password">
                    密码:<input type="password" id="password" defaultValue={""}/>
                  </label>
                </div>
              </div>
              <div>
                <button onClick={() => this.closeModal()}>关闭模态框</button>
              </div>
            </Modal>)
          }
        </div>
      </div>
    
    )
  }
}

export default App
  • 其中,App.css
css
body {
    margin: 0;
    padding: 0;
}

.modal {
    position: fixed;
    top: 50%;
    left: 50%;
    width: 300px;
    height: 200px;
    transform: translate(-50%, -50%);
    background-color: white;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    z-index: 9999;
}

.modal-open::before {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.6);
    z-index: 9998;
}
  • 其中,Modal.jsx
jsx
import {PureComponent} from "react"
import {createPortal} from "react-dom";

class Modal extends PureComponent {
  render() {
    return (
      createPortal(
        (<div className="modal">
          {this.props.children}
        </div>)
        , document.querySelector('body'))
    )
  }
}

export default Modal

第三章:Fragment(⭐)

3.1 概述

  • 之前,我们是这样定义的:
jsx
render() {
  return (
    <div>
      <ChildA />
      <ChildB />
      <ChildC />
    </div> 
  );
}
  • 但是,有的时候,我们不希望多定义这样无用的 div 元素,就可以使用 Fragment 了;因为,Fragment 允许我们将子列表分组,而无需将 DOM 中添加额外节点。
jsx
render() {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment> 
  );
}
  • React 还提供了 Fragment 的短语法:
jsx
render() {
  return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </> 
  );
}

但是,如果我们需要在 Fragment 中添加 key ,就不能使用短语法了。

3.2 案例

  • 需求:遍历数组,渲染到页面上。

image-20231225151817519

  • 项目结构:

image-20231225151838933

  • 示例:
jsx
import React from 'react'

class App extends React.PureComponent {
  
  state = {
    message: 'Hello React',
    movies: ['星际穿越', '大话西游', '梦幻西游', '诛仙'],
  }
  
  render() {
    return (
      <> 
        <h2>电影列表</h2>
        <ul>
          {
            this.state.movies.map((movie,index) => (
              <li key={index}>{movie}</li>
            ))
          }
        </ul>
      </> 
    )
  }
}

export default App

第四章:StrictMode

4.1 概述

  • 我们在使用 React 官方提供的脚手架的时候,会显示如下的代码:
js
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from '@/App'

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
  <React.StrictMode>
      <App/>
  </React.StrictMode>
)
  • StrictMode 表示严格模式,并且 StrictMode 是一个用来突出显示应用程序中潜在问题的工具。
  • StrictModeFragment 一样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。

注意⚠️:严格模式检查仅在开发模式下运行;它们不会影响生产构建!!!

4.2 严格模式检查什么?

  • 严格模式,有助于检测:

    • ① 识别不安全的生命周期。
    • ② 关于使用过时字符串 ref API 的警告。
    • ③ 关于使用废弃的 findDOMNode 方法的警告。
    • ④ 检测意外的副作用:
      • 组件的 constructor 会被调用两次;
      • 这是严格模式下故意进行的操作,让我们来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用;
      • 在生产环境中,是不会被调用两次的;
    • ⑤ 检测过时的context API 。
    • ⑥ 确保可复用的 state 。
  • 当然,未来的 React 版本将添加更多额外功能。

Released under the MIT License.