My Little World

如何保存组件状态和使用生命周期

那么什么样的值应该保存在 state 中呢?

在一个函数组件的多次渲染之间,这个 state 是共享的。
这是日常开发中需要经常思考的问题。通常来说,我们要遵循的一个原则就是:state 中永远不要保存可以通过计算得到的值。
比如说:

  1. 从 props 传递过来的值。有时候 props 传递过来的值无法直接使用,而是要通过一定的计算后再在 UI 上展示,比如说排序。
    那么我们要做的就是每次用的时候,都重新排序一下,或者利用某些 cache 机制,而不是将结果直接放到 state 里。
  2. 从 URL 中读到的值。比如有时需要读取 URL 中的参数,把它作为组件的一部分状态。那么我们可以在每次需要用的时候从 URL 中读取,而不是读出来直接放到 state 里。
  3. 从 cookie、localStorage 中读取的值。通常来说,也是每次要用的时候直接去读取,而不是读出来后放到 state 里。

useEffect执行

useEffect 让我们能够在下面四种时机去执行一个回调函数产生副作用:

  1. 每次 render 后执行:不提供第二个依赖项参数。比如useEffect(() => {})。
  2. 仅第一次 render 后执行:提供一个空数组作为依赖项。比如useEffect(() => {}, [])。
  3. 第一次以及依赖项发生变化后执行:提供依赖项数组。比如useEffect(() => {}, [deps])。
  4. 组件 unmount 后执行:返回一个回调函数。比如useEffect() => { return () => {} }, [])。

定义依赖项注意点

那么在定义依赖项时,我们需要注意以下三点:

  1. 依赖项中定义的变量一定是会在回调函数中用到的,否则声明依赖项其实是没有意义的。
  2. 依赖项一般是一个常量数组,而不是一个变量。因为一般在创建 callback 的时候,你其实非常清楚其中要用到哪些依赖项了。
  3. React 会使用浅比较来对比依赖项是否发生了变化,所以要特别注意数组或者对象类型。
    如果你是每次创建一个新对象,即使和之前的值是等价的,也会被认为是依赖项发生了变化。这是一个刚开始使用 Hooks 时很容易导致 Bug 的地方。

Hooks 的使用规则

只能在函数组件的顶级作用域使用

第一,所有 Hook 必须要被执行到。第二,必须按顺序执行。

所谓顶层作用域,就是 Hooks 不能在循环、条件判断或者嵌套函数内执行,而必须是在顶层。
同时 Hooks 在组件的多次渲染之间,必须按顺序被执行。
因为在 React 组件内部,其实是维护了一个对应组件的固定 Hooks 执行列表的,以便在多次渲染之间保持 Hooks 的状态,并做对比。
也不能在return语句后执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

function MyComp() {
const [count, setCount] = useState(0);
if (count > 10) {
// 错误:不能将 Hook 用在条件判断里
useEffect(() => {
// ...
}, [count])
}

// 这里可能提前返回组件渲染结果,后面就不能再用 Hooks 了
if (count === 0) {
return 'No content';
}

// 错误:不能将 Hook 放在可能的 return 之后
const [loading, setLoading] = useState(false);

//...
return <div>{count}</div>
}

Hooks 只能在函数组件或者其它 Hooks 中使用

如果一定要在 Class 组件中使用,那应该如何做呢?
其实有一个通用的机制,那就是利用高阶组件的模式,将 Hooks 封装成高阶组件,从而让类组件使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
a.
import React from 'react';
import { useWindowSize } from '../hooks/useWindowSize';

export const withWindowSize = (Comp) => {
return props => {
const windowSize = useWindowSize();
return <Comp windowSize={windowSize} {...props} />;
};
};

b.
import React from 'react';
import { withWindowSize } from './withWindowSize';

class MyComp {
render() {
const { windowSize } = this.props;
// ...
}
}

// 通过 withWindowSize 高阶组件给 MyComp 添加 windowSize 属性
export default withWindowSize(MyComp);

小结
hooks使用时应包括这么三点:

  1. 在 useEffect 的回调函数中使用的变量,都必须在依赖项中声明;
  2. Hooks 不能出现在条件语句或者循环中,也不能出现在 return 之后;
  3. Hooks 只能在函数组件或者自定义 Hooks 中使用。