likes
comments
collection
share

react hooks设计初衷

作者站长头像
站长
· 阅读数 21

从刚学习react到现在有半年时间,对react的hooks写法和class写法都有接触过,基于此想写一篇文章对hooks的设计初衷作简要介绍和总结。文章分为三部分:

  • hooks设计初衷
  • 与class比较
  • ahooks库介绍

一、hooks设计初衷

官方文档里面有说明,主要有三个动机

1、在组件之间复用状态逻辑很难

2、复杂组件变得难以理解

3、难以理解的class

  第二条动机可以看作第一个问题的结果,由于逻辑复用很难,导致复杂组件的逻辑很混乱,难以理解。所以可以简化为两条动机,进行一一说明

· 逻辑复用

    传统class组件逻辑复用有两种方法,高阶组件、render props、mixins(由于mixins会带来命名冲突,隐式依赖等问题,不建议使用)。从一个table请求的例子来讲。 react hooks设计初衷     实现一个table组件,分为两部分,一部分是逻辑,另一部分是视图(render函数)。逻辑部分大致分为以下几个关注点

1、异步加载的 loading 2、page 3、pageSize 4、筛选条件切换的时候,要重置 page 到第一页。page/pageSize 变化要重新发起请求 5、组件卸载的时候,要清掉正在进行的网络请求。

  如果只复用table组件的渲染函数(即table是纯函数组件,没有生命周期),则每次都要在引用table的class组件里面重新写生命周期函数,逻辑处理部分。如果把生命周期部分放到table组件里面,当想在通用逻辑之外加上自己的逻辑,就会很难实现。此时table就是一个完整的组件,可以通过render props等方法向table组件注入额外的内容,这样会增加复杂度,hooks就是一种更好的逻辑复用方式。 

const { tableProps, search, params } = useAntdTable(getTableData, {    
    defaultPageSize: 5,
    form,  
    });  
//自己的逻辑//  
<Table columns={columns}  {...tableProps} />

   hooks可以很好的解决这个问题,自己封装的hooks就像是一个参数,传给组件就可以,也可以复用class的生命周期写法。如果使用hooks来模拟继承,就会更加明白。

  但是hooks也不能完全替代高阶组件,render props,需要结合具体情况来判断。

· 难以理解的class

  class难以理解,是因为this比较复杂。传统的this问题就不多说,Dan Abramov写的函数式组件和类组件之间的差异就是最核心的问题。

现在有如下两个组件:

class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user);
  };

  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}
function ProfilePage(props) {
  const showMessage = () => {
    alert('Followed ' + props.user);
  };

  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };

  return (
    <button onClick={handleClick}>Follow</button>
  );
}

  以上两个组件功能相同,延迟3s后打印所关注的user。一个是类组件,一个是函数式组件。如果在3s内props有变化,类组件的user会变化,函数式组件不会变化。这里和新旧数据无关,而是组件不应该混淆实际上真正的user,在某些情况下会遇到这样的问题。关注了A之后不应该随意变为B。如果对类组件进行改写就能得到和函数式组件一样的结果。

class ProfilePage extends React.Component {
  render() {
    const props = this.props;

    const showMessage = () => {
      alert('Followed ' + props.user);
    };

    const handleClick = () => {
      setTimeout(showMessage, 3000);
    };

    return <button onClick={handleClick}>Follow</button>;
  }
}

  这样类组件的结果就和函数式组件一样。如果进一步观察,把这个render函数提取出去正是上文的函数式组件,所以hooks一直隐藏在其中。

比较这两种组件可以得出结论

1、函数式组件捕获了渲染所使用的值

2、this永远会改变

  捕获渲染所使用的值就是利用了闭包。闭包就是能够读取其他函数内部变量的函数。常见的节流,防抖原理就是闭包,闭包也能作为私有属性使用。

二、与class组件的比较

 这里不列举开发上面的具体差异,这些网上也有很多资料,主要列举基础的异同和开发思路比较。这里主要参考官方文档。

