useEffect 閉包陷阱

使用 useEffect 比較容易出現(xiàn)問題是閉包陷阱,盡量嘗試不使用 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 函數(shù):


image

在 events 中存了4個 handleClick,分別打印 0、1、2、3,分別引用了4個閉包變量 count。在使用 useEffect 時,沒有沒有 deps,那么當前的 handleClick,永遠是第一次渲染 App 時,所創(chuàng)建的 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,創(chuàng)建了新的 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 移到內部后,發(fā)現(xiàn) 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();
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容