useEffect 闭包陷阱

2021-10-11  本文已影响0人  菜鸡前端

使用 useEffect 比较容易出现问题是闭包陷阱,尽量尝试不使用 useEffect,见第4。

1. 错误演示

import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { Button } from "antd";
import "./styles.css";

window.events = [];

window.test = function () {
  window.events.forEach(it => {
    it()
  });
}

function App() {
  const [count, setCount] = useState(0);
  const ref = useRef();

  const handleClick = (event) => {
    console.log(count);
  }
  window.events.push(handleClick);

  useEffect(() => {
    ref.current.addEventListener("click", handleScroll);
    return () => {
      ref.current.removeEventListener("click", handleScroll);
    };
  }, []);

  return (
    <div ref={ref}>
      <div>{count}</div>
      <Button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        点击
      </Button>
    </div>
  );
}

const rootElement = document.getElementById("root");

function render() {
  ReactDOM.render(<App />, rootElement);
}

render();

点击3次后,在控制台调用 test 函数:


image

在 events 中存了4个 handleClick,分别打印 0、1、2、3,分别引用了4个闭包变量 count。在使用 useEffect 时,没有没有 deps,那么当前的 handleClick,永远是第一次渲染 App 时,所创建的 handleClick。

2. 使用 useEffect 时要保证依赖正确

上述问题因为依赖的配置不正确导致的,如果 effect 中有依赖外部变量,需要添加到依赖中

import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { Button } from "antd";
import "./styles.css";

function App() {
  const [count, setCount] = useState(0);
  const ref = useRef();

  const handleClick = (event) => {
    console.log(count);
  }

  useEffect(() => {
    ref.current.addEventListener("scroll", handleClick);
    return () => {
      ref.current.removeEventListener("scroll", handleClick);
    };
    // effect 中依赖了 handleClick,
    // 每次 App reRender,创建了新的 handleClick
    // 需要添加到 deps 中,否则依赖的 handleClick 所在的作用域,永远是第一次 render,已经过期了
  }, [handleClick]);

  return (
    <div ref={ref} style={{ overflow: 'auto', height: '100vh', border: '1px solid red' }}>
      <div>{count}</div>
      <Button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        点击
      </Button>
      <div style={{ height: 20000}}></div>
    </div>
  );
}

const rootElement = document.getElementById("root");

function render() {
  ReactDOM.render(<App />, rootElement);
}

render();

3. 尝试将 effect 的依赖项,定义到 effect 内部

import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { Button } from "antd";
import "./styles.css";

function App() {
  const [count, setCount] = useState(0);
  const ref = useRef();

  useEffect(() => {
    // 将 handleClick 移到内部后,发现 effect 内 依赖了 count state
    // 将 count 添加到依赖中
    // 否则 handleClick 依赖的 count,永远是第一次 App 调用的作用域下的
    const handleClick = (event) => {
      console.log(count);
    }
    ref.current.addEventListener("scroll", handleClick);
    return () => {
      ref.current.removeEventListener("scroll", handleClick);
    };
  }, [count]);

  return (
    <div ref={ref} style={{ overflow: 'auto', height: '100vh', border: '1px solid red' }}>
      <div>{count}</div>
      <Button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        点击
      </Button>
      <div style={{ height: 20000}}></div>
    </div>
  );
}

const rootElement = document.getElementById("root");

function render() {
  ReactDOM.render(<App />, rootElement);
}

render();

4. 尽量不使用 useEffect

import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { Button } from "antd";
import "./styles.css";

function App() {
  const [count, setCount] = useState(0);
  const ref = useRef();

  const handleScroll = (event) => {
    console.log(count);
  }

  return (
    <div ref={ref} onScroll={handleScroll} style={{ overflow: 'auto', height: '100vh', border: '1px solid red' }}>
      <div>{count}</div>
      <Button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        点击
      </Button>
      <div style={{ height: 20000}}></div>
    </div>
  );
}

const rootElement = document.getElementById("root");

function render() {
  ReactDOM.render(<App />, rootElement);
}

render();
上一篇 下一篇

猜你喜欢

热点阅读