Представляем шаблоны проектирования React: Flux, Redux и Context API

Представляем шаблоны проектирования React Изучение

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

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

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

Архитектура потока

Один из наиболее распространенных шаблонов проектирования называется шаблоном проектирования MVC, что означает модель, представление, контроллер. Этот шаблон объединяет ваш код в три блока:

  • Исходные данные, которые будут моделью вашего приложения
  • Логика, которая принимает, что исходные данные и повороты в функции, которые будут контролировать состояние приложения
  • Пользовательский интерфейс, где ваш клиент будет взаимодействовать и просматривать приложения

По мере масштабирования приложений поток данных MVC может стать большей проблемой, потому что приложения MVC имеют данные, которые могут перемещаться в нескольких направлениях, создавая двусторонние потоки данных. Код сложно поддерживать. Если мы хотим быстро решать проблемы, такой способ написания кода не сработает.

Facebook решил эту проблему, создав Flux Architecture — шаблон проектирования, который позволяет данным перемещаться только в одном направлении, работая вместе с React, чтобы обновлять веб-страницу только при изменении состояния компонента.

Части архитектуры потока

Части архитектуры потока

1. Действие

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

//action types
export const ADD_USER = «ADD_USER»;
export const UPDATE_USER = «UPDATE_USER»;
//action creators
export const addUser = (payload) => {
   return ({
       type: ADD_USER,
       payload
   })
}
export const updateUser = (payload) => {
   return ({
       type: UPDATE_USER, payload
   })
}

Свойство type описывает действие, а полезная нагрузка — это новые передаваемые данные. Эта полезная нагрузка может быть любого типа (объект, строка и т. Д.) В зависимости от создателя действия, для которого она будет использоваться. В этом случае они, вероятно, будут объектами, поскольку мы, вероятно, будем иметь дело с идентификатором пользователя и именем пользователя или адресом электронной почты.

При вызове этих создателей действий отвечает диспетчер.

2. Диспетчер

Диспетчер, по сути, является контроллером трафика в этом сценарии. Это каталог функций обратного вызова, которые будут использоваться для обновления магазина. Подобно диспетчеру полиции, который говорит полицейской машине, куда следует ехать в экстренной ситуации, диспетчер Flux сообщает нам, что следует предпринять.

var Dispatcher = function () {
  return {
    _stores: [],
    register: function (store) {
      this._stores.push({ store: store });
    },
    // checks for updates in each store
    dispatch: function (action) {
      if (this._stores.length > 0) {
        this._stores.forEach(function (entry) {
          entry.store.update(action);
        });
      }
    }
  }
};

3. Магазин

Магазин занимается состоянием нашего приложения. В Flux мы можем иметь несколько хранилищ, по одному для каждого компонента, если захотим. Состояние — это объект, содержащий пары ключ: значение, которые помогают в рендеринге этого конкретного компонента и / или его дочерних элементов.

import React, { Component } from ‘react’;
class SampleContainerComponent extends Component {
   constructor(props) {
       super(props);
       this.state = {
           loading: true,
           user: {
               name: «Jane Doe»,
               age: 42,
               numKids: 0
           },
           movie: {
               title: «E.T.: The Extra-Terrestrial»,
               director: «Steven Spielberg»,
               music: «John Williams»
           }
       }
   }
   render() {
       return (
           <>
           <Movie movie={this.state.movie} />
           <User user={this.state.user} />
           </>
       );
   }
}
export default SampleContainerComponent;

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

4. Просмотр

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

Полная реализация архитектуры Flux

Давайте посмотрим на полную реализацию архитектуры Flux. Здесь, если мы добавим поддержку AMD, CommonJS и глобального использования, мы получим 66 строк кода, 1,7 КБ простого или 795 байт после минификации JavaScript.

var Dispatcher = function () {
  return {
    _stores: [],
    register: function (store) {
      // expecting an `update` method for each store
      if (!store || !store.update) {
        throw new Error(
          ‘You should provide a store that has an `update` method’
        );
      } else {
        var consumers = [];
        var change = function () {
          consumers.forEach(function (consumer) {
            consumer(store);
          });
        };
        var subscribe = function (consumer, noInit) {
          consumers.push(consumer);
          !noInit ? consumer(store) : null;
        };
        this._stores.push({ store: store, change: change });
        return subscribe;
      }
      return false;
    },
    dispatch: function (action) {
      // check all stores for update
      if (this._stores.length > 0) {
        this._stores.forEach(function (entry) {
          entry.store.update(action, entry.change);
        });
      }
    }
  }
};
module.exports = {
  create: function () {
    var dispatcher = Dispatcher();
    return {
      createAction: function (type) {
        if (!type) {
          throw new Error(‘Please, provide action\’s type.’);
        } else {
          return function (payload) {
            // actions are passed to dispatcher
            return dispatcher.dispatch({
              type: type,
              payload: payload
            });
          }
        }
      },
      createSubscriber: function (store) {
        return dispatcher.register(store);
      }
    }
  }
};

