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

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


当前回答

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

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

其他回答

我个人喜欢这个模式:

文件: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>
    )
}

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

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

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

下面是我的方法,基于@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。

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