我在React中看到的新Context API的所有示例都在一个文件中,例如https://github.com/wesbos/React-Context
当我试图让它在多个文件中工作时,我显然遗漏了一些东西。
我希望创建一个GlobalConfiguration
组件(下面的MyProvider
)来创建和管理上下文中的值,为任何子组件(下面的MyConsumer
)做好准备从它。
render() {
return (
<MyProvider>
<MyConsumer />
</MyProvider>
);
}
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 >
);
}
}
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
我完全错过了这一点吗?是否有文档或如何跨多个文件工作的示例?
答案 0 :(得分:14)
我认为您遇到的问题是您正在创建两个不同的上下文,并试图将它们用作一个上下文。由Context
创建的React.createContext
链接了Provider
和Consumer
。
制作一个文件(我将其称为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)
我目前解决同一问题的方法是使用Unstated library,它是React Context API的一个方便的包装器。 “Unstated”还提供了依赖注入,允许创建容器的离散实例;这对于代码重用和测试很方便。
以下框架API服务包含loggedIn
等状态属性,以及两种服务方法:login()
和logout()
。这些道具和方法现在可以在整个应用程序中使用,只需在每个需要上下文的文件中导入。
例如:
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
}
现在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"));
最后,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!