1、class里面的生命周期方法目前还没有全部对应到hooks上面,getSnapshotBeforeUpdate,componentDidCatch 以及 getDerivedStateFromError会尽快添加对应的hook。

2、组件内部的任何函数,包括事件处理函数和 effect,都是从它被创建的那次渲染中被「看到」的。这里其实就是闭包,开发里经常会遇到的问题就是过时闭包。

3、类组件面向对象的编程思想,能够获得最大程度的操作权限,对象的所有属性都可以掌控。可以方便进行侵入性操作。

4、函数式组件类似面向过程编程,处理模块为一个个函数,由于hooks的存在使得函数可以拥有副作用,具有面向对象的一些特征。此外函数式可以无缝转换为逻辑流。很自然就联想到流程控制。

5、逻辑复用,hooks和mixin。mixin可以看作一个类,混入的组件都有这个类,可以对这个mixin重写,相当于继承。hooks纯粹的函数逻辑,看作一个参数。

三、ahooks介绍

ahooks是一个开源的hooks库,内置了许多常用的hooks,这里选取几个看一下设计思路。

function useCreation(factory, deps) {
  var current = react_1.useRef({
    deps: deps,
    obj: undefined,
    initialized: false
  }).current;

  if (current.initialized === false || !depsAreSame(current.deps, deps)) {
    current.deps = deps;
    current.obj = factory();
    current.initialized = true;
  }

  return current.obj;
}

防止重复创建实例,使用useRef作为标志储存,类似单例。

var useDeepCompareEffect = function useDeepCompareEffect(effect, deps) {
  var ref = react_1.useRef();
  var signalRef = react_1.useRef(0);

  if (!depsEqual(deps, ref.current)) {
    ref.current = deps;
    signalRef.current += 1;
  }

  react_1.useEffect(effect, [signalRef.current]);
};

深比较useEffect依赖项,3.0版本新增hooks,比较实用。使用useRef储存是否更新

var useSetState = function useSetState(initialState) {
  var _a = __read(react_1.useState(initialState), 2),
      state = _a[0],
      setState = _a[1];

  var setMergeState = react_1.useCallback(function (patch) {
    setState(function (prevState) {
      var newState = utils_1.isFunction(patch) ? patch(prevState) : patch;
      return newState ? __assign(__assign({}, prevState), newState) : prevState;
    });
  }, []);
  return [state, setMergeState];
};

自动合并state的hooks,setState是默认不合并的。这里也需要注意要合理使用setState,把所有状态合在一起有时不利于状态抽离,封装hooks。

function useMemoizedFn(fn) {
  ......
  var fnRef = react_1.useRef(fn);

  fnRef.current = react_1.useMemo(function () {
    return fn;
  }, [fn]);
  var memoizedFn = react_1.useRef();

  if (!memoizedFn.current) {
    memoizedFn.current = function () {
      var args = [];

      for (var _i = 0; _i < arguments.length; _i++) {
        args[_i] = arguments[_i];
      }

      return fnRef.current.apply(this, args);
    };
  }

  return memoizedFn.current;
}

代替useCallBack,避免useCallBack重新渲染,进行性能优化。使用ref.current避免引起更新通知

四、总结

  以上是hooks的基本介绍,如何使用参考官网完全可以入门。关于hooks设计的深入理解可以查看Dan Abramov的相关博客,里面会深入分析hooks的理念。而ahooks的本质不是魔法,没有高深的内容,却设计非常巧妙,值得借鉴思想。在之后的思考中会关注hooks与class的更新渲染方式。

  关于hooks原理和react源码的关系详见后面的文章 :)

参考链接:

react.docschina.org/

zhuanlan.zhihu.com/p/104126843

ahooks.gitee.io/zh-CN/hooks…

overreacted.io/zh-hans/a-c…

react.docschina.org/blog/2016/0…