Translate

Поиск по этому блогу

Показаны сообщения с ярлыком React Hooks. Показать все сообщения
Показаны сообщения с ярлыком React Hooks. Показать все сообщения

воскресенье, 5 июля 2020 г.

Следует ли использовать React Context поверх Redux в приложении React?







Redux - это библиотека, которая помогает управлять состоянием вашего приложения.

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

Redux может добавить ненужную сложность вашему приложению, если оно действительно маленькое с простым потоком данных.

С другой стороны, в версии 16.3 React команда представила Context, который также помогает управлять состоянием React - приложения.

Зачем нам нужен инструмент управления состоянием - state

Если у нас есть несколько компонентов, которые совместно используют один и тот же фрагмент данных, и у них нет родительских и дочерних отношений, то для решения проблемы используют Redux. В Redux у нас есть один фрагмент идентичных данных (identical data), который синхронизирован со всеми компонентами, которые используют этот фрагмент данных.

Одним из решений может быть использование event raising в Angular и использование props в React. Позже в большом коде это решение превратится в спагетти событий ( event spaghetti) и в React - приложении, мы будем сталкиваться с проблемой получения (доступности) props - так называемым - prop drilling.

Со всей базы кода мы начнем получать события, и будет сложно отслеживать их все. Таким образом, поток данных становится непредсказуемым. Facebook столкнулся с этой проблемой еще в 2014 году, поэтому они представили Flux Architecture.

Redux - это упрощенная и легкая реализация Flux Architecture.

Основные преимущества Redux

  • Отделенная, расцепленная архитектура
  • Тестируемость
  • Отличная поддержка инструментов в Chrome, Firefox
  • Простая реализация функций отмены / повтора - выкл./вкл.


Когда использовать Redux

В следующих случаях мы можем использовать redux:
  • Один и тот же фрагмент данных в нескольких местах
  • Несколько (views) - представлений, которые должны работать с теми же данными в синхронизации
  • Данные могут быть изменены несколькими действиями / несколькими пользователями


Прежде чем мы получим context, для решения проблемы использовали redux, такой как спагетти - event spaghetti и prop drilling. Redux приносит много сложности для простых приложений. Позже React - команда представила контекстную концепцию для простоты.

С помощью контекста - context мы можем передавать данные по нашему дереву компонентов без prop drilling. Таким образом, в одном компоненте мы предоставим некоторый контекст или общие данные, а в другом компоненте мы будем использовать данные.

Теперь мы увидим, как мы можем использовать контекст поддерживаемым и инкапсулированным образом с помощью typescript и React - хуков. В нашем примере мы будем использовать понятия действий - actions, редуктора - reducer и состояния - state из redux. Итак, сначала мы рассмотрим основные концепции Redux.

Основная концепция Redux

  • Хранилище - Store - один объект JS, который содержит состояние приложения.
  • Действия - Actions - Обычный объект JS, представляющий, что что-то произошло. Подобные события.

  • {type: ‘USER_LOGGED_IN’}

  • Редуктор Reducer- функция, которая определяет, как состояние state изменяется в ответ на действие - action. Это не меняет состояние. Возвращает новое состояние.
В Redux мы не обновляем напрямую состояние. Сначала мы отправим действие (action). Оно попадет в хранилище (store). Хранилище (store) передает действие (action) корневому редуктор (reducer). На основе действия редуктор (reducer) возвращает новое состояние (state), и хранилище (store) обновляется внутри.


На картинке показано изменение состояния компонента при клике


Использование контекста в приложении React

Для демонстрации идея состоит в том, чтобы создать компонент ,mark>Header, который будет отвечать за отображение кнопки «Вход» или «Выход из системы» в зависимости от состояния из хранилища - store. Мы отправим действие action LOGIN и LOGOUT из компонента Header.

Мы сделаем все 5 простых шагов.
  1. Create Actions - Создать действия
  2. Create State - Создадим состояние
  3. Create Reducer - Создадим редуктор
  4. Create Context - Создадим контекст
  5. Create Header component - Создадим сам компонент Header

1 - Создание действий - Actions

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

Action.ts
export enum Action {
  LOGIN = "LOGIN",
  LOGOUT = "LOGOUT"
}


Создание состояния - State

Теперь мы должны определить, какие данные мы хотим сохранить в нашем хранилище контекста. Поэтому мы создадим интерфейс для этого, а также начальное значение для состояния. В нашем случае мы просто сохраним имя пользователя.

state.ts
export interface StateType {
  username: string | null;
}
export const initialState: StateType = {
  username: null
};


Создание редуктора - Reducer

