反应新的上下文API - 跨多个文件访问现有上下文

时间:2018-03-21 12:40:15

标签: reactjs

我在React中看到的新Context API的所有示例都在一个文件中,例如https://github.com/wesbos/React-Context

当我试图让它在多个文件中工作时,我显然遗漏了一些东西。

我希望创建一个GlobalConfiguration组件(下面的MyProvider)来创建和管理上下文中的值,为任何子组件(下面的MyConsumer)做好准备从它。

App.js

render() {
    return (
        <MyProvider>
            <MyConsumer />
        </MyProvider>
    );
}

provider.js

import React, { Component } from 'react';

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

export default class MyProvider extends Component {

    render() {
        return (
            <MyContext.Provider
                value={{ somevalue: 1 }}>
                {this.props.children}
            </MyContext.Provider >
        );
    }
}

consumer.js

import React, { Component } from 'react';

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

export default class MyConsumer extends Component {

    render() {

        return (
            <MyContext.Consumer>
                {(context) => (
                    <div>{context.state.somevalue}</div>
                )}
            </MyContext.Consumer>
        );
    }
}

不幸的是,在控制台中失败了:

consumer.js:12 Uncaught TypeError: Cannot read property 'somevalue' of undefined

我完全错过了这一点吗?是否有文档或如何跨多个文件工作的示例?

5 个答案:

答案 0 :(得分:14)

我认为您遇到的问题是您正在创建两个不同的上下文,并试图将它们用作一个上下文。由Context创建的React.createContext链接了ProviderConsumer

