在 react-toolkit 中使用 Immer 将状态设置为零填充数组

Setting state to a zero-filled array with Immer in react-toolkit

我一直在关注 TS usage docs,现在我正在尝试使用 Redux Toolkit 清除数组,但是我的 turnOffAll(state) reducer 导致了数千次重新渲染(控制台日志输出)和一个关于它的错误浏览器。

read 可以 return 状态,而不是从 turnOffAll 中“改变”它。 因此,我尝试了 turnOffAll 的变体,具有:return Array(screenSize.width * screenSize.height).fill(0) 但这会导致 app.jsx 中出现错误,即 dispatch(turnOffAll()); 没有提供足够的参数。

我在 turnOffAll 中尝试的另一种方法是 state = initialState; 但是虽然这不会导致不必要的重新渲染,但如果我在 [=39= 中执行 console.log(useAppSelector((state) => state.pixels)); 则状态不会重置].

文件中的相关信息如下。此代码是我上面第一段中的情况,在从 app.jsx:

调用 dispatch(turnOffAll()); 时会导致数千次重新渲染

pixelsSlice.ts:

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import type { RootState } from "./store";
import { Pixel, ScreenSize } from "./types";

//Defining a type for the slice state
interface PixelsState {
  pixels: Array<number>;
}

const screenSize = { width: 128, height: 59 };

//Define the initial state using the above type.
const initialState: PixelsState = {
  pixels: Array(screenSize.width * screenSize.height).fill(0),
};

export const pixelsSlice = createSlice({
  name: "pixels",
  initialState,
  reducers: {
    turnOn(state, action: PayloadAction<Pixel>) {
      //This may look like mutating state, but Immer makes it ok:
      state.pixels[
        screenSize.width * action.payload.row + action.payload.column
      ] = 1;
    },
    turnOffAll(state) {
      state = initialState;
    },
  },
});

export const { turnOn, turnOffAll } = pixelsSlice.actions;

//Other code such as selectors can use the imported `RootState` type...
export const selectPixels = (state: RootState) => state.pixels;

export default pixelsSlice.reducer;

app.tsx:

import React, { useEffect } from "react";
import { useAppSelector, useAppDispatch } from "./app/hooks";
import { turnOn, turnOffAll } from "./app/pixelsSlice";
import type { Pixel } from "./app/types";

function App() {
  const dispatch = useAppDispatch();

  //Demonstrating reading and writing pixels from the redux store.
  console.log(useAppSelector((state) => state.pixels));

  let p: Pixel = { row: 1, column: 2 };
  dispatch(turnOn(p));
  console.log(useAppSelector((state) => state.pixels));

  dispatch(turnOffAll()); //THIS CAUSES THOUSANDS OF RERENDERS AND CONSOLE LOG OUTPUTS :(
  console.log(useAppSelector((state) => state.pixels));

  return <div className="App"></div>;
}

export default App;

hooks.ts:

import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

您在这里违反了 React 的一个非常基本的规则——甚至 Redux 也不行:

您在渲染过程中触发了副作用。
React 可以多次触发该渲染函数而不将其提交给 DOM。它可以被触发,因为父组件重新渲染,因为状态改变,任何数量的原因。其中一个原因可能是您的 Redux 状态发生了变化。

因此,您分派、更改状态、触发重新渲染、再次分派,这会更改状态(设置为另一个空数组 - [] !== [])并触发另一个渲染。

最重要的是,您的副作用如 dispatches 或状态 setter 调用 总是 发生在 useEffect 或事件中处理程序 - 在 useEffect 的情况下,您还使用了正确的依赖项数组 - 点击 React 文档。

此外,您的 dispatch - useSelector - 调度 useSelector 的工作流程可能不会在这里执行您想要的操作。状态可靠地只在渲染之间改变,而不是在渲染内。

除了@phry 的评论外,Immer-powered reducer 中的行 state = 总是不正确的。您需要改变 stateinside 值,或者 return 一个新值。

如果要替换现有状态,请执行 return initialState

有关详细信息,请参阅 the RTK usage guide page on "Writing Reducers with Immer"