My Little World

react 自带的hooks

useCallback:缓存回调函数

在 React 函数组件中,每一次 UI 的变化,都是通过重新执行整个函数来完成的

如果组件中依赖的处理函数没有被useCallback做缓存处理,那么每次重新执行时都会被重新创建
那么依赖处理函数的组件就会因为处理函数更新进行重现渲染

为避免频繁的重新渲染组件,useCallback的作用就是将函数缓存起来,
只有当函数逻辑中依赖的状态发生变化才会去重新生成,然后引起组件更新,
避免了依赖没有变化时的无效更新

useCallback 可以减少不必要的渲染,主要体现在将回调函数作为属性传给某个组件。
如果每次都不一样就会造成组件的重新渲染。
但是如果确定子组件多次渲染也没有太大问题,特别是原生的组件,比如 button,
那么不用 useCallback 也问题不大。所以这和子组件的实现相关,和函数是否轻量无关。

useMemo:缓存计算的结果

同处理函数类似,如果组件中用到的数据A通过其他状态值计算得到,
在依赖值没有发生变化情况下,其实就不用重新计算,

如果某个数据是通过其它数据计算得到的,那么只有当用到的数据,也就是依赖的数据发生变化的时候,才应该需要重新计算
因此当组件因为其他原因刷新,进而重新执行函数,引起的重新计算是没有必要的

可以实现这个功能的hooks就是useMemo,useMemo就是只有在依赖的状态值发生变化时才会去重新执行计算的过程
避免不必要的重新计算过程
同时,对于依赖数据A的组件来说,没有重新计算产生的新值,可以在一定程度上避免子组件重复渲染

如果将函数也看做一个状态值/变量的话,其实,useMemo可以实现useCallback的功能

1
2
3
4
5
6
const myEventHandler = useMemo(() => {
// 返回一个函数作为缓存结果
return () => {
// 在这里进行事件处理
}
}, [dep1, dep2]);

二者的本质就是
建立了一个绑定某个结果到依赖数据的关系。只有当依赖变了,这个结果才需要被重新得到。

useRef:在多次渲染之间共享数据

可以把 useRef 看作是在函数组件之外创建的一个容器空间
在这个容器上,我们可以通过唯一的 current 属设置一个值,从而在函数组件的多次渲染之间共享这个值

使用 useRef 保存的数据一般是和 UI 的渲染无关的,因此当 ref 的值发生变化时,是不会触发组件的重新渲染的,
这也是 useRef 区别于 useState 的地方。
比如保存定时器句柄,在回调函数中关掉定时器,然后将ref.current 设置为null, 这个句柄没有在组件中被用到,所以不会引起重新渲染。
但如果用useState去保存句柄,将定时器关掉时,同时将state设置为null,即使没有被组件用到,也会引起重新组件渲染。

useRef 还有一个重要的功能,就是保存某个 DOM 节点的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// current 属性指向了真实的 input 这个 DOM 节点,从而可以调用 focus 方法
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}

可以看到 ref 这个属性提供了获得 DOM 节点的能力,并利用 useRef 保存了这个节点的应用

useContext:定义全局状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

const MyContext = React.createContext(initialValue);

const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// 创建一个 Theme 的 Context

const ThemeContext = React.createContext(themes.light);
function App() {
// 整个应用使用 ThemeContext.Provider 作为根组件
return (
// 使用 themes.dark 作为当前 Context
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}

// 在 Toolbar 组件中使用一个会使用 Theme 的 Button
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}

// 在 Theme Button 中使用 useContext 来获取当前的主题
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{
background: theme.background,
color: theme.foreground
}}>
I am styled by theme context!
</button>
);
}

Context 提供了一个方便在多个组件之间共享数据的机制。
不过需要注意的是,它的灵活性也是一柄双刃剑。
你或许已经发现,Context 相当于提供了一个定义 React 世界中全局变量的机制,
而全局变量则意味着两点:

  1. 会让调试变得困难,因为你很难跟踪某个 Context 的变化究竟是如何产生的。
  2. 让组件的复用变得困难,因为一个组件如果使用了某个 Context,它就必须确保被用到的地方一定有这个 Context 的 Provider 在其父组件的路径上。

需要再三强调的是,Context 更多的是提供了一个强大的机制, 让 React 应用具备定义全局的响应式数据的能力。