使用 React Hooks 更改数组中的对象 属性 不会引发重新渲染
Changing object property in an array with React Hooks does not evoke a re-render
当我使用 React 钩子更改数组中对象的 属性 时,组件不会重新呈现。
我咨询了我的首席技术人员,他解释说 React 搜索 "breadth" 而不是 "depth,",这意味着 React 可以看到对象,但看不到对象属性的更改。
const [array, setArray] = useState([
{'id': 0, text: '0'},
{'id': 1, text: '1'},
{'id': 2, text: '2'}
]);
我希望组件在状态更改时重新呈现,但行为就像对象数组根本没有更改一样。
如果我将数组副本创建为 newArray 并更改 newArray[2].text = '3' 和 setArray(newArray),React 认为数组没有更改。因此,组件不会重新加载。
作为解决方法,我使用
const [trigger, setTrigger] = useState(false);
setTrigger(!trigger);
强制重新加载。然而,这是一种重新加载组件的 hacky 方式,我想要一种更 React 的方式来完成它。
提前谢谢!
我认为问题出在您复制数组的方式上。 js 中的数组是 reference type
所以你不能只这样做:
const arr = [1,2,4]
const copy = arr
因为您只是更改指针而不是引用本身。试试这个:
const copy = [...arr]
现在您正在创建一个新数组,每个 arr
个元素的 spread
个。为此,请使用 useState
const Comp = () =>{
const [arr, setArr] = useState([1,2,3,4])
return <button onClick={() => setArr([...arr, 5])}>Change</button>
}
您的首席技术人员 "breath over depth" 的意思是正确的。
React 仅监视 "reference" 更改。当您更改对象的 属性 时,引用是相同的。
把它想象成当你这样做的时候,你仍然可以改变a.value
,即使它被声明为常量。
const a = {value: 'abc'}
a.value = 'xxx'
console.log(a.value) // prints 'xxx'
但你做不到
const a = {value: 'abc'}
a = {something: 'bad'}
not allowed.
这同样适用于对象数组(或普通的旧数组)。
它是引用类型,因此更改数组元素的 属性 不会更改 newArray
.
的引用
newArray[2].text = '3' and setArray(newArray)
newArray
仍然指向同一个地址 space.
因此,当您使用扩展语法或 Array.from
创建一个全新的数组时,您会创建一个全新的引用(副本)并设置一个新值,最后设置状态。
const [array, setArray] = useState([
{'id': 0, text: '0'},
{'id': 1, text: '1'},
{'id': 2, text: '2'}
]);
// ... somewhere else.
const copy = [...array, {'id': 2, ]
// or -> const copy = Array.from(array)
copy[2].text = '3'
setArray(copy)
上面的代码创建了一个新的 copy
of array
状态。使用更新值设置该副本将触发重新渲染。 (但要注意,它会创建 "shallow copy",而不是 "deep copy")
由于您的数据结构很复杂,您不妨使用 ImmerJS,这样您就可以像编写当前代码一样更改引用。
当我使用 React 钩子更改数组中对象的 属性 时,组件不会重新呈现。
我咨询了我的首席技术人员,他解释说 React 搜索 "breadth" 而不是 "depth,",这意味着 React 可以看到对象,但看不到对象属性的更改。
const [array, setArray] = useState([
{'id': 0, text: '0'},
{'id': 1, text: '1'},
{'id': 2, text: '2'}
]);
我希望组件在状态更改时重新呈现,但行为就像对象数组根本没有更改一样。
如果我将数组副本创建为 newArray 并更改 newArray[2].text = '3' 和 setArray(newArray),React 认为数组没有更改。因此,组件不会重新加载。
作为解决方法,我使用
const [trigger, setTrigger] = useState(false);
setTrigger(!trigger);
强制重新加载。然而,这是一种重新加载组件的 hacky 方式,我想要一种更 React 的方式来完成它。
提前谢谢!
我认为问题出在您复制数组的方式上。 js 中的数组是 reference type
所以你不能只这样做:
const arr = [1,2,4]
const copy = arr
因为您只是更改指针而不是引用本身。试试这个:
const copy = [...arr]
现在您正在创建一个新数组,每个 arr
个元素的 spread
个。为此,请使用 useState
const Comp = () =>{
const [arr, setArr] = useState([1,2,3,4])
return <button onClick={() => setArr([...arr, 5])}>Change</button>
}
您的首席技术人员 "breath over depth" 的意思是正确的。
React 仅监视 "reference" 更改。当您更改对象的 属性 时,引用是相同的。
把它想象成当你这样做的时候,你仍然可以改变a.value
,即使它被声明为常量。
const a = {value: 'abc'}
a.value = 'xxx'
console.log(a.value) // prints 'xxx'
但你做不到
const a = {value: 'abc'}
a = {something: 'bad'}
not allowed.
这同样适用于对象数组(或普通的旧数组)。
它是引用类型,因此更改数组元素的 属性 不会更改 newArray
.
newArray[2].text = '3' and setArray(newArray)
newArray
仍然指向同一个地址 space.
因此,当您使用扩展语法或 Array.from
创建一个全新的数组时,您会创建一个全新的引用(副本)并设置一个新值,最后设置状态。
const [array, setArray] = useState([
{'id': 0, text: '0'},
{'id': 1, text: '1'},
{'id': 2, text: '2'}
]);
// ... somewhere else.
const copy = [...array, {'id': 2, ]
// or -> const copy = Array.from(array)
copy[2].text = '3'
setArray(copy)
上面的代码创建了一个新的 copy
of array
状态。使用更新值设置该副本将触发重新渲染。 (但要注意,它会创建 "shallow copy",而不是 "deep copy")
由于您的数据结构很复杂,您不妨使用 ImmerJS,这样您就可以像编写当前代码一样更改引用。