Рубрики
Без рубрики

Reimplement Redux с ванильным реагированием в 12 строках кода

Redux – это потрясающая библиотека для обработки состояния больших приложений, реагировать или нет. Но когда ты думаешь … Tagged with React, JavaScript.

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

Отказ от ответственности: этот пост следует использовать для лучшего понимания понятий редукторов и контекстов, а не для реализации глобальной системы управления государством. Видеть эта почта по @markerikson .

Контексты

В React, Контексты Предложите элегантный способ реализации шаблона «провайдера/потребителя». Как следует из названия, этот шаблон состоит из двух основных элементов: a поставщик чья цель состоит в том, чтобы обеспечить определенную ценность и Потребители , компоненты, которые будут потреблять это значение. Обычно вы инкапсулируете свой основной компонент внутри Поставщик Компонент, а затем в детских компонентах вы можете использовать крючки, при условии библиотеки контекста:

// Main component:
return (
  
    
  
)

// In App or a child component:
const value = useValueFromProvider()

Чтобы создать контекст, мы называем CreateContext Функция предоставлена React. Объект, который он возвращает, содержит Поставщик составная часть. Инкапсулируя иерархию компонентов внутри этого компонента, они смогут получить доступ к значению контекста.

const myContext = createContext()

const App = () => (
  
    
  
)

const SomeComponent = () => {
  const value = useContext(myContext)
  return 

Value: {value}

}

Очень полезный шаблон – создать пользовательского поставщика, чтобы украсить тот, который предоставлен контекстом. Например, вот как мы можем заставить наш провайдер обрабатывать местное состояние (которое фактически будет использоваться во всем мире):

const GlobalStateProvider = ({ initialState, children }) => {
  const [state, setState] = useState(initialState)
  return (
    
      {children}
    
  )
}

Контекст теперь содержит объект с Государство и SetState атрибут. Чтобы сделать это еще проще для пользователя нашего контекста, давайте создадим два пользовательских крючка для доступа к ним:

const useGlobalState = () => useContext(globalStateContext).state
const useSetGlobalState = () => useContext(globalStateContext).setState

Теперь у нас есть первая жизнеспособная реализация глобального государственного управления. Теперь давайте посмотрим, как мы можем реализовать основное понятие Redux для обработки обновлений состояния: Reducer Анкет

Редукторы

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

Допустим, мы хотим обновить состояние после успеха HTTP. Мы хотим обновить загрузка Флаг, установив его на ложный и поместите результат запроса в результат атрибут. С помощью редукторов мы можем рассмотреть вопрос о том, чтобы иметь это действие:

{ type: 'request_succeeded', result: {...} }

Это действие будет передано как параметр для Reducer функция Это функция, которая принимает два параметра: текущее состояние и действие. Традиционно действие – это объект с Тип атрибут и, возможно, некоторые другие атрибуты, специфичные для действия. Основываясь на этом действии и текущем состоянии, функция редуктора должна вернуть новую версию состояния.

Мы можем представить, что этот редуктор справится с нашим первым действием:

const reducer = (state, action) => {
  switch (action.type) {
    case 'request_succeeded':
      return { ...state, loading: false, result: action.result }
    default:
      // If we don't know the action type, we return
      // the current state unmodified.
      return state
  }
}

Хорошие новости: в реагировании есть крючок, чтобы использовать редуктор для обработки локального состояния и его обновлений, используя действия: userEducer Анкет Вы можете увидеть это как улучшенную версию USESTATE , но вместо возвращения функции сеттера для обновления состояния он возвращает отправка Функция для отправки действий в редукторе.

const [state, dispatch] = useReducer(reducer, initialState)

В нашем случае Начальный стат Параметр может содержать этот объект:

const initialState = { loading: false, error: false, result: undefined }

Чтобы обновить состояние с помощью действия, просто позвоните отправка с действием в качестве параметра:

dispatch({ type: 'request_succeeded', result: {...} })

Глобальный редуктор в контексте

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

const storeContext = createContext()

Тогда давайте создадим ShoreProvider Компонент с использованием контекста Поставщик Анкет Как мы видели ранее, наш контекст будет содержать локальное состояние, но вместо того, чтобы использовать USESTATE , мы будем использовать userEducer Анкет Два параметра userEducer (Рудовой и начальное состояние) будут переданы в качестве реквизита нашему ShoreProvider :

