react使用小细节

112 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

在使用react的时候,有时候不注意一些小细节就很容易出问题,今天要分享的就是useEffect的使用。

基本使用:

useEffect总共可以接受两个参数,第一个参数是一个回调函数,其函数体会执行一次,且在依赖数组中的依赖项改变的时候会进行重新执行,组件销毁的时候会执行react后面的代码,这里的依赖数组就是这个hook的第二个参数。

  useEffect(() => {
    effect
    return () => {
      cleanup
    };
  }, [input]);

这里的依赖数组是指在数组中的依赖项一旦改变的时候就要重新执行函数体(不包括return及以后的内容),所以我们要把函数中引用到的依赖项都写到依赖数组中,但是有的时候我们可能没有这么做,那么执行的时候就会出现一些问题,让我们一起看看。

场景重显

需求:我们想在组件一加载的时候就请求后台已经在购物车里面的数据,然后展示按钮才可以正常使用,点击展示按钮,就会在控制塔输出我们已经加入购物车的物品,在组件销毁的时候就把购物车清空。很简单我们就写出了如下的代码

import React from 'react';
import { useRef,useState,useEffect } from 'react';

const App = () => {
  const [goods,setGoods]= useState([]);
  const btn = useRef();
  useEffect(() => {
    //发送请求获取已经在购物车中的商品
    setGoods(["手机","电脑"]);
    //给button绑定事件
    btn.current.onclick=showGoods;
    return () => {
      setGoods([]);
    };
  }, []);
  //展示购物车
  function showGoods(){
    console.log(goods)
  }
  return (
    <div>
      {goods}
      <button ref={btn}>点我显示购物车东西</button>
    </div>
  );
}

export default App;

1.gif

每次都是输出空的数组,这很明显不是我们要的效果。

分析

我们这里用到了setState看过我前面的文章的应该知道,在react自己代码中的时候setState是异步的,所以在下面绑定函数的时候我们的goods还是空数组,所以输出的时候就是空的也理所应当,但是我们来看一下函数showGoods,这难道不是一个闭包吗?函数体内的东西引用着我们的goods,所以虽然是异步更改但是最后通过闭包引用的goods应该也是改变后的goods呀,这样一看好像又解释不清楚了。

其实这样的错误要“归功于”我们的函数式组件,在每次重新渲染的时候我们的showGoods就已经不再是原来的那个函数了,goods也改变了,老的showGoods引用的闭包是老的goods,所以在我们点击展示按钮的时候输出的是空数组,这样一看好像又解释的通了。

那么我们要做的事情就是让我们的绑定函数操作在setState成功之后,在类式组件中这很好实现,我们可以通过setState的第二个参数,通过传入一个回调函数,这个函数就会在state改变之后进行执行,这是我们之前的文章有讲到的,但是我们这里是函数式组件,总不能强行改成类式组件吧。其实实现方法也是很简单,我们只要在每次goods改变的时候我们才进行函数的绑定这样就可以解决我们的问题了。怎么做?我们前面刚提到的依赖数组呀,我们再写一个useEffect,只要我们的goods改变就重新绑定我们的函数,这样不就解决了。

import React from 'react';
import { useRef,useState,useEffect,createRef } from 'react';

const App = () => {
  const [goods,setGoods]= useState([]);
  const btn = createRef();
  useEffect(() => {
    //给button绑定事件
    btn.current.onclick=showGoods;
    return () => {
    };
  }, [goods]);
  useEffect(() => {
    //发送请求获取已经在购物车中的商品
    setGoods(["手机","电脑"]);
    return () => {
      setGoods([]);
    };
  }, []);
  //展示购物车
  function showGoods(){
    console.log(goods)
  }
  return (
    <div>
      {goods}
      <button ref={btn}>点我显示购物车东西</button>
      <h1>{Math.random()}</h1>
    </div>
  );
}

export default App;

2.gif

可以看到我们的需求已经实现了。

其他

这里一共用了两个effect钩子,反正都是在goods改变的时候进行重新绑定函数,那我们可不可以直接把依赖写在加载的那个effect呢?代码如下:

import React from 'react';
import { useRef,useState,useEffect,createRef } from 'react';

const App = () => {
  const [goods,setGoods]= useState([]);
  const btn = createRef();
  useEffect(() => {
    //发送请求获取已经在购物车中的商品
    setGoods(["手机","电脑"]);
    //给button绑定事件
    btn.current.onclick=showGoods;
    return () => {
      setGoods([]);
    };
  }, [goods]);
  //展示购物车
  function showGoods(){
    console.log(goods)
  }
  return (
    <div>
      {goods}
      <button ref={btn}>点我显示购物车东西</button>
      <h1>{Math.random()}</h1>
    </div>
  );
}

export default App;

这肯定是不可以的,如果effect的依赖数组在函数体中有修改,那么这就会陷入死递归,这样你的电脑就会起飞了。

总结

useEffect是我们很经常使用的一个hook,其中的依赖数组一定要小心使用,多了少了都会出现问题。