39-useEffect副作用-组件渲染后执行
39-useEffect 副作用
使用 Effect
为了解决这个问题 React 专门为我们提供了钩子函数 useEffect()
,Effect 的翻译过来就是副作用,专门用来处理那些不能直接写在组件内部的代码。
哪些代码 不能直接写在组件内部 呢?像是:获取数据、记录日志、检查登录、设置定时器等。简单来说,就是那些和组件渲染无关,但却有可能对组件产生副作用的代码
useEffect 语法:
useEffect(didUpdate);
useEffect()
需要一个函数作为参数,你可以这样写:
useEffect(()=>{
/* 编写那些会产生副作用的代码 */
});
useEffect()
中的代码是在组件渲染后才执行
useEffect()
中的回调函数会在组件每次渲染完毕之后执行
这也是它和写在函数体中代码的最大的不同,函数体中的代码会在组件渲染前执行,而 useEffect()
中的代码是在组件渲染后才执行,这就避免了代码的执行影响到组件渲染。
通过使用这个 Hook,我设置了 React 组件在渲染后所要执行的操作。React 会将我们传递的函数保存(我们称这个函数为 effect),并且在 DOM 更新后执行调用它。React 会确保 effect 每次运行时,DOM 都已经更新完毕。
限制 effect 的执行时机
组件每次渲染 effect 都会执行,这似乎并不总那么必要。因此在useEffect()
中我们可以限制 effect 的执行时机,在useEffect()
中可以将一个数组作为第二个参数传递,像是这样:
useEffect(()=>{
/* 编写那些会产生副作用的代码 */
return () => {
/* 这个函数会在下一次effect执行前调用 */
};
}, [a, b]);
指定后,只有当依赖发生变化时,Effect 才会被触发,通常会将 Effect 中 使用的所有的局部变量都设置为依赖项 。
这样一来可以确保这些值发生变化时,会触发 Effect 的执行,像 setState()
是由钩子函数 useState()
生成的,useState()
会确保组件的每次渲染都会获取到相同 setState()
对象,所以 setState()
方法可以不设置到依赖中
如果依赖项设置了一个空数组,则意味 Effect 只会在组件初始化时触发一次
useEffect() 可以传递一个第二个参数
在《汉堡到家》的练习中,存在着一个 bug。当我们在购物车或结账界面减少商品的数量全部为 0 时(购物车中没有商品时)。购物车或结账页面并不能自动关闭,这里我们就可以借用 Effect 来解决问题。可以直接修改Cart.js
直接向组件中添加如下的代码:
useEffect(() => {
if(ctx.totalAmount === 0) {
setShowCheckout(false);
setShowDetails(false);
}
}, [ctx]);
这样一来,当购物车中的商品发生变化时,就会触发 useEffect,从而检查商品的总数量,如果总数量为 0 的话就会将购物车详情页和结账也直接隐藏。
清除 Effect ,用于防抖
降低数据过滤的次数,提高用户体验 用户输入完了你在过滤,用户输入的过程中,不要过滤 当用户停止输入动作1秒后,我们才做查询 在开启一个定时器的同时,应该关掉上一次
组件的每次重新渲染 effect 都会执行,有一些情况里,两次 effect 执行会互相影响。比如,在 effect 中设置了一个定时器,总不能每次 effect 执行都设置一个新的定时器,所以我们需要在一个 effect 执行前,清除掉前一个 effect 所带来的影响。要实现这个功能,可以在 effect 中将一个函数作为返回值返回,像是这样:
useEffect(()=>{
/* 编写那些会产生副作用的代码 */
return () => {
/* 这个函数会在下一次effect执行钱调用 */
};
});
在Effect的回调函数中,可以指定一个函数作为返回值
effect 返回的函数,会在下一次 effect 执行前调用,我们可以在这个函数中清除掉前一次 effect 执行所带来的影响。
这个函数可以称其为 清理函数 ,它会在下次Effect执行前调用
可以在这个函数中,做一些工作来清除上次Effect执行所带来的的影响
除了Cart.js
以外,FilterMeals 组件也存在一个问题,首先,该组件中的表单项我们并没有使用 state,所以这个组件是一个非受控组件,虽然目前看来没什么太大的问题,但是我们还是应该处理一下,因为受控组件使用时会更加的灵活,可以适用于更多的场景。其次、该组件的主要作用是过滤汉堡的列表,当用户输入关键字时它可以根据关键字的内容对食物列表进行过滤。问题正在于此,由于每次用户输入都需要过滤,这就意味着它的过滤频率过高了。举个例子,用户要输入“汉堡”这个关键字,他需要一次输入 h-a-n-g-b-a-o 七个字母,由于每次输入都会触发一次过滤,所以在“汉堡”打出来之前,列表完全是一个空白的状态,同时无用的过滤也对应用的性能造成了一定的影响。怎么办呢?同样可以使用 Effect 来解决这个问题,修改 FilterMeals 中的代码如下:
const [keyword, setKeyword] = useState('');
// 通过Effect来改造练习
useEffect(()=>{
// 降低数据过滤的次数,提高用户体验
// 用户输入完了你在过滤,用户输入的过程中,不要过滤
// 当用户停止输入动作1秒后,我们才做查询
// 在开启一个定时器的同时,应该关掉上一次
const timer = setTimeout(()=>{
console.log('Effect触发了!');
props.onFilter(keyword);
}, 1000);
return () => {
clearTimeout(timer);
};
}, [keyword]);