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

IT技术 javascript reactjs react-context
2021-04-01 01:55:51

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

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个回答

使用钩子

Hooks 是在 16.8.0 中引入的,因此以下代码要求最低版本为 16.8.0(向下滚动查看类组件示例)。代码沙盒演示

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 中的 provider 组件时提供它们的值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>
  );
};

现在,无论何时单击语言切换器,它都会动态更新上下文。

代码沙盒演示

使用类组件

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

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将由上下文提供者发送给消费者函数。这些只是默认值,我将在使用 parent 中的 provider 组件时提供它们的值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>
    );
  }
}

现在,无论何时单击语言切换器,它都会动态更新上下文。

代码沙盒演示

为什么上下文仅限于设置/获取一个简单的值......这似乎非常低效。一个更好的例子是突出显示具有默认对象的上下文并相应地更新对象。
2021-05-26 01:55:51
这次真是万分感谢。这是最清楚的操作方法,并没有让我想要拔掉我的头发。
2021-06-01 01:55:51
如果 setLanguage 没有参数,Typescript 会在我的情况下抱怨。 setLanguage: (language: string) => {}为我工作。
2021-06-03 01:55:51
很棒的答案。非常感谢你这样解释。
2021-06-11 01:55:51
您初始化上下文的默认值的目的是什么?这些默认值不是总是被Provider?
2021-06-13 01:55:51

由于 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
感谢您的赞赏,它真的鼓励我继续做这样的工作!
2021-05-28 01:55:51
我真的很喜欢你组织 LanguageContextMangement.js 文件的方式。在我看来,这是一种干净的做事方式,我现在将开始这样做。谢谢!
2021-05-29 01:55:51
澄清一下,我认为在你的例子中state.setLanguage('pk')不会做任何事情,因为const state = useContext(LanguageContext)LanguageContextProvider. 我通过将提供者上一级然后useContext在下一级的孩子上使用来解决我的问题
2021-05-31 01:55:51
我正在这样做,我的子组件中的 set 函数始终是我们在创建上下文时最初声明的函数: () => {}
2021-06-05 01:55:51
在情况下,如果你不想要移动境提供一个级别也可以使用情境消费者是这样的:<LanguageContext.Consumer> {value => /* access your value here */} </LanguageContext.Consumer>
2021-06-11 01:55:51

一个非常简单的解决方案是通过在您的提供者中包含一个 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>

我个人喜欢这种模式:

文件: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-05-22 01:55:51