Hook使用规则
- 只能在函数的最外层调用Hook,不能在循环、条件判断或子函数中调用。
- 只能在React函数组件或自定义Hook中调用Hook,不可在其他JavaScript函数中使用。
useEffect一句话简介
useEffect相当于componentDidMount、componentDidUpdate、componentWillUnmount这三个生命周期方法的组合。
参数
参数1:函数,即effect
- 函数体:执行各种副作用操作,如网络请求、添加订阅、添加计时器等。
- 函数返回值:执行取消订阅、取消计时器等清理。
参数2:依赖项数组
- 在依赖项数组中的effect需要引用且值会改变的外部变量,当它们的值改变时:
- 挂载阶段,effect函数体会在组件挂载后延迟一段时间被调用,相当于componentDidMount。
- 更新阶段,effect函数体会在组件更新后延迟一段时间被调用,相当于componentDidUpdate。
- 卸载阶段,effect函数返回值会在组件将要卸载前被调用,相当于componentWillUnmount。
- 如果依赖项是空数组:
- 挂载阶段,effect函数体会在组件挂载后后延迟一段时间被调用,componentDidMount
- 更新阶段,effect函数体不会被调用,相当于componentDidUpdate。
- 卸载阶段,effect函数返回值会在组件将要卸载前被调用,相当于componentWillUnmount。
- 如果effect使用的外部变量不在依赖项中,这些变量的值不会更新。
- 值不会改变的外部变量不要加在依赖项中。例如React保证useState的更新函数不会改变,它们无需加入依赖数组。
如何区分变量是否会改变?
存放数据的变量是否会改变得看具体的代码才能确定;存放函数的变量是否会改变的规律总结如下:
- useState的更新函数不会改变。
- useCallback的返回值函数在依赖项不变的情况下不会改变。
- 函数组件中的匿名函数和命名函数都会改变。
案例
import React, { memo, useCallback, useState } from "react";
type Style = {
color: string;
background: string;
};
const Show: React.FC<{ state: Style }> = (props) => {
const { color, background } = props.state;
console.log("Show");
return (
<div style={{ color, background }}>
测试文字,文字的颜色和背景色可以切换!
</div>
);
};
const HandleColor: React.FC<{
setState: React.Dispatch<React.SetStateAction<Style>>;
}> = memo((props) => {
const { setState } = props;
console.log("HandleColor");
return (
<button
onClick={() =>
setState((preState) =>
preState.color === "red"
? { ...preState, color: "blue" }
: { ...preState, color: "red" }
)
}
>
点我改变颜色
</button>
);
});
const HandleBackground1: React.FC<{
setBackground: () => void;
}> = memo((props) => {
const { setBackground } = props;
console.log("HandleBackground-1");
return <button onClick={() => setBackground()}>点我改变背景</button>;
});
const HandleBackground2: React.FC<{
setBackground: () => void;
}> = memo((props) => {
const { setBackground } = props;
console.log("HandleBackground-2");
return <button onClick={() => setBackground()}>点我改变背景</button>;
});
const App = () => {
const [state, setState] = useState<Style>({
color: "red",
background: "black"
});
console.log("渲染了App");
const setBackground = useCallback(
() =>
setState((preState) =>
preState.background === "black"
? { ...preState, background: "gray" }
: { ...preState, background: "black" }
),
[]
);
const setBackground2 = () =>
setState((preState) =>
preState.background === "black"
? { ...preState, background: "gray" }
: { ...preState, background: "black" }
)
return (
<>
<Show state={state} />
<HandleColor setState={setState} />
<HandleBackground1 setBackground={setBackground} />
<HandleBackground2 setBackground={setBackground2} />
</>
);
};
export default App;
在线体验 仔细看console.log打印的日志,点击HandleBackground1按钮时,只会提示“HandleBackground-2”,这说明HandleBackground2组件收到的props发生了改变,而HandleBackground1组件收到的props未发生改变。
建议
- 在涉及多个业务逻辑的复杂组件中,建议按业务逻辑拆分useEffect,这样可以提高代码可维护性。
例如:某组件需要向2个API接口分别请求数据,此时就应该分开写在2个useEffect中,而不应集中写在一个useEffect中。这样以后遇到某个API接口有变动需要改代码时,分开写useEffect比集中写会更方便维护。 - 集中在一起的useEffect与按业务逻辑拆成多个的useEffect相比运行效率几乎没差别,拆分useEffect不会导致额外的渲染。
- 条件判断不能写在effect函数外面,必须写在effect函数内部。
代码片段
|