【问题标题】:React: Changing Context in ConsumerReact:改变消费者的上下文
【发布时间】:2019-03-26 19:02:42
【问题描述】:

以下问题与 React Context 文档中的以下部分有关:

  1. Dynamic Context
  2. Updating Context from a Nested Component

免责声明:对以下所有背景信息表示歉意。它提供了上下文,并有望对未来的访问者有所帮助


我们所知道的

  • 链接 1
    • (默认)上下文值设置为themes.dark(包含两个属性的对象:foregroundbackground
    • 只有在组件树中Consumer 上方没有Providers 时才会使用默认值
    • 在这种情况下,顶级组件中存在Provider (App)
    • 这个Provider (App) 将自己的state 作为上下文值
    • 明智的做法是让Provider 提供的值在结构和类型上与默认的上下文值相同(避免Consumers 混淆)
    • 因此,顶级组件 (App) 中的state 拥有与默认上下文值格式相同的对象:themes.light
    • 以上结论:当Consumer读取上下文时,它读取App的状态
    • 换句话说,我们在这里使用 context 将父级 (App) state 传递到组件树的深处,而无需将其传递给中间的每个组件
    • 当顶级组件 (App) 中的状态发生变化时,它会重新渲染,并为 Consumer 提供新的状态值
    • 这样,Consumer 通过 context 读取父级的 state
    • ...
    • 继续前进,我们在链接 1 中看到 set state (toggleTheme) 的函数作为正常的 prop 向下传递组件树
    • 因此,在链接 1 中,context 包含 读取 state 的对象
    • 我们能够设置 Consumer 中的状态,方法是将setState 函数作为普通propProvider 的子级传递到所有中间组件,并进入Consumer
    • 在顶级组件 (App) 中设置 state 会导致重新渲染自身,从而导致重新渲染 Provider,然后传递新的 App state 的值通过 context 降至其 Consumer
    • 因此,Consumer 始终通过 context 知道 App 的状态
    • 总之,流程是:
      1. 父母的state作为上下文值提供给孩子Consumer(s)
      2. 家长的state 已被某个孩子更新
      3. 父级重新渲染
      4. Provider 看到 上下文值Appstate)已更改,并使用新值重新渲染其所有 Consumers
  • 链接 2
    • 在链接 2 中,我们设置 Consumer 中的state,方法是在上下文中传递setState 函数
    • 这与链接 1 不同,在链接 1 中,我们依靠普通的 prop 来设置 state

问题

我们从the docs 得知:

每个 Context 对象都带有一个 Provider React 组件,它允许 使用组件来订阅上下文更改....

