useEffect:
用途:
- 获取数据
- 事件监听或订阅
- 监控/改变DOM
- 设置定时器,输出日志
- 该 Hook 接收一个包含命令式、且可能有副作用代码的函数。
定义语法使用:
该函数有两个参数,第一个是 异步函数,第二个是 监听的数据
useEffect (() => {
// 这里是用来放,需要执行的异步函数
}, [‘这里面是,用来放,需要监听的数据’]);
当 useEffect 的第二个 参数 [] 监听的数据发生变化,那么,就会执行第一个参数里边的,异步函数,并且对页面重新进行渲染。
第一个参数是一个函数
,必传项。是组件要执行的副作用。可以看做componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个函数的组合。
useEffect(() => {
console.log('执行副作用'); // 普通函数,执行副作用,可以实现componentDidMount、componentDidUpdate
return () => { // return函数, 组件销毁时清除副作用,可以实现componentWillUnmount
console.log("清除副作用");
};
}, [count]);
useEffect 的基本语法:(第二参数为 基本类型的时候)
1、当 不写 第二个参数的时候:
useEffect (() => {
getUserList(); // 这是一个从后端获取用户列表的 函数
});
// 所有更新都执行这个函数
这时没useEffect不传递第二个参数会导致每次渲染都会运行useEffect。然后,当它运行时,它获取数据并更新状态。然后,一旦状态更新,组件将重新呈现,这将再次触发useEffect,如此就会反复调用接口,反复渲染,这就是问题所在。
2、当第二个参数 [ ] 为空的时候:
const [data, setData] = useState({ hits: [] });
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
};
fetchData();
}, []);
// 第二个参数为空,那么就没有监听任何函数,所以只在挂载和卸载的时候dom 发生改变,才会执行。
这个时候就只在,挂载和卸载的时候执行。
3、第二个参数为某个变量
useEffect(()=>{
console.log(count);
getUserList(); // 获取用户列表的函数
}, [count]);
// 我下边绑定了一个按钮,当按钮点击的时候,count++。
当按钮点击,count 改变,那么就会触发 这个 useEffect ,执行 getUserList() 和 console.log 并且重新渲染视图。
4、useEffect 可以有返回值
useEffect 本质上是一个函数,既然是一个函数,那么就可以拥有一个返回值 return。
return函数, 组件销毁时清除副作用,可以实现componentWillUnmount。
return 函数,会在组件销毁前执行。
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); // 可以取消请阅
};
});
在组件销毁前,清除订阅。
进阶用法:(第二参数为引用类型)
1、当参数是一个数组的时候:
const [count, setCount] = useState(1);
const newArr = [4,5];
useEffect(() => {
setTimeout(() => {
setCount(count+1);
}, 1000);
console.log(`第二个参数: 数组, 第 ${count} 次执行`);
}, [newArr]);
// 打印log,无限循环
// 第二个参数: 数组, 第 1 次执行
// 第二个参数: 数组, 第 2 次执行
// 第二个参数: 数组, 第 3 次执行
// 第二个参数: 数组, 第 ... 次执行
原因:
useEffect依赖项arr发生变化,此处依赖数组执行浅层比较
([...] === [...] 为false
)useEffect重新执行,useEffect中回调函数改变state值,state值改变触发组件重新渲染,无限循环
。
解决办法:
使用 useRef, useRef 会在每次渲染的时候,返回同一个ref对象,返回的ref在组件的整个生命周期保持不变。
const [count, setCount] = useState(1);
const refArr = useRef([4, 5, 6]);
useEffect(() => {
setCount(count+1);
console.log(`第二个参数: 数组, 第 ${count} 次执行`);
}, [refArr.current]);
// 打印log,执行一次
// 第二个参数: 数组, 第 1 次执行
2、当参数为 function的时候(useCallback)
const [count, setCount] = useState(1);
const consoleFunction = () => {
console.log('consoleFunction');
};
useEffect(() => {
setTimeout(() => {
setCount(count + 1);
}, 1000);
console.log(`第二个参数: 函数, 第 ${count} 次执行`);
}, [consoleFunction]);
// 打印log,无限循环
// 第二个参数: 函数, 第 1 次执行
// 第二个参数: 函数, 第 2 次执行
// 第二个参数: 函数, 第 3 次执行
// 第二个参数: 函数, 第 ... 次执行
原因:
第一次渲染后执行一次useEffect,useEffect中回调函数改变state值,state值改变触发组件重新渲染,useEffect依赖项consoleFunction函数发生变化,此处依赖函数执行浅层比较(每次渲染都重新创建一个新的函数 function(前) === function(后)为false)useEffect重新执行,useEffect中回调函数改变state值,state值改变触发组件重新渲染,无限循环。
解决办法:
使用 useCallback,它会返回该回调函数的 memorized 版本,该回调函数仅在某个依赖改变才会改变。
const [count, setCount] = useState(1);
const consoleFunction = useCallback(() => {
console.log('consoleFunction');
}, []);
useEffect(() => {
setCount(count + 1);
console.log(`第二个参数: 函数, 第 ${count} 次执行`);
}, [consoleFunction]);
// 打印log,执行一次
// 第二个参数: 函数, 第 1 次执行
3、当参数是 对象的时候(useMemo)
const [count, setCount] = useState(1);
const obj = {name: 'zhangsan'};
useEffect(() => {
setTimeout(() => {
setCount(count + 1);
}, 1000);
console.log(`第二个参数: 对象, 第 ${count} 次执行`);
}, [obj]);
// 打印log,无限循环
// 第二个参数: 对象, 第 1 次执行
// 第二个参数: 对象, 第 2 次执行
// 第二个参数: 对象, 第 3 次执行
// 第二个参数: 对象, 第 ... 次执行
原因:
浅比较
解决办法:
使用useMemo,useMemo该回调函数仅在某个依赖项改变时才会更新。此处使用[]依赖,组件重新渲染后对象不再重新定义。
const [count, setCount] = useState(1);
const obj = useMemo(() => ({name: 'zhangsan'}), []);
useEffect(() => {
setCount(count + 1);
console.log(`第二个参数: 对象, 第 ${count} 次执行`);
}, [obj]);
// 打印log
// 第二个参数: 对象, 第 1 次执行