Pure Function или Чистая функция - это такая функция, которая при получении однинх и тех же данных на входе, всегда получаем один и тот же выход из функции, то функция является чистой функцией. Так что побочных эффектов не будет. Это дает нам легкую тестируемость и простой способ реализации функций отмены / возврата.
Если вы забыли некоторые возможности JavaScript, то я рекомендую вам посмотреть статьи в моем блоге - ES6 и JS
Далее мы создадим редуктор. Редуктор это чистая функция. Это функция с двумя аргументами. Первый - это состояние по умолчанию с типом StateType, а второй - объект с props- свойствами type и полезной нагрузки - payload. Свойство type определяет тип действия, а payload содержит дополнительные данные от диспетчера.

Reducer.ts
import { StateType, initialState } from "./state";
import { Action } from "./Action";
export type ActionType = {
  type: Action;
  payload?: StateType;
};
const reducer = (state: StateType, { type, payload }: ActionType) => {
switch (type) {
    case Action.LOGIN:
      return { ...state, ...payload };
  
    case Action.LOGOUT:
      return initialState;
default:
      return state;
  }
};
export default reducer;


Для action -действия LOGIN мы передадим имя пользователя в качестве полезной нагрузки - payload, когда мы отправим действие - action. Для выхода из системы нам не нужны никакие данные в качестве полезной нагрузки -payload. Мы просто сбросим состояние до начального значения state.

4 - Создадим контекст -Context

Теперь мы создадим контекст. Сначала мы определим ContextType, а затем создадим Store. После этого мы создадим StoreProvider.

Context.tsx
import React, { createContext, Dispatch, useReducer } from "react";
import reducer, { ActionType } from "./Reducer";
import { StateType, initialState } from "./state";
export interface ContextType {
  state: StateType;
  dispatch: Dispatch<ActionType>;
}
export const RootStore = createContext({} as ContextType);
export const RootStoreProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer<React.Reducer<StateType, ActionType>>(reducer, initialState);
const value = { state, dispatch };
return <RootStore.Provider value={value}>{children}</RootStore.Provider>;
};


Теперь мы обернем наш корневой компонент этим RootStoreProvider для доступа к хранилищу - store глобально.

index.tsx
const App = () => {
  return <RootStoreProvider>....</RootStoreProvider>;
};



Теперь у нас все готово. Мы создадим простой компонент Header, из которого мы будем использовать RootStore для получения информации о пользователе, а также для обновления информации.

5 - Создадим сам компонент Header



Header/index.tsx
import React from "react";
const Header = () => {
  const username = null;
  const toggleLoginLogoutHandler = () => {};
  return (
    <div
      style={{
        backgroundColor: "#dee5ec",
        padding: 10,
        display: "flex",
        justifyContent: "space-between",
      }}
    >
      <div>
        <p>Header</p>
        {username && <p>Logged in as {username}</p>}
      </div>
      <button onClick={toggleLoginLogoutHandler}>
        {username ? "Logout" : "Login"}
      </button>
    </div>
  );
};
export default Header;



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

