【问题标题】:How to update React Context from inside a child component?如何从子组件内部更新 React Context?
【发布时间】:2017-04-23 03:47:29
【问题描述】:

我在上下文中有如下语言设置

class LanguageProvider extends Component {
  static childContextTypes = {
    langConfig: PropTypes.object,
  };

  getChildContext() {
    return { langConfig: 'en' };
  }

  render() {
    return this.props.children;
  }
}

export default LanguageProvider;

我的应用程序代码如下所示

<LanguageProvider>
  <App>
    <MyPage />
  </App>
</LanguageProvider>

我的页面有一个切换语言的组件

<MyPage>
  <LanguageSwitcher/>
</MyPage>

LanguageSwitcher 在这个MyPage 需要更新上下文以将语言更改为'jp',如下所示

class LanguageSwitcher extends Component {
  static contextTypes = {
    langConfig: PropTypes.object,
  };

  updateLanguage() {
    //Here I need to update the langConfig to 'jp' 
  }

  render() {
    return <button onClick={this.updateLanguage}>Change Language</button>;
  }
}

export default LanguageSwitcher;

如何从 LanguageSwitcher 组件内部更新上下文?

【问题讨论】:

  • 你读过这个吗? facebook.github.io/react/docs/context.html#updating-context 也许这更适合状态而不是上下文
  • @azium 是的。在该文档中,上下文是从组件本身更新的,或者在文档中添加了博客链接,其中包含作为道具传递给上下文提供者的上下文我需要更新它从子组件
  • 为其他人更新:自从@azium 的评论以来,该方法可能已经改变,因为该文档确实提供了一种从子组件更新上下文的方法:“通常需要从一个组件更新上下文嵌套在组件树的某个深处。在这种情况下,您可以通过上下文向下传递一个函数,以允许使用者更新上下文。”
  • @LondonRob 您在寻找什么样的规范答案? IMO 文档的内容对我来说看起来很好。如果您想在孩子中设置上下文,只需在提供者的组件中创建一个设置器并将其传递给子消费者。然后在子消费者中调用该设置器并将其设置为子消费者中的任何数据。仍然坚持 React 提升数据的想法。
  • @azium 只是提醒其他人多年后阅读此评论。现在支持从子组件更新上下文并且非常简单:hyp.is/FiP3mG6fEeqJiOfWzfKpgw/reactjs.org/docs/context.html

标签: javascript reactjs react-context


【解决方案1】:

使用钩子

挂钩是在 16.8.0 中引入的,因此以下代码需要 16.8.0 的最低版本(向下滚动查看类组件示例)。 CodeSandbox Demo

1。为动态上下文设置父状态

首先,为了有一个可以传递给消费者的动态上下文,我将使用父母的状态。这确保了我有一个单一的事实来源。例如,我的父 App 将如下所示:

const App = () => {
  const [language, setLanguage] = useState("en");
  const value = { language, setLanguage };

  return (
    ...
  );
};

language 存储在状态中。稍后我们将通过上下文传递language 和setter 函数setLanguage

2。创建上下文

接下来,我创建了这样的语言上下文:

// set the defaults
const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
});

在这里,我设置了 language ('en') 和 setLanguage 函数的默认值,这将由上下文提供者发送给消费者。这些只是默认值,我将在使用父 App 中的提供程序组件时提供它们的值。

注意:无论您使用挂钩还是基于类的组件,LanguageContext 都保持不变。

3。创建上下文消费者

为了让语言切换器设置语言,它应该可以通过上下文访问语言设置器功能。它可能看起来像这样:

const LanguageSwitcher = () => {
  const { language, setLanguage } = useContext(LanguageContext);
  return (
    <button onClick={() => setLanguage("jp")}>
      Switch Language (Current: {language})
    </button>
  );
};

这里我只是将语言设置为“jp”,但您可能有自己的逻辑来为此设置语言。

4。将消费者包装在提供者中

现在我将在LanguageContext.Provider 中呈现我的语言切换器组件,并将必须通过上下文发送到更深层次的值传递。这是我父母App 的样子:

