You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React函数组件事件监听器无法获取Redux当前状态求助

问题分析

你遇到的这个问题是闭包陷阱导致的。在函数组件中,useEffect 依赖空数组 [] 时,attachShortcutEvent 只会在组件挂载时执行一次,此时事件监听器里捕获的 dataTable 是组件初始化时的Redux初始状态。之后即使Redux状态更新,这个闭包中的dataTable也不会同步更新——因为函数组件的变量是在每次渲染时重新创建的,而旧的监听器仍然持有第一次渲染时的变量引用。

而类组件中之所以正常,是因为类组件里我们通过this.state访问状态,this是一个始终指向组件实例的引用,每次状态更新后this.state都会指向最新的状态对象,所以事件回调里能拿到最新值。


解决方案

下面提供两种可靠的解决方式,你可以根据需求选择:

方案1:使用useRef保存最新状态引用

我们可以用useRef来维护一个始终指向最新dataTable的引用,每次状态更新时同步更新这个ref,这样事件监听器里就能访问到最新的状态了:

import React, { useState, useEffect, useRef } from "react";
import { useSelector, useDispatch } from "react-redux";
import Paginate from "react-paginate";
import { loadDataTable } from "../../actions/index";

const TabularListPagination = () => {
  const dispatch = useDispatch();
  const dataTable = useSelector((state) => state.dataTable);
  // 创建ref保存最新的dataTable
  const dataTableRef = useRef(dataTable);

  // 每次dataTable更新时,同步更新ref
  useEffect(() => {
    dataTableRef.current = dataTable;
  }, [dataTable]);

  useEffect(() => {
    const handleKeyDown = (event) => {
      // 从ref中获取最新的状态
      const currentData = dataTableRef.current;
      if (event.altKey && event.key === "ArrowLeft") {
        if (currentData.current_page !== 1) {
          dispatch(loadDataTable(currentData.current_page - 1));
        }
      } else if (event.altKey && event.key === "ArrowRight") {
        if (currentData.current_page !== currentData.total_pages) {
          dispatch(loadDataTable(currentData.current_page + 1));
        }
      } else if (event.altKey && event.key === "Home") {
        dispatch(loadDataTable(1));
      } else if (event.altKey && event.key === "End") {
        dispatch(loadDataTable(currentData.total_pages));
      }
    };

    document.addEventListener("keydown", handleKeyDown);
    // 组件卸载时移除监听器,防止内存泄漏
    return () => {
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [dispatch]); // 依赖dispatch,因为dispatch是稳定的引用

  return (
    <Paginate
      containerClassName="pagination"
      forcePage={dataTable.current_page - 1} // 注意:react-paginate的forcePage是从0开始的,原代码存在匹配问题
      pageCount={dataTable.total_pages}
      marginPagesDisplayed={2}
      pageRangeDisplayed={5}
      onPageChange={({ selected }) => {
        dispatch(loadDataTable(selected + 1));
      }}
      nextLabel="&gt;"
      previousLabel="&lt;"
    />
  );
};

export default TabularListPagination;

方案2:让useEffect依赖dataTable,每次状态更新时重新绑定监听器

这种方式是每次dataTable变化时,先移除旧的监听器,再绑定新的监听器,确保监听器里的dataTable始终是最新的:

import React, { useState, useEffect, useRef } from "react";
import { useSelector, useDispatch } from "react-redux";
import Paginate from "react-paginate";
import { loadDataTable } from "../../actions/index";

const TabularListPagination = () => {
  const dispatch = useDispatch();
  const dataTable = useSelector((state) => state.dataTable);

  useEffect(() => {
    const handleKeyDown = (event) => {
      if (event.altKey && event.key === "ArrowLeft") {
        if (dataTable.current_page !== 1) {
          dispatch(loadDataTable(dataTable.current_page - 1));
        }
      } else if (event.altKey && event.key === "ArrowRight") {
        if (dataTable.current_page !== dataTable.total_pages) {
          dispatch(loadDataTable(dataTable.current_page + 1));
        }
      } else if (event.altKey && event.key === "Home") {
        dispatch(loadDataTable(1));
      } else if (event.altKey && event.key === "End") {
        dispatch(loadDataTable(dataTable.total_pages));
      }
    };

    document.addEventListener("keydown", handleKeyDown);
    return () => {
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [dispatch, dataTable]); // 依赖dataTable,每次状态更新时重新绑定

  return (
    <Paginate
      containerClassName="pagination"
      forcePage={dataTable.current_page - 1}
      pageCount={dataTable.total_pages}
      marginPagesDisplayed={2}
      pageRangeDisplayed={5}
      onPageChange={({ selected }) => {
        dispatch(loadDataTable(selected + 1));
      }}
      nextLabel="&gt;"
      previousLabel="&lt;"
    />
  );
};

export default TabularListPagination;

这种方式实现更直接,虽然状态更新时会频繁绑定/解绑监听器,但分页场景下的更新频率完全在可接受范围内。


额外提示
  • 无论哪种方案,都要记得在useEffect的清理函数中移除事件监听器,避免内存泄漏。
  • dispatch是Redux提供的稳定引用,加入依赖数组不会导致不必要的重渲染。

内容的提问来源于stack exchange,提问作者Jake Jeon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 16:42:52