import React, { useContext } from "react";
import { RootStore } from "../../Context";
import { Action } from "../../Action";
const Header = () => {
  const {
    state: { username },
    dispatch
  } = useContext(RootStore);
const toggleLoginLogoutHandler = () => {
    if (username) {
      dispatch({ type: Action.LOGOUT });
    } else {
      dispatch({ type: Action.LOGIN, payload: { username: "Ashraful" } });
    }
  };
.......


Наконец, обновите корневой компонент.

.......
import Header from "./src/components/Header";
const App = () => {
  return (
    <RootStoreProvider>
      <Header />
    </RootStoreProvider>
  );
};


Вот и все. Мы внедрили React context со всеми концепциями Redux - компонентов. Вот ссылка на полный код.







Хотите освоить самые современные методы написания React приложений? Надоели простые проекты? Нужны курсы, книги, руководства, индивидуальные занятия по React и не только? Хотите стать разработчиком полного цикла, освоить стек MERN, или вы только начинаете свой путь в программировании, и не знаете с чего начать, то пишите через форму связи, подписывайтесь на мой канал в Телеге, вступайте в группу на Facebook. Пишите мне - kolesnikovy70 почта gmail.com


Удачного кодирования!                                                                                                                                                              

Телеграм канал - Full Stack JavaScript Developer
Помочь проекту (любая валюта). DONATE

четверг, 28 мая 2020 г.

React. Как использовать «useContext» в React Hooks

Следующая статья посвящена пониманию работы хуков «useContext» в компонентах React.



Хук «useContext» используется для создания общих данных, к которым можно обращаться по всей иерархии компонентов, не пропуская реквизиты - props вручную до каждого уровня. Определенный контекст будет доступен для всех дочерних компонентов без использования props.

Ниже приводится цитата о контексте с официальной веб-страницы React:

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

В типичном React-приложении данные передаются сверху вниз (от родителя к дочернему компоненту) с помощью пропсов. Однако, этот способ может быть чересчур громоздким для некоторых типов пропсов (например, выбранный язык, UI-тема), которые необходимо передавать во многие компоненты в приложении. Контекст предоставляет способ делиться такими данными между компонентами без необходимости явно передавать пропсы через каждый уровень дерева.


В статье ниже мы сосредоточимся на том, как «useContext» работает программно. Но прежде чем начать с «useContext», давайте сначала разберемся, как сделать данные доступными от родительских к дочерним компонентам, используя атрибуты «props». Затем мы введем хук «useContext» для компонента, чтобы понять его влияние.

Передача данных в дочерние компоненты без «useContext»

Ниже приведено описание создания родительского компонента, содержащего сведения о конкретном сотруднике. Добавленные сведения о сотруднике не используются в родительском компоненте, но могут использоваться некоторыми дочерними компонентами, которые мы расскажем чуть позже. Но давайте сначала посмотрим на реализацию родительского компонента.

import React, { useState } from "react";

function UserDetailsComponent() {
  var [userDetails, setUserDetails] = useState({
    name: "Yaroslav",
    age: 50
  });
                                               
  return (
    <div>
      <h1>This is the Parent Component</h1>
    </div>                                         
  )
}


Теперь давайте введем еще два компонента в иерархию, мы добавим к иерархии «ChildComponent», который по-прежнему не будет получать доступ к данным, доступным из родительского компонента. ChildComponent должен далее передать эти данные своему дочернему компоненту «SubChildComponent», где данные, наконец, отображаются.

Расширяя код далее, мы отправим переменную состояния далее в дочерний компонент «ChildComponent» с помощью «props», который отправит эти данные дальше в его дочерний компонент «SubChildComponent». Давайте посмотрим на код ниже, чтобы объяснить дальнейшие сценарии.

import React, { useState } from "react";

function UserDetailsComponent() {
  var [userDetails, setUserDetails] = useState({
    name: "Yaroslav",
    age: 50,
  });

  return (
    <div>
      <h1>This is the Parent Component</h1>
      <hr />
      <ChildComponent userDetails={userDetails}></ChildComponent>
    </div>
  );
}

function ChildComponent(props) {
  return (
    <div>
      <h2>This is Child Component</h2>
      <hr />
      <SubChildComponent userDetails={props.userDetails}></SubChildComponent>
    </div>
  );
}

function SubChildComponent(props) {
  return (
    <div>
      <h3>This is Sub Child Component</h3>
      <h4>User Name: {props.userDetails.name}</h4>
      <h4>User Age: {props.userDetails.age}</h4>
    </div>
  );
}



В приведенном выше коде родительский компонент определяет некоторые переменные состояния, которые не используются внутри компонента, вместо этого он отправляется далее дочернему компоненту «ChildComponent» в качестве данных «props». «ChildComponent» не использует «userDetails» и далее отправляет данные своему дочернему компоненту «SubChildComponent» в качестве «propsх» данных. Этот «SubChildComponent» использует данные, переданные ему как «props», и отображает имя пользователя и возраст в компоненте.

Вы можете поиграть с вышеуказанным кодом в следующем онлайн-редакторе: Code Sandbox link

Проблема с вышеуказанным кодом

Я бы попытался объяснить проблему с приведенным выше кодом, указав приведенные ниже утверждения. Надеюсь, это будет иметь смысл для вас.
  1. Данные состояния определяются на верхнем уровне, определенные данные передаются дочернему компоненту, где данные больше не используются. Данные от родителя передаются как «props» данные. Эти «props» данные не влияют на дочерний компонент, но, тем не менее, дочерний компонент должен поддерживать данные «props».
  2. «ChildComponent» далее передает эти «props» данные «SubChildComponent». «ChildComponent» здесь просто идут издержки на управление «props» из родительского компонента, только для того, чтобы сделать его доступным для других дочерних компонентов.
  3. Нам необходимо явно передавать «props» даже тем компонентам, которые даже не используют его только для того, чтобы сделать данные доступными для иерархии ниже. Мы поддерживаем постоянную передачу данных «props» по всей иерархии приложения.
Я надеюсь, что вы сможете решить проблему с передачей «props» по всей иерархии приложения. 😉

«UseContext» для спасения…

Чтобы решить вышеупомянутую проблему передачи «props» данных даже тем компонентам, которые не требуют этого только потому, что данные требуются в дальнейшей иерархии, мы можем использовать «Context API».

«Context API» позволяет нам определять контекстный объект, который хранит некоторые данные и сделает его доступным по всей иерархии, не передавая данные как «props». Для упрощения, контекст предоставляет контейнер, содержащий некоторые данные и делающий его доступным для всей иерархии компонентов ниже.

Давайте воссоздадим приведенный выше пример с «Context API» и увидим разницу.

Чтобы достичь этого, мы сначала создадим объект контекста. После того, как объект создан с использованием React.createContext. Затем мы будем использовать этот объект контекста в компоненте верхнего уровня и добавим данные, необходимые для всей иерархии. UserDetailContext.Provider используется для предоставления значения созданному объекту контекста. Объект, который необходимо добавить, предоставляется атрибуту value.

import React, { useState } from "react";

var userDetailContext = React.createContext(null);

export default function UserDetailsComponent() {
  var [userDetails] = useState({
    name: "Mayank",
    age: 30
  });

  return (
    <userDetailContext.Provider value={userDetails}>
      <h1>This is the Parent Component</h1>
      <hr />
      <ChildComponent userDetails={userDetails} />
    </userDetailContext.Provider>
  );
}


В приведенном выше коде мы создали объект контекста userDetailsContext и добавляем данные о состоянии в контекст в UserDetailsComponent. Поскольку данные добавляются в контекст, предоставленный в этом компоненте, любой компонент в иерархии будет иметь доступ к данным контекста.

Далее, мы хотим, чтобы данные получали доступ через SubChildComponent, не передавая их как «props» данные. Чтобы получить доступ к данным в SubChildComponent, нам нужно получить доступ к контексту, созданному ранее на верхнем уровне, и получить доступ к данным, доступным в этом контексте.

К контекстным данным, добавленным сверху, можно получить доступ через SubChildComponent, используя ключевое слово useContext

React.useContext(userDetailContext);

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

import React, { useState } from "react";

var userDetailContext = React.createContext(null);

export default function UserDetailsComponent() {
  var [userDetails] = useState({
    name: "Yaroslav",
    age: 50,
  });

  return (
    <userDetailContext.Provider value={userDetails}>
      <h1>This is the Parent Component</h1>
      <hr />
      <ChildComponent userDetails={userDetails} />
    </userDetailContext.Provider>
  );
}

function ChildComponent(props) {
  return (
    <div>
      <h2>This is Child Component</h2>
      <hr />
      <SubChildComponent />
    </div>
  );
}

function SubChildComponent(props) {
  var contextData = React.useContext(userDetailContext);
  return (
    <div>
      <h3>This is Sub Child Component</h3>
      <h4>User Name: {contextData.name}</h4>
      <h4>User Age: {contextData.age}</h4>
    </div>
  );
}



В приведенном выше примере мы видим, что ключевое слово useContext сделает данные родительского компонента доступными для дочерних компонентов на любом уровне в иерархии, не раскрывая их как «props» данные. Вы можете играть с кодом в следующем онлайн-редакторе ... Code Sandbox link

Использование

Считается, что связка двух хуков - useContext + useReducer(о нем я расскажу в следующий раз) может полностью заменить Redux! Мнения "за":
  • Вы можете использовать хуки (useContext + useReducer) вместо Redux в не больших приложениях (где нет необходимости в больших комбинированных Reducers). В данном случае Redux действительно может оказаться избыточным.
Мнения "против":
  • Большое количество кода уже написано на связке React + Redux и переписывать его на хуки (useContext + useReducer) кажется мне не целесообразным, по крайней мере сейчас.
  • Redux — проверенная библиотека, хуки — нововведение, их интерфейсы и поведение может измениться в дальнейшем.
  • Для того чтобы сделать использование useContext + useReducer действительно удобным, придется написать некоторые велосипеды.
Выводы делайте сами, а мой ниже...😉

Вывод

«UseContext» - это удивительный способ избавиться от издержек, связанных с передачей данных через props на разные уровни иерархии, даже если это не требуется.

Попробуйте поработать с React Hooks некоторое время и эти интересные функции, которые заставят вас задуматься о функциональном программировании.

Хотите оставаться в курсе новинок в области JS-программирования? Подписывайтесь на мой канал, вступайте в группу на Facebook. Если понравившаяся статья оказалась еще и полезной, то буду благодарен вашим пожертвованиям на развитие сайта - кнопка DONATE ниже (любая валюта).

Удачного кодирования!                                                                                                                                                              

Телеграм канал - Full Stack JavaScript Developer
Помочь проекту (любая валюта). DONATE


Хотите освоить самые современные методы написания React приложений? Надоели простые проекты? Нужны курсы, книги, руководства, индивидуальные занятия по React и не только? Хотите стать разработчиком полного цикла, освоить стек MERN, или вы только начинаете свой путь в программировании, и не знаете с чего начать, то пишите через форму связи, подписывайтесь на мой канал в Телеге, вступайте в группу на Facebook.Пишите мне - kolesnikovy70 почта gmail.com