Redux - это крошечная библиотека Javascript, которая управляет состоянием вашего приложения более последовательным и практичным способом.
Считайте состояние репозиторием в памяти, в котором хранятся данные из базы данных, API, локального кеша, элемента пользовательского интерфейса на экране, такого как поле формы, и т. Д.
Создатель Redux, Дэн Абрамов, начал работу над этим проектом, когда готовил свое выступление для React Europe.
Он хотел создать контейнер с предсказуемым состоянием, который поддерживает ведение журнала, горячую перезагрузку, путешествия во времени и универсальные приложения.
Redux - это более простая реализация паттерна Flux, архитектуры, которую Facebook использует для создания своих веб-приложений и мобильных приложений.
В нем реализованы основные концепции функционального программирования, вдохновленные Elm.
Он отлично работает с современными библиотеками и фреймворками, такими как React, Vue, Angular и многими другими.
Как это работает
Мы храним все в простом объекте JavaScript:
{
todos: [{
text: 'Eat food',
completed: true
}, {
text: 'Exercise',
completed: false
}]
}
Действия
Чтобы внести изменения, мы должны отправить Action. Это также простые объекты JavaScript:
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
Создатели действий
Обычно мы создаем функции, возвращающие эти объекты. Мы называем их создателями действий:
function addTodo(text = '') { return { type: 'ADD_TODO', text } } function toggleTodo(index = 0) { return { type: 'TOGGLE_TODO', index } }
Редукторы
Для изменения состояния мы используем простые функции. Они возвращают новое состояние в соответствии с отправленным действием. Мы называем эти функции редукторами:
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([{ text: action.text, completed: false }])
case 'TOGGLE_TODO':
return state.map(
(todo, index) =>
action.index === index
? { text: todo.text, completed: !todo.completed }
: todo
)
default:
return state
}
}
По мере роста вашего приложения сложность объекта состояния увеличивается. Вы можете организовать его с помощью составных редукторов и объединить их в более крупный:
function todoApp(state = {}, action) { return { todos: todos(state.todos, action), files: files(state.files, action), folders: folders(state.folders, action), notifications: notifications(state.notifications, action), filter: filter(state.filter, action), searchField: searchField(state.searchField, action), ... } }
Магазин
Чтобы использовать Redux, вы должны создать магазин. Этот объект управляет всем за вас. Инициализируем его корневым редуктором:
import { createStore } from 'redux' import todoApp from './reducers'
// create the store let store = createStore(todoApp)
// read the state object store.getState()
// dispatch actions that eventually modify the state store.dispatch(addTodo('Learn about actions')) store.dispatch(toggleTodo(0))
Наконец, магазин должен быть подписан на слушателя. Каждый раз, когда мы изменяем состояние, оно будет уведомлять ваше приложение:
// log state to the console every time it gets updated let unsubscribe = store.subscribe(() => console.log(store.getState()) )
// stop listening to state updates unsubscribe()
Это полный жизненный цикл. Мы следуем одному и тому же процессу при каждом изменении. Мы описываем поведение приложения, не касаясь пользовательского интерфейса.
Знаменитые три принципа
Когда вы думаете о Redux, вы должны думать о следующем:
Единый источник истины. Состояние всего вашего приложения хранится в дереве объектов в едином хранилище.
Нет нескольких хранилищ, как в других библиотеках потоков. Это упрощает тестирование, отладку. Такие вещи, как путешествия во времени, универсальные приложения становятся реальностью.
Состояние доступно только для чтения: единственный способ изменить состояние - вызвать действие, объект, описывающий, что произошло.
Никто не пишет напрямую в государство. Никогда! Мы всегда используем действия. Они лучше нас умеют видоизменить состояние. И мы им доверяем. Всегда!
Изменения вносятся с помощью чистых функций: никогда не изменяйте состояние напрямую, всегда возвращайте новый объект.
чистая функция - это функция, в которой возвращаемое значение определяется только входными значениями без наблюдаемых побочных эффектов.
Каждый редуктор должен возвращать новый объект, а не изменять существующий. Это более простой способ иметь неизменяемые данные в вашем приложении.
Как использовать Redux с React
Redux отлично работает с такими библиотеками, как React. Каждый раз, когда у вас происходит мутация состояния, корневой компонент получает обновление и повторно визуализирует пользовательский интерфейс. Довольно просто, правда?
Установить 👩💻
Существует пакет под названием react-redux
, который обрабатывает привязку за вас:
npm install --save react-redux
Предоставить 🤗
Прежде всего вы должны использовать Provider:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import store from './pathToStore'
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Это функция высшего порядка, которая использует Контекст, чтобы сделать хранилище доступным для каждого компонента в дереве. Это просто делает ваш магазин доступным везде.
Подключить 👪
Первый шаг - решить, какой компонент вы хотите связать с Redux. В React есть два вида компонентов:
Компоненты презентации описывают, как все выглядит. Они не знают редукции. Они используют реквизиты для чтения данных и для их обновления.
Компоненты контейнера описывают, как все работает. Они просто передают состояние с помощью реквизита. Они подписываются на Redux.
Выбор места для размещения контейнеров в дереве компонентов аналогичен теме какие компоненты должны иметь состояние.
Чтобы связать компонент React с Redux, используйте HOF connect()
:
import { connect } from 'react-redux' import TodoList from './TodoList'
const TodoListContainer = connect( mapStateToProps, mapDispatchToProps )(TodoList) export default TodoListContainer
Он создает родительский компонент, который выполняет привязку и передает все как свойства дочернему компоненту.
Он также оптимизирует производительность, поэтому вам вообще не нужно использовать shouldComponentUpdate()
в компонентах React.
Как видите, он принимает два обратных вызова. Каждый из них возвращает объект. Используйте первый, чтобы сопоставить состояние Redux с реквизитами:
const mapStateToProps = state => {
return {
todos: state.todos
}
}
Второй отображает диспетчерские функции на реквизиты как функции обратного вызова:
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
В нашем примере компонент <TodoList>
примет следующие реквизиты:
TodoList.propTypes = {
onTodoClick: PropTypes.func.isRequired,
todos: PropTypes.object.isRequired
}
Наслаждайтесь 🍾
Вы успешно соединили свой магазин с вашим представлением. Теперь помните:
- Каждый раз, когда что-то происходит, вы вызываете действие
- Редукторы решают, какие мутации они должны применить к своему состоянию.
- Ваши контейнеры получают обновленное состояние и передают данные в презентационные компоненты.
Асинхронный Redux
Redux по умолчанию синхронный. Есть несколько способов добавить асинхронное поведение.
Асинхронные действия
Каждое асинхронное поведение обычно имеет 3 различных типа действий:
FETCH_POSTS_REQUEST
: Запрос начался. Вы можете сохранить в своем состоянии флагisFetching
и, если он верен, показывать индикатор загрузки.FETCH_POSTS_SUCCESS
: запрос успешно завершен. Поменяйте флагisFetching
на false, удалите загрузчик и покажите посты.FETCH_POSTS_FAILURE
: запрос не выполнен. Измените флагisFetching
на false, сохраните и отобразите ошибку на экране.
Создатели асинхронных действий
Самая простая реализация - использовать преобразователи . Вы должны установить и интегрировать промежуточное ПО под названием redux-thunk
. Вот как это работает:
export const REQUEST_POSTS = 'REQUEST_POSTS' function requestPosts(subreddit) { return { type: REQUEST_POSTS, subreddit } } export const RECEIVE_POSTS = 'RECEIVE_POSTS' function receivePosts(subreddit, json) { return { type: RECEIVE_POSTS, subreddit, posts: json.data.children.map(child => child.data), receivedAt: Date.now() } } // This is a thunk action creator! export function fetchPosts(subreddit) { return function (dispatch) {
// dispatch the request dispatch(requestPosts(subreddit)) return fetch(`https://www.reddit.com/r/${subreddit}.json`) .then( response => response.json(), error => console.log('An error occured.', error) ) .then(json => dispatch(receivePosts(subreddit, json)) ) } }
Мы отправим fetchPosts()
, как и раньше. Он немедленно отправит requestPosts()
, а затем receivePosts()
, когда получит данные.
ПО промежуточного слоя
Промежуточное ПО - это код, который можно поместить между платформой, получающей запрос, и платформой, генерирующей ответ.
В Redux они предоставляют стороннюю точку расширения между отправкой действия и моментом его достижения редуктором.
Люди используют промежуточное ПО Redux для ведения журналов, отчетов о сбоях, взаимодействия с асинхронным API, маршрутизации и многого другого.
Вот как можно применить промежуточное ПО в действии:
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import { createStore, applyMiddleware } from 'redux'
import { selectSubreddit, fetchPosts } from './actions'
import rootReducer from './reducers'
const loggerMiddleware = createLogger()
const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware // neat middleware that logs actions
)
)
В этом примере мы добавили регистратор, который регистрирует каждое действие в консоли. Мы также добавили redux-thunk
, как описано выше.
DevTools
Redux имеет замечательный набор инструментов разработчика, которые улучшат ваш опыт отладки. Расширения доступны для Chrome и Firefox.
Чтобы включить эту функцию, вы должны установить пакет redux-devtools-extension
. Это просто еще одно промежуточное программное обеспечение, которое вы должны применить в своем магазине.
Просто проверьте страницу и откройте вкладку Redux. Вы можете воспроизвести каждое действие, пропустить действия, проанализировать мутации состояний или даже экспортировать автоматизированные модульные тесты!
Тестирование
Поскольку большая часть кода Redux, который вы пишете, является функциями, а многие из них являются чистыми, их легко протестировать без имитации.
Вот как протестировать создателя действий с помощью Jest:
import * as actions from '../../actions/TodoActions'
import * as types from '../../constants/ActionTypes'
describe('actions', () => {
it('should create an action to add a todo', () => {
const text = 'Finish docs'
const expectedAction = {
type: types.ADD_TODO,
text
}
expect(actions.addTodo(text)).toEqual(expectedAction)
})
})
Когда дело доходит до редуктора, вы должны тестировать его поведение в каждом отдельном действии с двумя объектами, один для до и один после:
describe('todos reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual([
{
text: 'Use Redux',
completed: false,
id: 0
}
])
})
it('should handle ADD_TODO', () => {
expect(
reducer([], {
type: types.ADD_TODO,
text: 'Run the tests'
})
).toEqual([
{
text: 'Run the tests',
completed: false,
id: 0
}
])
expect(
reducer(
[
{
text: 'Use Redux',
completed: false,
id: 0
}
],
{
type: types.ADD_TODO,
text: 'Run the tests'
}
)
).toEqual([
{
text: 'Run the tests',
completed: false,
id: 1
},
{
text: 'Use Redux',
completed: false,
id: 0
}
])
})
})
Советы и хитрости
- Вместо
Object.assign()
вы можете использовать оператор распространения, чтобы упростить редукторы:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return { ...state, visibilityFilter: action.filter }
default:
return state
}
}
- Ускорьте загрузку приложений с помощью универсального рендеринга.
- Используйте
reselect
для вычисления производных данных. - Реляционные данные? Просто нормализуйте форму своего состояния, чтобы облегчить себе жизнь.
Ресурсы
- Официальная документация Redux содержит множество полезных статей и практик для Redux и React в целом: http://redux.js.org
- Замечательная серия видео для начинающих: https://egghead.io/courses/getting-started-with-redux
- Та же отличная серия видео для продвинутых концепций Redux: https://egghead.io/courses/building-react-applications-with-idiomatic-redux
Цель этой статьи - дать вам более быстрый обзор библиотеки. Если вы решили использовать Redux, прочтите официальную документацию. Мы использовали большинство идей, примеров и определений непосредственно оттуда. 📖