const App = () => {
  const [language, setLanguage] = useState("en");
  const value = { language, setLanguage };

  return (
    <LanguageContext.Provider value={value}>
      <h2>Current Language: {language}</h2>
      <p>Click button to change to jp</p>
      <div>
        {/* Can be nested */}
        <LanguageSwitcher />
      </div>
    </LanguageContext.Provider>
  );
};

现在,只要单击语言切换器,它就会动态更新上下文。

CodeSandbox Demo

使用类组件

最新的context API 是在 React 16.3 中引入的,它提供了一种拥有动态上下文的好方法。以下代码要求最低版本为 16.3.0。 CodeSandbox Demo

1。为动态上下文设置父状态

首先,为了有一个可以传递给消费者的动态上下文,我将使用父母的状态。这确保了我有一个单一的事实来源。例如,我的父 App 将如下所示:

class App extends Component {
  setLanguage = language => {
    this.setState({ language });
  };

  state = {
    language: "en",
    setLanguage: this.setLanguage
  };

  ...
}

language 与语言设置器方法一起存储在状态中,您可以将其保留在状态树之外。

2。创建上下文

接下来,我创建了这样的语言上下文:

// set the defaults
const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
});

在这里,我设置了 language ('en') 和 setLanguage 函数的默认值,这将由上下文提供者发送给消费者。这些只是默认值,我将在使用父 App 中的提供程序组件时提供它们的值。

3。创建上下文消费者

为了让语言切换器设置语言,它应该可以通过上下文访问语言设置器功能。它可能看起来像这样:

class LanguageSwitcher extends Component {
  render() {
    return (
      <LanguageContext.Consumer>
        {({ language, setLanguage }) => (
          <button onClick={() => setLanguage("jp")}>
            Switch Language (Current: {language})
          </button>
        )}
      </LanguageContext.Consumer>
    );
  }
}

这里我只是将语言设置为“jp”,但您可能有自己的逻辑来为此设置语言。

4。将消费者包装在提供者中

现在我将在LanguageContext.Provider 中呈现我的语言切换器组件,并将必须通过上下文发送到更深层次的值传递。这是我父母App 的样子:

class App extends Component {
  setLanguage = language => {
    this.setState({ language });
  };

  state = {
    language: "en",
    setLanguage: this.setLanguage
  };

  render() {
    return (
      <LanguageContext.Provider value={this.state}>
        <h2>Current Language: {this.state.language}</h2>
        <p>Click button to change to jp</p>
        <div>
          {/* Can be nested */}
          <LanguageSwitcher />
        </div>
      </LanguageContext.Provider>
    );
  }
}

现在,只要单击语言切换器,它就会动态更新上下文。

CodeSandbox Demo

【讨论】:

  • 你初始化上下文的默认值的目的是什么?这些默认值不是总是被Provider 覆盖吗?
  • 为什么上下文被限制为设置/获取一个简单的值......这似乎非常低效。一个更好的例子是突出显示默认为对象的上下文并相应地更新该对象。
  • Typescript 在我的情况下会抱怨 setLanguage 没有参数。 setLanguage: (language: string) =&gt; {} 为我工作。
  • 谢谢你。这是最清楚的方法,我不想拔头发。
  • 很棒的答案。非常感谢您这样解释。
【解决方案2】:

由于 React 推荐使用 功能组件和钩子,所以我将使用 useContext 和 useState 钩子来实现它。以下是从子组件中更新上下文的方法。

LanguageContextMangement.js

import React, { useState } from 'react'

export const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
})

export const LanguageContextProvider = (props) => {

  const setLanguage = (language) => {
    setState({...state, language: language})
  }

  const initState = {
    language: "en",
    setLanguage: setLanguage
  } 

  const [state, setState] = useState(initState)

  return (
    <LanguageContext.Provider value={state}>
      {props.children}
    </LanguageContext.Provider>
  )
}

App.js

import React, { useContext } from 'react'
import { LanguageContextProvider, LanguageContext } from './LanguageContextManagement'

