我有语言设置在上下文中如下所示
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组件内部更新上下文?
由于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
只是想补充一下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'})或函数表达式在每次运行组件时都会获得一个新的标识。这会使上下文认为它获得了一个新对象,并可能导致不必要的重显示和意想不到的结果。
这可能会造成相当大的性能损失,因为它不仅会导致上下文提供者和使用者使用其子树中的所有元素重新呈现,而且树扫描反应用于呈现提供者和查找使用者的处理也被浪费了。
更新一个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。
我个人喜欢这个模式:
文件: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>
)
}
一个非常简单的解决方案是通过在你的提供程序中包含一个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>