一、useEffect
我们可以在一个组件中 多次调用 useState 和 useEffect ,它们是完全独立的。
数据获取,设置订阅、手动更改 React 组件中的 DOM 都属于副作用。
不管你是否知道这些操作,或 是“副作用”这个名字,应该都在组件中使用过它们。
// 使用 state、effect
import React, { useState, useEffect } from "react";
export default function HookPage(props) {
// 声明⼀一个叫 “count” 的 state 变量量,初始化为0
const [count, setCount] = useState(0);
// 与 componentDidMount 和 componentDidUpdate相似
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return <div>
<h3>HookPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
</div>
}
在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产⽣莫名其妙的 bug 并破坏 UI 的一致性。
- 使用 useEffect 完成副作用操作,赋值给useEffect的函数会在组件渲染到屏幕之后执行。
你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。
- 默认情况下,effect 将在每轮渲染结束后执⾏
二、useEffect 的条件执⾏
此时,只有当 useEffect第⼆个参数组⾥里的数值 改变后才会重新创建订阅。
import React, { useState, useEffect } from "react";
export default function HookPage(props) {
// 声明⼀一个叫 “count” 的 state 变量量,初始化为0
const [count, setCount] = useState(0);
const [date, setDate] = useState(new Date());
// 与 componentDidMount 和 componentDidUpdate相似
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
useEffect(() => {
const timer = setInterval(() => {setDate(new Date())}, 1000);
}, []);
return <div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<p>{date.toLocaleTimeString()}</p>
</div>
}
三、useEffect 的清除
通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器器 ID 等资源。要实现这一点, useEffect函数需返回一个清除函数,以防⽌内存泄漏,清除函数会在组件卸载前执⾏。
useEffect(() => {
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
四、自定义Hook
- 资源
有时候我们会想要在组件之间重⽤⼀些状态逻辑。
目前为⽌,有两种主流方案来解决这个问题:《⾼阶组件》和 《render+props》。
自定义 Hook 可以让你在不不增加组件的情况下达到同样的目的。
⾃定义 Hook 是⼀个函数,其名称以 “use” 开头,函数内部可以调⽤其他的 Hook。
// 函数式组件
import React, { useState, useEffect, useMemo } from "react";
export default function CustomHookPage(props) {
//定义⼀一个叫count的state变量量,初始化为0
const [count, setCount] = useState(0);
//和didMount、didUpdate类似
useEffect(() => {
document.title = `点击了${count}次`;
}, [count]);
return <div>
<h3>⾃自定义Hook</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<p>{useClock().toLocaleTimeString()}</p>
</div>
}
// 自定义hook,命名必须以use开头
function useClock() {
const [date, setDate] = useState(new Date());
useEffect(() => {
//只需要在didMount时候执行就可以了
const timer = setInterval(() => {setDate(new Date());}, 1000);
//清除定时器,类似willUnmount
return () => clearInterval(timer);
}, []);
return date;
}
五、Hook 使用规则
本质: Hook 就是 JavaScript 函数,但是使⽤它们会有两个额外的规则:
1、只能在 函数最外层 调⽤ Hook。
不要在循环、条件判断 或者 子函数中调⽤。
2、只能在 函数组件 调用 Hook。
不要在其他 JavaScript 函数中调用。
(还有⼀个地⽅可 以调用 Hook —— 就是自定义的 Hook 中。)
六、API
如果你刚开始接触 Hook,那么可能需要先查阅 Hook 概览。你也可以在 Hooks FAQ 章节中获取有用的信息。
-
useStateuseEffectuseContext
-
useReduceruseCallbackuseMemouseRefuseImperativeHandleuseLayoutEffectuseDebugValue``
七、useMemo
把“创建”函数和依赖项数组作为参数传⼊ useMemo ,它仅会在某个依赖项改变时才重新计算memoized 值。
这种优化有助于避免在每次渲染时都进⾏高开销的计算。
import React, { useState, useMemo } from "react";
export default function UseMemoPage(props) {
const [count, setCount] = useState(0);
const [value, setValue] = useState("");
const expensive = useMemo(() => {
console.log("compute");
let sum = 0;
for (let i = 0; i < count; i++) { sum += i; }
return sum;
//只有count变化,这⾥里里才重新执⾏行行
}, [count]);
return <div>
<h3>UseMemoPage</h3>
<p>expensive:{expensive}</p>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={value} onChange={event => setValue(event.target.value)} />
</div>
}
八、useCallback
把内联回调函数及依赖项数组作为参数传⼊ useCallback ,它将返回该回调函数的 memoized 版本, 该回调函数仅在某个依赖项改变时才会更新。
当你把回调函数 传递给经过优化的并使用引⽤相等性 去避免非必要渲染 (例如 shouldComponentUpdate ) 的 子组件时 ,它将⾮常有用 。
import React, { useState, useCallback, PureComponent } from "react";
export default function UseCallbackPage(props) {
const [count, setCount] = useState(0);
const [value, setValue] = useState("");
const addClick = useCallback(() => {
let sum = 0;
for (let i = 0; i < count; i++) { sum += i; }
return sum;
}, [count]);
return <div>
<h3>UseCallbackPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={value} onChange={event => setValue(event.target.value)} />
<Child addClick={addClick} />
</div>
}
// 经过优化的并使用引⽤相等性去避免非必要渲染的子组件
class Child extends PureComponent {
render() {
console.log("child render");
const { addClick } = this.props;
return <div>
<h3>Child</h3>
<button onClick={() => console.log(addClick())}>add</button>
</div>
}
}
useCallback(fn, deps) 相当于 useMemo(() => fn, deps) 。
注意:
依赖项数组不会作为参数传给“创建”函数。
虽然从概念上来说它表现为:所有“创建”函数中引⽤的值都应该出现在依赖项数组中。
未来编译器器会更加智能,届时⾃动创建数组将成为可能。