我是新手,所以如果这是一个非常基本的问题,我深表歉意。我无法在任何地方找到答案。
我有一个 ExtensionsContext
组件,用于存储扩展类对象数组作为状态。
import React, { useContext, useState } from 'react'
const ExtensionsContext = React.createContext()
export function useExtensions() {
return useContext(ExtensionsContext)
}
export function ExtensionsProvider ({ children }) {
const [exts, setExtensions] = useState(startingExtensions) // startingExtentions is an array of Extention class objects
AppExtensions = {
extensions: exts,
update: function () { setExtensions(this.extensions) }
}
return (
<ExtensionsContext.Provider value={AppExtensions}>
{children}
</ExtensionsContext.Provider>
)
}
class Extension {
constructor (name, index) {
this.name = name
this.isOpen = false
this.id = index
}
open () {
this.isOpen = true
AppExtensions.update()
}
close () {
this.isOpen = false
AppExtensions.update()
}
}
这些扩展正在一个组件中使用,该组件在打开和关闭之间列出和分组它们。目前,如果我在其中一个扩展上调用 open 方法,它将更改扩展值,但不会重新渲染任何组件。我目前已将其设置为当单击按钮时,它会在组件中的布尔状态在 true 和 1 之间切换以强制重新渲染,但我知道我做错了什么,并且有更好的解决方案。如果我在多个地方使用这些扩展,这也不起作用。我想问题与我传入 AppExtensions
对象和 exts
的副本而不是直接传入 exts
的事实有关。我希望能够调用extension.open()
单击并处理 ExtensionsProvider
中的所有逻辑。
答案 0 :(得分:0)
React 通过浅层比较当前值和先前值来检测变化。如果它是一个原始值(例如一个数字),它会比较实际值。如果它是一个对象或数组,React 会比较对该对象的引用,而不是实际内容,即使它发生了变化。
这意味着设置相同的扩展名数组不会有任何作用,因为它是同一个数组。这也意味着更改单个属性也无济于事。您需要重新创建数组,并且您需要重新创建更改的对象。
在我下面的示例中,每个 Extension
对象都有 toggle
方法,该方法返回一个基于前一个对象的新对象,并切换了 isOpen
属性。 update
方法通过映射扩展并切换对象之一来重新创建 extensions
数组。
const { useContext, useState, useCallback } = React
const ExtensionsContext = React.createContext()
function useExtensions() {
return useContext(ExtensionsContext)
}
class Extension {
constructor (name, index, isOpen = false) {
this.name = name
this.isOpen = isOpen
this.id = index
}
toggle() {
return new Extension(this.name, this.id, !this.isOpen);
}
}
const createStartingExtensions = () =>
['X', 'Y', 'Z'].map((name, i) => new Extension(name, i))
function ExtensionsProvider ({ children }) {
const [extensions, setExtensions] = useState(createStartingExtensions) // startingExtentions is an array of Extention class objects
const update = useCallback((target) => {
setExtensions(extensions =>
extensions.map(o =>
o === target ? o.toggle() : o
))
}, []) // recreate the array of extensions
const appExtensions = {
extensions,
update
}
return (
<ExtensionsContext.Provider value={appExtensions}>
{children}
</ExtensionsContext.Provider>
)
}
const List = () => {
const { extensions, update } = useExtensions()
return (
<ul>
{extensions.map(ext => (
<li key={ext.id} onClick={() => update(ext)}>{ext.name} {ext.isOpen ? 'open' : 'close' }</li>
))}
</ul>
)
}
const Demo = () => (
<ExtensionsProvider>
<List />
</ExtensionsProvider>
)
ReactDOM.render(
<Demo />,
root
)
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="root"></div>