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

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


当前回答

使用钩子

钩子是在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演示

其他回答

只是想补充一下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'})或函数表达式在每次运行组件时都会获得一个新的标识。这会使上下文认为它获得了一个新对象,并可能导致不必要的重显示和意想不到的结果。

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

使用钩子

钩子是在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

更新一个React上下文:

通过useContext消费之前的内容, 用该上下文的Provider组件重新包装子组件 为它设置一个新的值道具,从之前的上下文值派生(通过useContext提取)

const {useState, Fragment, createContext, useContext, Provider} = React // create a context const MyContext = React.createContext() // Dummy - a simple component which uses the context const Dummy = () => { const ctx = useContext(MyContext) // print contexy return <p> Context value: <mark>{JSON.stringify(ctx)}</mark> </p> } // Some mid-level component const MidLevel = () => { const ctx = useContext(MyContext) // update ancestor context return <MyContext.Provider value={{...ctx, foo: 2, bar: 4}}> <Dummy/> </MyContext.Provider> } // Top-level component (with default context value) const App = () => <MyContext.Provider value={{ foo: 1, baz: 3 }}> <MidLevel/> <Dummy/> </MyContext.Provider> // Render ReactDOM.render(<App />, root) <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script> <div id="root" style="font: 20px Arial"></div>

下面是我的方法,基于@Nicholas Hamilton的回答,但对于TypeScript和应用@LIIT推荐。

注意:我使用的是Next.js和ESLint的一个高度自信的设置。

import { createContext, useContext, useMemo, useState, Dispatch, SetStateAction } from "react"

interface TemplateContextProps {
  myValue: number | null
  setMyValue: Dispatch<SetStateAction<number | null>>
}

const TemplateContext = createContext<TemplateContextProps>({
  myValue: null,
  setMyValue: (prevState: SetStateAction<number | null>) => prevState,
})

interface TemplateProviderProps {
  children: React.ReactNode
}

function TemplateProvider({ children }: TemplateProviderProps): JSX.Element {
  const [myValue, setMyValue] = useState<number | null>(null)

  const value = useMemo(() => ({ myValue, setMyValue }), [myValue, setMyValue])

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

const TemplateConsumer = TemplateContext.Consumer

const useTemplate = () => useContext(TemplateContext)

export { TemplateProvider, TemplateConsumer, useTemplate }

我喜欢将值初始化为null,这是一种更动态的方法,但您可以将类型限制为数字并默认设置为0。