在 useEffect 第二个参数中使用对象,而不必将其字符串化为 JSON
use object in useEffect 2nd param without having to stringify it to JSON
在 JS 中两个对象不相等。
const a = {}, b = {};
console.log(a === b);
所以我不能在 useEffect
(React hooks)中使用一个对象作为第二个参数,因为它总是被认为是假的(所以它会重新渲染):
function MyComponent() {
// ...
useEffect(() => {
// do something
}, [myObject]) // <- this is the object that can change.
}
这样做(上面的代码),导致 运行每次 宁效果 组件重新渲染,因为每次都认为对象不相等。
我可以 "hack" 通过将对象作为 JSON 字符串化值传递,但 IMO 有点脏:
function MyComponent() {
// ...
useEffect(() => {
// do something
}, [JSON.stringify(myObject)]) // <- yuck
是否有更好的方法来避免不必要的效果调用?
旁注:该对象具有嵌套属性。效果必须 运行 对此对象内部的每个更改。
您可以创建一个自定义挂钩来跟踪 ref
中的先前依赖项数组并将对象与例如Lodash isEqual
并且仅在它们不相等时才运行提供的函数。
例子
const { useState, useEffect, useRef } = React;
const { isEqual } = _;
function useDeepEffect(fn, deps) {
const isFirst = useRef(true);
const prevDeps = useRef(deps);
useEffect(() => {
const isFirstEffect = isFirst.current;
const isSame = prevDeps.current.every((obj, index) =>
isEqual(obj, deps[index])
);
isFirst.current = false;
prevDeps.current = deps;
if (isFirstEffect || !isSame) {
return fn();
}
}, deps);
}
function App() {
const [state, setState] = useState({ foo: "foo" });
useEffect(() => {
setTimeout(() => setState({ foo: "foo" }), 1000);
setTimeout(() => setState({ foo: "bar" }), 2000);
}, []);
useDeepEffect(() => {
console.log("State changed!");
}, [state]);
return <div>{JSON.stringify(state)}</div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
@Tholle 的上述回答是完全正确的。我在 dev.to
上写了一个 post
在 React 中,可以使用 useEffect
hook 在功能组件中处理副作用。在这个 post 中,我将讨论保存我们的 props/state 的依赖数组,特别是当依赖数组中有一个对象时会发生什么。
useEffect
挂钩 运行 即使依赖数组中的一个元素发生变化。 React 这样做是为了优化目的。另一方面,如果你传递一个空数组,那么它永远不会重新 运行s.
但是,如果此数组中存在对象,事情就会变得复杂。那么即使修改了对象,钩子也不会重新运行,因为它不会对那个对象的这些依赖变化做深度对象比较。有几种方法可以解决这个问题。
使用 lodash 的 isEqual
method and usePrevious
hook。此挂钩在内部使用一个 ref 对象,该对象包含一个可以保存值的可变 current
属性。
It’s possible that in the future React will provide a usePrevious Hook out of the box since it is a relatively common use case.
const prevDeeplyNestedObject = usePrevious(deeplyNestedObject)
useEffect(()=>{
if (
!_.isEqual(
prevDeeplyNestedObject,
deeplyNestedObject,
)
) {
// ...execute your code
}
},[deeplyNestedObject, prevDeeplyNestedObject])
使用 useDeepCompareEffect
hook 作为 useEffect
对象钩子的替代品
import useDeepCompareEffect from 'use-deep-compare-effect'
...
useDeepCompareEffect(()=>{
// ...execute your code
}, [deeplyNestedObject])
使用类似于解决方案#2
的useCustomCompareEffect
hook
我准备了一个与这个post相关的CodeSandbox example。 fork 自己查一下
依赖数组中的普通(非嵌套)对象
我只是想挑战这两个答案,并询问如果 object
in dependency array 没有嵌套会发生什么。如果那是没有属性的普通对象,那么更深一层。
在我看来,在这种情况下,useEffect
功能无需任何额外检查即可工作。
我只是想写这篇文章,学习并在我错的时候更好地向自己解释。非常欢迎任何建议,解释。
这里可能更容易检查和使用示例:https://codesandbox.io/s/usehooks-bt9j5?file=/src/App.js
const {useState, useEffect} = React;
function ChildApp({ person }) {
useEffect(() => {
console.log("useEffect ");
}, [person]);
console.log("Child");
return (
<div>
<hr />
<h2>Inside child</h2>
<div>{person.name}</div>
<div>{person.age}</div>
</div>
);
}
function App() {
const [person, setPerson] = useState({ name: "Bobi", age: 29 });
const [car, setCar] = useState("Volvo");
function handleChange(e) {
const variable = e.target.name;
setPerson({ ...person, [variable]: e.target.value });
}
function handleCarChange(e) {
setCar(e.target.value);
}
return (
<div className="App">
Name:
<input
name="name"
onChange={(e) => handleChange(e)}
value={person.name}
/>
<br />
Age:
<input name="age" onChange={(e) => handleChange(e)} value={person.age} />
<br />
Car: <input name="car" onChange={(e) => handleCarChange(e)} value={car} />
<ChildApp person={person} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-
dom.development.js"></script>
<div id="root"></div>
你可以只扩展useEffect
数组中的属性:
var obj = {a: 1, b: 2};
useEffect(
() => {
//do something when any property inside "a" changes
},
Object.entries(obj).flat()
);
Object.entries(obj)
returns 对数组 ([["a", 1], ["b", 2]]
) 和 .flat()
将数组展平为:
["a", 1, "b", 2]
请注意,对象中的属性数必须保持不变,因为数组的长度不能更改,否则 useEffect
将引发错误。
在 JS 中两个对象不相等。
const a = {}, b = {};
console.log(a === b);
所以我不能在 useEffect
(React hooks)中使用一个对象作为第二个参数,因为它总是被认为是假的(所以它会重新渲染):
function MyComponent() {
// ...
useEffect(() => {
// do something
}, [myObject]) // <- this is the object that can change.
}
这样做(上面的代码),导致 运行每次 宁效果 组件重新渲染,因为每次都认为对象不相等。
我可以 "hack" 通过将对象作为 JSON 字符串化值传递,但 IMO 有点脏:
function MyComponent() {
// ...
useEffect(() => {
// do something
}, [JSON.stringify(myObject)]) // <- yuck
是否有更好的方法来避免不必要的效果调用?
旁注:该对象具有嵌套属性。效果必须 运行 对此对象内部的每个更改。
您可以创建一个自定义挂钩来跟踪 ref
中的先前依赖项数组并将对象与例如Lodash isEqual
并且仅在它们不相等时才运行提供的函数。
例子
const { useState, useEffect, useRef } = React;
const { isEqual } = _;
function useDeepEffect(fn, deps) {
const isFirst = useRef(true);
const prevDeps = useRef(deps);
useEffect(() => {
const isFirstEffect = isFirst.current;
const isSame = prevDeps.current.every((obj, index) =>
isEqual(obj, deps[index])
);
isFirst.current = false;
prevDeps.current = deps;
if (isFirstEffect || !isSame) {
return fn();
}
}, deps);
}
function App() {
const [state, setState] = useState({ foo: "foo" });
useEffect(() => {
setTimeout(() => setState({ foo: "foo" }), 1000);
setTimeout(() => setState({ foo: "bar" }), 2000);
}, []);
useDeepEffect(() => {
console.log("State changed!");
}, [state]);
return <div>{JSON.stringify(state)}</div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
@Tholle 的上述回答是完全正确的。我在 dev.to
上写了一个 post在 React 中,可以使用 useEffect
hook 在功能组件中处理副作用。在这个 post 中,我将讨论保存我们的 props/state 的依赖数组,特别是当依赖数组中有一个对象时会发生什么。
useEffect
挂钩 运行 即使依赖数组中的一个元素发生变化。 React 这样做是为了优化目的。另一方面,如果你传递一个空数组,那么它永远不会重新 运行s.
但是,如果此数组中存在对象,事情就会变得复杂。那么即使修改了对象,钩子也不会重新运行,因为它不会对那个对象的这些依赖变化做深度对象比较。有几种方法可以解决这个问题。
使用 lodash 的
isEqual
method andusePrevious
hook。此挂钩在内部使用一个 ref 对象,该对象包含一个可以保存值的可变current
属性。It’s possible that in the future React will provide a usePrevious Hook out of the box since it is a relatively common use case.
const prevDeeplyNestedObject = usePrevious(deeplyNestedObject) useEffect(()=>{ if ( !_.isEqual( prevDeeplyNestedObject, deeplyNestedObject, ) ) { // ...execute your code } },[deeplyNestedObject, prevDeeplyNestedObject])
使用
useDeepCompareEffect
hook 作为useEffect
对象钩子的替代品import useDeepCompareEffect from 'use-deep-compare-effect' ... useDeepCompareEffect(()=>{ // ...execute your code }, [deeplyNestedObject])
使用类似于解决方案#2
的useCustomCompareEffect
hook
我准备了一个与这个post相关的CodeSandbox example。 fork 自己查一下
依赖数组中的普通(非嵌套)对象
我只是想挑战这两个答案,并询问如果 object
in dependency array 没有嵌套会发生什么。如果那是没有属性的普通对象,那么更深一层。
在我看来,在这种情况下,useEffect
功能无需任何额外检查即可工作。
我只是想写这篇文章,学习并在我错的时候更好地向自己解释。非常欢迎任何建议,解释。
这里可能更容易检查和使用示例:https://codesandbox.io/s/usehooks-bt9j5?file=/src/App.js
const {useState, useEffect} = React;
function ChildApp({ person }) {
useEffect(() => {
console.log("useEffect ");
}, [person]);
console.log("Child");
return (
<div>
<hr />
<h2>Inside child</h2>
<div>{person.name}</div>
<div>{person.age}</div>
</div>
);
}
function App() {
const [person, setPerson] = useState({ name: "Bobi", age: 29 });
const [car, setCar] = useState("Volvo");
function handleChange(e) {
const variable = e.target.name;
setPerson({ ...person, [variable]: e.target.value });
}
function handleCarChange(e) {
setCar(e.target.value);
}
return (
<div className="App">
Name:
<input
name="name"
onChange={(e) => handleChange(e)}
value={person.name}
/>
<br />
Age:
<input name="age" onChange={(e) => handleChange(e)} value={person.age} />
<br />
Car: <input name="car" onChange={(e) => handleCarChange(e)} value={car} />
<ChildApp person={person} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-
dom.development.js"></script>
<div id="root"></div>
你可以只扩展useEffect
数组中的属性:
var obj = {a: 1, b: 2};
useEffect(
() => {
//do something when any property inside "a" changes
},
Object.entries(obj).flat()
);
Object.entries(obj)
returns 对数组 ([["a", 1], ["b", 2]]
) 和 .flat()
将数组展平为:
["a", 1, "b", 2]
请注意,对象中的属性数必须保持不变,因为数组的长度不能更改,否则 useEffect
将引发错误。