抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

钩子是什么

在类组件中,我们可以定义 state 来管理组件的状态,使用生命周期函数来管理组件的生命周期。

但函数组件就没有这些功能。因此 React 16.8 推出了 钩子 (Hook),让函数组件也拥有了类似的功能。

函数组件默认情况下,是无法使用 state 和生命周期函数的。但 钩子 让用户有能力通过间接的方式,将 state 和 生命周期函数勾入函数组件中,让函数组件拥有了类似的能力。

状态钩子

状态钩子 (State Hook),可以让函数组件定义状态。[1]

定义状态钩子

1
2
3
4
5
6
7
8
9
10
function Counter() {
// 定义一个状态
let [count, setCount] = React.useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}

在这里,我们在函数组件内调用 useState ,来定义一个状态。

函数括号里面的 0 是这个状态的初始值,返回两个对象,一个是当前的状态,一个是更新状态的函数。

除此之外,我们还可以定义很多的状态:

1
2
let [count, setCount] = React.useState(0);
let [index, setIndex] = React.useState(1);

同步还是异步

通过 useState 返回的状态更新函数 setXxxx ,和 setState 的行为是一致的。

副作用钩子

副作用和副作用钩子

提前副作用,首先我们要了解函数式编程(Functional Programming)的一个基本概念 纯函数(Pure Function)。

通俗的讲,纯函数是一种特殊的函数,它的返回值只跟参数有关。例如,数学中的 sincos 都是纯函数,它的返回值都只跟参数有关。

如果一个函数除了做与返回值有关的事情,还做了一些例如修改全局变量、网络请求等与函数外部数据互动的事情,那么这些事便称为 副作用(Side Effect)。副作用就会使得函数的输出结果变得不太确定。

副作用钩子(Effect Hook),则是用来为函数组件引入副作用的,例如网络请求,修改状态、监听生命周期、操作DOM

定义副作用钩子

我们在上面例子的基础上,添加了副作用钩子:[1:1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Counter() {
// 定义一个状态
let [count, setCount] = React.useState(0);
// 定义一个副作用钩子
React.useEffect(() => {
console.log(count);
});
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}

在这里,我们在函数组件内调用 useEffect ,来定义一个副作用钩子。

副作用钩子跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API。它会在UI创建、更新或即将销毁时触发。[1:2]

清除副作用钩子

有一些副作用钩子,是不需要清除的,例如网络请求,请求结束后会自动终止。

但还有一些副作用钩子是需要手动清除的,比如 setInterval,它不会自动停止。

为了解决这个问题,我们可以定义一个清除副作用的钩子。

定义它很简单,只需要让传入的函数返回一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Counter() {
// 定义一个状态
let [count, setCount] = React.useState(0);
let intervalId;
// 定义一个副作用钩子
React.useEffect(() => {
intervalId = setInterval(() =>console.log('loop'), 1000);
return () => {console.log('destroyed');clearInterval(intervalId);}
});
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}

当页面更新时,React 会清除上一次的副作用,再执行新的副作用。

跳过某些状态

前面说过了, useEffect 会在 componentDidUpdate 的时候调用。如果在 useEffect 代码中调用了 setXxxx 函数,那么组件会再次更新,componentDidUpdate会被再次调用。这样就陷入了无限循环。看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Counter() {
// 定义一个状态
let [count, setCount] = React.useState(0);
let [updateCount, setUpdateCount] = React.useState(0);
// 定义一个副作用钩子
React.useEffect(() => {
setUpdateCount(updateCount+1)
});
return (
<div>
<p>{count}:{updateCount}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}

这个例子下,count 就会陷入了无休止的更新,尽管我们没有做任何操作。

那有没有办法,我可以跳过某些状态的更新,来避免这个问题呢?

答案是有的,那就是 useEffect 的第二个参数,它是一个数组,用来指定哪些状态改变时会进行更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Counter() {
// 定义一个状态
let [count, setCount] = React.useState(0);
let [updateCount, setUpdateCount] = React.useState(0);
// 定义一个副作用钩子
React.useEffect(() => {
setUpdateCount(updateCount+1)
}, [count]);
return (
<div>
<p>{count}:{updateCount}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}

再例如网络请求。通常情况下,我们不需要每次组件更新就发送一次请求,我们可以设置第二个参数为空数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function ApiList() {
// 定义一个状态
let [list, setList] = React.useState([]);
// 定义一个副作用钩子
React.useEffect(() => {
fetch("https://api.github.com")
.then((res) => res.json())
.then((data) => setList(Object.entries(data)));
}, []);
return (
<ul>
{list.map((x, i) => (
<li key={i}>
{x[0]}:{x[1]}
</li>
))}
</ul>
);
}

其他钩子

上下文钩子

上下文钩子(Context Hook)是一种不太常用的钩子。它可以让函数组件引用上下文对象。

使用上下文钩子的函数:

1
上下文对象数据 = React.useContext(上下文实例);

例如,我们在上下文那一节中定义的设置主题的钩子:

1
2
3
const config = { theme: "light", setTheme: () => null };
const Config = React.createContext(config);
Config.displayName = "Config";

我们把上下文那一节的 ThemedButton 改成函数组件:

1
2
3
4
5
6
7
8
function ThemedButton(props) {
let config = React.useContext(Config);
return (
<button {...props} onClick={() => config.setTheme()}>
{config.theme}
</button>
);
}

引用钩子

引用钩子(Reference Hook)是一种不太常用的钩子。

useRef 返回一个可变的 ref 对象,可以将其挂载到 ref ,通过其 .current 属性便可以访问组件实例或DOM对象。

它的用法和 createRef 函数是类似的。

如何使用钩子

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。[1:3]
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。[1:4]

自定义钩子

除了内置的钩子,我们还可以自己定义钩子。

自定义 Hook 是一个函数,其名称以 use 开头,函数内部可以调用其他的 Hook。

例如,我们可以自定义一个只触发一次的副作用钩子:

1
2
3
function useEffectOnce(func) {
return React.useEffect(func,[])
}

  1. Hook 概览 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

评论