Доступ до контексту реакції поза функцією візуалізації


93

Я розробляю новий додаток, використовуючи новий React Context API замість Redux, і раніше, Reduxколи, коли мені потрібно було отримати список користувачів, наприклад, я просто закликаю componentDidMountсвою дію, але тепер з React Context, мої дії живуть всередині мій споживач, який знаходиться всередині моєї функції візуалізації, що означає, що кожного разу, коли викликається моя функція візуалізації, він викликатиме мою дію, щоб отримати список моїх користувачів, і це погано, оскільки я буду робити багато непотрібних запитів.

Отже, як я можу зателефонувати лише один раз на свою дію, наприклад, 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не перебувають у моєму перегляді користувачів. Який правильний спосіб це зробити, якщо це взагалі можливо?

Відповіді:


145

РЕДАГУВАТИ: Завдяки введенню реакційних хуків у v16.8.0 , ви можете використовувати контекст у функціональних компонентах, використовуючи useContextхук

const Users = () => {
    const contextValue = useContext(UserContext);
    // rest logic here
}

РЕДАГУВАТИ: починаючи з версії 16.6.0 . Ви можете використовувати контекст у методі життєвого циклу, використовуючи this.contextlike

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);

Це спосіб зробити це! Але я обгортаю свої компоненти іншими матеріалами, тому код буде дедалі складнішим, роблячи таким чином. Отже, використання бібліотеки with-context та його декораторів робить код набагато простішим. Але дякую за відповідь!
Густаво Мендонса

У мене це не працює, я використовую github with-context
PassionateDeveloper

1
Шубхам Хатрі прав у своїй відповіді, є лише невеличка помилка, яка заважає цьому працювати. Фігурні дужки перешкоджають неявному поверненню. Просто замініть їх дужками.
VDubs

1
Чи можете ви пояснити, як використовувати параметр this.contextз кількома контекстами всередині одного компонента? Припустимо, у мене є компонент, який повинен отримати доступ до UserContext та PostContext, як з цим працювати? Що я бачу, це створити контекст, що змішує обидва, але це єдине чи краще рішення для цього?
Густаво

1
@ GustavoMendonça Ви можете підписатись лише на один контекст, використовуючи реалізацію API this.context. Якщо вам потрібно прочитати більше одного, вам потрібно дотримуватися шаблону рендерингу
Shubham

3

Добре, я знайшов спосіб зробити це з обмеженнями. За допомогою with-contextбібліотеки мені вдалося вставити всі свої споживчі дані у свій компонентний реквізит.

Але, якщо вставити більше одного споживача в один і той же компонент, зробити це складно, вам доведеться створити змішаних споживачів за допомогою цієї бібліотеки, що робить код не елегантним і непродуктивним.

Посилання на цю бібліотеку: https://github.com/SunHuawei/with-context

EDIT: Насправді вам не потрібно використовувати багатоконтекстний API, який with-contextнадає, насправді, ви можете використовувати простий API і зробити декоратор для кожного вашого контексту, і якщо ви хочете використовувати більше одного споживача у вашому компоненті, просто заявіть над своїм класом скільки завгодно декораторів!


1

Зі свого боку цього було достатньо, щоб додати .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>
  }
}

1
цей код виглядає набагато простішим, але насправді він взагалі не отримує доступ до значень і не взаємодіє з екземпляром RootStore. Ви можете показати приклад, розділений на два файли та реактивні оновлення інтерфейсу?
dcsan

-8

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.