轻巧快艇 Hooks
背景
在 React 16.8 之前 函数式组件又被称为无状态组件
因为函数在每次执行执行的时候 都会重新执行内部的代码 导致函数无法维护自身内部的状态 例如
function add(n) {
const result = 0;
return result + 1;
}
add(1); //1
add(1); //1我们无法在函数中保存 result 的状态 因为每一次执行函数时 都会重新初始化 result
我们来看看 16.8 之前 类组件和函数式组件有哪些差别
类组件需要继承 class,函数组件不需要
类组件可以访问生命周期方法,函数组件不能
类组件中可以获取到实例化后的 this,并基于这个 this 做各种各样的事情,而函数组件不可以
类组件中可以定义并维护 state(状态),而函数组件不可以
......
类组件的状态 一般由组件内部维护 这样就会造成组件的复用性很差
前几章中 我提到了 HOC 和 Render Props
究其根本 它们都是尽可能的为了优雅的实现代码的复用
但是 这些名词的背后无疑是繁重的学习成本
而函数式组件 又无法保存状态
这个时候 hook 就横空出世了 它想要解决的问题就是
让函数式组件拥有类似类组件的功能
我们来看一个简单的 demo
代码有点长 但是它做的事情很简单
不管是类组件还是函数式组件都只是输出父组件给它的 props
但是我们通过 setTimeout 将预期中的渲染推迟了 3s,打破了 this.props 和渲染动作之间的这种时机上的关联
进而导致类组件在渲染时捕获到的是一个错误的、修改后的 this.props
因为虽然 props 本身是不可变的,但 this 却是可变的,this 上的数据是可以被修改的
this.props 的调用每次都会获取最新的 props 确保数据实时性
而函数式组件可以确保在任何时机下读取到的 props,都是最初捕获到的那个 props
所以函数组件是一个更加匹配其设计理念、也更有利于逻辑拆分与重用的组件表达形式
了解了为什么需要 hook 后 我们来看看 👀 有哪些 React 已经帮我们封装好的 hook 吧
useState
useState 填补了函数式组件内部不能保存状态的空白
更新 useState 中保存的状态时 都是走的异步更新 对同一个属性的多次更新 会被合并 只取最新的一次
使用回调函数更新状态时 可以拿到最新的 state
我们来对比一下类组件中的 setState
假如我们执行了一次 this.setState 那么组件大概会走如下流程
this.setState --> shouldComponentUpdate --> componentWillUpdate --> render --> componentDidUpdate
设想一下 如果我们每次执行 this.setState React 都要做出上述响应
那么 大概没有几次 你的页面就会出现卡顿了 所以 React 做了批量更新
但是在 hook 中 所有的更新都是异步的
然而在 类组件中 如果你使用 setTimeout/Promise 这些异步的方法包裹 setState 的话 它会让这些更新摆脱 React 的控制 从而看上去表现出了同步的样子
但是请注意 ⚠️ 无论是在函数式组件还是类组件中 更新状态永远是异步的
这意味着你无法马上获取到最新的状态 如果需要的话 请使用回调函数的方式
关于批量更新 可以参考 github 上的discussions
文中提到了在即将到来的 React18 中将会默认开始批量更新
包括在类组件中 如果我们使用 setTimeout/Promise 的这些情况 现在 React 也都能对它们进行控制 从而使类组件和函数式组件的表现趋于一致
上述的代码表示 不同的 state 之间互不影响 但是对同一个 state 的多次操作会导致批量更新
useReducer
说完 useState 再来看看 useReducer
两者都是函数式组件中对状态管理的手段
前者可看成是后者的一种实现
和 redux 没有关系 组件之间不共享数据
是 useState 的一种替代方案
对于复杂的业务 使用 useReducer 比 useState 会有更好的可读性
执行代码后 你会发现 不同组件之间的 state 互不影响
useEffect
允许函数组件执行副作用操作 在一定程度上弥补了生命周期的缺席
可以接收两个参数,分别是回调函数与依赖数组 useEffect(callBack, [])
第一个参数 函数形式 可实现等同于 componentDidMount shouldComponentUpdate componentWillUnmount
并且可以返回一个函数 用来消除副作用 类似 componentWillUnmount 可以做一些事件的解绑 定时器的关闭等
第二个参数 数组 状态依赖项 实现性能优化 如果传[] 则等同于不开启 shouldComponentUpdate
useContext
可以在子组件之间共享数据
在组件外创建 context 对象 const AppContext = React.createContext({})
父组件内使用 context 对象下的 Provider 并赋值
在子组件内过去 context 对象 React.useContext(AppContext)
useRef
获取元素结点
保存数据 (永远指向最初的那个值)
useCallback && useMemo
在函数式组件中使用 memo 包裹我们的组件 可以帮助我们对前后的 props 进行一个浅比较
但是如果 props 是引用类型 那么此时 memo 就会失效
我们可以使用 useCallBack 和下面提到 useMemo 来使我们的引用类型变成可记忆的
两者的关系如下 useMemo 的能力边界大于 useCallBack 但是两者在某些情况下可以相互转换
useCallback(fn, deps) is equivalent to useMemo(() => fn, deps) -- React 官网
useCallback(() => {}, [])
useCallback 接受两个如参
第一个参数 是一个函数
第二个参数 是一个数组 是这个函数的依赖项 只有依赖项更新 函数才会重新执行
自定义 hook
自定义 hook 的函数名 必须以 use 开头 比如下面是一个打印组件创建/销毁的 useLogger hook
Hooks 使用注意点
hook 的本质是链表 React 在执行 hook 的时候 是按顺序执行的
如果将 hook 放在条件/判断语句中 那么就会打破它的执行顺序 产生意想不到的结果
所以 请将你的 hook 放在最顶层进行使用
总结
告别难以理解的 Class
this
生命周期
解决业务逻辑难以拆分的问题
逻辑一度与生命周期耦合在一起
使状态逻辑复用变得简单可行
HOC(高阶组件)
Render Props
函数组件从设计思想上来看,更加契合 React 的理念
Last updated
Was this helpful?