- Оригинальный адрес:A Complete React Redux Tutorial for 2019
- Оригинальный автор:Dave Ceddia
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:xilihuasi
- Корректор:xionglong58,Fengziyin1234
Полное руководство по React Redux 2019
Попытка понять, как работает Redux, — настоящая головная боль. Тем более как новичок.
Слишком много терминов! Действия, редукторы, создатели действий, промежуточное ПО, чистые функции, неизменность, преобразователи и многое другое.
Как объединить все это с React для создания работающего приложения?
Вы можете часами читать блоги и пытаться собрать их воедино из сложных приложений «реального мира».
В этом руководстве по Redux я шаг за шагом объясню, как использовать Redux с React — начиная с простого React — и очень простого случая React + Redux. я объяснюПочемуКаждая функция полезна (и где можно найти компромиссы).
Затем мы увидим более продвинутый контент, рука об руку, пока вывсепока не поймешь. Давайте начнем :)
Обратите внимание: это руководствовполне полный. значит космосдольше. Я превратил его в полный бесплатный курс,иЯ сделал красивые PDF-файлы, которые вы можете читать на своем iPad или [любом устройстве Android]. Оставьте свой адрес электронной почты, чтобы получить его немедленно.
Видеообзор основы Redux
Если вы предпочитаете смотреть видео, а не читать, в этом видео показано, как шаг за шагом добавить Redux в ваше приложение React:
Это похоже на первую часть этого руководства, где мы шаг за шагом добавим Redux в простое приложение React.
Или продолжайте читать! Этот урок охватывает не только все, что показано в видео, но и другие сухие вещи.
Стоит ли использовать Redux?
Ему 9102 года, и стоит подумать, стоит ли вам все еще использовать Redux. Есть ли сейчас лучшие альтернативы с использованием хуков, контекста или других библиотек?
Вкратце: несмотря на то, что есть много альтернатив,Redux еще не умер. Но применимо ли это к вашему приложению, зависит от конкретного сценария.
супер легко? Сколько состояний нужно использовать только в одном или двух местах? Внутреннее состояние компонента просто прекрасное. Вы можете проходить занятия,HooksИли делать то и другое вместе.
Чтобы еще больше усложнить ситуацию, есть ли что-то «глобальное», что должно быть общим для всего приложения?Context APIможет быть идеальным для вас.
Множество глобальных состояний, которые взаимодействуют с отдельными частями приложения? Или большое приложение, которое со временем будет только увеличиваться? Попробуйте Редукс.
вы также можетепослеС Redux вам не нужно принимать решение в первый же день. Начните с простого и усложняйте, когда вам это нужно.
Вы знаете Реакт?
React можно использовать независимо от Redux. Редукс — это реакцияДополнительные предметы.
Даже если вы планируете использовать их оба, я настоятельно рекомендую начатьвырватьсяRedux изучает чистый React. Разберитесь в свойствах, состоянии и одностороннем потоке данных, изучите «Идеи программирования React», прежде чем изучать Redux. Изучение обоих одновременно определенно запутает вас.
Если вы хотите начать работу с React, я подготовил бесплатный 5-дневный курс, который учит всем основам:
Следующие 5 дней изучайте React, создавая несколько простых приложений.
первый урок
Преимущества Редукса
Если вы какое-то время использовали React, вы, вероятно, знаете о свойствах и одностороннем потоке данных. Данные передаются по дереву компонентов через пропсыВнизперечислить. Нравится этот компонент:
count
существуетApp
В состоянии он будет передаваться в виде реквизита:
к даннымначальствоЭто необходимо реализовать через функцию обратного вызова, поэтому функция обратного вызова должна быть сначала отправлена вВнизлюбому компоненту, который хочет передать данные, вызвав его.
Вы можете думать о данных какТекущий, подключите компоненты, которые в этом нуждаются, с помощью цветных проводов. Данные передаются вверх и вниз по проводам, но провода не могут проходить по воздуху — они должны соединяться от одного компонента к другому.
Передача данных через несколько уровней — это боль
Рано или поздно вы окажетесь в таком сценарии, когда компонент контейнера верхнего уровня имеет некоторые данные, а есть подкомпонент уровня выше 4, которому нужны эти данные. Вот пример из Твиттера, где все аватары обведены:
Примем корневую компонентуApp
Государство имеетuser
объект. Этот объект содержит текущий аватар пользователя, псевдоним и другую информацию о профиле.
чтобы положитьuser
данные передаются на все 3Avatar
компоненты, должны пройти через кучу промежуточных компонентов, которым не нужны данные.
Получение данных похоже на использование иглы в экспедиции по добыче полезных ископаемых. Подожди, это вообще не имеет смысла. так или иначе,это больно. Также известен как «пропорное бурение».
Что еще более важно, это не хороший дизайн программного обеспечения. Промежуточные компоненты вынуждены принимать и передавать реквизиты, которые им не нужны. Это означает, что рефакторинг и повторное использование этих компонентов становится сложнее, чем должно быть.
Разве не было бы здорово, если бы компоненты, которым не нужны эти данные, вообще не должны были бы их видеть?
Redux — один из способов решить эту проблему.
Передача данных между соседними компонентами
Если у вас есть родственные компоненты, которым необходимо обмениваться данными, способ React заключается в отправке данных вначальствоПередайте его родительскому компоненту, а затем передайте его через реквизиты.
Но это может быть обременительно. Redux предоставит вам глобальный «родитель», где вы можете хранить данные, а затем вы можете использовать React-Redux для хранения компонентов и данных родственных компонентов.connect
Встаньте.
Подключайте данные к любому компоненту с помощью React-Redux
использоватьreact-redux
изconnect
вы можете вставить любой компонент в хранилище Redux и получить необходимые данные.
Redux также делает некоторые интересные вещи, такие как упрощение отладки (Redux DevTools позволяет вам проверять каждое изменение состояния), отладка с перемещением во времени (вы можетеоткатизменения состояния, чтобы увидеть, как ваше приложение выглядело раньше), это делает код более удобным для сопровождения в долгосрочной перспективе. Кроме того, вы узнаете больше о функциональном программировании.
Встроенная альтернатива Redux
Если Redux слишком громоздкий для вас, попробуйте эти альтернативы. Они встроены в React.
Альтернатива Redux: React Context API
Под капотом React-Redux использует встроенный контекстный API React для передачи данных. Если вы хотите, вы можете пропустить Redux и напрямую использовать Context. Вам будет не хватать классных функций Redux, упомянутых выше, но если ваше приложение простое и вы хотите передавать данные простым способом, контекста будет достаточно.
Поскольку вы читаете это, я думаю, вы действительно хотите изучить Redux, меня здесь не будет.Сравнение Redux и Context APIилииспользовать контекстиИспользование редукторовКрючки. Вы можете нажать на ссылку, чтобы узнать больше.
Если вы хотите погрузиться в Context API, посмотрите мой курс на Egghead.Управление состоянием контекста React
Другие варианты: использоватьchildren
Prop
В зависимости от того, как вы создали свое приложение, вы можете передавать данные дочерним компонентам более прямым способом, то есть использоватьchildren
В сочетании с другим реквизитом как «слоты». Если вы правильно его организуете, вы можете фактически пропустить несколько уровней в иерархии.
У меня есть соответствующая статьяПаттерн «слот» и как организовать дерево компонентовдля эффективной передачи данных.
Изучите Redux, начните с простого React
Мы будем использовать поэтапный подход, начиная с простого приложения React с состоянием компонента, постепенно добавляя Redux и попутно устраняя ошибки. Мы называем это «разработкой, управляемой ошибками» :)
Вот счетчик:
В этом случае компонент счетчика имеет состояние, а приложение, которое его обертывает, является простой оболочкой.
Counter.js
import React from 'react';
class Counter extends React.Component {
state = { count: 0 }
increment = () => {
this.setState({
count: this.state.count + 1
});
}
decrement = () => {
this.setState({
count: this.state.count - 1
});
}
render() {
return (
<div>
<h2>Counter</h2>
<div>
<button onClick={this.decrement}>-</button>
<span>{this.state.count}</span>
<button onClick={this.increment}>+</button>
</div>
</div>
)
}
}
export default Counter;
Краткий обзор, как это работает:
-
count
состояние хранится вCounter
компоненты - Когда пользователь нажимает «+», кнопка
onClick
процессор выполняетincrement
функция. -
increment
Функция обновит значение счетчика состояния. - React будет повторно отображать, потому что состояние изменилось
Counter
компонент (и его дочерние элементы), чтобы отображалось новое значение счетчика.
Если вам нужны дополнительные сведения о механизме изменения состояния, см.Визуальное руководство по состоянию в ReactТогда возвращайся сюда.
Но давайте будем честными: если вышесказанное для васнетДля обзора вам нужно изучить ReduxДоУзнайте, как работает состояние React, иначе вы запутаетесь. Присоединяйся ко мнеБесплатный 5-дневный курс React, наберитесь уверенности с помощью простого React и вернитесь сюда.
успевать!
Лучший способ научиться — попробовать! Итак, вот CodeSandbox, которому вы можете следовать:
--> Откройте CodeSandbox в новой вкладке
Я настоятельно рекомендую вам поддерживать CodeSandbox в актуальном состоянии с помощью этого руководства и на самом деле выбивать примеры по мере продвижения.
Добавьте Redux в свое приложение React
В CodeSandbox разверните параметр «Зависимости» слева и нажмите «Добавить зависимость».
поискredux
Добавьте зависимость, затем снова нажмите «Добавить зависимость», чтобы выполнить поиск.react-redux
Добавить к.
В локальном проекте можно установить через Yarn или NPM:npm install --save redux react-redux
.
redux vs react-redux
redux
Предоставляет вам хранилище, в котором вы можете сохранять состояние, извлекать состояние и реагировать при изменении состояния. Но это все, что он может сделать.
в действительностиreact-redux
Подключайте различные состояния к компонентам React.
Верно:redux
реагироватьфундаментальныйНе понимаю.
Хотя эти две библиотеки как две капли воды. В 99,999% случаев, когда кто-либо ссылается на «Redux» в контексте React, он имеет в виду эти две библиотеки. Так что имейте это в виду, когда увидите Redux на StackOverflow, Reddit или где-либо еще.
redux
Библиотеки можно использовать вне приложений React. Его можно использовать с Vue, Angular и даже с серверными приложениями Node/Express.
Redux имеет глобально уникальный магазин
Мы начнем с небольшой части Redux: магазина.
Мы уже обсуждали, как Redux хранит состояние вашего приложения в отдельном хранилище. и как извлечь часть состояния и встроить его в качестве реквизита в свой компонент.
Вы часто будете видеть, что слова «состояние» и «хранилище» используются взаимозаменяемо. С технической точки зрения, состояние — это данные, а хранилище — это место, где данные хранятся.
Поэтому: как наш первый шаг от простого реагирования на реконструкцию Redux, мы должны создать магазин, чтобы сохранить состояние.
Создайте магазин Redux
В Redux есть удобная функция для создания магазинов, которая называетсяcreateStore
. Очень логично, а?
мы вindex.js
Создайте магазин в . вводитьcreateStore
Затем назовите это так:
index.js
import { createStore } from 'redux';
const store = createStore();
const App = () => (
<div>
<Counter/>
</div>
);
Это приведет к ошибке «Ожидается, что редуктор будет функцией».
Магазину нужен Редуктор
Итак, вот что касается Redux: он не очень умен.
Вы можете ожидать этого, создав хранилище, которое придаст вашему состоянию соответствующее значение по умолчанию. Может пустой объект?
Но не так. Здесь нет соглашения о конфигурации.
Redux Не будуДелайте какие-либо предположения о своем состоянии. Это может быть объект, число, строка или все, что вам нужно. Тебе решать.
Мы должны предоставить функцию, которая возвращает состояние. Эта функция называется редуктором (сейчас мы поймем, почему). Затем мы создаем очень простой редюсер и передаем его вcreateStore
, и посмотрим, что произойдет:
index.js
function reducer(state, action) {
console.log('reducer', state, action);
return state;
}
const store = createStore(reducer);
После внесения изменений откройте консоль (в CodeSandbox нажмите кнопку Console внизу).
Вы должны увидеть сообщения журнала, подобные этому:
(Буквы и цифры после INIT генерируются Redux случайным образом)
Обратите внимание, как Redux вызывает ваш редюсер одновременно с созданием хранилища. (Чтобы подтвердить это: позвонитеcreateStore
вывод сразу послеconsole.log
, чтобы увидеть, что будет напечатано после редуктора)
Также обратите внимание, как Redux передаетundefined
изstate
, а действие естьtype
свойства объекта.
О действиях поговорим позже. Теперь давайте посмотримreducer.
Что такое редукторы Redux?
Термин «редуктор» может показаться немного странным и пугающим, но после этого раздела, я думаю, вы согласитесь, что, как говорится, «просто функция».
вы использовали массивreduce
функция?
Вот как это работает: вы передаете функцию, и функция, которую вы передаете, вызывается при переборе каждого элемента массива, что-то вродеmap
роль — вы можете отображать список в React, а неmap
очень знакомо.
Ваша функция вызывается с двумя аргументами: результатом предыдущей итерации и текущим элементом массива. Он объединяет текущий элемент с предыдущим «общим» результатом и возвращает новое итоговое значение.
Будет понятнее на следующем примере:
var letters = ['r', 'e', 'd', 'u', 'x'];
// `reduce` 接收两个参数:
// - 一个用来 reduce 的函数 (也称为 "reducer")
// - 一个计算结果的初始值
var word = letters.reduce(
function(accumulatedResult, arrayItem) {
return accumulatedResult + arrayItem;
},
''); // <-- 注意这个空字符串:它是初始值
console.log(word) // => "redux"
вы даетеreduce
Передаваемая функция должна называться «редуктор», потому что она принимает весь массив элементов.reducesв результат.
Redux в основномэто массивreduce
делюкс издание. Ранее вы видели, что редукторы Redux имеют эту замечательную функцию:
(state, action) => newState
Значение: он получает токstate
иaction
, затем вернутьсяnewState
. Это выглядит какArray.reduce
Особенности редуктора!
(accumulatedValue, nextItem) => nextAccumulatedValue
Редьюсеры Redux действуют как функции, которые вы передаете в Array.reduce! :) Что они уменьшают, так это действия. Они сводят набор действий (с течением времени) к одному состоянию. Разница в том, что сжатие Array происходит немедленно, а Redux происходит на протяжении всего жизненного цикла работающего приложения.
Если вы все еще не уверены, ознакомьтесь с моим руководством [Как работают редукторы Redux] (Дэйв Definition.com/what-is-ah-day…). В противном случае продолжаем смотреть вниз.
Дайте редюсеру начальное состояние
Помните, что работа редуктора заключается в получении текущегоstate
иaction
Затем вернитесь в новое состояние.
У него есть еще одна обязанность: он должен возвращать начальное состояние при первом вызове. Это что-то вроде «начальной страницы» приложения. Это должно с чего-то начинаться, верно?
Идиоматический способ заключается в определенииinitialState
Затем переменная задается с использованием параметров ES6 по умолчанию.state
Назначьте начальное значение.
Так как мы хотимCounter
Состояние переносится в Redux, и мы сразу создаем его начальное состояние. существуетCounter
В компоненте наше состояниеcount
объект свойства, поэтому здесь мы создаем то же самое InitialState.
index.js
const initialState = {
count: 0
};
function reducer(state = initialState, action) {
console.log('reducer', state, action);
return state;
}
Если вы снова посмотрите на консоль, вы увидитеstate
Напечатанное значение равно{count: 0}
. Это то, что мы хотим.
Итак, это говорит нам о важном правиле о редьюсерах.
Важное правило 1 для редукторов: редуктор никогда не должен возвращать значение undefined.
Обычно состояние всегда должно быть определено. Определенное состояние — это хорошее состояние. иеще нетопределенныйНетЭто нормально (и сломает ваше приложение).
Отправка действий для изменения состояния
Да, сразу два имени: какие-то "акции" мы "рассылаем".
Что такое Redux Action?
В Redux сtype
Обычные объекты со свойствами называются действиями. Вот и все, пока соблюдаются эти два правила, это действие:
{
type: "add an item",
item: "Apple"
}
This is also an action:
{
type: 7008
}
Here's another one:
{
type: "INCREMENT"
}
Действия имеют очень свободный формат. пока этоtype
Подойдут свойства объекта.
Чтобы транзакции были разумными и удобными в сопровождении, мы, пользователи Redux, обычно назначаем простые строки атрибуту type действий, обычно в верхнем регистре, чтобы указать, что они являются константами.
Объект Action описывает изменение, которое вы хотите внести (например, «счетчик приращений»), или событие, которое будет запущено (например, «сбой службы запроса с сообщением об ошибке»).
Несмотря на свою репутацию, действия — это скучные и унылые объекты. они на самом деле неДелатьчто-либо. Сами они все равно не делают.
для акцииДелатьЧтобы что-то сделать, нужно отправить.
Как работает Redux Dispatch
Только что созданный нами магазин имеет встроенную функциюdispatch
. При вызове с действием Redux вызовет редюсер с действием (и затем возвращаемое значение редуктора обновит состояние).
Попробуем в магазине.
index.js
const store = createStore(reducer);
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "DECREMENT" });
store.dispatch({ type: "RESET" });
Добавьте эти диспетчерские вызовы в свою CodeSandbox и проверьте консоль
каждый звонокdispatch
В конце концов редуктор будет вызван!
Также заметили, что состояние всегда одно и то же?{count: 0}
Он не изменился.
Это потому, что наш редуктор не имеетДействующий наэти действия. Но это легко исправить. Начать сейчас.
Обработка действий в Redux Reducer
Для того, чтобы действия что-то делали, нам нужно написать несколько строк кода в редюсере, чтобыtype
значение для обновления состояния соответственно.
Есть несколько способов сделать это.
Вы можете создать объект для поиска соответствующей функции-обработчика по типу действия.
Или вы можете написать целую кучу операторов if/else
if(action.type === "INCREMENT") {
...
} else if(action.type === "RESET") {
...
}
Или вы можете использовать простойswitch
Этот оператор также используется ниже, потому что он очень интуитивно понятен, а также является распространенным методом для этого сценария.
Хотя некоторые люди ненавидятswitch
, если вы тоже - не стесняйтесь писать редукторы так, как вам нравится :)
Вот наша логика для обработки действий:
index.js
function reducer(state = initialState, action) {
console.log('reducer', state, action);
switch(action.type) {
case 'INCREMENT':
return {
count: state.count + 1
};
case 'DECREMENT':
return {
count: state.count - 1
};
case 'RESET':
return {
count: 0
};
default:
return state;
}
}
Попробуйте и посмотрите, что выводится в консоли.
Проверьте это!count
измененный!
Мы готовы подключить его к React, но перед этим давайте поговорим об этом коде редюсера.
Как сохранить чистые редукторы
Еще одно правило редюсеров заключается в том, что они должны быть чистыми функциями. То есть они не могут изменять свои параметры и не могут иметь побочных эффектов.
Правило редюсера 2: Редюсеры должны быть чистыми функциями.
«Побочный эффект» — это любое изменение функции, выходящее за рамки. Не изменяйте переменные вне области действия функции и не вызывайте другие изменяющиеся функции (например,fetch
, связанные с сетью и другими системами), не отправлять действия и т. д.
техническая точка зренияconsole.log
является побочным эффектом, но мы его игнорируем.
Самое главное: не изменятьstate
параметр.
Это означает, что вы не можете выполнитьstate.count = 0
,state.items.push(newItem)
,state.count++
и другие виды изменений - не менятьstate
Себя и любые его свойства.
Вы можете думать об этом как об игре, единственное, что вы можете сделать, этоreturn { ... }
. Это веселая игра. Сначала это может немного раздражать. Но с практикой вы станете лучше.
я собралКак сделать неизменяемые обновления в ReduxПолное руководство с семью общими шаблонами для обновления объектов и массивов в состоянии.
УстановитьImmerЭто также хороший способ использовать его в редьюсерах. Immer позволяет вам писать обычный изменяемый код и автоматически генерировать неизменяемый код.Нажмите, чтобы узнать, как использовать Immer.
Рекомендация: если вы запускаете совершенно новое приложение, начните с Immer. Это избавит вас от многих проблем. но я покажу тебе этотрудностьДело в том, что, поскольку большая часть кода все еще остается такой, вы обязательно увидите редукторы, написанные без Immer.
все правила
Должен возвращать состояние, не менять состояние, не подключать все компоненты, есть брокколи, не выходить после 11 часов... это просто бесконечно. Как фабрика правил, я даже не знаю, что это такое.
Да, Redux похож на властного родителя. Но это от любви. Любовь к функциональному программированию.
Redux построен на неизменности, потому что изменение глобального состояния — это путь к гибели.
Вы пытались сохранить свое состояние в глобальном объекте? Сначала все было хорошо. Красиво и просто. Что угодно может коснуться состояния, потому что оно всегда доступно и легко меняется.
Затем состояние начинает меняться непредсказуемым образом, и найти код для его изменения становится практически невозможно.
Чтобы избежать этих проблем, Redux предлагает следующие правила.
- Состояние доступно только для чтения, единственный способ изменить его — действия.
- Единственный способ обновления: диспетчеризация (действие) -> редуктор -> новое состояние.
- Функция Reducer должна быть «чистой» — она не может изменять свои параметры и не может иметь побочных эффектов.
Как использовать Redux с React
На данный момент у нас очень маленькийreducer
изstore
, при полученииaction
когда он знает, как обновитьstate
.
Настало время подключить Redux к React.
Для этого используйтеreact-redux
две вещи в библиотеке: библиотека с именемProvider
компоненты иconnect
функция.
используяProvider
Компонент оборачивает все приложение, если хочет, в дерево приложений.каждый компонентОба имеют доступ к магазину Redux.
существуетindex.js
, ИмпортироватьProvider
затем используйте его дляApp
содержимое упаковано.store
будет передан как опора.
index.js
import { Provider } from 'react-redux';
...
const App = () => (
<Provider store={store}>
<Counter/>
</Provider>
);
После этого,Counter
,Counter
, и дети детей и т.д. - все они теперь имеют доступ к Redux строе.
Но не автоматически. Нам нужно использовать в нашем компонентеconnect
функция доступа к магазину.
Механизм работы React-Redux Provider
Provider
Это может быть немного похоже на магию. На самом деле он использует React под капотомАтрибут контекста.
Контекст подобен секретному каналу, соединяющему каждый компонент, используяconnect
Дверь в секретный проход можно открыть.
Представьте, что вы поливаете сиропом стопку блинов ивсеКак блины, даже если сиропом полить только верхний слой.Provider
То же самое сделал с Redux.
Подготовка компонента Counter для Redux
Теперь счетчик имеет внутреннее состояние. Мы собираемся убить его, чтобы получить его в качестве реквизита от Redux.count
подготовиться к.
удалить инициализацию состояния вверху иincrement
иdecrement
внутренне называетсяsetState
. Затем положитеthis.state.count
заменитьthis.props.count
.
Counter.js
class Counter extends React.Component {
// state = { count: 0 }; // 删除
increment = () => {
/*
// 删除
this.setState({
count: this.state.count + 1
});
*/
};
decrement = () => {
/*
// 同样删除
this.setState({
count: this.state.count - 1
});
*/
};
render() {
return (
<div className="counter">
<h2>Counter</h2>
<div>
<button onClick={this.decrement}>-</button>
<span className="count">{
// 把 state:
//// this.state.count
// 替换成:
this.props.count
}</span>
<button onClick={this.increment}>+</button>
</div>
</div>
);
}
}
в настоящее времяincrement
иdecrement
пусто. Скоро мы снова их пополним.
Вы заметите, что счетчик пропал — так и должно быть, поскольку в настоящее время нетCounter
перечислитьcount
опора
Соедините компоненты и Redux
Чтобы получить от Reduxcount
, нам сначала нужно импортировать вверху Counter.jsconnect
функция.
Counter.js
import { connect } from 'react-redux';
Затем нам нужно соединить компонент Counter с Redux внизу:
Counter.js
// 添加这个函数:
function mapStateToProps(state) {
return {
count: state.count
};
}
// 然后把:
// export default Counter;
// 替换成:
export default connect(mapStateToProps)(Counter);
Раньше мы экспортировали только сам компонент. Теперь мы используемconnect
Вызов функции оборачивает его, чтобы мы могли экспортироватьсвязанныйПрилавок. В остальном приложение выглядит как обычный компонент.
Затем должен снова появиться счет! Это не изменится, пока мы не реализуем инкремент/декремент заново.
Как использовать Реакт Редуксconnect
Вы могли заметить, что этот звонок выглядит немного... странно. Зачемconnect(mapStateToProps)(Counter)
вместоconnect(mapStateToProps, Counter)
илиconnect(Counter, mapStateToProps)
? Что это делает?
Это написано, потому чтоconnect
ЯвляетсяФункции высшего порядка, что просто означает, что когда вы его вызываете, он возвращает функцию. тогда позвонивозвращениеКогда функции передается компонент, она возвращает новый (обернутый) компонент.
другое его названиекомпоненты более высокого порядка(называемый "НОК"). В прошлом у HOC были плохие отзывы в прессе, но это все еще довольно полезный мод.connect
хороший пример.
Connect
Что он делает, так это подключается к Redux, берет все состояние и передает его вmapStateToProps
функция. Это пользовательская функция, потому что толькотыЗнайте «структуру» вашего состояния в Redux.
Как работает mapStateToProps
connect
Передайте все состояние своемуmapStateToProps
функции, это все равно, что сказать: «Эй, скажи мне, чего ты хочешь от этой кучи хлама».
mapStateToProps
Возвращенный объект передается вашему компоненту в качестве реквизита. Если взять в качестве примера вышеизложенное, тоstate.count
значениеcount
Передача реквизита: свойства объекта становятся именами реквизита, а соответствующие им значения становятся значениями реквизита. Видите ли, эта функция работает как литералОпределить отображение из состояния в реквизит.
Кстати --mapStateToProps
Название является условным, но не конкретным. вы можете сократить это какmapState
Или называйте это как хотите. пока вы получаетеstate
объект, а затем вернуть объект, полный реквизита, это нормально.
Почему бы не пройти весь штат?
В приведенном выше примере наша структура состоянияужеправильно, похожеmapDispatchToProps
может быть ненужным. Какой смысл, если вы по существу копируете параметр (состояние) в объект, который совпадает с состоянием?
В небольших случаях можно передать все состояние целиком, но обычно вы выбираете только те данные, которые нужны части компонента, из большего набора состояний.
и нетmapStateToProps
функция,connect
Состояние не передается.
тыМожетПередайте все состояние и дайте компонентам разобраться. Но это не очень хорошая практика, потому что компоненту необходимо знать структуру состояния Redux и выбирать из него нужные ему данные, что усложняет позже, если вы захотите изменить структуру.
Отправка действий Redux из компонентов React
Теперь наш счетчик былconnect
, мы также получилиcount
ценность. Теперь, как мы можем отправить действия для изменения счетчика?
Хорошо,connect
Поддержка для вас: помимо передачи (сопоставления) состояния,Такжепрошел из магазинаdispatch
функция!
Чтобы отправить действие внутри счетчика, мы можем вызватьthis.props.dispatch
Проведите акцию.
Наш редуктор готов справитьсяINCREMENT
иDECREMENT
действия, затем отправка из приращения/уменьшения:
Counter.js
increment = () => {
this.props.dispatch({ type: "INCREMENT" });
};
decrement = () => {
this.props.dispatch({ type: "DECREMENT" });
};
Теперь мы закончили. Кнопка должна снова работать.
Попробуй это! добавить кнопку сброса
Вот небольшое упражнение: добавьте кнопку «сброс» в счетчик и отправляйте действие «СБРОС» при нажатии.
Редюсер уже написан для обработки этого действия, поэтому вам нужно только изменить Counter.js.
Константа действия
В большинстве приложений Redux вы можете видеть, что константы действий представляют собой простые строки. Это дополнительный уровень абстракции, который может сэкономить вам много времени в долгосрочной перспективе.
Константы действий помогают избежать опечаток, а опечатки в именовании действий могут доставить массу неудобств: никаких ошибок, никаких явных признаков того, что что-то не работает, и ваше действие ничего не делает? Это может быть опечатка.
Константы действия легко написать: используйте переменную для хранения строки действия.
поместить эти переменные вactions.js
file — хорошая идея (когда ваше приложение маленькое).
actions.js
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
Затем вы можете импортировать эти имена действий и использовать их вместо рукописных строк:
Counter.js
import React from "react";
import { INCREMENT, DECREMENT } from './actions';
class Counter extends React.Component {
state = { count: 0 };
increment = () => {
this.props.dispatch({ type: INCREMENT });
};
decrement = () => {
this.props.dispatch({ type: DECREMENT });
};
render() {
...
}
}
Что такое генератор действий Redux?
Теперь мы написали объект действия от руки. как язычник.
если у тебя естьфункцияКаково было бы написать это для вас? Перестаньте писать актино по ошибке!
Я могу сказать вам, это безумие. почерк{ type: INCREMENT }
И следить за тем, чтобы не было бардака Насколько сложно?
Когда в ваших приложениях становится все больше и больше действий, и эти действия стали усложняться - передавать больше данных, а не простоtype
- Генераторы действий очень помогут.
как константы действия, но они ненеобходимость. Это еще один уровень абстракции, если вы не хотите использовать его в своем приложении, ничего страшного.
Но я все равно объясню, что они из себя представляют. Затем вы можете решить, хотите ли вы иногда/всегда/никогда их использовать.
Генератор действий — это простой функциональный термин в терминологии Redex, который возвращает объект действия. Это оно :)
Вот два из них, возвращающие знакомые действия. Кстати, они прекрасно вписываются в «actions.js» констант действий.
actions.js
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
export function increment() {
return { type: INCREMENT };
}
export const decrement = () => ({ type: DECREMENT });
Я использовал два разных способа - одинfunction
и функция стрелки - чтобы показать, что не имеет значения, как вы это пишете. Просто выберите способ, который вам нравится.
Вы могли заметить, что имена функций написаны строчными буквами (верблюжий регистр, если длиннее), в то время как константы действий будутUPPER_CASE_WITH_UNDERSCORES
. Опять же, это просто условность. Это позволит вам с первого взгляда различать генераторы действий и константы действий. Но вы также можете назвать его, как вам нравится. Редукс не волнует.
Теперь, как использовать генераторы действий? Импорт и отправка просто отлично, конечно!
Counter.js
import React from "react";
import { increment, decrement } from './actions';
class Counter extends React.Component {
state = { count: 0 };
increment = () => {
this.props.dispatch(increment()); // << 在这使用
};
decrement = () => {
this.props.dispatch(decrement());
};
render() {
...
}
}
Ключ в том, чтобы не забыть вызвать action Creator()!
не хотетьdispatch(increment)
🚫
долженdispatch(increment())
✅
Помните, что генератор действий — тривиальная функция. Отправка требует, чтобы действие былообъект, а не функция.
И: здесь вы определенно можете ошибиться и сильно запутаться. Хоть раз, может много раз. Это нормально. я иногдавсе ещезабуду.
Как использовать React Redux mapDispatchToProps
Теперь, когда вы знаете, что такое генератор действий, мы можем обсудитьсновауровень абстракции. (Я знаю себязнание. Это необязательно. )
знаешьconnect
как пройтиdispatch
функция? Ты знаешь, как ты устаешь все время стучатьthis.props.dispatch
И как неряшливо это выглядит? (Подписывайтесь на меня)
написатьmapDispatchToProps
объект (или функция! но обычно объект), а затем передать его тому, который вы хотите обернуть компонентомconnect
функцию, вы получите эти генераторы действий каквызываемые реквизиты. Посмотрите на код:
Counter.js
import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from './actions';
class Counter extends React.Component {
increment = () => {
// 我们可以调用 `increment` prop,
// 它会 dispatch action:
this.props.increment();
}
decrement = () => {
this.props.decrement();
}
render() {
// ...
}
}
function mapStateToProps(state) {
return {
count: state.count
};
}
// 在这个对象中, 属性名会成为 prop 的 names,
// 属性值应该是 action 生成器函数.
// 它们跟 `dispatch` 绑定起来.
const mapDispatchToProps = {
increment,
decrement
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
Это здорово, потому что вам не нужно вручную вызыватьdispatch
освобожден от.
ты можешь поставитьmapDispatch
Напишите это как функцию, но объект может удовлетворить 95% необходимых вам сценариев. Подробнее см.Функционал mapDispatch и почему он вам скорее всего не нужен.
Как получить данные с помощью Redux Thunk
Поскольку редукторы должны быть «чистыми», мы не можем выполнять какие-либо вызовы API или диспетчерские действия внутри редукторов.
Мы не можем делать такие вещи и в генераторах действий!
Но если мы поместим генератор действиявозвращениеКак насчет функции, которая может справиться с нашей работой? нравится:
function getUser() {
return function() {
return fetch('/current_user');
};
}
Вне границ Redux не поддерживает такие действия. Stubborn Redux принимает толькопростой объекткак действия.
Вот где нужен redux-thunk. Это промежуточное программное обеспечение, в основном плагин для Redux, который позволяет Redux обрабатывать такие вещи, как выше.getUser()
действия такие.
Вы можете отправить эти «преобразователи», как и любой другой генератор действий:dispatch(getUser())
.
Что такое "тук"?
"преобразователь" (редко) используется как возвращаемое значение из других функцийфункция.
В терминологии Redux это генератор действий, который возвращает функцию вместо простого объекта действия, например:
function doStuff() {
return function(dispatch, getState) {
// 在这里 dispatch actions
// 或者获取数据
// 或者该干啥干啥
}
}
Технически, возвращаемая функция — это «преобразователь», а та, которая возвращает его, — «генератор действий». Обычно я называю их «thunk action» вместе.
Функция, возвращаемая генератором действий, принимает два параметра:dispatch
функция иgetState
.
Для большинства сценариев вам просто нужноdispatch
, но иногда хочется сделать что-то дополнительно на основе значений в состоянии Redux. В этом случае позвонитеgetState()
Вы получаете полное значение состояния, а затем получаете его по запросу.
Как установить Redux-преобразователь
Чтобы установить redux-thunk с помощью NPM или Yarn, запуститеnpm install --save redux-thunk
.
Затем в index.js (или где бы вы ни создавали магазин) импортируйтеredux-thunk
Затем через ReduxapplyMiddleware
Функция применяет его к магазину.
import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
function reducer(state, action) {
// ...
}
const store = createStore(
reducer,
applyMiddleware(thunk)
);
должны обеспечитьthunk
упаковано вapplyMiddleware
Звоните внутрь, иначе не подействует. Не проходите напрямуюthunk
.
Пример объединения Redux для запроса данных
Представьте, что вы хотите отобразить список продуктов. У вас есть внутренний API для ответаGET /products
, поэтому вы создаете действие thunk для запроса данных из бэкэнда:
productActions.js
export function fetchProducts() {
return dispatch => {
dispatch(fetchProductsBegin());
return fetch("/products")
.then(res => res.json())
.then(json => {
dispatch(fetchProductsSuccess(json.products));
return json.products;
})
.catch(error => dispatch(fetchProductsFailure(error)));
};
}
fetch("/products")
это часть, которая фактически запрашивает данные. Затем мы сделали некоторые до и после негоdispatch
перечислить.
Действие Dispatch для получения данных
Чтобы начать вызов и фактически получить данные, нам нужно отправитьfetchProducts
действие.
Куда звонить?
Если конкретному компоненту нужны данные, обычно лучше всего вызывать его, когда компонент только что загрузился.после, то есть егоcomponentDidMount
Функции жизненного цикла.
Или, если вы используете хуки, внутри хука useEffect также будет хорошее место.
Иногда вы хотите получить настоящуюГлобальныйДанные — такие как «Информация о пользователе» или «Интернационализация». В этом сценарии просто используйте его после создания магазина.store.dispatch
для отправки действий вместо ожидания загрузки компонента.
Как назвать действия Redux
Действия Redux для получения данных обычно используют стандартный триплет: BEGIN, SUCCESS, FAILURE. Это не жесткое требование, а просто условность.
Шаблон BEGIN/SUCCESS/FAILURE великолепен, потому что он дает вам ловушки для отслеживания того, что произошло — например, установка флага «загрузка» в «true» в ответ на операцию BEGIN после SUCCESS или FAILURE.false
.
И, как и все остальное в Redux, это соглашение, которое вы можете игнорировать, если оно вам не нужно.
когда вы вызываете APIДо, отправьте действие BEGIN.
успешный вызовпосле, вы можете отправить данные SUCCESS. Если запрос не выполнен, вы можете отправить сообщение об ошибке.
Иногда последний звонок ОШИБКА. На самом деле не имеет значения, как вы это называете, главное, чтобы вы были последовательны.
Примечание. Действие Dispatch Error для обработки FAILURE заставит вас отслеживать код без подсказки, зная, что действие отправлено правильно, но данные не обновлены. Учитесь на моем уроке :)
Вот эти действия и их генераторы действий:
productActions.js
export const FETCH_PRODUCTS_BEGIN = 'FETCH_PRODUCTS_BEGIN';
export const FETCH_PRODUCTS_SUCCESS = 'FETCH_PRODUCTS_SUCCESS';
export const FETCH_PRODUCTS_FAILURE = 'FETCH_PRODUCTS_FAILURE';
export const fetchProductsBegin = () => ({
type: FETCH_PRODUCTS_BEGIN
});
export const fetchProductsSuccess = products => ({
type: FETCH_PRODUCTS_SUCCESS,
payload: { products }
});
export const fetchProductsFailure = error => ({
type: FETCH_PRODUCTS_FAILURE,
payload: { error }
});
получилаFETCH_PRODUCTS_SUCCESS
После того, как действие вернет данные о продукте, мы напишем редюсер для их хранения в хранилище Redux. При запуске запросаloading
Флаг устанавливается в значение true и false в случае сбоя или завершения.
productReducer.js
import {
FETCH_PRODUCTS_BEGIN,
FETCH_PRODUCTS_SUCCESS,
FETCH_PRODUCTS_FAILURE
} from './productActions';
const initialState = {
items: [],
loading: false,
error: null
};
export default function productReducer(state = initialState, action) {
switch(action.type) {
case FETCH_PRODUCTS_BEGIN:
// 把 state 标记为 "loading" 这样我们就可以显示 spinner 或者其他内容
// 同样,重置所有错误信息。我们从新开始。
return {
...state,
loading: true,
error: null
};
case FETCH_PRODUCTS_SUCCESS:
// 全部完成:设置 loading 为 "false"。
// 同样,把从服务端获取的数据赋给 items。
return {
...state,
loading: false,
items: action.payload.products
};
case FETCH_PRODUCTS_FAILURE:
// 请求失败,设置 loading 为 "false".
// 保存错误信息,这样我们就可以在其他地方展示。
// 既然失败了,我们没有产品可以展示,因此要把 `items` 清空。
//
// 当然这取决于你和应用情况:
// 或许你想保留 items 数据!
// 无论如何适合你的场景就好。
return {
...state,
loading: false,
error: action.payload.error,
items: []
};
default:
// reducer 需要有 default case。
return state;
}
}
Наконец, нам нужно передать данные о продукте тому, кто их отображает, а также отвечает за запрос данных.ProductList
компоненты.
ProductList.js
import React from "react";
import { connect } from "react-redux";
import { fetchProducts } from "/productActions";
class ProductList extends React.Component {
componentDidMount() {
this.props.dispatch(fetchProducts());
}
render() {
const { error, loading, products } = this.props;
if (error) {
return <div>Error! {error.message}</div>;
}
if (loading) {
return <div>Loading...</div>;
}
return (
<ul>
{products.map(product =>
<li key={product.id}>{product.name}</li>
)}
</ul>
);
}
}
const mapStateToProps = state => ({
products: state.products.items,
loading: state.products.loading,
error: state.products.error
});
export default connect(mapStateToProps)(ProductList);
я имею в виду сstate.products.<whatever>
данные, а не простоstate.<whatever>
, потому что я предполагаю, что у вас может быть более одного редьюсера, каждый из которых обрабатывает свое собственное состояние. Чтобы убедиться в этом, мы можем написатьrootReducer.js
файл, чтобы собрать все вместе:
rootReducer.js
import { combineReducers } from "redux";
import products from "./productReducer";
export default combineReducers({
products
});
Затем, когда мы создаем хранилище, мы можем передать этот «корневой» редьюсер:
index.js
import rootReducer from './rootReducer';
// ...
const store = createStore(rootReducer);
Обработка ошибок в Redux
Обработка ошибок здесь проще, но базовая структура одинакова для большинства действий, вызывающих API. Основной момент:
- Когда вызов терпит неудачу, отправьте действие FAILURE
- Обрабатывайте действия FAILURE в редьюсерах, устанавливая некоторые переменные флагов и/или сохраняя сообщения об ошибках.
- Передайте флаг ошибки и сообщение (если есть) компоненту, который должен обработать ошибку, а затем отобразите сообщение об ошибке так, как считаете нужным.
Можно ли избежать дублирования рендеринга?
Это действительно распространенная проблема. Да, этовстречаРендеринг запускается более одного раза.
Сначала он отобразит пустое состояние, затем состояние загрузки, а затемсноваРендеринг для демонстрации продукта. Ужасный! Три рендера! (Если вы просто пропустите состояние «загрузка», вы можете сделать рендеринг дважды)
Вы можете беспокоиться о ненужном рендеринге, влияющем на производительность, но нет: одиночный рендеринг выполняется очень быстро. Если приложение, которое вы разрабатываете, заметно тормозит, проанализируйте его, чтобы выяснить причину медлительности.
Подумайте об этом так: приложение должно показывать, когда элемента нет, или когда оно загружается, или когда возникает ошибка.что-то. Вы, вероятно, не хотите просто показывать пустой экран, пока данные не будут готовы. Это дает вам возможность обеспечить хороший пользовательский опыт.
что дальше?
Надеюсь, этот урок помог вам лучше понять Redux!
Если вы хотите углубиться в детали,Редукс-документацияЕсть много хороших примеров. Марк Эриксон (один из сопровождающих Redux) ведет хороший блог.Общая серия Redux.
На следующей неделе я выпущу новый класс,Pure Redux, который охватывает все здесь, обогащенный более подробной информацией:
- Как сделать неизменяемые обновления
- Неизменяемость стала проще с Immer
- Отлаживайте свое приложение с помощью Redux DevTools
- Напишите модульные тесты для редюсеров, действий и действий-преобразователей.
Существует также целый модуль, объясняющий, как мы создаем законченное приложение, от начала до конца, включая следующее:
- Интеграция операций CRUD с Redux — CRUD
- Создать службу API
- Доступные маршруты и данные запроса при загрузке маршрутов
- Работа с модальными диалогами
- Использование нескольких редюсеров с CombineReducers
- как использовать селекторы и
reselect
для повышения производительности и ремонтопригодности - Разрешения и управление сессиями
- Разделение администратора и обычных пользователей
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.