98

MyContext.js

import React from "react";

const MyContext = React.createContext('test');
export default MyContext;

我在一个单独的js文件中创建了我的上下文,我可以在其中访问我的父组件以及我的子组件

父.js

import MyContext from "./MyContext.js";
import Child from "./Child.js";

class Parent extends Component {

    constructor(props) {
      super(props);
      this.state = {
        Message: "Welcome React",
        ReturnMessage:""
      };
    }
    
    render() {
        return (
           <MyContext.Provider value={{state: this.state}}>      
              <Child /> 
           </MyContext.Provider>
       )
    }
}

所以我用提供者上下文创建了父组件并在提供者选项卡中调用子组件

Child.js

import MyContext from "./MyContext.js";

class Child extends Component {

    constructor(props) {
      super(props);
      this.state = {        
        ReturnMessage:""
      };
    }
    
    ClearData(context){
        this.setState({
           ReturnMessage:e.target.value
        });
        context.state.ReturnMessage = ReturnMessage
    }

    render() {
        return (
           <MyContext.Consumer>                 
              {(context) => <p>{context.state.Message}</p>}
              <input onChange={this.ClearData(context)} />
           </MyContext.Consumer>
       )
    }
}

所以在孩子中通过使用Consumer,我可以在孩子渲染部分显示数据。

当我想从消费者那里更新状态时,我遇到了一个问题。

如何更新提供者状态或操纵提供者状态?

4

5 回答 5

73

您可以使用 useContext 挂钩来实现这一点。在 Provider 的子元素中使用它非常容易。举个例子...

authContext.js

import { createContext } from "react";

const authContext = createContext({
  authenticated: false,
  setAuthenticated: (auth) => {}
});

export default authContext;

Login.js(使用上下文的组件)

import React, { useContext } from "react";
import authContext from "./authContext";

export default () => {
  const { setAuthenticated } = useContext(authContext);
  const handleLogin = () => setAuthenticated(true);
  const handleLogout = () => setAuthenticated(false);

  return (
    <React.Fragment>
      <button onClick={handleLogin}>login</button>
      <button onClick={handleLogout}>logout</button>
    </React.Fragment>
  );
};

最后是 index.js

import ReactDOM from "react-dom";
import React, { useState } from "react";

import authContext from "./authContext";
import Login from "./Login";

const App = () => {
  const [authenticated, setAuthenticated] = useState(false);

  return (
    <authContext.Provider value={{ authenticated, setAuthenticated }}>
      <div> user is {`${authenticated ? "" : "not"} authenticated`} </div>
      <Login />
    </authContext.Provider>
  );
};

ReactDOM.render(<App />, document.getElementById("container"));

如您所见,使用 useContext 挂钩来使用存储在上下文中的数据变得非常容易。当然,就像每个 React 钩子一样,它只适用于功能组件。

如果你想看到代码工作。 https://codesandbox.io/s/react-playground-forked-wbqsh?file=/index.js

于 2020-10-24T19:12:09.397 回答
37

从嵌套组件更新上下文

通常需要从嵌套在组件树深处某处的组件更新上下文。在这种情况下,您可以通过上下文传递一个函数以允许消费者更新上下文:

主题上下文.js

// Make sure the shape of the default value passed to
// createContext matches the shape that the consumers expect!
export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {},
});

主题切换器按钮.js

import {ThemeContext} from './theme-context';

function ThemeTogglerButton() {
  // The Theme Toggler Button receives not only the theme
  // but also a toggleTheme function from the context
  return (
    <ThemeContext.Consumer>
      {({theme, toggleTheme}) => (
        <button
          onClick={toggleTheme}
          style={{backgroundColor: theme.background}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

export default ThemeTogglerButton;

应用程序.js

import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

    // State also contains the updater function so it will
    // be passed down into the context provider
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }

  render() {
    // The entire state is passed to the provider
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}

ReactDOM.render(<App />, document.root);

上面的示例直接来自 React Context API docs v16.8.6,并且是从消费者更新上下文值的推荐方法。https://reactjs.org/docs/context.html#updating-context-from-a-nested-component

于 2019-07-20T22:34:56.303 回答
32

首先,为了从消费者更新上下文,您需要访问渲染函数之外的上下文,有关如何执行此操作的详细信息,请查看

在渲染函数之外访问 React Context

其次,您应该从 Provider 提供一个处理程序,它更新上下文值而不是直接改变它。你的代码看起来像

父.js

import MyContext from "./MyContext.js";
import Child from "./Child.js";

class Parent extends Component {

    constructor(props) {
      super(props);
      this.state = {
        Message: "Welcome React",
        ReturnMessage:""
      };
    }

    updateValue = (key, val) => {
       this.setState({[key]: val});
    }
    render() {
        return (
           <MyContext.Provider value={{state: this.state, updateValue: this.updateValue}}>      
              <Child /> 
           </MyContext.Provider>
       )
    }
}

孩子

import MyContext from "./MyContext.js";

class Child extends Component {

    constructor(props) {
      super(props);
      this.state = {        
        ReturnMessage:""
      };
    }

    ClearData(e){
        const val = e.target.value;
        this.setState({
           ReturnMessage:val
        });
        this.props.context.updateValue('ReturnMessage', val);
    }

    render() {
        return (
           <React.Fragment>
             <p>{this.props.context.state.Message}</p>}
             <input onChange={this.ClearData} />
           </React.Fragment>
       )
    }
}

const withContext = (Component) => {
   return (props) => {
       <MyContext.Consumer>    
            {(context) => {
               return <Component {...props} context={context} />
            }}
       </MyContext.Consumer>
   }
}

export default withContext(Child);
于 2018-05-24T06:53:44.370 回答
5

您需要在 Provider 组件中编写一个函数来更新 State。确切地说,Consumer 只能使用您在 Provider 组件中编写的值和函数。

在父组件中

updateReturnMessage = (ReturnMessage) => {
  this.setState((prevState) => ({ ...prevState, ReturnMessage }))
}

<MyContext.Provider value={{ state: this.state, updateReturnMessage: this.updateReturnMessage }}>
// your code goes here
</MyContext.Provider>

在子组件中:

ClearData(e){
  const val = e.target.value;
  this.context.updateReturnMessage(val);
}

此功能类似于action creators可用的Reduxflux

于 2018-11-26T19:21:48.400 回答
-12

@nowshad,您是否尝试与 redux 一起使用然后我建议使用提供程序

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
​
const store = createStore(todoApp)
​
render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

如果您只使用几个组件,并且您希望根据您的语句为所有嵌套组件提供值

For nested components can i have one provider and multiple consumers For an Example : 1 is an parent , 1.1 is a child to 1 and 1.1.1 is child to 1.1, Can i have provider to 1 and consumers to 1.1 and 1.1.1 

那么我建议您只需将处理程序作为道具传递,一旦您想更改状态调用处理程序,它将更改整个组件的值。(如果您只有几个子组件,则应该这样做,它们都需要相同的值)

***Using context, we can avoid passing props through intermediate elements***

根据反应文档

不要为了避免将道具向下传递几个级别而使用上下文。坚持需要在多个级别的许多组件中访问相同数据的情况。

查看官方文档,了解为什么以及为什么不使用 Context: https ://reactjs.org/docs/context.html

如果您对为什么以及如何使用上下文仍有疑问或疑问,请告诉我

于 2018-05-25T07:39:01.730 回答