You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

额外优化建议

  1. 初始化状态优化:将App.jsx中todos的初始值从null改为空数组[],简化后续条件判断:
const [todos, setTodos] = useState([]);

对应InputTodo.jsx的提交逻辑可简化为:

setTodos((current) => [...current, { id: uuid(), name: inputValue, completed: false }])
  1. 减少不必要的 Context 消费:TodoItem仅需setTodos,无需解构todos,避免不必要的依赖绑定。

内容的提问来源于stack exchange,提问作者jokay03J-V2

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 11:10:41