使用 useEffect 通信 Parent 和 Child 组件
Communication Parent and Child component with useEffect
我在 parent 和 child 组件之间的通信有问题。
我希望 parent(主持人)保持他自己的状态。我希望 child (来宾)通过该状态并对其进行修改。 child 有他的本地版本的状态,可以随 child 的需要而改变。但是,一旦 child 完成状态播放,他会将其传递给 parent 以实际 "Save" 实际状态。
我该如何正确实施?
我的代码中的问题:
在 updateGlobalData 处理程序上,我记录了数据和 newDataFromGuest,它们是相同的。我想data代表旧版本的数据,newDataFromGuest代表new
updateGlobalData 被调用了 2 次。我可以通过从 useEffect 中的 deps 数组中删除 updateGlobalData ref 来解决这个问题,但我不想搞砸它。
我想要的结果应该是:
- 数据状态应保存旧数据,直到调用 updateGlobalData
- 我希望 updateGlobalData 在我单击按钮时只触发一次
来自 Codesandbox 的代码:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const Host = () => {
const [data, setData] = useState({ foo: { bar: 1 } });
const updateGlobalData = newDataFromGuest => {
console.log(data);
console.log(newDataFromGuest);
setData(newDataFromGuest);
};
return <Guest data={data} updateGlobalData={updateGlobalData} />;
};
const Guest = ({ data, updateGlobalData }) => {
const [localData, setLocalData] = useState(data);
const changeLocalData = newBarNumber => {
localData.foo = { bar: newBarNumber };
setLocalData({ ...localData });
};
useEffect(() => {
updateGlobalData(localData);
}, [localData, updateGlobalData]);
return (
<div>
<span>{localData.foo.bar}</span> <br />
<button onClick={() => changeLocalData(++localData.foo.bar)}>
Increment
</button>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Host />, rootElement);
注意:下面的代码解决方案
- 问题 1:
I want updateGlobalData to be fired only once when I click the button
为了解决这个问题,我混合使用了 React.createContext and the hook useReducer。这个想法是通过其上下文使主机调度程序可用。这样,您不需要将 "updateGlobalData" 回调发送给 Guest,也不需要使 useEffect 挂钩依赖于它。因此,useEffect只会被触发一次。
但请注意,useEffect 现在依赖于主机 dipatcher,您需要将其包含在其依赖项中。不过,如果您阅读 useReducer 上的第一条注释,调度程序是稳定的,不会导致重新渲染。
- 问题 2:
the data state should hold the old data until updateGlobalData is called
解决方法很简单:不要直接更改状态数据!!请记住,Javascript 中的大多数值都是通过引用传递的。如果你给Guest发数据你直接修改,像这里
const changeLocalData = newBarNumber => {
localData.foo = { bar: newBarNumber }; // YOU ARE MODIFYING STATE DIRECTLY!!!
...
};
这里
<button onClick={() => changeLocalData(++localData.foo.bar)}> // ++ OPERATOR MODIFYES STATE DIRECLTY
它们也将在主机中被修改,除非您通过 useState 挂钩更改该数据。我认为(不是 100% 确定)这是因为 Guest 中的 localData 是使用与来自 Host 的数据相同的引用进行初始化的。因此,如果您在 Guest 中直接更改它,它也会在 Host 中更改。只需将本地数据的值加 1 即可更新来宾状态,而无需使用 ++ 运算符。像这样:
localData.foo.bar + 1
这是my solution:
import React, { useState, useEffect, useReducer, useContext } from "react";
import ReactDOM from "react-dom";
const HostContext = React.createContext(null);
function hostReducer(state, action) {
switch (action.type) {
case "setState":
console.log("previous Host data value", state);
console.log("new Host data value", action.payload);
return action.payload;
default:
throw new Error();
}
}
const Host = () => {
// const [data, setData] = useState({ foo: { bar: 1 } });
// Note: `dispatch` won't change between re-renders
const [data, dispatch] = useReducer(hostReducer, { foo: { bar: 1 } });
// const updateGlobalData = newDataFromGuest => {
// console.log(data.foo.bar);
// console.log(newDataFromGuest.foo.bar);
// setData(newDataFromGuest);
// };
return (
<HostContext.Provider value={dispatch}>
<Guest data={data} /*updateGlobalData={updateGlobalData}*/ />
</HostContext.Provider>
);
};
const Guest = ({ data /*, updateGlobalData*/ }) => {
// If we want to perform an action, we can get dispatch from context.
const hostDispatch = useContext(HostContext);
const [localData, setLocalData] = useState(data);
const changeLocalData = newBarNumber => {
// localData.foo = { bar: newBarNumber };
// setLocalData({ ...localData });
setLocalData({ foo: { bar: newBarNumber } });
};
useEffect(() => {
console.log("useEffect", localData);
hostDispatch({ type: "setState", payload: localData });
// updateGlobalData(localData);
}, [localData, hostDispatch /*, updateGlobalData*/]);
return (
<div>
<span>{localData.foo.bar}</span> <br />
<button onClick={() => changeLocalData(localData.foo.bar + 1)}>
Increment
</button>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Host />, rootElement);
如果您发现任何与您想要的不符的地方,请告诉我,我会重新检查。
希望对您有所帮助。
最好,
最大
我在 parent 和 child 组件之间的通信有问题。
我希望 parent(主持人)保持他自己的状态。我希望 child (来宾)通过该状态并对其进行修改。 child 有他的本地版本的状态,可以随 child 的需要而改变。但是,一旦 child 完成状态播放,他会将其传递给 parent 以实际 "Save" 实际状态。 我该如何正确实施?
我的代码中的问题:
在 updateGlobalData 处理程序上,我记录了数据和 newDataFromGuest,它们是相同的。我想data代表旧版本的数据,newDataFromGuest代表new
updateGlobalData 被调用了 2 次。我可以通过从 useEffect 中的 deps 数组中删除 updateGlobalData ref 来解决这个问题,但我不想搞砸它。
我想要的结果应该是:
- 数据状态应保存旧数据,直到调用 updateGlobalData
- 我希望 updateGlobalData 在我单击按钮时只触发一次
来自 Codesandbox 的代码:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const Host = () => {
const [data, setData] = useState({ foo: { bar: 1 } });
const updateGlobalData = newDataFromGuest => {
console.log(data);
console.log(newDataFromGuest);
setData(newDataFromGuest);
};
return <Guest data={data} updateGlobalData={updateGlobalData} />;
};
const Guest = ({ data, updateGlobalData }) => {
const [localData, setLocalData] = useState(data);
const changeLocalData = newBarNumber => {
localData.foo = { bar: newBarNumber };
setLocalData({ ...localData });
};
useEffect(() => {
updateGlobalData(localData);
}, [localData, updateGlobalData]);
return (
<div>
<span>{localData.foo.bar}</span> <br />
<button onClick={() => changeLocalData(++localData.foo.bar)}>
Increment
</button>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Host />, rootElement);
注意:下面的代码解决方案
- 问题 1:
I want updateGlobalData to be fired only once when I click the button
为了解决这个问题,我混合使用了 React.createContext and the hook useReducer。这个想法是通过其上下文使主机调度程序可用。这样,您不需要将 "updateGlobalData" 回调发送给 Guest,也不需要使 useEffect 挂钩依赖于它。因此,useEffect只会被触发一次。
但请注意,useEffect 现在依赖于主机 dipatcher,您需要将其包含在其依赖项中。不过,如果您阅读 useReducer 上的第一条注释,调度程序是稳定的,不会导致重新渲染。
- 问题 2:
the data state should hold the old data until updateGlobalData is called
解决方法很简单:不要直接更改状态数据!!请记住,Javascript 中的大多数值都是通过引用传递的。如果你给Guest发数据你直接修改,像这里
const changeLocalData = newBarNumber => {
localData.foo = { bar: newBarNumber }; // YOU ARE MODIFYING STATE DIRECTLY!!!
...
};
这里
<button onClick={() => changeLocalData(++localData.foo.bar)}> // ++ OPERATOR MODIFYES STATE DIRECLTY
它们也将在主机中被修改,除非您通过 useState 挂钩更改该数据。我认为(不是 100% 确定)这是因为 Guest 中的 localData 是使用与来自 Host 的数据相同的引用进行初始化的。因此,如果您在 Guest 中直接更改它,它也会在 Host 中更改。只需将本地数据的值加 1 即可更新来宾状态,而无需使用 ++ 运算符。像这样:
localData.foo.bar + 1
这是my solution:
import React, { useState, useEffect, useReducer, useContext } from "react";
import ReactDOM from "react-dom";
const HostContext = React.createContext(null);
function hostReducer(state, action) {
switch (action.type) {
case "setState":
console.log("previous Host data value", state);
console.log("new Host data value", action.payload);
return action.payload;
default:
throw new Error();
}
}
const Host = () => {
// const [data, setData] = useState({ foo: { bar: 1 } });
// Note: `dispatch` won't change between re-renders
const [data, dispatch] = useReducer(hostReducer, { foo: { bar: 1 } });
// const updateGlobalData = newDataFromGuest => {
// console.log(data.foo.bar);
// console.log(newDataFromGuest.foo.bar);
// setData(newDataFromGuest);
// };
return (
<HostContext.Provider value={dispatch}>
<Guest data={data} /*updateGlobalData={updateGlobalData}*/ />
</HostContext.Provider>
);
};
const Guest = ({ data /*, updateGlobalData*/ }) => {
// If we want to perform an action, we can get dispatch from context.
const hostDispatch = useContext(HostContext);
const [localData, setLocalData] = useState(data);
const changeLocalData = newBarNumber => {
// localData.foo = { bar: newBarNumber };
// setLocalData({ ...localData });
setLocalData({ foo: { bar: newBarNumber } });
};
useEffect(() => {
console.log("useEffect", localData);
hostDispatch({ type: "setState", payload: localData });
// updateGlobalData(localData);
}, [localData, hostDispatch /*, updateGlobalData*/]);
return (
<div>
<span>{localData.foo.bar}</span> <br />
<button onClick={() => changeLocalData(localData.foo.bar + 1)}>
Increment
</button>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Host />, rootElement);
如果您发现任何与您想要的不符的地方,请告诉我,我会重新检查。
希望对您有所帮助。
最好, 最大