React Hooks 梳理

自 React 16.8 发布之后,在已有项目中,把 package.json 中的 react 和 react-dom 版本一升,就能够抄起 Hooks 开干了。笔者目前已经在项目中开始了实操,但不妨先总结下官方文档中一些值得梳理的点。javascript

useState

为何 useState 不叫 createState 呢?

  • 初始渲染时,useState 返回的是 initState
  • 下次渲染时,useState 返回的是 curState

也就是说,create 的叫法就不太符合初始渲染以后获取到的是「当前状态」这么一个事实了。html

为何 useState 不经过 this 也知道本身是哪一个 Component 的状态?

每一个组件内部都有一个「内存格子」的列表,他们就是一些存放数据的 JS 对象,当咱们使用如 useState 的 Hooks 时,就会去读取当前的格子(或者在初始渲染的时候进行初始化),而后将指针移动到下一个 Hooks。这就是为何一个组件内部的多个 useState 都能获取到各自的局部状态。java

可是须要注意的是,这也是为何官方建议咱们要将 hooks 的调用顺序保持一致react

useEffect

和过去的生命周期有什么区别?

其一,React 会在每次渲染完成后会调用 useEffect,若是使用传统的生命周期钩子的话,当咱们但愿每次 render 后执行某种反作用时,咱们不得不在 componentDidMount 和 componentDidUpdate 里都塞上相同的逻辑,带来冗余。所以,传统的生命周期是不能代替 useEffect 的。这一点可参考 React Class 生命周期json

固然,相比较考虑 mount 和 update,只考虑 render 是要简单清晰很多。dom

其二,Hooks 让咱们能够基于逻辑而拆分代码,而不是基于生命周期。这一点很是重要,由于基于生命周期来拆分代码,势必让逻辑相关联的代码分散各处。使用 Hooks,咱们就能够按照咱们指定的顺序使用每个反作用。ide

传入的函数每次 render 都是新的?

是的,这是为了保证在 useEffect 中使用到的内部状态都是最新的。这样 useEffect 就很像是 render 的一部分了 —— 每次使用的 useEffect 都属于其对应的的 render。函数

不只如此,咱们在 useEffect 中 return 的方法,也即一般用来作取消订阅这类 cleanup 工做的,每次 render 后也都会执行一次新的反作用(准确的说会先走 return 的方法,再从新走一次 useEffect 中的方法),而毫不是 unmount 的时候才执行一次。这种模式会有更少的 bug。oop

什么样的 bug 呢?能够看官方文档的例子,大体就是说,若是咱们订阅的人的 id 变了,就须要取消订阅而后从新订阅新的人。这样一来,若是在使用 class 作订阅这类处理时,就须要在 3 个生命周期(componentDidMount、componentDidUpdate、componentWillUnmount)里散布逻辑,即在 componentDidUpdate 补充上取消并从新订阅的逻辑!优化

若是用了 useEffect,这些东西根本不须要去考虑。整个过程如文档中给的例子同样依次执行:

function FriendStatus(props) {
  // ...
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}

// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // Run first effect

// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // Run next effect

// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // Run next effect

// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect

useEffect 第二个参数的优化做用

对 return 的 cleanup 一样适用,不要忘了,每次 render 完就会先执行一次 cleanup,最终 unmount 的时候也会执行一次 cleanup。

useEffect(() => {
  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // 只会在 props.friend.id 变化的时候从新订阅

若是咱们不提供该参数,每次更新都会从新执行;若是只想 mount 和 unmount 的时候各执行一次,可指定 [],但这不是好的实践方式,考虑到 useEffect 都是在 render 完后执行的,多作点工做可能会少点问题。

Hooks 使用原则

Only Call Hooks at the Top Level. Don’t call Hooks inside loops, conditions, or nested functions.

这一条的缘由是,Hooks 是经过调用顺序分配存放位置的,只有每次 run 的时候顺序保持一致,才能挨个取得正确的 useState、useEffect。比方说,若是咱们把 Hooks 放到条件语句里,而后第一次 render 的时候每一个都执行,第二次 render 却有一个 Hook 不执行,那么后面的对应就出错了。很好理解吧。

但若是咱们必定要有条件的执行 useEffect 呢?咱们能够在 useEffect 内部加条件

useEffect(function persistForm() {
    // 👍 这样就不会破坏第一条原则
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });

Only Call Hooks from React Functions.

这条没什么说的,总之只在下面两处用 Hooks:

  • ✅ Call Hooks from React function components.
  • ✅ Call Hooks from custom Hooks.
相关文章
相关标签/搜索