React useContext未在状态更改时重新绘制



我是一个全新的反应者,所以如果这是一个非常基本的问题,我很抱歉。我在任何地方都找不到答案。

我有一个ExtensionsContext组件,它存储一个Extension类对象数组作为状态。

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中的所有逻辑。

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>

最新更新