如何用TypeScript不可变更新Redux状态数组?TodoList开发问题
TypeScript + Redux TodoList 状态更新问题
我正在开发简易TodoList应用学习TypeScript,目前遇到Redux Slice中无法正确不可变更新状态数组的问题。以下是我的代码和报错信息:
主组件 Todolist.tsx
import React, { FC, ChangeEvent, FormEvent, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { update } from '../todoSlice'; import { RootState } from '../todoStore'; const TodoList: FC = () => { // React state array for the todos const [todos, setTodos] = useState<string[]>([]); // Get the input from this state const [input, changeInput] = useState<string>(''); const dispatch = useDispatch() // Selecting the state of the todoSlice component const selector = useSelector((state: RootState) => { return state.todoCard }) const handleChange = (event: ChangeEvent<HTMLInputElement>): void => { event.preventDefault(); changeInput(event.target.value); } const addTodos = (event: FormEvent<HTMLFormElement>): void => { event.preventDefault(); setTodos([...todos, input]); dispatch(update(todos)); console.log("REACT: ", todos); console.log("REDUX: ", selector); } const removeTodos = (index: number): void => { let filteredArray = todos.filter((todo, todoIndex) => ( todoIndex !== index )) setTodos(filteredArray); } return ( <React.Fragment> <div className='bg-light d-inline-block text-center mt-4' style={{ padding: '10vh', marginLeft: '4vw' }}> <h1 className='mb-4'>Todo List</h1> <section className='text-wrap mb-4' style={{ overflowY: 'auto', height: '10rem' }}> { // Mapping thee todo-list items todos.map((todo, index) => ( <React.Fragment key={index}> <div className='d-flex justify-content-between mb-3'> <li className='d-block'>{todo}</li> <button onClick={() => {removeTodos(index)}}>X</button> </div> </React.Fragment> )) } </section> <form onSubmit={addTodos}> <input type='text' name="input" className='p-1 rounded border-success' onChange={handleChange}></input> <button type="submit" className='ms-3 btn btn-success mb-1'>Enter</button> </form> </div> </React.Fragment> ) } export default TodoList
Redux Slice todoSlice.ts
import { createSlice } from '@reduxjs/toolkit' export const todoSlice = createSlice({ name: 'todo', initialState: { todoCard: [], }, reducers: { update: (state, action) => { return { // Return a copy of the array and SHOULD update the state array here "immutably" ...state, todoCard: [ ...state.todoCard, action.payload ] } } } }) export const { update } = todoSlice.actions export default todoSlice.reducer
报错信息
TypeScript类型错误:
Type 'string[][]' is not assignable to type 'string[]'. Type 'string[]' is not assignable to type 'string'.
问题根源与解决方案
1. 传递错误的payload到Redux
你在addTodos中dispatch(update(todos))传递了整个todos数组,而非新添加的单个input值,导致Redux的todoCard变成数组嵌套数组,触发类型不匹配错误。
2. 冗余维护React状态与Redux状态
同时维护React本地状态和Redux状态会导致数据不一致,应以Redux作为单一数据源。
3. 未利用Redux Toolkit的Immer能力
Redux Toolkit内置Immer,可以直接"修改"状态,无需手动编写复杂的不可变更新逻辑。
修正后的代码
修正 todoSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit' // 明确定义状态类型 interface TodoState { todoCard: string[] } const initialState: TodoState = { todoCard: [], } export const todoSlice = createSlice({ name: 'todo', initialState, reducers: { // 指定payload为单个字符串类型 addTodo: (state, action: PayloadAction<string>) => { // Immer自动处理不可变更新,直接push即可 state.todoCard.push(action.payload); }, // 添加删除todo的reducer removeTodo: (state, action: PayloadAction<number>) => { state.todoCard = state.todoCard.filter((_, index) => index !== action.payload); } } }) export const { addTodo, removeTodo } = todoSlice.actions export default todoSlice.reducer
修正 Todolist.tsx
import React, { FC, ChangeEvent, FormEvent, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { addTodo, removeTodo } from '../todoSlice'; import { RootState } from '../todoStore'; const TodoList: FC = () => { // 直接从Redux获取todos,不再维护本地状态 const todos = useSelector((state: RootState) => state.todo.todoCard); const [input, changeInput] = useState<string>(''); const dispatch = useDispatch() const handleChange = (event: ChangeEvent<HTMLInputElement>): void => { changeInput(event.target.value); } const addTodos = (event: FormEvent<HTMLFormElement>): void => { event.preventDefault(); if (!input.trim()) return; // 过滤空输入 // 传递单个input值到Redux dispatch(addTodo(input)); changeInput(''); // 清空输入框 } return ( <React.Fragment> <div className='bg-light d-inline-block text-center mt-4' style={{ padding: '10vh', marginLeft: '4vw' }}> <h1 className='mb-4'>Todo List</h1> <section className='text-wrap mb-4' style={{ overflowY: 'auto', height: '10rem' }}> { todos.map((todo, index) => ( <React.Fragment key={index}> <div className='d-flex justify-content-between mb-3'> <li className='d-block'>{todo}</li> {/* 直接dispatch删除动作 */} <button onClick={() => dispatch(removeTodo(index))}>X</button> </div> </React.Fragment> )) } </section> <form onSubmit={addTodos}> <input type='text' name="input" className='p-1 rounded border-success' onChange={handleChange} value={input}></input> <button type="submit" className='ms-3 btn btn-success mb-1'>Enter</button> </form> </div> </React.Fragment> ) } export default TodoList
补充说明
确保你的RootState类型正确,包含todo切片:
// todoStore.ts import { configureStore } from '@reduxjs/toolkit' import todoReducer from './todoSlice' export const store = configureStore({ reducer: { todo: todoReducer, }, }) export type RootState = ReturnType<typeof store.getState> export type AppDispatch = typeof store.dispatch
内容的提问来源于stack exchange,提问作者lgab1219
相关产品推荐
相关产品推荐

