为啥 `useContext` 不重新渲染我的组件?

Posted

技术标签:

【中文标题】为啥 `useContext` 不重新渲染我的组件?【英文标题】:Why isn't `useContext` re-rendering my component?为什么 `useContext` 不重新渲染我的组件? 【发布时间】:2020-05-06 18:05:04 【问题描述】:

根据docs:

当组件上方最近的<MyContext.Provider> 更新时,此 Hook 将使用传递给该 MyContext 提供程序的最新上下文值触发重新渲染。即使祖先使用React.memoshouldComponentUpdate,仍然会使用useContext 从组件本身开始重新渲染。 ... 当上下文值发生变化时,调用 useContext 的组件总是会重新渲染。

在我的 Gatsby JS 项目中,我这样定义我的上下文:

Context.js

import React from "react"

const defaultContextValue = 
  data: 
    filterBy: 'year',
    isOptionClicked: false,
    filterValue: ''
  ,
  set: () => ,


const Context = React.createContext(defaultContextValue)

class ContextProviderComponent extends React.Component 
  constructor() 
    super()

    this.setData = this.setData.bind(this)
    this.state = 
      ...defaultContextValue,
      set: this.setData,
    
  

  setData(newData) 
    this.setState(state => (
      data: 
        ...state.data,
        ...newData,
      ,
    ))
  

  render() 
    return <Context.Provider value=this.state>this.props.children</Context.Provider>
  


export  Context as default, ContextProviderComponent 

在一个包含多个组件的 layout.js 文件中,我放置了上下文提供程序:

Layout.js

import React from 'react'
import  ContextProviderComponent  from '../../context'

const Layout = (children) => 

    return(
        <React.Fragment>
            <ContextProviderComponent>
                children
            </ContextProviderComponent>
        </React.Fragment>
    )

在我希望使用上下文的组件中:

import React,  useContext  from 'react'
import Context from '../../../context'

const Visuals = () => 

    const filterByYear = 'year'
    const filterByTheme = 'theme'

    const value = useContext(Context)
    const  filterBy, isOptionClicked, filterValue  = value.data

    const data = <<returns some data from backend>>

    const works = filterBy === filterByYear ? 
        data.nodes.filter(node => node.year === filterValue) 
        : 
        data.nodes.filter(node => node.category === filterValue)

   return (
        <Layout noFooter="true">
            <Context.Consumer>
                ( data, set ) => (
                    <div onClick=() => set(  filterBy: 'theme' )>
                        data.filterBy === filterByYear ? <h1>Year</h1> : <h1>Theme</h1> 
                    </div>
                )
            </Context.Consumer>
        </Layout>
    )

Context.Consumer 可以正常工作,因为它成功更新并反映了对上下文的更改。但是,如代码中所示,我希望能够访问组件其他部分中更新的上下文值,即在 return 函数之外,其中 Context.Consumer 专门使用。我假设使用 useContext 钩子会对此有所帮助,因为每次单击 div 时,我的组件都会使用上下文中的新值重新渲染 - 但事实并非如此。任何帮助弄清楚为什么会这样。

TL;DR:&lt;Context.Consumer&gt; 更新并反映子组件对上下文的更改,useContext 不会,尽管组件需要它。

更新: 我现在发现useContext 将从传递给createContext 的默认上下文值中读取,并且基本上独立于Context.Provider 运行。这就是这里发生的事情,Context.Provider 包含一个修改状态的方法,而默认上下文值没有。我现在的挑战是想办法在默认上下文值中包含一个函数,该函数可以修改该值的其他属性。就目前而言:

const defaultContextValue = 
  data: 
    filterBy: 'year',
    isOptionClicked: false,
    filterValue: ''
  ,
  set: () => 

set 是一个在ContextProviderComponent 中定义的空函数(见上文)。我如何(如果可能)直接在上下文值中定义它,以便:

const defaultContextValue = 
  data: 
    filterBy: 'year',
    isOptionClicked: false,
    filterValue: ''
  ,
  test: 'hi',
  set: (newData) => 
     //directly modify defaultContextValue.data with newData
  

【问题讨论】:

