FE

React 组件和逻辑复用

HOC、render props、 hooks

Posted by nolan on July 2, 2019
  • 高阶组件 HOC

    • 优点
      • 外层组件通过 props 影响内部组件状态,而不是通过改变其 state 存在冲突和干扰,降低耦合度;
    • 缺点
      • 无法从外部访问子组件的 state,因此无法通过 shouldComponentUpdate 过滤不必要的更新(后来通过 React.PureComponent 解决);
      • ref 传递问题:ref 被隔断(可通过 React.forwardRef 解决);
      • 命名冲突: 嵌套过深的组件之间的 prop 容易产生命名冲突;
    • 示例代码

      function higherOrderComponent(WrappedComponent) {
        class CommonComponent extends React.Component {
        /* ... */
            render(){
                return <WrappedComponent />
            }
        }
      
        return CommonComponent;
      }
      
      const EnhancedComponent = higherOrderComponent(WrappedComponent);
      
      
  • 渲染属性 render props

    • 优点
      • 解决了 HOC 上面的缺点;
    • 缺点
      • 使用繁琐: HOC 使用可借助装饰器语法通常一行代码进行复用,render props 必须通过函数;
      • 容易造成函数回调嵌套;
    • 示例代码
    class A extends React.Component{
    
        return (
            <>
                {this.props.render(this.state.C)}
            </>
        )
    }
    
    class B extends React.component{
    
        return <A
            render={(para)=>{
                return <div>{para}</div>
            }}
        />
    }
    
    
  • react-hooks

    • 优点
      • 简洁:解决了 HOC 和 render props 的嵌套问题
      • 解耦:UI 和状态分离
      • 组合: hooks 中可以引另外的 hooks
      • 函数友好:函数组件解决了类组件的几大问题
        • this 指向容易错误
        • 分布在不同生命周期中的逻辑较分散
        • 高阶组件易使代码量剧增
    • 缺点
      • 学习成本较高
      • 写法限制:不能出现在条件、循环中
      • 破坏了 PureComponent、React.memo 浅比较的性能优化效果(为了获取最新的 prop 和 state,每次 render 都要重新创建事件处理函数)
      • 内部黑盒,对外部不可见
      • React.memo 不能完全代替 shouldComponentUpdate(因为拿不到 state change,只针对 props change)
    • 示例代码

      
          function useHttp(url,dependecies){
              let [loaded,setLoaded] = useState(false);
              let [data,setData] = useState({})
      
              useEffect(()=>{
                  _fetch(url)
              },dependecies)
      
              const _fetch = async (url) => {
                  let res = await api.fetch(url);
      
                  setLoaded(true);
                  setData(res.data)
              }
      
      
              return [loaded, data]
          }
      
          function A(){
              let [loaded, data] = useHttp('api.com/demo',selectedTarget)
      
              return <>{data}</>
          }