В архитектуре Flux Architecture и в том, как она работает с React, есть много замечательных вещей. Однако, если наши приложения становятся большими или если у нас есть дочерние компоненты, которым необходимо передать свойства более чем на четыре или пять уровней (концепция, известная как бурение свойств), оно становится более подверженным ошибкам и менее читаемым для нового инженера, который может быстро освоить код.

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

Архитектура Redux

Redux — это библиотека, которая действует как контейнер состояний и помогает управлять потоком данных вашего приложения. Она похожа на архитектуру Flux и имеет много общего с ней. Архитектура Redux берет то, что мы уже знаем об архитектуре Flux, и расширяет ее, чтобы сделать ее более эффективной для более грандиозных и сложных приложений.

Два самых больших улучшения, которые предлагает Redux, — это одно хранилище, доступное для всех компонентов React с возможностью подписки, и редуктор внутри этого хранилища, который будет определять изменения в состоянии вашего приложения.

Части архитектуры Redux

Большая часть архитектуры Redux очень похожа на архитектуру Flux Architecture. Мы рассмотрим части архитектуры, которые отличаются друг от друга:

Один магазин для хранения состояния

Один магазин для хранения состояния

Когда мы думаем об архитектуре Flux и о том, как она работает, мы знаем, что у нее однонаправленный поток данных. Мы склонны думать о наших приложениях React с точки зрения родительско-дочерних отношений. Что, если бы у нас был дочерний компонент, который сказал, например, что нужно поговорить с кузеном?

При использовании Flux Architecture состояние будет повышено до ближайшего общего предка обоих компонентов. Это становится немного сложным, когда ближайший общий предок находится на пять поколений назад. Это может привести к проблеме, называемой сверлением реквизита, и сделать ваш код менее читабельным. Здесь на помощь приходит Redux.

Redux инкапсулирует наш компонент React с помощью, Providerкоторый дает нам, Storeкоторый содержит большую часть состояния нашего приложения. Componentsподключиться к этому магазину и подписаться mapStateToPropsна состояние, которое им необходимо использовать в этом конкретном компоненте.

CreateStore

import React from ‘react’;
import ReactDOM from ‘react-dom’;
import { Provider } from ‘react-redux’;
import { createStore } from ‘redux’;
import { taskReducer } from ‘./reducers/taskReducer’;
import ‘./index.css’;
import App from ‘./App’;
const store = createStore(taskReducer);
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById(‘root’));

Connect to Component via MapStateToProps

const mapStateToProps = state => {
   return {
       tasks: state.tasks
   }
}
export default connect(mapStateToProps, { addNewTask, toggleTask })(Tasks);

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

Редуктор, определяющий, как изменяется состояние

Редуктор передается в наш Магазин в качестве аргумента. Редюсер сам принимает действие и текущее состояние приложения. Логика внутри вашего редуктора может выглядеть так, как вы хотите, пока текущее состояние копируется, а затем изменяется. Кроме того, новое состояние может быть вычислено только с использованием переданных аргументов.

Нельзя использовать случайные значения, асинхронную логику или другие методы, которые вызовут какое-либо прерывание потока. Основная задача редуктора — помочь Redux отслеживать, как состояние менялось с течением времени.

//Example Reducer for a ToDo List Application:
import { ADD_TASK, TOGGLE_COMPLETE } from ‘../actions’;
const initialState = {
   tasks: [
       {task: ‘Wash the car’, complete: false},
       {task: «Laundry», complete: false}
   ]
}
export const taskReducer = (state = initialState, action) => {
   switch(action.type) {
       case ADD_TASK:
           return {
               …state, tasks: [
                   …state.tasks, { task: action.payload, complete: false}
               ]
           }
       case TOGGLE_COMPLETE:
          return {
              …state, tasks: state.tasks.map((item, index) => {
              if(action.payload === index) {
                  return {
                      …item, complete: !item.complete
                  }
              } else {
                  return item;
              }
          })
       }
       default:
           return state;
   }
}

Заключительные мысли о Redux

Прелесть Redux в том, что у нас есть один источник истины, в котором содержится наше состояние. Существует редуктор (или редукторы), который получает действие от диспетчера / обработчика событий и затем выполняет это действие, чтобы сообщить магазину, что с ним делать.

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

Redux — отличный инструмент, если он вам нужен. Обратите особое внимание на структуру вашей базы данных, на то, как она будет использоваться и как будет выполняться ваше приложение. Redux часто используется, когда это не обязательно. Используйте его только в том случае, если он вам нужен.

Последний шаблон проектирования, о котором мы поговорим, — это то, что многие могут посчитать заменой Redux: Context API.

Контекстный API с перехватчиками React

Теперь нам доступен инструмент, который сочетает в себе лучшие возможности redux с лучшими функциями flux. Он называется Context API и делает управление состоянием намного проще, чем когда-то.

В контексте у нас есть несколько хранилищ для всего приложения, которые служат для предоставления состояния на глобальном уровне. Это делается с помощью поставщика контекста. В этом контексте провайдер — это хуки, которые помогут вам создать объект контекста, который будет доступен из всех компонентов вашего приложения.

