上下文 Api 状态没有改变
Context Api state is not changing
我正在尝试从使用 useContext 挂钩使用上下文的组件调用上下文提供程序内部名为 deleteTask 的函数,该函数从上下文提供程序状态的数组中删除某个项目,但是当我这样做时它,提供者的状态根本没有改变,我尝试跟踪问题并且函数执行但它似乎是在复制的提供者的范围内执行?还尝试了一个添加任务的功能,我遇到了同样的问题。我还添加了一个功能来设置活动任务,我不知道为什么那个有效,而其他的却没有。我真的不知道发生了什么,这是代码,请帮助我:
tasks-context.jsx
import React, { useState } from 'react';
import { useEffect } from 'react';
const dummyTasks = [{
task: {
text: 'hello',
},
key: 0,
isActive: false
},
{
task: {
text: 'hello 2',
},
key: 1,
isActive: false
}];
export const TasksContext = React.createContext({ });
export const TasksProvider = ( props ) => {
const [ tasks, setTasks ] = useState( dummyTasks );
const [ activeTask, setActiveTask ] = useState();
//NOT WORKING
const deleteTask = ( taskToDeleteKey ) =>{
setActiveTask( null );
setTasks( tasks.filter( task => task.key !== taskToDeleteKey ));
};
//THIS ONE WORKS (??)
const handleSelectTask = ( taskToSelect, key ) =>{
setActiveTask( taskToSelect );
const newTaskArray = tasks.map( task => {
if( task.key === key ){
task.isActive = true;
}else{
ficha.isActive = false;
}
return task;
});
setTask( newTaskArray );
};
return ( <TasksContext.Provider
value={{ tasks,
activeTask,
addTask,
deleteTask,
handleSelectTask}}>
{props.children}
</TasksContext.Provider>
);
};
“主要”
Main.jsx
import React from 'react';
import './assets/styles/gestion-style.css';
import './assets/styles/icons.css';
import { TasksProvider } from '../../Context/tasks-context';
import TaskContainer from './components/taskContainer.jsx';
function Main( props ) {
return (
<TasksProvider>
<TaskContainer />
</TasksProvider>
);
}
任务容器映射任务数组:
TaskContainer.jsx
import React, { useContext, useEffect } from 'react';
import TaskTab from './TaskTab';
import { TasksContext } from '../../Context/tasks-context';
function TaskContainer( props ) {
const { tasks } = useContext( TasksContext );
return (
<div className="boxes" style={{ maxWidth: '100%', overflow: 'hidden' }}>
{tasks? tasks.map( taskTab=>
( <TaskTab task={taskTab.task} isActive={taskTab.isActive} key={taskTab.key} taskTabKey={taskTab.key} /> ))
:
null
}
</div>
);
}
export default TaskContainer;
以及我调用上下文函数删除的任务组件:
TaskTab.jsx
import React, { useContext } from 'react';
import { TasksContext } from '../../Context/tasks-context';
function TaskTab( props ) {
let { task, isActive, taskTabKey } = props;
const { handleSelectTask, deleteTask } = useContext( TasksContext );
const selectTask = ()=>{
handleSelectTask( task, taskTabKey );
};
const handleDelete = () =>{
deleteTask( taskTabKey );
};
return (
<div onClick={ selectTask }>
<article className={`${task.type} ${isActive ? 'active' : null}`}>
<p className="user">{task.text}</p>
<button onClick={handleDelete}>
<i className="icon-close"></i>
</button>
</article>
</div>
);
}
export default TaskTab;
Lucas,这不是 Context 或 Provider 的问题。
您面临的问题实际上是一种称为事件冒泡的机制,在该机制中,当前处理程序先执行,然后再执行父处理程序。
可以在此处找到有关事件冒泡的更多信息。 https://javascript.info/bubbling-and-capturing.
在您的情况下,首先调用 handleDelete 函数,然后调用 handleSelect 函数。
解法:event.stopPropagation();
将您的 handleDelete 和 handleSelect 函数更改为此
const selectTask = () => {
console.log("handle select called");
handleSelectTask(task, taskTabKey);
};
const handleDelete = event => {
console.log("handle delete called");
event.stopPropagation();
deleteTask(taskTabKey);
};
现在检查您的控制台,您会发现只会打印调用的 handle delete,这有望解决您的问题。
如果还是不行,请告诉我。我会为你制作一个codesandbox版本。
编码愉快。
感谢您的提问!
这里发生的事情令人困惑是可以理解的,我自己花了一段时间才意识到这一点。
TL;DR:由于事件传播,每次单击 deleteTask
按钮时都会调用提供程序中的 handleSelectTask
。 handleSelectTask
没有使用已被 deleteTask
修改的状态,即使它在它之后是 运行,因为它已经关闭了初始 tasks
数组。
快速解决方案 1
阻止事件从删除按钮单击传播到任务选项卡 div 单击,这可能是所需的行为。
// in TaskTab.jsx
const handleDelete = (event) => {
event.stopPropagation(); // stops event from "bubbling" up the tree
deleteTask(taskTabKey);
}
在 DOM(也被 React 模拟)中,事件在树中“冒泡”,因此父节点可以处理来自其子节点的事件。在示例中,<button onClick={handleDelete}>
是 <div onClick={selectTask}>
的子项,这意味着当从按钮触发单击事件时,它会像我们想要的那样首先调用 handleDelete
函数,但是之后它将 也 从父 div 调用 selectTask
函数,这可能是无意的。您可以在 MDN.
上阅读有关事件传播的更多信息
快速解决方案 2
编写状态更新以在调用它们时使用中间状态值。
// in tasks-context.jsx
const deleteTask = ( taskToDeleteKey ) => {
setActiveTask(null);
// use the function version of setting state to read the current value whenever it is run
setTasks((stateTasks) => stateTasks.filter(task => task.key !== taskToDeleteKey));
}
const handleSelectTask = ( taskToSelect, key ) =>{
setActiveTask( taskToSelect );
// updated to use the callback version of the state update
setTasks((stateTasks) => stateTasks.map( task => {
// set the correct one to active
}));
};
使用 setTasks
状态更新的回调版本,它实际上会在应用更新时读取值(包括尤其是在更新中间!),因为handleSelectTask
是在后面调用的,意思是它实际上先看到了运行 已经被deleteTask
修改过的数组!您可以在 React 文档 (hooks) (setState) 中阅读有关设置状态的回调变体的更多信息。请注意,此“修复”意味着即使任务已被删除,您的组件仍将调用 handleSelectTask
。它不会有任何 ill-effects,请注意。
让我们更详细地了解一下发生了什么:
首先,tasks
变量是从 useState
创建的。整个组件都使用了这个相同的变量,这是完全正常的。
// created here
const [ tasks, setTasks ] = useState( dummyTasks );
const [ activeTask, setActiveTask ] = useState();
const deleteTask = ( taskToDeleteKey ) =>{
setActiveTask( null );
// referenced here, no big deal
setTasks( tasks.filter( task => task.key !== taskToDeleteKey ));
};
const handleSelectTask = ( taskToSelect, key ) =>{
setActiveTask( taskToSelect );
// tasks is referenced here, too, awesome
const newTaskArray = tasks.map( task => {
if( task.key === key ){
task.isActive = true;
}else{
task.isActive = false;
}
return task;
});
setTasks( newTaskArray );
};
问题来了,如果两个函数都试图在同一个渲染周期中更新同一个状态值,它们都将引用任务数组的原始值,即使另一个函数已尝试更新状态值!在您的情况下,因为 handleSelectTask
是 运行 after deleteTask
,这意味着 handleSelectTask
将使用具有的数组更新状态已修改!当它运行时,它仍然会看到数组中的两个项目,因为 tasks
变量在更新实际提交并且所有内容重新呈现之前不会更改。这使得删除部分看起来好像不起作用,而实际上它的效果只是被丢弃了,因为 handleSelectTask
不知道删除发生在它之前。
我正在尝试从使用 useContext 挂钩使用上下文的组件调用上下文提供程序内部名为 deleteTask 的函数,该函数从上下文提供程序状态的数组中删除某个项目,但是当我这样做时它,提供者的状态根本没有改变,我尝试跟踪问题并且函数执行但它似乎是在复制的提供者的范围内执行?还尝试了一个添加任务的功能,我遇到了同样的问题。我还添加了一个功能来设置活动任务,我不知道为什么那个有效,而其他的却没有。我真的不知道发生了什么,这是代码,请帮助我:
tasks-context.jsx
import React, { useState } from 'react';
import { useEffect } from 'react';
const dummyTasks = [{
task: {
text: 'hello',
},
key: 0,
isActive: false
},
{
task: {
text: 'hello 2',
},
key: 1,
isActive: false
}];
export const TasksContext = React.createContext({ });
export const TasksProvider = ( props ) => {
const [ tasks, setTasks ] = useState( dummyTasks );
const [ activeTask, setActiveTask ] = useState();
//NOT WORKING
const deleteTask = ( taskToDeleteKey ) =>{
setActiveTask( null );
setTasks( tasks.filter( task => task.key !== taskToDeleteKey ));
};
//THIS ONE WORKS (??)
const handleSelectTask = ( taskToSelect, key ) =>{
setActiveTask( taskToSelect );
const newTaskArray = tasks.map( task => {
if( task.key === key ){
task.isActive = true;
}else{
ficha.isActive = false;
}
return task;
});
setTask( newTaskArray );
};
return ( <TasksContext.Provider
value={{ tasks,
activeTask,
addTask,
deleteTask,
handleSelectTask}}>
{props.children}
</TasksContext.Provider>
);
};
“主要”
Main.jsx
import React from 'react';
import './assets/styles/gestion-style.css';
import './assets/styles/icons.css';
import { TasksProvider } from '../../Context/tasks-context';
import TaskContainer from './components/taskContainer.jsx';
function Main( props ) {
return (
<TasksProvider>
<TaskContainer />
</TasksProvider>
);
}
任务容器映射任务数组:
TaskContainer.jsx
import React, { useContext, useEffect } from 'react';
import TaskTab from './TaskTab';
import { TasksContext } from '../../Context/tasks-context';
function TaskContainer( props ) {
const { tasks } = useContext( TasksContext );
return (
<div className="boxes" style={{ maxWidth: '100%', overflow: 'hidden' }}>
{tasks? tasks.map( taskTab=>
( <TaskTab task={taskTab.task} isActive={taskTab.isActive} key={taskTab.key} taskTabKey={taskTab.key} /> ))
:
null
}
</div>
);
}
export default TaskContainer;
以及我调用上下文函数删除的任务组件:
TaskTab.jsx
import React, { useContext } from 'react';
import { TasksContext } from '../../Context/tasks-context';
function TaskTab( props ) {
let { task, isActive, taskTabKey } = props;
const { handleSelectTask, deleteTask } = useContext( TasksContext );
const selectTask = ()=>{
handleSelectTask( task, taskTabKey );
};
const handleDelete = () =>{
deleteTask( taskTabKey );
};
return (
<div onClick={ selectTask }>
<article className={`${task.type} ${isActive ? 'active' : null}`}>
<p className="user">{task.text}</p>
<button onClick={handleDelete}>
<i className="icon-close"></i>
</button>
</article>
</div>
);
}
export default TaskTab;
Lucas,这不是 Context 或 Provider 的问题。
您面临的问题实际上是一种称为事件冒泡的机制,在该机制中,当前处理程序先执行,然后再执行父处理程序。
可以在此处找到有关事件冒泡的更多信息。 https://javascript.info/bubbling-and-capturing.
在您的情况下,首先调用 handleDelete 函数,然后调用 handleSelect 函数。
解法:event.stopPropagation();
将您的 handleDelete 和 handleSelect 函数更改为此
const selectTask = () => {
console.log("handle select called");
handleSelectTask(task, taskTabKey);
};
const handleDelete = event => {
console.log("handle delete called");
event.stopPropagation();
deleteTask(taskTabKey);
};
现在检查您的控制台,您会发现只会打印调用的 handle delete,这有望解决您的问题。
如果还是不行,请告诉我。我会为你制作一个codesandbox版本。
编码愉快。
感谢您的提问!
这里发生的事情令人困惑是可以理解的,我自己花了一段时间才意识到这一点。
TL;DR:由于事件传播,每次单击 deleteTask
按钮时都会调用提供程序中的 handleSelectTask
。 handleSelectTask
没有使用已被 deleteTask
修改的状态,即使它在它之后是 运行,因为它已经关闭了初始 tasks
数组。
快速解决方案 1
阻止事件从删除按钮单击传播到任务选项卡 div 单击,这可能是所需的行为。
// in TaskTab.jsx
const handleDelete = (event) => {
event.stopPropagation(); // stops event from "bubbling" up the tree
deleteTask(taskTabKey);
}
在 DOM(也被 React 模拟)中,事件在树中“冒泡”,因此父节点可以处理来自其子节点的事件。在示例中,<button onClick={handleDelete}>
是 <div onClick={selectTask}>
的子项,这意味着当从按钮触发单击事件时,它会像我们想要的那样首先调用 handleDelete
函数,但是之后它将 也 从父 div 调用 selectTask
函数,这可能是无意的。您可以在 MDN.
快速解决方案 2
编写状态更新以在调用它们时使用中间状态值。
// in tasks-context.jsx
const deleteTask = ( taskToDeleteKey ) => {
setActiveTask(null);
// use the function version of setting state to read the current value whenever it is run
setTasks((stateTasks) => stateTasks.filter(task => task.key !== taskToDeleteKey));
}
const handleSelectTask = ( taskToSelect, key ) =>{
setActiveTask( taskToSelect );
// updated to use the callback version of the state update
setTasks((stateTasks) => stateTasks.map( task => {
// set the correct one to active
}));
};
使用 setTasks
状态更新的回调版本,它实际上会在应用更新时读取值(包括尤其是在更新中间!),因为handleSelectTask
是在后面调用的,意思是它实际上先看到了运行 已经被deleteTask
修改过的数组!您可以在 React 文档 (hooks) (setState) 中阅读有关设置状态的回调变体的更多信息。请注意,此“修复”意味着即使任务已被删除,您的组件仍将调用 handleSelectTask
。它不会有任何 ill-effects,请注意。
让我们更详细地了解一下发生了什么:
首先,tasks
变量是从 useState
创建的。整个组件都使用了这个相同的变量,这是完全正常的。
// created here
const [ tasks, setTasks ] = useState( dummyTasks );
const [ activeTask, setActiveTask ] = useState();
const deleteTask = ( taskToDeleteKey ) =>{
setActiveTask( null );
// referenced here, no big deal
setTasks( tasks.filter( task => task.key !== taskToDeleteKey ));
};
const handleSelectTask = ( taskToSelect, key ) =>{
setActiveTask( taskToSelect );
// tasks is referenced here, too, awesome
const newTaskArray = tasks.map( task => {
if( task.key === key ){
task.isActive = true;
}else{
task.isActive = false;
}
return task;
});
setTasks( newTaskArray );
};
问题来了,如果两个函数都试图在同一个渲染周期中更新同一个状态值,它们都将引用任务数组的原始值,即使另一个函数已尝试更新状态值!在您的情况下,因为 handleSelectTask
是 运行 after deleteTask
,这意味着 handleSelectTask
将使用具有的数组更新状态已修改!当它运行时,它仍然会看到数组中的两个项目,因为 tasks
变量在更新实际提交并且所有内容重新呈现之前不会更改。这使得删除部分看起来好像不起作用,而实际上它的效果只是被丢弃了,因为 handleSelectTask
不知道删除发生在它之前。