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=">" previousLabel="<" /> ); }; 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=">" previousLabel="<" /> ); }; export default TabularListPagination;
这种方式实现更直接,虽然状态更新时会频繁绑定/解绑监听器,但分页场景下的更新频率完全在可接受范围内。
额外提示
- 无论哪种方案,都要记得在
useEffect的清理函数中移除事件监听器,避免内存泄漏。 dispatch是Redux提供的稳定引用,加入依赖数组不会导致不必要的重渲染。
内容的提问来源于stack exchange,提问作者Jake Jeon
相关产品推荐
相关产品推荐

