Контекст доступа к контексту вне функции рендеринга
Я разрабатываю новое приложение, используя новый React Context API вместо Redux, и раньше, с Redux
, когда мне нужно было получить список пользователей, например, я просто вызывал componentDidMount
мое действие, но теперь с React Context мои действия жить в моем Consumer, который находится внутри моей функции рендеринга, что означает, что каждый раз, когда вызывается моя функция рендеринга, он вызывает мое действие для получения списка пользователей, и это не хорошо, потому что я буду выполнять много ненужных запросов.
Итак, как я могу вызвать только один раз мое действие, как в componentDidMount
вместо вызова в рендере?
Просто в качестве примера, посмотрите на этот код:
Предположим, я упаковываю всех моих Providers
в один компонент, например так:
import React from 'react';
import UserProvider from './UserProvider';
import PostProvider from './PostProvider';
export default class Provider extends React.Component {
render(){
return(
<UserProvider>
<PostProvider>
{this.props.children}
</PostProvider>
</UserProvider>
)
}
}
Затем я помещаю этот компонент провайдера, оборачивая все мое приложение, например так:
import React from 'react';
import Provider from './providers/Provider';
import { Router } from './Router';
export default class App extends React.Component {
render() {
const Component = Router();
return(
<Provider>
<Component />
</Provider>
)
}
}
Теперь, на мой взгляд пользователей, это будет примерно так:
import React from 'react';
import UserContext from '../contexts/UserContext';
export default class Users extends React.Component {
render(){
return(
<UserContext.Consumer>
{({getUsers, users}) => {
getUsers();
return(
<h1>Users</h1>
<ul>
{users.map(user) => (
<li>{user.name}</li>
)}
</ul>
)
}}
</UserContext.Consumer>
)
}
}
Что я хочу это:
import React from 'react';
import UserContext from '../contexts/UserContext';
export default class Users extends React.Component {
componentDidMount(){
this.props.getUsers();
}
render(){
return(
<UserContext.Consumer>
{({users}) => {
getUsers();
return(
<h1>Users</h1>
<ul>
{users.map(user) => (
<li>{user.name}</li>
)}
</ul>
)
}}
</UserContext.Consumer>
)
}
}
Но, конечно, что приведенный выше пример не работает, потому что getUsers
не живут в моем представлении пользователей. Как правильно это сделать, если это вообще возможно?
Ответы
Ответ 1
РЕДАКТИРОВАТЬ: С введением ответных хуков в v16.8.0, вы можете использовать контекст в функциональных компонентах, используя хук useContext
const Users = () => {
const contextValue = useContext(UserContext);
// rest logic here
}
РЕДАКТИРОВАТЬ: Начиная с версии 16.6.0. Вы можете использовать контекст в методе жизненного цикла, используя this.context
как
class Users extends React.Component {
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of UserContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render something based on the value of UserContext */
}
}
Users.contextType = UserContext; // This part is important to access context values
До версии 16.6.0 вы могли сделать это следующим образом
Чтобы использовать Context в своем методе lifecyle, вы должны написать свой компонент как
class Users extends React.Component {
componentDidMount(){
this.props.getUsers();
}
render(){
const { users } = this.props;
return(
<h1>Users</h1>
<ul>
{users.map(user) => (
<li>{user.name}</li>
)}
</ul>
)
}
}
export default props => ( <UserContext.Consumer>
{({users, getUsers}) => {
return <Users {...props} users={users} getUsers={getUsers} />
}}
</UserContext.Consumer>
)
Как правило, вы должны поддерживать один контекст в своем приложении, и имеет смысл упаковать указанный выше логин в HOC, чтобы использовать его повторно. Вы можете написать это как
import UserContext from 'path/to/UserContext';
const withUserContext = Component => {
return props => {
return (
<UserContext.Consumer>
{({users, getUsers}) => {
return <Component {...props} users={users} getUsers={getUsers} />;
}}
</UserContext.Consumer>
);
};
};
и тогда вы можете использовать его как
export default withUserContext(User);
Ответ 2
Хорошо, я нашел способ сделать это с ограничением. В библиотеке with-context
мне удалось вставить все мои потребительские данные в мои реквизиты компонентов.
Но для того, чтобы вставить более одного потребителя в один и тот же компонент, сложно сделать, вам нужно создать смешанных потребителей с этой библиотекой, что делает изящный код и непродуктивным.
Ссылка на эту библиотеку: https://github.com/SunHuawei/with-context
EDIT: На самом деле вам не нужно использовать multi context api, что в with-context
обеспечивает, по сути, вы можете использовать простой api и сделать декоратор для каждого вашего контекста, и если вы хотите использовать в вас более одного потребителя компонент, просто объявите над своим классом столько декораторов, сколько захотите!
Ответ 3
Шубхам Хатри правильно ответил в ответ, есть небольшая опечатка, препятствующая этому бежать. Кудрявые фигурные скобки предотвращают неявное возвращение. Просто замените их скобками.
import UserContext from 'path/to/UserContext';
const withUserContext = (Component) => {
return (props) => (
<UserContext.Consumer>
{({users, getUsers}) => {
return <Component {...props} users={users} getUsers={getUsers} />
}}
</UserContext.Consumer>
);
}
Ответ 4
С моей стороны было достаточно добавить .bind(this)
к событию. Вот так выглядит мой компонент.
// Stores File
class RootStore {
//...States, etc
}
const myRootContext = React.createContext(new RootStore())
export default myRootContext;
// In Component
class MyComp extends Component {
static contextType = myRootContext;
doSomething() {
console.log()
}
render() {
return <button onClick={this.doSomething.bind(this)}></button>
}
}
Ответ 5
может быть, вы называете компонент строчными буквами, например
<Context.consumer>
это неправильно, вы должны звонить в верхнем регистре, как
<Context.Consumer>
Ответ 6
Вы должны передать контекст в более высоком родительском компоненте, чтобы получить доступ в качестве реквизита для ребенка.