制作一个文件(我将其称为configContext.js

configContext.js

import React, { Component, createContext } from "react";

// Provider and Consumer are connected through their "parent" context
const { Provider, Consumer } = createContext();

// Provider will be exported wrapped in ConfigProvider component.
class ConfigProvider extends Component {
  state = {
    userLoggedIn: false,                            // Mock login
    profile: {                                      // Mock user data
      username: "Morgan",
      image: "https://morganfillman.space/200/200",
      bio: "I'm Mogran—so... yeah."
    },
    toggleLogin: () => {
      const setTo = !this.state.userLoggedIn;
      this.setState({ userLoggedIn: setTo });
    }
  };

  render() {
    return (
      <Provider
        value={{
          userLoggedIn: this.state.userLoggedIn,
          profile: this.state.profile,
          toggleLogin: this.state.toggleLogin
        }}
      >
        {this.props.children}
      </Provider>
    );
  }
}

export { ConfigProvider };

// I make this default since it will probably be exported most often.
export default Consumer;

index.js

...
// We only import the ConfigProvider, not the Context, Provider, or Consumer.
import { ConfigProvider } from "./configContext";
import Header from "./Header";
import Profile from "./Profile";

import "./styles.css";

function App() {
  return (
    <div className="App">
      <ConfigProvider>
        <Header />
        <main>
          <Profile />
        </main>
        <footer>...</footer>
      </ConfigProvider>
    </div>
  );
}
...

Header.js

import React from 'react'
import LoginBtn from './LoginBtn'
... // a couple of styles
const Header = props => {
  return (
... // Opening tag, etc.
      <LoginBtn />  // LoginBtn has access to Context data, see file.
... // etc.
export default Header

LoginBtn.js

import React from "react";
import Consumer from "./configContext";

const LoginBtn = props => {
  return (
    <Consumer>
      {ctx => {
        return (
          <button className="login-btn" onClick={() => ctx.toggleLogin()}>
            {ctx.userLoggedIn ? "Logout" : "Login"}
          </button>
        );
      }}
    </Consumer>
  );
};

export default LoginBtn;

Profile.js

import React, { Fragment } from "react";
import Consumer from "./configContext"; // Always from that same file.

const UserProfile = props => {...}; // Dumb component

const Welcome = props => {...}; // Dumb component

const Profile = props => {
  return (
    <Consumer>
      ...
        {ctx.userLoggedIn ? (
          <UserProfile profile={ctx.profile} />
        ) : (<Welcome />)}
      ...
    </Consumer>
  ...

答案 1 :(得分:10)

阅读React-Context的源代码,

<MyContext.Provider value={{
  state: this.state,
}}>

<MyContext.Consumer>
  {(context) => <p>{context.state.age}</p>}

所以,如果你这样做

<MyContext.Provider value={{ somevalue: 1 }}>
  {this.props.children}
</MyContext.Provider>

你应该得到somevalue那样的

<MyContext.Consumer>
  {(context) => <div>{context.somevalue}</div>}
</MyContext.Consumer>

修改

如果使用以下内容创建名为myContext.js的文件,该怎么办?

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

然后导入它:

import MyContext form '<proper_path>/myContext';

答案 2 :(得分:2)

截至目前,即使名称相同,您在文件中创建的两个上下文也不尽相同。您需要导出在其中一个文件中创建的上下文,并在整个过程中使用它。

就像这样,在您的provider.js文件中:

import React, { Component } from 'react';

const MyContext = React.createContext();
export const MyContext;

export default class MyProvider extends Component {
    render() {
        return (
            <MyContext.Provider
                value={{ somevalue: 1 }}>
                {this.props.children}
            </MyContext.Provider >
        );
    }
}

然后在您的consumer.js文件中

import MyContext from 'provider.js';
import React, { Component } from 'react';
export default class MyConsumer extends Component {
    render() {
        return (
            <MyContext.Consumer>
                {(context) => (
                    <div>{context.somevalue}</div>
                )}
            </MyContext.Consumer>
        );
    }
}

答案 3 :(得分:2)

TLDR; Demo on CodeSandbox

我目前解决同一问题的方法是使用Unstated library,它是React Context API的一个方便的包装器。 “Unstated”还提供了依赖注入,允许创建容器的离散实例;这对于代码重用和测试很方便。

如何将React / Unstated-Context包装为服务

以下框架API服务包含loggedIn等状态属性,以及两种服务方法:login()logout()。这些道具和方法现在可以在整个应用程序中使用,只需在每个需要上下文的文件中导入。

例如:

Api.js

import React from "react";

// Import helpers from Unstated 
import { Provider, Subscribe, Container } from "unstated";

// APIContainer holds shared/global state and methods
class APIContainer extends Container {
  constructor() {
    super();

    // Shared props
    this.state = {
      loggedIn: false
    };
  }

  // Shared login method
  async login() {
    console.log("Logging in");
    this.setState({ loggedIn: true });
  }

  // Shared logout method
  async logout() {
    console.log("Logging out");
    this.setState({ loggedIn: false });
  }
}

// Instantiate the API Container
const instance = new APIContainer();

// Wrap the Provider
const ApiProvider = props => {
  return <Provider inject={[instance]}>{props.children}</Provider>;
};

// Wrap the Subscriber
const ApiSubscribe = props => {
  return <Subscribe to={[instance]}>{props.children}</Subscribe>;
};

// Export wrapped Provider and Subscriber
export default {
    Provider: ApiProvider,
    Subscribe: ApiSubscribe
}

App.js

现在Api.js模块可以在App.js中用作全局提供:

import React from "React";
import { render } from "react-dom";
import Routes from "./Routes";
import Api from "./Api";

class App extends React.Component {
  render() {
    return (
      <div>
        <Api.Provider>
          <Routes />
        </Api.Provider>
      </div>
    );
  }
}

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

页/ Home.js:

最后,Api.js可以从React树中深入订阅API的状态。

import React from "react";
import Api from "../Api";

const Home = () => {
  return (
    <Api.Subscribe>
      {api => (
        <div>
          <h1> Home</h1>
          <pre>
            api.state.loggedIn = {api.state.loggedIn ? " true" : " false"}
          </pre>
          <button onClick={() => api.login()}>Login</button>
          <button onClick={() => api.logout()}>Logout</button>
        </div>
      )}
    </Api.Subscribe>
  );
};

export default Home;

在此处尝试CodeSandbox演示:https://codesandbox.io/s/wqpr1o6w15

希望有所帮助!

PS:如果我以错误的方式这样做,有人会快速打击我。我很想学习不同/更好的方法。 - 谢谢!

答案 4 :(得分:1)

我要把解决方案扔进锅里-它受到@Striped的启发,只是将出口重命名为对我来说有意义的东西。

import React, { Component } from 'react'
import Blockchain from './cloudComputing/Blockchain'

const { Provider, Consumer: ContextConsumer } = React.createContext()

class ContextProvider extends Component {
  constructor(props) {
    super(props)
    this.state = {
      blockchain: new Blockchain(),
    }
  }

  render() {
    return (
      <Provider value={this.state}>
        {this.props.children}
      </Provider>
    )
  }
}

module.exports = { ContextConsumer, ContextProvider }

现在很容易在任何组件中实现ContextConsumer

...
import { ContextConsumer } from '../Context'
...
export default class MyComponent extends PureComponent {
...
render() {
return (
  <ContextConsumer>
    {context => {
      return (
        <ScrollView style={blockStyle.scrollView}>
          {map(context.blockchain.chain, block => (
              <BlockCard data={block} />
          ))}
        </ScrollView>
      )
    }}
  </ContextConsumer>
)
}

我已经完成了redux!