如何从消费者更新提供者中的上下文值?

IT技术 javascript reactjs react-context
2021-03-31 08:15:46

我的上下文.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>
       )
    }
}

所以我用 Provider 上下文创建了父组件,并在 provider 选项卡中调用子组件

儿童.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 渲染部分显示数据。

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

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

5个回答

您可以使用 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 函数如何在参数刚刚被抛出时更新上下文。我见过的每个上下文“更新程序”函数基本上都是一个空函数/结果,看起来像是一个“什么都不做”的函数。这是如何运作的?!
2021-05-28 08:15:46
我需要扩展@DataMastery 的评论,因为我花了整整 15 分钟来解决这个问题。状态仍然在父组件中处理,但在您可以setAuthenticateduseStateto传递之前authContext.Provider,您需要setAuthenticated在上下文中定义的形状最简单的方法是创建一个接受参数的空函数,稍后由 setState 函数替换。希望可以为您节省 15 分钟!
2021-05-28 08:15:46
@tejasvi88setAuthenticated: (auth) => {}只是一个占位符。您在此处提供函数:value={{ authenticated, setAuthenticated }}
2021-06-03 08:15:46
应该得到最公认的答案!节省了我的工作时间。
2021-06-09 08:15:46
它读起来像魔术。为什么setAuthenticated: (auth) => {}是空的?与 Lo-Tan 的问题相同。它是如何工作的?
2021-06-12 08:15:46

从嵌套组件更新上下文

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

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

主题切换器-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;

应用程序.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

是的,这是最好的答案
2021-05-22 08:15:46
杰出的。谢谢你的评论。这应该在官方文档中
2021-05-27 08:15:46
考虑到 Context Provider 总是会设置它,默认上下文值的目的是什么?
2021-05-31 08:15:46
是的,它确实重新渲染了整个树。Context.Provider 在其子属性在后续渲染周期中更改时重新渲染。
2021-06-03 08:15:46
@SébastienDeVarennes 您说得有道理,但如果设置了默认值,则更容易识别值的作用。
2021-06-08 08:15:46

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

在渲染函数之外访问 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);
对于嵌套组件,我可以有一个提供者和多个消费者吗例如:1 是父级,1.1 是 1 的子级,1.1.1 是 1.1 的子级,我可以有 1 的提供者和 1.1 和 1.1.1 的消费者吗?
2021-05-22 08:15:46
您可以拥有任意数量的共享相同上下文值的消费者。
2021-05-25 08:15:46
<p>{this.props.context.state.Message}</p> 类型错误:无法读取未定义的属性“状态”
2021-05-25 08:15:46
感谢您的解决方案 Shubham Khatri,如果我需要更新多个状态,那么在父级中我将像这样设置状态,子级 updateReturnValue = (val) => { this.setState({ state }); }
2021-06-19 08:15:46
@NowshadSyed,是的,您也可以拥有一个更新所有状态的通用函数。我更新了我的答案
2021-06-19 08:15:46

您需要在 Provider 组件中编写一个函数来更新 State。确切地说,消费者只能使用您在 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

我这样做了,但是 this.setState 是未定义的。“this”是调用该方法的上下文消费者控件的 this.props。我试图在提供者上使用箭头 (=>) 函数来确保“this”是正确的,但仍然是同样的问题。有什么建议?
2021-06-09 08:15:46
代码和盒子.io/s/5mrk843z94检查此链接我以您询问@Pinny 的方式使用了上下文
2021-06-15 08:15:46

@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 

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

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

根据 React 文档

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

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

如果您对使用上下文的原因和方法仍有疑问或疑虑,请告诉我

Redux 不是上下文 API。
2021-06-08 08:15:46
他们正在尝试使用 Context API 解决他们的问题,因此 Redux 术语中的解决方案不是我们想要的。
2021-06-19 08:15:46