const StoreProvider = ({ reducer, initialState, children }) => {
  const [state, dispatch] = useReducer(reducer, initialState)
  return (
    
      {children}
    
  )
}

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

Чтобы прочитать состояние, вместо того, чтобы просто создавать крючок, возвращающий все состояние, давайте сделаем то же самое, что и то, что предлагает React-Redux: крючок, принимающий в качестве параметра селектор, то есть функция, извлекающая из состояния, которое нас интересует.

Селектор обычно очень прост:

const selectPlanet = (state) => state.planet

Крюк Использует селектор принимает этот селектор как параметр и называет его, чтобы вернуть правильное государство:

const useSelector = (selector) => selector(useContext(storeContext).state)

Наконец, Используется Ispatch Крюк просто возвращает отправка атрибут из значения контекста:

const useDispatch = () => useContext(storeContext).dispatch

Наша реализация завершена, и код содержит почти дюжину строк кода! Конечно, он не реализует все функции, которые делают Redux настолько мощными, такие как Middlewares для обработки побочных эффектов (Redux-Thunk, Redux-Saga и т. Д.). Но это заставляет задуматься, действительно ли вам нужно Redux, чтобы просто отслеживать (небольшое) глобальное состояние с логикой редуктора.

Вот полный код для нашей реализации Redux:

const storeContext = createContext()

export const StoreProvider = ({ reducer, initialState, children }) => {
  const [state, dispatch] = useReducer(reducer, initialState)
  return (
    
      {children}
    
  )
}

const useSelector = (selector) => selector(useContext(storeContext).state)

const useDispatch = () => useContext(storeContext).dispatch

Используя нашу реализацию

Использование нашей реализации Redux выглядит очень похоже на использование реального Redux. Давайте посмотрим на это в примере, выполняя вызов HTTP API.

Сначала давайте создадим наш магазин: начальное состояние, редуктор, создатели действий и селекторы:

// Initial state
const initialState = {
  loading: false,
  error: false,
  planet: null,
}

// Reducer
const reducer = (state, action) => {
  switch (action.type) {
    case 'load':
      return { ...state, loading: true, error: false }
    case 'success':
      return { ...state, loading: false, planet: action.planet }
    case 'error':
      return { ...state, loading: false, error: true }
    default:
      return state
  }
}

// Action creators
const fetchStart = () => ({ type: 'load' })
const fetchSuccess = (planet) => ({ type: 'success', planet })
const fetchError = () => ({ type: 'error' })

// Selectors
const selectLoading = (state) => state.loading
const selectError = (state) => state.error
const selectPlanet = (state) => state.planet

Затем давайте создадим компонент, чтение из штата и отправим действия, чтобы обновить его:

const Planet = () => {
  const loading = useSelector(selectLoading)
  const error = useSelector(selectError)
  const planet = useSelector(selectPlanet)
  const dispatch = useDispatch()

  useEffect(() => {
    dispatch(fetchStart())
    fetch('https://swapi.dev/api/planets/1/')
      .then((res) => res.json())
      .then((planet) => {
        dispatch(fetchSuccess(planet))
      })
      .catch((error) => {
        console.error(error)
        dispatch(fetchError())
      })
  }, [])

  if (loading) {
    return 

Loading…

} else if (error) { return

An error occurred.

} else if (planet) { return

Planet: {planet.name}

} else { return null } }

И, наконец, давайте инкапсулируем наше приложение (Planet Компонент) внутри поставщика нашего магазина:

const App = () => {
  return (
    
      
    
  )
}

Вот и все! Redux кажется менее загадочным сейчас, когда вы знаете, как написать свою собственную реализацию?

Я также создал Коделесовый ящик Если вы хотите играть с этой реализацией.

Бонус: переписывание пользователя

Мы использовали userEducer Потому что этот крюк обеспечивается React. Но если это не так, знаете ли вы, что это тоже можно переписать, и менее чем в пяти строках кода?

const useReducer = (reducer, initialState) => {
  const [state, setState] = useState(initialState)
  const dispatch = (action) => setState(reducer(state, action))
  return [state, dispatch]
}

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

Вы также можете Следуй за мной в Твиттере (@Scastiel) , где я регулярно публикую о React, Hooks, Frontend в целом и других предметах 😉

Оригинал: “https://dev.to/scastiel/reimplement-redux-with-vanilla-react-in-12-lines-of-code-3k6m”