请记住,只有在消费者组件祖先中没有上下文提供者时才会使用默认上下文值。 @nicooga 我认为在这种情况下默认上下文值作为值传递给上下文提供程序,因此无论如何都使用 是的,你是对的,但它是多余的。如果您使用useContext,您也不需要将元素包装在Context.Consumer 中。检查这个例子:codesandbox.io/s/react-context-usage-example-04vkj. “我现在的挑战是想办法在默认上下文值中包含一个可以修改该值的其他属性的函数 -通常的模式是使用useReducer()ref 更新上下文,尽管您的设置函数方法可能有效。 【参考方案1】:

您无需同时使用&lt;Context.Consumer&gt;useContext 挂钩。

通过使用useContext 挂钩,您可以访问存储在 Context 中的值。

关于您的具体示例,在 Visuals 组件中使用 Context 的更好方法如下:

import React,  useContext  from "react";
import Context from "./context";

const Visuals = () => 
  const filterByYear = "year";
  const filterByTheme = "theme";

  const  data, set  = useContext(Context);
  const  filterBy, isOptionClicked, filterValue  = data;

  const works =
    filterBy === filterByYear
      ? "filter nodes by year"
      : "filter nodes by theme";

  return (
    <div noFooter="true">
      <div>
        data.filterBy === filterByYear ? <h1>Year</h1> : <h1>Theme</h1>
        the value for the 'works' variable is: works
        <button onClick=() => set( filterBy: "theme" )>
          Filter by theme
        </button>
        <button onClick=() => set( filterBy: "year" )>
          Filter by year
        </button>
      </div>
    </div>
  );
;

export default Visuals;

此外,您似乎没有在组件中使用 works 变量,这可能是您未获得所需结果的另一个原因。

您可以查看具有上述useContext 实现的工作示例,该示例与您在此sandbox 中的示例有些相似

希望这会有所帮助。

【讨论】:

感谢您花时间回答。我现在明白同时使用Context.ConsumeruseContext 是多余的。然而,根本问题仍然是组件不会在上下文状态更改时重新渲染。 我相信问题可能源自您代码的其他地方。如果您可以在沙箱中重现您的错误,则更容易找到导致您遇到麻烦的确切问题。如果您查看我分享的沙箱,您会看到每次上下文更改时组件都会重新呈现。 这是一个沙盒:codesandbox.io/s/…。目前也在通过你的工作 看来isOptionsClicked 总是假的所以你总是渲染你&lt;Works/&gt; 组件并且filterValue 总是一个空字符串所以你的works 数组总是空的。这不是上下文的问题,而是组件逻辑的实现问题。如果没有看到完整的回购,我真的无法提供更多的见解。对不起。 没关系。非常感谢您的帮助。我让这个组件完美地工作,但我需要状态来坚持到其他页面,因此我重写了它。【参考方案2】:

问题非常简单——&lt;Visuals&gt; 在组件树中的位置比&lt;Layout 更高,出于某种原因,我仍在努力解决。将 Itai 的答案标记为正确,因为它最接近于根据情况来解决问题

【讨论】:

【参考方案3】:

除了Itai引用的解决方案,我相信我的问题可以帮助到这里的其他人

在我的例子中,我发现了一些已经发生在我身上的事情,但现在又出现了另一种症状,即没有重新渲染依赖于上下文中存储的状态的视图。

这是因为主机和设备之间的日期不同。在这里解释:https://github.com/facebook/react-native/issues/27008#issuecomment-592048282

这与我之前发现的其他症状有关:https://***.com/a/63800388/10947848

要解决这个问题,只需按照第一个链接中的步骤操作,或者如果您认为有必要禁用调试模式

【讨论】:

以上是关于为啥 `useContext` 不重新渲染我的组件?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用自定义钩子和 useContext 仅渲染相关组件

在使用数据库数据更新上下文后,React 'useContext' 钩子不会重新渲染

为啥我的 vue 组件不重新渲染?

如何有效减少使用useContext导致的不必要渲染

React useContext,useRedux子组件不更新

JSF2 - 为啥渲染响应不重新渲染组件设置?