181

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

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 组件内部更新上下文?

4

4 回答 4

450

使用钩子

挂钩是在 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将由上下文提供者发送给消费者的函数。这些只是默认值,我将在使用 parent 中的提供程序组件时提供它们的值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方法一起存储在状态中,您可以将其保存在状态树之外。

2. 创建上下文

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

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

在这里,我设置了language('en') 的默认值和一个setLanguage将由上下文提供者发送给消费者的函数。这些只是默认值,我将在使用 parent 中的提供程序组件时提供它们的值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 演示

于 2018-07-28T17:46:58.587 回答
81

由于 React 推荐使用函数式组件和钩子,所以我将使用 useContext 和 useState 钩子来实现它。以下是如何从子组件中更新上下文。

语言上下文管理.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>
  )
}

应用程序.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
于 2019-08-09T18:30:03.450 回答
7

我个人喜欢这种模式:

文件: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>
    )
}
于 2021-07-03T13:29:21.333 回答
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-01-28T06:58:25.617 回答