function App() {

  const state = useContext(LanguageContext)

  return (
    <LanguageContextProvider>
      <button onClick={() => state.setLanguage('pk')}>
        Current Language is: {state.language}
      </button>
    </LanguageContextProvider>
  )
}

export default App

【讨论】:

  • 我正在这样做,我的子组件中的 set 函数始终是我们在创建上下文时最初声明的函数:() =&gt; {}
  • 澄清一下,我认为在您的示例中state.setLanguage('pk') 不会做任何事情,因为const state = useContext(LanguageContext)LanguageContextProvider 之外。我解决了我的问题,将提供者上移一级,然后在下一级的孩子上使用useContext
  • 如果您不想将上下文提供者上移一级,也可以像这样使用上下文消费者:&lt;LanguageContext.Consumer&gt; {value =&gt; /* access your value here */} &lt;/LanguageContext.Consumer&gt;
  • 我非常喜欢您组织 LanguageContextMangement.js 文件的方式。在我看来,这是一种干净的做事方式,我现在就开始这样做。谢谢!
  • 感谢您的赞赏,它真的鼓励我继续做这样的工作!
【解决方案3】:

我个人喜欢这种模式:

文件:context.jsx

import React from 'react';

// The Context 
const TemplateContext = React.createContext({});

// Template Provider
const TemplateProvider = ({children}) => {

    const [myValue, setMyValue] = React.useState(0);

    // Context values passed to consumer
    const value = {
        myValue,    // <------ Expose Value to Consumer
        setMyValue  // <------ Expose Setter to Consumer
    };

    return (
        <TemplateContext.Provider value={value}>
            {children}
        </TemplateContext.Provider>
    )
}

// Template Consumer
const TemplateConsumer = ({children}) => {
    return (
        <TemplateContext.Consumer>
            {(context) => {
                if (context === undefined) {
                    throw new Error('TemplateConsumer must be used within TemplateProvider');
                }
                return children(context)
            }}
        </TemplateContext.Consumer>
    )
}

// useTemplate Hook
const useTemplate = () => {
    const context = React.useContext(TemplateContext);
    if(context === undefined)
        throw new Error('useTemplate must be used within TemplateProvider');
    return context;
}

export {
    TemplateProvider,
    TemplateConsumer,
    useTemplate
}

然后你可以创建一个功能组件,如果它是提供者的树中的一个孩子:

文件:component.jsx

import React            from 'react';
import {useTemplate}    from 'context.jsx';
const MyComponent = () => {

    // Get the value and setter from the consumer hook
    const {myValue,setMyValue} = useTemplate();

    // Demonstrate incrementing the value
    React.useEffect(()=>{
        let interval = setInterval(()=>{
            setMyValue(prev => prev + 1); // Increment, set in context
        }, 1000) // Every second
        return (
            clearInterval(interval); // Kill interval when unmounted
        )
    },[]) // On mount, no dependencies

    // Render the value as it is pulled from the context
    return (
        <React.Fragment>
            Value of MyValue is: {myValue}
        </React.Fragment>
    )
}

【讨论】:

  • 这太棒了,简单明了!
【解决方案4】:

一个非常简单的解决方案是通过在您的提供程序中包含一个 setState 方法来设置您的上下文的状态,如下所示:

return ( 
            <Context.Provider value={{
              state: this.state,
              updateLanguage: (returnVal) => {
                this.setState({
                  language: returnVal
                })
              }
            }}> 
              {this.props.children} 
            </Context.Provider>
        )

在你的消费者中,像这样调用 updateLanguage:

// button that sets language config
<Context.Consumer>
{(context) => 
  <button onClick={context.updateLanguage({language})}> 
    Set to {language} // if you have a dynamic val for language
  </button>
<Context.Consumer>

【讨论】:

    猜你喜欢
    • 2020-08-23
    • 1970-01-01
    • 2022-07-22
    • 1970-01-01
    • 2019-03-11
    • 2020-08-08
    • 1970-01-01
    • 2022-01-07
    • 2020-07-18
    相关资源
    最近更新 更多