39-useEffect副作用-组件渲染后执行

Huxzhi大约 5 分钟react18

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]);