2020-08-17
Hooks
Class
忘了那个它,别再用 class 写 React 了
React 组件渲染结果不外乎 props state 和生命周期对 props state 的副作用。Class 组件已经在业界存在多年,一直以来都有下面几个问题
  1. state 和 state mutaion 的逻辑不容易复用 (redux 是一种方式, 但 redux 也它的问题 …)
  2. 生命周期太多导致组件异常臃肿, componentDidXXXX 地狱; 且生命周期逻辑不好复用
  3. class 为了实现复用不得不搞出很多 HOC, 这个会造成组件嵌套地狱 wrapper hell
Hooks 始于 2018 年,在经过了严密的 issues 论证后正式确定规范并在 React Conf 2018 [1]可见 youtube 上的相关视频 中首次曝光

# Hooks 描述 Props

函数式组件的 Props 比类组件的简洁,且在形式上更能说明 props + state 决定 render 的 React 哲学:
00import React from 'react';
01
02export interface AppProps {}
03
04export function App(props: AppProps) {
05    return <div>hello, world</div>
06}

# Hooks 描述 State

利用 useState 我们可以快速做到:
  1. 声明式风格的状态 React.useState
  2. useState 解构可以一口气拿到 [state, setState] 这一对 pair
  3. state 及其 mutation 可以被抽象成独立个体达到复用的目的
下面是一个计数器例子
00import React from 'react';
01
02export function Counter() {
03    const [count, setCount] = React.useState();
04    return (
05        <div onClick={() => setCount(count + 1)}>{count}</div>
06    )
07}
针对第三点, 状态及其 mutaion 可以进一步抽象:
00import React from 'react';
01
02export function useCounter(initCount: number) {
03    const [count, setCount] = React.useState(initCount);
04    // 自加 mutation
05    const inc = () => setCount(count + 1);
06    return [count, inc, setCount] as const;
07}
08
09export function EcznCounter() {
10    const [count, inc] = useCounter(10)
11    return <div onClick={inc}>EcznCounter {count}</div>
12}
13export function AppCounter() {
14    const [count, inc] = useCounter(0)
15    return <div onClick={inc}>AppCounter {count}</div>
16}
React State Hooks 原语相比 class 来说,最强大的地方就在于 Hooks 可以以函数抽象的形式抽象成独立的状态及其 mutaion,这是 class 很难搞的一点 (class 搞起来比较麻烦, 很绕)

# Hooks 描述 LifeCycle

React 组件还有个大问题,就是组件的生命周期,在 class 组件里这个概念跟 DOM 强相关,需要用很多 componentDidXXX 来描述组件的生命周期,很容易让组件变得非常臃肿
此外这次在 A 里面用过的生命周期逻辑不能直接复用在 B 里,要做一些处理,比如包一层… 或者复制粘贴。
didxxx 的作用在于让组件能感知到外部环境的变化,进而作出 props 或者 state 的 mutation 以改变组件行为,比方在 didMount 的时候绑定一个 window.addEventListener,最后需要在 unmount 的时候移除这个 eventListener,在这个过程中事件是副作用,它影响组件状态,而 componentDidXXX 只是 React 开的一个钩子函数让你好监听这些事件。
后来你也知道了,DOM 还是比较复杂的,React 不得不开了很多生命周期钩子给组件来感知外部变化… 逐渐臃肿复杂化。
那 hooks 风格下的组件如何感知到外部环境变化, 是这样吗 ?
00import React from 'react';
01export function App() {
02    React.useDidMount(() => {
03        console.log('dom mounted');
04    });
05    React.useUnMount(() => {
06        console.log('dom will unmount');
07    })
08}
这么看起来还可以啊 ? 但仔细想想这个本质上跟类的处理手段一样,提供众多钩子让组件监听外部变化 … 显然不可取,但不这样我们能处理生命周期吗?
看过文档的你肯定知道用的是 useEffect 来解决,但 useEffect 是怎么抽象地表述钩子和副作用的关系的呢 ? 看例子:
00import React from 'react'
01
02export interface AppProps {/* 略 */}
03
04export function App(props: AppProps) {
05    React.useEffect(() => {
06        const fn = () => {/* state mutaion */}
07        window.addEventListener('resize', fn);
08        return () => {
09            window.removeEventListener('resize', fn);
10        }
11    }, []);
12}
useEffect 做了什么 ?
  1. useEffect 第一个入参称为 effect 其返回值称为 cancelEffect;
  2. React 会在组件渲染完成后调用 effect, 并在下一次渲染前调用 cancelEffect; React 保证了每次运行 effect 的同时,DOM 都已经更新完毕 (某些场景下需要考虑到这个特性)
  3. 函数里面包函数真的没有问题吗? 借助这个闭包, 副作用里可以直接访问到 state props 等状态无需引入其他 react api; 担心内存泄漏? 记住一定要在 cancelEffect 里吧所有的引用拿掉
  4. 每次渲染完都会执行吗? 也不尽然,useEffect 还有第二个参数 deps, 这个参数用于指定依赖,只有这个列表的里面的元素出现 mutation 的时候才会调 effect
didMount & unMount
useEffect(fn, []) 可以实现上述这对生命周期的描述
didReceiveNextProps
useEffect(fn, [props]) 可以在 props 变化的时候执行,但还需要处理 prev props 和 next props 这两个入参:
00function usePrevious<T>(value: T) {
01    const ref = React.useRef<T>();
02    React.useEffect(() => {
03        ref.current = value;
04    });
05    return ref.current;
06}
07
08function App(props: {/* 略 */}) {
09    const prevPropsRef = usePreviousRef(props);
10
11    React.useEffect(() => {
12        const prevProps = prevPropsRef.current;
13        if (!prevProps) return console.log('第一次渲染还么有 prevProps');
14        console.log('prevProps', prevProps);
15        console.log('nowProps', props);
16    }, [props]);
17
18    return <div>app</div>
19}
componentDidXxx
总之我自己遇到的生命周期都可以用 useEffect 来描述 (偶尔遇到不会的可以 stackoverflow 看大神操作)
总之 useEffect 比 componentDidXxx 来描述生命周期高明多了

# 也就是说…

忘了那个它, 只用 hooks 来描述组件吧
Example

  1. 可见 youtube 上的相关视频 ↩︎




回到顶部