React New Context API - Доступ к существующему контексту в нескольких файлах
Все примеры, которые я видел в новом API контекста в React, находятся в одном файле, например 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
Я полностью упустил момент? Есть ли документация или пример того, как это работает в нескольких файлах?
Ответы
Ответ 1
Читая исходный код 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
Я думаю, что проблема, с которой вы сталкиваетесь, заключается в том, что вы создаете два разных контекста и пытаетесь использовать их как один. Это 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>
...
Ответ 3
На данный момент два контекста, которые вы создали в файлах, не совпадают, даже если они совпадают. Вам нужно экспортировать контекст, который вы создали в одном из файлов, и использовать его через out.
так что-то вроде этого, в файле 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>
);
}
}
Ответ 4
TL;DR; Демо на CodeSandbox
Мой текущий метод решения одной и той же проблемы заключается в использовании библиотеки Unstated, которая является удобной оболочкой API-интерфейса React 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
может подписаться на состояние API из глубины дерева React.
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: Кто-то меня очень сильно избивает, если я делаю это неправильно. Мне бы хотелось узнать разные/лучшие подходы. - Спасибо!
Ответ 5
Я собираюсь бросить свое решение в банк - он был вдохновлен @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>
)
}
Я так делаю с редуксом!