0%

详解useEffect

每次渲染都有它自己的Effects

count是某个特定渲染中的常量。事件处理函数“看到”的是属于它那次特定渲染中的count状态值。对于effects也同样如此:

并不是count的值在“不变”的effect中发生了改变,而是effect 函数本身在每一次渲染中都不相同,概念上它是组件输出的一部分,可以看到属于某次特定渲染的props和state

Effects的清除

effects有时候需要有一个清理步骤,目的是消除副作用,React只会在浏览器绘制后运行effects,这使得你的应用更流畅,因为大多数effects不会阻塞屏幕的更新,effects的清除同样被延迟,上一次的effect会在重新渲染后被清除

  • React 渲染{id: 20}的UI。
  • 浏览器绘制。我们在屏幕上看到{id: 20}的UI。
  • React 清除{id: 10}的effect。
  • React 运行{id: 20}的effect。

effect的清除不会读取最新的props,它只能读取到定义它的那次渲染中华的prop值

告诉React去比对你的Effects

这是为什么你如果想要避免effects不必要的重复调用,你可以提供给useEffect一个依赖数组参数(deps):

1
2
3
useEffect(() => {
document.title = 'Hello, ' + name;
}, [name]); // Our deps

这好比你告诉React:“Hey,我知道你看不到这个函数里的东西,但我可以保证只使用了渲染中的name,别无其他。”

移除依赖

1
2
3
4
5
6
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, [count]);

定时器会在每一次count改变后清除和重新设定

1
2
3
4
5
6
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id);
}, []);

React已经知道当前的count,我们需要告知React的仅仅是去递增状态,不管它现在具体是什么值

解耦来自Actions的更新

当你写setSomething(something=>…)这种代码时可以考虑使用reducer,reducer可以让你把组件内发生了什么和状态如何响应并更新分开描述

我们用一个dispatch依赖去替换effect的step依赖

1
2
3
4
5
6
7
8
9
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;

useEffect(() => {
const id = setInterval(() => {
dispatch({ type: 'tick' }); // Instead of setCount(c => c + step);
}, 1000);
return () => clearInterval(id);
}, [dispatch]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const initialState = {
count: 0,
step: 1,
};

function reducer(state, action) {
const { count, step } = state;
if (action.type === 'tick') {
return { count: count + step, step };
} else if (action.type === 'step') {
return { count, step: action.step };
} else {
throw new Error();
}
}

React保证dispatch在每次渲染中都是一样的,所以可以在依赖中去掉它,不会引起effect不必要的重复执行,当dispatch时,React只记住了action,它会在下一次渲染中再次调用reducer

无限重复请求问题:

1 没有依赖数组,那么每次渲染都会触发这个副作用

1
2
3
useEffect(()=>{
fetchData()
})

2 设置了依赖数组,但是依赖数组里的变量一直在变

1
2
3
4
5
6
7
8
9
10
const [data,setData] = useState()

useEffect(()=>{
const fetchData = async() => {
const res = await fetchNewData()
setData(res.data)
}
fetchData()
},[data])

定义函数请求

  • 某些函数只在effect中使用,那就在effect中定义

  • 某些函数在多个地方使用,就独立定义,最好用useCallBack包裹,并且在依赖数组里把依赖项写全

    eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function SearchResults() {
const [query, setQuery] = useState('react');

// ✅ Preserves identity until query changes
const getFetchUrl = useCallback(() => {
return 'https://hn.algolia.com/api/v1/search?query=' + query;
}, [query]); // ✅ Callback deps are OK

useEffect(() => {
const url = getFetchUrl();
// ... Fetch data and do something ...
}, [getFetchUrl]); // ✅ Effect deps are OK

// ...
}

如果query不变,getFetchUrl也会保持不变,effect也不会重新运行,反之,query改变了,getFetchUrl也会随之改变

参考: