如何从子组件内部更新React Context?


109

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

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是的。在该文档中,上下文是从组件本身更新的,或者在文档中添加了博客链接,其中包含作为道具传递给上下文提供者的上下文,我需要从子组件中进行更新
mshameer

呃,没有文档说如果您需要更新上下文,请不要使用上下文。准确地说,“不要这样做”。我重申一下,使用状态而不是上下文
azium

2
@LondonRob您正在寻找什么样的规范答案?IMO对我来说,文档的内容看起来还不错。如果要在子级中设置上下文,只需在提供程序的组件中创建一个setter并将其传递给子级使用者。然后在子使用者中调用该setter,并将其设置为子使用者中的任何数据。仍然与React提出的提升数据的想法保持一致。
安德鲁·李

2
这些年来,@ azium直指其他人阅读此评论。现在支持从子组件更新上下文,并且非常简单:hyp.is/FiP3mG6fEeqJiOfWzfKpgw/reactjs.org/docs/context.html
lustig

Answers:


249

使用挂钩

挂钩是在16.8.0中引入的,因此以下代码要求最低版本为16.8.0(向下滚动以获得类组件示例)。CodeSandbox演示

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

首先,为了拥有可以传递给消费者的动态上下文,我将使用父级的状态。这样可以确保我拥有单一的真理来源。例如,我的父应用程序将如下所示:

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.将消费者包装在提供者中

现在,我将在a中渲染语言切换器组件,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演示

使用类组件

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

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

首先,为了拥有可以传递给消费者的动态上下文,我将使用父级的状态。这样可以确保我拥有单一的真理来源。例如,我的父应用程序将如下所示:

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

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

  ...
}

language存储在状态语言setter方法,你可以保持状态树外沿。

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.将消费者包装在提供者中

现在,我将在a中渲染语言切换器组件,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演示


初始化上下文的默认值的目的是什么?那些默认值不是总是被Provider?覆盖吗?
ecoe

@ecoe是正确的,但是如果提供者未通过value,则使用方将使用默认值。
Divyanshu Maithani

1
为什么将上下文限制为设置/获取一个简单值。...这似乎效率很低。一个更好的示例是突出显示一个默认值为对象的上下文,并相应地更新该对象。
AlxVallejo

1
如果setLanguage没有参数,Typescript在我的情况下会抱怨。 setLanguage: (language: string) => {}为我工作。
alex351 '20

1
这次真是万分感谢。这是最清晰的方法,并没有使我想要拔头发。
c0dezer019

49

Maithani的上述答案是一个不错的解决方案,但这是不使用钩子的类组件。由于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

1
我正在执行此操作,而我的子组件内部的set函数始终是创建上下文时最初声明的函数:() => {}
Alejandro Corredor,

5
需要澄清的是,我认为在您的示例中state.setLanguage('pk')不会做任何事情,因为const state = useContext(LanguageContext)它不在中LanguageContextProvider。我通过将提供程序上移一个级别,然后useContext在下一个级别使用一个子级来解决我的问题。
亚历杭德罗·科雷多

2
如果您不想将上下文提供程序上移一个级别,也可以使用上下文使用者,例如:<LanguageContext.Consumer> {value => /* access your value here */} </LanguageContext.Consumer>
Mateen Kiani

3
我非常喜欢您组织LanguageContextMangement.js文件的方式。我认为这是一种干净的做事方式,我现在就开始做。谢谢!
lustig

2
感谢您的赞赏,它真的鼓励我继续做这样的工作!
Mateen Kiani

3

一个非常简单的解决方案是通过在提供程序中包含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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.