课程目标

了解高阶组件的用法,封装以及Hooks详解以及异步组件;

知识要点

高阶组件用法及封装

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

简单点说,就是组件作为参数,返回值也是组件的函数,它是纯函数,不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。

属性代理

使用组合的方式,将组件包装在容器上,依赖父子组件的生命周期关系来;

  1. 返回stateless的函数组件

  2. 返回class组件

  • 操作props

    // 可以通过属性代理,拦截父组件传递过来的porps并进行处理。
    // 返回一个无状态的函数组件
    function HOC(WrappedComponent) {
      const newProps = { type: 'HOC' };
      return props => ;
    }
    // 返回一个有状态的 class 组件
    function HOC(WrappedComponent) {
      return class extends React.Component {
        render() {
          const newProps = { type: 'HOC' };
          return ;
        }
      };
    }
  • 抽象state

    // 通过属性代理无法直接操作原组件的state,可以通过props和cb抽象state
    function HOC(WrappedComponent) {
      return class extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            name: '',
          };
          this.onChange = this.onChange.bind(this);
        }
        onChange = (event) => {
          this.setState({
            name: event.target.value,
          })
        }
        render() {
          const newProps = {
            name: {
              value: this.state.name,
              onChange: this.onChange,
            },
          };
          return ;
        }
      };
    }
    // 使用
    @HOC
    class Example extends Component {
      render() {
        return ;
      }
    }
反向继承

使用一个函数接受一个组件作为参数传入,并返回一个继承了该传入组件的类组件,且在返回组件的 render() 方法中返回 super.render() 方法