所有作为 Provider 后代的消费者都将重新渲染 每当 Provider 的 value 属性发生变化时。

  1. 假设我们使用App 中的普通变量作为上下文值。从上面的引用中我们知道,更改它会导致 Provider 重新渲染。那么,为什么我们还要麻烦使用state 作为 context 值呢?与在 App 中使用任何普通变量相比,这样做有什么好处?
  2. 上述两种方法都允许我们更新state。为什么链接 2 包含更新 state within state 本身的功能?我们能不能把它作为一个单独的setState 函数,通过 context 在一个具有两个属性的对象中传递给Consumer(一个是state,另一个是独立的更新state)的函数?

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    假设我们使用 App 中的普通变量作为上下文值。我们从上面的引用中知道,更改它会导致 Provider 重新渲染。那么,为什么我们还要费心使用状态作为上下文值呢?与仅在 App 中使用任何普通变量相比,这样做有什么好处?

    确实,当提供者使用更改的值重新呈现时,任何关心上下文的后代都会重新呈现。但是您首先需要一些东西来使提供者重新呈现。这将在 App 的状态或其道具发生变化时发生(或者当您调用 forceUpdate,但不要这样做)。大概,这是在您的应用程序的顶部,所以没有道具进入,这意味着您将使用状态来使其重新呈现。

    上述两种方法都允许我们更新状态。为什么链接 2 包含在状态本身内更新状态的功能?难道我们不能把它作为一个单独的 setState 函数,通过上下文传递给消费者,该对象有两个属性(一个是状态,另一个是更新状态的独立函数)?

    当决定是否由于上下文变化而重新渲染后代时,react 基本上会在旧值和新值之间做一个===。这非常快,并且与 React 对不可变数据的偏好配合得很好,但是当使用对象作为您的值时,您需要注意不要在每次渲染时都创建新对象。例如,如果 App 正在执行以下操作,它将在每次渲染时创建一个全新的对象,因此也将强制所有上下文消费者重新渲染:

    class App extends Component {
      state = {
        data: { 
          hello: 'world',
        }
      }
    
      updateData() {
        // some function for updating the state  
      }
    
      render() {
        return (
          <MyContext.Provider value={{ 
            data: this.state.data, 
            updateData: this.updateData
          }} />
        )
      }
    }
    

    因此,他们将函数存储在状态中的示例是确保他们提供的整个值不会从一个渲染变为另一个渲染。

    【讨论】:

    • 谢谢,这很有帮助。关于第二个问题:我们更新 state 是为了让 App 重新渲染,让 Provider 向下传递一个新值给 Consumers,最后让 Consumers 用这个新值重新渲染。为什么我们那么关心消费者根据完整的价值变化重新渲染?无论如何,它们都必须重新渲染,对吧(因为上下文值已更新)?
    • PS:我打算使用钩子来实现 Link 2 示例。因此,我计划将状态和更新状态的函数组合到一个对象中,并将其用作上下文值。这甚至有必要吗?我可以将整个 useState 作为值上下文传递吗?毕竟,它包括 state 和 setState。
    • 应用程序可以(可以想象)重新渲染,因为它与通过上下文提供的价值无关。如果发生这种情况,您确实希望消费者重新呈现。为此,您需要通过=== 检查之前的值和之后的值。如果你提供一个对象,这意味着你不能在 App 的 render 方法中创建一个全新的对象,否则你最终会不必要地重新渲染消费者。
    • 关于钩子,我不确定。这取决于 useState 返回的数组是否是从渲染到渲染的相同数组引用。如果不是,那么您将遇到同样的问题,即不必要的重新渲染。
    • 再次感谢。我刚才尝试实现这两个场景,很高兴看到您在 Q1 上的观点被证明是正确的(即没有发生消费者的重新渲染)。 Q2 发生了一些有趣的事情:基本上它陷入了无限循环的重新渲染。链接:codesandbox.io/s/9jln6qy8pp。是不是因为以下原因: 1)在App中,状态和更新状态的功能是通过新对象中的Provider传递下来的。 2) 消费者接收到这些并继续使用 updateValue2 回调更新父级的状态.... 1/2
    【解决方案2】:

    假设我们使用 App 中的普通变量作为上下文值。我们从上面的引用中知道,更改它会导致 Provider 重新渲染。那么,为什么我们还要费心使用状态作为上下文值呢?与仅在 App 中使用任何普通变量相比,这样做有什么好处?

    当你使用 state 并更新它时——无论你是否使用 provider 都无关紧要——它下的所有 provider 和组件都会更新。那是在官方 React Context 文档下,是错误的。这意味着更改提供者值根本不会调用消费者更新。

    您可以通过使用状态(不会在提供者内部)创建一个单独的组件并将该状态变量分配给提供者来验证这一点。因此,当组件状态发生变化时,状态中的值会发生变化,反过来,提供者应该注意到这一点并更新消费者。它没有做什么。

    为了更新消费者下的组件,很遗憾,您必须手动进行。除非您打算更新提供程序下的所有内容。

    截至 2021 年 4 月 21 日,这在 React 17.0.2 下是正确的——提供者值的变化没有受到监控,消费者也没有得到更新。除非您将所有提供程序都放在具有状态的组件中,但是更改其状态会强制更新提供程序下的所有组件。很遗憾。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-16
      • 2022-08-08
      • 2019-05-06
      • 1970-01-01
      • 2013-10-07
      • 2019-04-29
      相关资源
      最近更新 更多