React上下文值变更时组件不重渲染问题求助
React Context 修改 Todo 状态后组件不重渲染问题
我用 TailwindCSS、React 18.2.0 和 Vite 3.2.4 开发 Todo 应用,点击 TodoItem 组件时希望通过 React Context 修改对应 todo 的completed字段,但上下文值变更后组件没有重渲染。以下是代码实现:
App.jsx
import { useState } from 'react' import InputTodo from './components/InputTodo' import Todos from './components/Todos' import TodosContext from './contexts/todos' function App() { const [todos, setTodos] = useState(null); const value = { todos, setTodos } return ( <TodosContext.Provider value={value}> <div className="App flex flex-col items-center w-screen h-screen"> <InputTodo></InputTodo> <Todos></Todos> </div> </TodosContext.Provider> ) } export default App
TodoContext.js
import { createContext } from "react"; const TodosContext = createContext(null) export default TodosContext;
InputTodo.jsx(向上下文添加 todo 的组件)
import { useContext, useState } from 'react' import TodosContext from '../contexts/todos'; import { v4 as uuid} from "uuid" function InputTodo() { const [inputValue, setInputValue] = useState(""); const { todos, setTodos } = useContext(TodosContext); const handleSubmit = (ev) => { ev.preventDefault(); setTodos((current) => { if (todos) return [...todos, { id: uuid(), name: inputValue, completed: false }] else { return [{ id: uuid(), name: inputValue, completed: false }] } }) setInputValue("") } return ( <form className="App flex m-8" onSubmit={handleSubmit}> <input className='border-sm border-2 border-black text-center focus:outline-none rounded-lg' value={inputValue} placeholder='entrer votre todo' onChange={(ev) => setInputValue(ev.target.value)}></input> <button type='submit' className='flex justify-center items-center mx-4 border-2 border-black rounded-lg'>envoyer</button> </form> ) } export default InputTodo
Todos.jsx(遍历所有 todo 的组件)
import { useContext } from "react"; import TodosContext from "../contexts/todos"; import TodoItem from "./TodoItem"; function Todos() { const { todos, setTodos } = useContext(TodosContext) return ( <div className="w-10/12 h-5/6 overflow-visible border border-black">{!todos ? <h1 className="text-center text-3xl">aucune todo</h1> : todos.map((todo) => <TodoItem key={todo.id} name={todo.name} id={todo.id} completed={todo.completed}></TodoItem>)}</div> ) } export default Todos;
TodoItem.jsx(渲染在 Todos.jsx 中的组件)
import { useContext } from "react"; import TodosContext from "../contexts/todos"; function TodoItem({ id, name, completed }) { const { todos, setTodos } = useContext(TodosContext) return ( <li className="w-full h-12 flex items-center justify-center" onClick={() => { setTodos((prevState)=>{ const index = todos.findIndex((todoItem) => todoItem.id === id) const newState = todos newState[index].completed = true return newState; }) }}><div className={`${completed ? "line-through" : ""}`}>{name}</div></li> ) } export default TodoItem;
问题原因与修复方案
核心问题
在TodoItem.jsx的点击事件处理中,你直接修改了原数组的元素且未创建新数组引用:
const newState = todos newState[index].completed = true return newState;
React 依赖状态引用的变化触发重渲染,这里newState和原todos指向同一个数组,React 判定状态未变更,因此不会重新渲染组件。同时,回调中使用外部的todos而非prevState,存在闭包导致的状态过期风险。
修复后的 TodoItem.jsx
import { useContext } from "react"; import TodosContext from "../contexts/todos"; function TodoItem({ id, name, completed }) { const { setTodos } = useContext(TodosContext) return ( <li className="w-full h-12 flex items-center justify-center" onClick={() => { setTodos((prevState) => { const index = prevState.findIndex((todoItem) => todoItem.id === id) // 复制原数组,创建新引用 const newTodos = [...prevState] // 复制目标 todo 对象并切换 completed 状态 newTodos[index] = { ...newTodos[index], completed: !newTodos[index].completed } return newTodos; }) }}><div className={`${completed ? "line-through" : ""}`}>{name}</div></li> ) } export default TodoItem;
额外优化建议
- 初始化状态优化:将
App.jsx中todos的初始值从null改为空数组[],简化后续条件判断:
const [todos, setTodos] = useState([]);
对应InputTodo.jsx的提交逻辑可简化为:
setTodos((current) => [...current, { id: uuid(), name: inputValue, completed: false }])
- 减少不必要的 Context 消费:
TodoItem仅需setTodos,无需解构todos,避免不必要的依赖绑定。
内容的提问来源于stack exchange,提问作者jokay03J-V2
相关产品推荐
相关产品推荐