const HOC = (WrappedComponent) => {
  return class extends WrappedComponent {
    render() {
      return super.render();
    }
  }
}
  1. 允许HOC通过this访问到原组件,可以直接读取和操作原组件的state/ref等;

  2. 可以通过super.render()获取传入组件的render,可以有选择的渲染劫持;

  3. 劫持原组件生命周期方法

    function HOC(WrappedComponent){
      const didMount = WrappedComponent.prototype.componentDidMount;
      // 继承了传入组件
      return class HOC extends WrappedComponent {
        async componentDidMount(){
          // 劫持 WrappedComponent 组件的生命周期
          if (didMount) {
            await didMount.apply(this);
          }
          ...
        }
        render(){
          //使用 super 调用传入组件的 render 方法
          return super.render();
        }
      }
    }

    读取/操作原组件的state属性代理和反向继承对比

    function HOC(WrappedComponent){
      const didMount = WrappedComponent.prototype.componentDidMount;
      // 继承了传入组件
      return class HOC extends WrappedComponent {
        async componentDidMount(){
          if (didMount) {
            await didMount.apply(this);
          }
          // 将 state 中的 number 值修改成 2
          this.setState({ number: 2 });
        }
        render(){
          //使用 super 调用传入组件的 render 方法
          return super.render();
        }
      }
    }

    属性代理和反向继承对比
    1. 属性代理:从“组合”角度出发,有利于从外部操作wrappedComp,可以操作props,或者在wrappedComp 外加一些拦截器(如条件渲染等);

    2. 反向继承:从“继承”角度出发,从内部操作wrappedComp,可以操作组件内部的state,生命周期和render等,功能能加强大;

    Hooks详解

    Hooks是react16.8以后新增的钩子API;

    目的:增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。

    为什么要使用Hooks?

    1. 开发友好,可扩展性强,抽离公共的方法或组件,Hook 使你在无需修改组件结构的情况下复用状态逻辑;

    2. 函数式编程,将组件中相互关联的部分根据业务逻辑拆分成更小的函数;

    3. class更多作为语法糖,没有稳定的提案,且在开发过程中会出现不必要的优化点,Hooks无需学习复杂的函数式或响应式编程技术;

    useState
    const [number, setNumber] = useState(0);
      1. setState支持stateless组件有自己的state;

      2. 入参:具体值或一个函数;

      3. 返回值:数组,第一项是state值,第二项负责派发数据更新,组件渲染;

      useEffect
      1. 使用条件:当组件init、dom render完成、操纵dom、请求数据(如componentDidMount)等;

      2. 不限制条件,组件每次更新都会触发useEffect --> componentDidUpdate 与 componentwillreceiveprops;

      3. useEffect 第一个参数为处理事件,第二个参数接收数组,为限定条件,当数组变化时触发事件,为[]只在组件初始化时触发;

      4. useEffect第一个参数有返回时,一般用来消除副作用(如去除定时器、事件绑定等);

        * 模拟数据交互 /
        function getUserInfo(a)
          return new Promise((resolve)=>{
            setTimeout(()=>{
               resolve({
                   name:a,
                   age:16,
               })
            },500)
          })
        }
        const Demo = ({ a }) => {
          const [ userMessage , setUserMessage ] = useState({})
          const [number, setNumber] = useState(0)
          const div= useRef()
          const handleResize =()=>{}
          useEffect(()=>{
             getUserInfo(a).then(res=>{
                 setUserMessage(res)
             })
             console.log(div.current) / div /
              window.addEventListener('resize', handleResize)
          /
             只有当props->a和state->number改变的时候 ,useEffect副作用函数重新执行 ,
             如果此时数组为空[],证明函数只有在初始化的时候执行一次相当于componentDidMount
          /
          },[ a ,number ])
          return (
        { userMessage.name } { userMessage.age }
        setNumber(1) } >{ number }
        ) } ———————————————————————————————————————————————— const Demo = ({ a }) => { const handleResize =()=>{} useEffect(()=>{ const timer = setInterval(()=>console.log(666),1000) window.addEventListener('resize', handleResize) / 此函数用于清除副作用 */ return function(){ clearInterval(timer) window.removeEventListener('resize', handleResize) } },[ a ]) return (
        ) }

      useLayoutEffect

      渲染更新之前的 useEffect

      useEffect: 组件更新挂载完成 -> 浏览器dom 绘制完成 -> 执行useEffect回调 ;

      useLayoutEffect : 组件更新挂载完成 -> 执行useLayoutEffect回调-> 浏览器dom 绘制完成;

      const DemoUseLayoutEffect = () => {
        const target = useRef()
        useLayoutEffect(() => {
            /*我们需要在dom绘制之前,移动dom到制定位置*/
            const { x ,y } = getPositon() /* 获取要移动的 x,y坐标 */
            animate(target.current,{ x,y })
        }, []);
        return (
          
      ) }
      useRef

      用来获取元素、缓存数据;

      入参可以作为初始值

      // 获取元素
      const DemoUseRef = ()=>{
        const dom= useRef(null)
        const handerSubmit = ()=>{
          /*  
      表单组件
      dom 节点 */ console.log(dom.current) } return
      表单组件
      } // 缓存数据,小技巧 // 不同于useState,useRef改变值不会使comp re-render const currenRef = useRef(InitialData) currenRef.current = newValue

        异步组件

        React16.6中,引入了 React.lazy 和 React.Suspense 两个API,再配合动态 import() 语法就可以实现组件代码打包分割和异步加载。

        传统模式:渲染组件-> 请求数据 -> 再渲染组件

        异步模式:请求数据-> 渲染组件;

        // demo
        import React, { lazy, Suspense } from 'react';
        // lazy 和 Suspense 配套使用,react原生支持代码分割
        const About = lazy(() => import(/* webpackChunkName: "about" */'./About'));
        class App extends React.Component {
          render() {
            return (
              

        App

        loading
        }>
        ); } } export default App;

        React V 16中引入,部分UI的JS错误不会导致整个应用崩溃;

        错误边界是一种 React 组件,错误边界在 渲染期间、生命周期方法和整个组件树的构造函数 中捕获错误,且会渲染出备用UI而不是崩溃的组件。

        // comp ErrorBoundary
        import React from 'react'
        class ErrorBoundary extends React.Component {
          constructor(props) {
            super(props);
            this.state = { hasError: false };
          }
          static getDerivedStateFromError(error) {
            // 更新 state 使下一次渲染能够显示降级后的 UI
            return { hasError: true };
          }
          componentDidCatch(error, errorInfo) {
            // 你同样可以将错误日志上报给服务器
            console.log(error, errorInfo)
          }
          render() {
            if (this.state.hasError) {
                // 你可以自定义降级后的 UI 并渲染
                return 

        Something went wrong.

        ; } return this.props.children; } } export default ErrorBoundary // comp App import React, from 'react'; import ErrorBoundary from './ErrorBoundary' class App extends React.Component { state = { count: 1 } render() { const { count } = this.state if (count === 3) { throw new Error('I crashed!'); } return (

        App

        {count}

        ) } } export default App;