我有一个 React 项目,我试图通过将逻辑移动到钩子来将逻辑与 UI 分开。所讨论的钩子从 RTK 查询收集分页数据并将其聚合到状态变量
transactions
中。如果我将Transactions 设置为这个聚合,则不会有问题,但如果我尝试以任何方式更改它,我会看到组件重新渲染多次,从而导致最大深度超出错误。
我已将这个问题提炼为最基本的要素,删除了 UI 和所有不会导致错误的逻辑。
AccountTransactions 组件:
export default function AccountTransactions() {
const { transactions, handleMoreTransactionsClick, handleDeleteTransaction } = useTransactions(68);
console.log('transactions', transactions); // I see this log many times
return (
<div></div>
);
}
TransactionsHook 自定义挂钩:
export default function useTransactions (accountId) {
const [page, setPage] = useState(1);
const [transactions, setTransactions] = useState([]);
// load transactions
const {
data = [],
isSuccess,
} = useGetTransactionsQuery({accountId: accountId, page: page});
useEffect(() => {
// setTransactions(transactions); // this doesn't cause rerenders
setTransactions([]); // this causes rerenders
}, [data]);
return { transactions };
}
我使用
data
作为钩子 useEffect 的依赖项,因为 isLoading
在加载第一页后保持 true,并且后续页面不会加载。但是,使用 data
作为依赖项并更改 transactions
的某种组合会触发组件的重新渲染。让我更困惑的是,在这个简化代码中,transactions
是[]
,但为其分配新值[]
也会导致重新渲染。
为了完整起见,这里是 RTK 减速器,尽管我不认为这会导致问题。
getTransactions: build.query({
query: ({accountId, page}) => ({
url: `/api/transactions/account/${accountId}`,
method: 'get',
params: {page}
}),
providesTags: (result = [], error, arg) => [
'Transaction',
...result.data.map(({ id }) => ({ type: 'Transaction', id })),
],
}),
我仍然在思考钩子(甚至渲染过程),但我看不出我是如何违反任何规则的。其他人能发现什么吗?
setTransactions(transactions);
不会导致渲染,因为先前状态 transactions
和新状态 transactions
是相同的对象(相同的对象引用)。因此,钩子 useGetTransactionsQuery
仅调用一次。
setTransactions([])
导致渲染循环,因为[]
创建了新对象(状态更改),这导致渲染,这导致useGetTransactionsQuery
钩子调用,这改变了data
并导致效果。