Пример использования контекста темы, который в конечном итоге будет переключаться между светлой и темной темой:

import React, { useState, createContext } from ‘react’;
//my resources
import LightCode from ‘../assets/screenshotlight.png’;
import DarkCode from ‘../assets/screenshotdark.png’;
//create context to hold themes
export const ThemeContext = createContext();
export const ThemeContextProvider = ({ children }) => {
   const [isLight, setIsLight] = useState(true);
   //define light and dark themes…
   const dark = {
       backgroundPicture: `url(‘${DarkCode}’)`,
       oppositeTheme: «light»,
       buttonbg: «#47a5bf»,
       buttoncolor: «#ffe54f»,
       background: ‘#121212’,
       cardColor: ‘darkslategrey’,
       primary: ‘#bb86fc’,
       primaryVariant: ‘#3700b3’,
       secondary: ‘#03dac6’,
       surface: ‘#121212’,
       error: ‘#cf6679’,
       onPrimary: ‘#000000’,
       onSecondary: ‘#000000’,
       onBackground: ‘#fafafa’,
       onSurface: ‘#fafafa’,
       onError: ‘#000000’,
   };
   const light = {
       backgroundPicture: `url(‘${LightCode}’)`,
       oppositeTheme: «dark»,
       buttonbg: «#1e39a6»,
       buttoncolor: «yellow»,
       background: ‘#F1EFE7’,
       cardColor: ‘whitesmoke’,
       primary: ‘#6200ee’,
       primaryVariant: ‘#3700b3’,
       secondary: ‘#03dac6’,
       secondaryVariant: ‘#018786’,
       surface: «#ffffff»,
       error: «#b00020»,
       onPrimary: «#ffffff»,
       onSecondary: «#000000»,
       onBackground: «#000000»,
       onSurface: «#000000»,
       onError: «#ffffff»,
   };
   const toggle = () => {
       return isLight ? setIsLight(false) : setIsLight(true);
   }
   const theme = isLight ? light : dark;
     return <ThemeContext.Provider value={{theme, toggle}}>{children}</ThemeContext.Provider>;
};

Значение, которое передается, — это то, как мы будем получать доступ к нашему контексту, когда мы извлекаем его в нашем компоненте, когда мы используем useContextловушку. Синтаксис для использования в вашем компоненте следующий:

const { theme, toggle } = useContext(ThemeProvider);

Деструктуризация объекта контекста соответствует тому, что вы передали в свойство value в . Таким образом, вы можете использовать эти свойства без необходимости говорить context.toggle или context.theme.

Обязательно экспортируйте его ThemeProviderи оберните вокруг всего вашего компонента, index.jsчтобы тему можно было менять везде, когда она переключается.

//installed packages
import «dotenv/config»;
import React from ‘react’;
import ReactDOM from ‘react-dom’;
import { BrowserRouter as Router } from ‘react-router-dom’;
//my contexts
import { ThemeContextProvider } from ‘./context/ThemeContext’;
//my components
import App from ‘./App’;
import ScrollToTop from ‘./ScrollToTop’;
ReactDOM.render(
 <Router>
     <ThemeContextProvider>
        <App />
     </ThemeContextProvider>
 </Router>,
 document.getElementById(‘root’)
);

Вам все равно нужно будет объявить правила для ваших компонентов React в отношении стиля. Для этого я бы порекомендовал ваш любимый пакет CSS-in-JS, чтобы вы могли получить доступ к теме при стилизации цветов и цветов фона ваших компонентов.

Заключительные мысли о Context API

Контекстный API — отличный инструмент для получения некоторых преимуществ Redux без необходимости иметь все стандартные шаблоны, которые требуются Redux. Это делает его популярным способом разработки приложений с тех пор, как стали популярны React Hooks.

Мы передаем состояние поставщику контекста, который передает состояние в качестве свойств нашему дереву компонентов, чтобы мы могли получить к нему доступ, когда нам это нужно, сочетая лучшее из Redux с лучшим из Flux.

Что изучать дальше?

В этой статье мы рассмотрели три способа структурирования приложения в React: Flux, Redux и Context. Все три варианта можно использовать в зависимости от ваших индивидуальных потребностей. Flux отлично подходит для небольших приложений, где не нужно слишком много пропускать опоры.

Redux отлично подходит для крупномасштабных приложений, у которых есть несколько частей состояния, которые необходимо отслеживать. Контекстный API — это лучшее из обоих предыдущих миров. У него есть глобальный объект контекста, доступный для всех наших компонентов, который передается провайдеру в качестве свойств.

Следующие вещи, которым вы захотите научиться, чтобы освоить шаблоны React:

Компоненты высшего порядка в React
Внедрение зависимостей с помощью React
Стилизация компонентов React
Интеграция сторонних библиотек

Читайте также:  Как отображать предложения для поля ввода в HTML?
Оцените статью
bestprogrammer.ru
Добавить комментарий