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

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>

My Page有一个组件来切换语言

<MyPage>
  <LanguageSwitcher/>
</MyPage>

这个MyPage中的LanguageSwitcher需要更新上下文以将语言更改为'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组件内部更新上下文?


当前回答

我个人喜欢这个模式:

文件: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(() => {

        // Increment, set in context
        const increment = () => setMyValue(prev => prev + 1); 

        // Increment every second
        let interval = setInterval(increment, 1000);

        // Cleanup, kill interval when unmounted
        return () => clearInterval(interval);

    },[]) // On mount, no dependencies

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

其他回答

我个人喜欢这个模式:

文件: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(() => {

        // Increment, set in context
        const increment = () => setMyValue(prev => prev + 1); 

        // Increment every second
        let interval = setInterval(increment, 1000);

        // Cleanup, kill interval when unmounted
        return () => clearInterval(interval);

    },[]) // On mount, no dependencies

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

使用钩子

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

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

首先,为了有一个可以传递给消费者的动态上下文,我将使用父节点的状态。这确保了我有一个唯一的真相来源。例如,我的父App看起来是这样的:

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

  return (
    ...
  );
};

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

2. 创建上下文

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

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

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

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

3.创建上下文使用者

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

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

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

4. 将使用者包装在提供者中

现在我将在LanguageContext中呈现我的语言切换器组件。提供程序,并传入必须通过上下文发送到任何更深层次的值。这是我的父应用程序的样子:

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演示

使用类组件

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

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

首先,为了有一个可以传递给消费者的动态上下文,我将使用父节点的状态。这确保了我有一个唯一的真相来源。例如,我的父App看起来是这样的:

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

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

  ...
}

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

2. 创建上下文

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

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

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

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中呈现我的语言切换器组件。提供程序,并传入必须通过上下文发送到任何更深层次的值。这是我的父应用程序的样子:

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演示

由于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

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

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

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

// 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>

只是想补充一下Divyanshu Maithani的答案,在将消费者包装在提供者中时,使用useMemo通常更安全。

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

  const value = useMemo(
    () => ({ language, setLanguage }),
    [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>
  );
};

从react/jsx-no-construct -context-values规则:

React上下文,以及它的所有子节点和消费者,当值道具发生变化时都会被重新呈现。因为每个Javascript对象都有自己的标识,所以像对象表达式({foo: 'bar'})或函数表达式在每次运行组件时都会获得一个新的标识。这会使上下文认为它获得了一个新对象,并可能导致不必要的重显示和意想不到的结果。

这可能会造成相当大的性能损失,因为它不仅会导致上下文提供者和使用者使用其子树中的所有元素重新呈现,而且树扫描反应用于呈现提供者和查找使用者的处理也被浪费了。