【问题标题】:React useContext not rerendering on state changeReact useContext 不会在状态更改时重新渲染
【发布时间】:2021-03-27 23:15:33
【问题描述】:

我是新手,所以如果这是一个非常基本的问题,我深表歉意。我无法在任何地方找到答案。

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

【问题讨论】:

    标签: reactjs react-hooks use-context


    【解决方案1】:

    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>

    【讨论】:

      猜你喜欢
      • 2018-11-29
      • 1970-01-01
      • 2020-10-03
      • 2021-11-24
      • 2018-03-17
      • 1970-01-01
      • 2019-02-27
      相关资源
      最近更新 更多