如何从Consumer更新Provider中的Context值

Now*_*yed 19 javascript reactjs react-context

MyContext.js

import React from "react";

const MyContext = React.createContext('test');
export default MyContext;
Run Code Online (Sandbox Code Playgroud)

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

Parent.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>
       )
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,在Provider选项卡中创建了一个父组件,其中包含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>
       )
    }
}
Run Code Online (Sandbox Code Playgroud)

因此在使用消费者的孩子可以显示在子渲染部分..

我正面临从消费者到提供者状态的更新.

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

小智 40

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

authContext.js

import { createContext } from "react";

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

export default authContext;
Run Code Online (Sandbox Code Playgroud)

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>
  );
};
Run Code Online (Sandbox Code Playgroud)

最后是 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"));
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我需要扩展 @DataMastery 的评论,因为我花了整整 15 分钟来解决这个问题。状态仍然在父组件中处理,但在将 setAuthenticated 从 useState 传递到 authContext.Provider 之前,您需要在上下文上定义 setAuthenticated 的形状。最简单的方法是创建一个接受参数的空函数,稍后由 setState 函数替换。希望这能为您节省 15 分钟! (19认同)
  • @tejasvi88 `setAuthenticated: (auth) =&gt; {}` 只是一个占位符。您在此处提供函数:“value={{authentiated, setAuthenticated}}”。 (16认同)
  • 读起来就像魔法一样。为什么 `setAuthenticated: (auth) =&gt; {}` 为空?和罗丹一样的问题。它是如何工作的? (15认同)
  • 我正在努力理解 setAuthenticated 函数在参数被抛出时如何更新上下文。我见过的每个上下文“更新程序”函数基本上都是一个空函数/结果,看起来像一个“什么也不做”函数。这是如何运作的?! (6认同)
  • 值得获得最佳接受的答案!节省了我的工作时间。 (5认同)

Jac*_*ack 33

从嵌套组件更新上下文

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

主题上下文.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: () => {},
});
Run Code Online (Sandbox Code Playgroud)

主题切换器-button.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;
Run Code Online (Sandbox Code Playgroud)

应用程序.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);
Run Code Online (Sandbox Code Playgroud)

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

  • 杰出的。感谢您的评论。这应该在官方文档中 (3认同)
  • 考虑到上下文提供程序无论如何都会设置默认上下文值,那么默认上下文值的用途是什么? (2认同)
  • 我对此很陌生,想知道调用 setState() 是否不会从应用程序组件的顶部重新渲染所有内容?如果应用程序组件包含其他不想重新渲染的昂贵组件怎么办? (2认同)

Shu*_*tri 15

首先,为了从使用者那里更新上下文,您需要在render函数之外访问上下文,有关如何执行此操作的详细信息,请检查

在渲染功能之外访问React Context

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

Parent.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>
       )
    }
}
Run Code Online (Sandbox Code Playgroud)

儿童

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);
Run Code Online (Sandbox Code Playgroud)


Ram*_*ban 8

您需要在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>
Run Code Online (Sandbox Code Playgroud)

在子组件中:

ClearData(e){
  const val = e.target.value;
  this.context.updateReturnMessage(val);
}
Run Code Online (Sandbox Code Playgroud)

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