Контекст доступа к контексту вне функции рендеринга

Я разрабатываю новое приложение, используя новый 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

Вы должны передать контекст в более высоком родительском компоненте, чтобы получить доступ в качестве реквизита для ребенка.