【问题标题】:How to update the Context value in a Provider from the Consumer?如何从消费者更新提供者中的上下文值?
【发布时间】:2018-11-03 06:53:14
【问题描述】:

MyContext.js

import React from "react";

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

我在一个单独的 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>
       )
    }
}

所以我创建了带有 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>
       )
    }
}

所以在child中使用Consumer,我可以在child渲染部分显示数据。

我想从消费者那里更新状态时遇到问题。

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

【问题讨论】:

    标签: javascript reactjs react-context


    【解决方案1】:

    您可以使用 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

    【讨论】:

    • 应该得到最受认可的答案!节省了我的工作时间。
    • 我很难理解 setAuthenticated 函数在刚刚抛出参数时如何更新上下文。我见过的每个上下文“更新程序”函数基本上都是一个空函数/结果,看起来像是一个“无所事事”函数。这是怎么回事?!
    • 它读起来像魔术。为什么setAuthenticated: (auth) =&gt; {} 是空的?和 Lo-Tan 一样的问题。它是如何工作的?
    • @tejasvi88 setAuthenticated: (auth) =&gt; {} 只是一个占位符。你在这里提供函数:value={{ authenticated, setAuthenticated }}.
    • 我需要扩展@DataMastery 的评论,因为我花了整整 15 分钟来解决这个问题。状态仍然在父组件中处理,但在将setAuthenticateduseState 传递到authContext.Provider 之前,您需要在上下文中定义setAuthenticated 的形状。最简单的方法是创建一个接受参数的空函数,稍后由 setState 函数替换。希望能为您节省 15 分钟!
    【解决方案2】:

    从嵌套组件更新上下文

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

    theme-context.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: () => {},
    });
    

    theme-toggler-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;
    

    app.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

    【讨论】:

    • 太棒了。谢谢你的评论。这应该在官方文档中
    • 是的,这是最好的答案
    • 默认上下文值的目的是什么,考虑到上下文提供者总是会设置它?
    • @SébastienDeVarennes 你说得有道理,但如果设置了默认值,则更容易识别值的作用。
    • 是的,它会重新渲染整个树。 Context.Provider 在其子属性在后续渲染周期中更改时重新渲染。
    【解决方案3】:

    首先,为了从消费者那里更新上下文,你需要访问渲染函数之外的上下文,关于如何做到这一点的详细信息,请查看

    Access React Context outside of render function

    其次,您应该从 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>
           )
        }
    }
    

    孩子

    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);
    

    【讨论】:

    • 感谢您的解决方案 Shubham Khatri,如果我需要更新多个状态,那么在父级中我将像这样设置状态,子级 updateReturnValue = (val) => { this.setState( { 状态 }); }
    • @NowshadSyed,是的,您也可以拥有一个更新所有状态的通用函数。我更新了相同的答案
    • 对于嵌套组件,我可以有一个提供者和多个消费者吗例如:1 是父级,1.1 是 1 的子级,1.1.1 是 1.1 的子级,我可以有 1 的提供者和消费者到 1.1 和 1.1.1
    • 您可以拥有任意数量的消费者,它们共享相同的上下文值。
    • {this.props.context.state.Message}

      TypeError: Cannot read property 'state' of undefined
    【解决方案4】:

    你需要在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);
    }
    

    此功能类似于Reduxflux 中的action creators

    【讨论】:

    • 我这样做了,但是 this.setState 是未定义的。 “this”是调用该方法的上下文消费者控件的 this.props。我尝试在提供程序上使用箭头(=>)功能来确保“this”是正确的,但仍然是同样的问题。有什么建议吗?
    • codesandbox.io/s/5mrk843z94。检查这个链接我按照你问@Pinny的方式使用了上下文
    【解决方案5】:

    @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***
    

    根据 React 文档

    不要仅仅为了避免将 props 向下传递几个级别而使用上下文。戳 对于需要在许多组件中访问相同数据的情况 在多个层面。

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

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

    【讨论】:

    • Redux 不是上下文 API。
    • 他们正在尝试使用 Context API 解决他们的问题,因此 redux 术语中的解决方案不是我们想要的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-02
    • 2021-03-17
    • 2022-08-08
    • 1970-01-01
    • 1970-01-01
    • 2020-02-06
    • 1970-01-01
    相关资源
    最近更新 更多