React Hook解析

本博客 hjy-xh,转载请申明出处

问题列表

带着以下问题学习:

  • Hook解决了什么问题?
  • Hook有哪些优势?
  • 为什么有Hook?
  • useState方括号有什么用?
  • 为什么每次更新的时候都要运行 Effect?
  • useMemo 和 shouldComponentUpdate 有什么区别?

Hook概述

  • React 16.8的新增特性

  • 它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性(是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数)

Hook的动机

  • 在无需修改组件结构的情况下复用状态逻辑

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

Hook出现之前,将可复用性行为”附加“到组件的解决方案有render props高阶组件,但是这类方案需要重新组织组件结构,可能会很麻烦,进而让代码难以理解。

  • Hook将组件中相互关联的部分拆分成更小的函数(比如监听事件、请求数据),而并非强制按照生命周期划分

组件期初很简单,但是逐渐会被状态逻辑和副作用充斥,相关关联且需要对照修改的代码被拆分(监听事件),不相关的代码在同一个方法中(componentDidMountcomponentWillUnmount)组合在一起,容易产生bug。

  • 降低学习门槛

对class的学习(需要理解JS中的this工作方式)

Hook使用规则

  • 只能在函数最外层调用Hook。不要在循环、条件判断或者子函数中调用

    这样能够确保Hook在每一次渲染中都按照同样的顺序被调用

  • 只能在React的函数组件中调用Hook(包括自定义的Hook)

常见的Hook

  • 基础 Hook

    • useState
    • useEffect
      • 说明
          - 可以把该Hook看做是`componentDidMount`、`componentDidUpdate`、`componentWillUnmout`三个函数的组合
          - React保证了每次运行effect的同时,DOM都已经更新完毕
          - 与`componentDidMount`或`componentDidUpdate`不同,使用useEffect调度的effect不会阻塞浏览器更新屏幕,这让应用看起来响应更快
          - effect中可选的清除机制在组件卸载的时候触发
        
      • 使用技巧
          - 使用多个 Effect 实现关注点分离(按照代码的用途分离它们),React将按照effect声明的顺序一次调用组建的每一个effect
          - 跳过 Effect 进行性能优化(第二个参数)
        
    • useContext
      接收一个 context 对象(`React.createContext` 的返回值)并返回该 context 的当前值
      
  • 额外的 Hook

    • useReducer
      useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
    • useCallback
      useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
    • useMemo
      可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证
      可以使用它缓存一些相对耗时的计算,也非常适合用于存储引用类型的数据,可以传入对象字面量,匿名函数等,甚至是 React Element
    • useRef
      useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)
    • useImperativeHandle
      • useImperativeHandle 可以在使用 ref 时自定义暴露给父组件的实例值
      • 在大多数情况下,应当避免使用 ref 这样的命令式代码
      • useImperativeHandle 应当与 forwardRef 一起使用
    • useLayoutEffect
      其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染
    • useDebugValue
      useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签,它接受一个格式化函数作为可选的第二个参数。该函数只有在 Hook 被检查时才会被调用。它接受 debug 值作为参数,并且会返回一个格式化的显示值

自定义Hook

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中

字应该始终以 use 开头,这样可以一眼看出其符合 Hook 的规则

自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性

原理

  • 函数组件执行函数renderWithHooks

    • 作用
      它是调用function组件函数的主要函数,从源码中看,它首先会置空即将调和渲染的workInProgress树memoizedStateupdateQueue,把新的hooks信息挂载到这两个属性上,然后在组件commit阶段,将workInProgress树替换成current树,替换真实的DOM元素节点。并在current树保存hooks信息。
    • 步骤
      • 执行函数组件
      • 改变ReactCurrentDispatcher对象
  • 初始化hooks

    相关hook实际执行的函数:

    1
    2
    3
    4
    5
    6
    7
    useState: mountState, // 初始化useState
    useEffect: mountEffect, // 初始化useEffect
    useLayoutEffect: mountLayoutEffect, // 初始化useLayoutEffect
    useMemo: mountMemo, // 初始化useMemo
    useReducer: mountReducer, // 初始化useReducer
    useRef: mountRef, // 初始化useRef
    useCallback: mountCallback, // 初始化useCallback

    mountWorkInProgressHook生成hook链表

    • 在一个函数组件第一次渲染时,每个hook执行,都会产生一个hook对象,并形成链表结构,绑定在workInProgressmemoizedState属性上
    • hook上的状态,绑定在当前hook对象的memoizedState属性上
    • 对于effect副作用钩子,会绑定在workInProgress.updateQueue上,等到commit阶段,dom树构建完成,再执行每个 effect 副作用钩子。
  • 更新hooks

    相关hook实际执行的函数:

    1
    2
    3
    4
    5
    6
    7
    useState: updateState, // 得到最新的state
    useEffect: updateEffect, // 更新updateQueue
    useLayoutEffect: updateLayoutEffect,
    useMemo: updateMemo,
    useReducer: updateReducer,
    useRef: updateRef, // 获取ref对象
    useCallback: updateCallback

    updateWorkInProgressHook更新hook链表,找到对应的hooks

参考

官方文档

React Hooks 最佳实践

react-hooks如何使用?

一文吃透react-hooks原理