Как работают редукторы Redux
Опубликовано: 2022-03-10state
, вы наверняка сталкивались с редьюсерами. В этом руководстве объясняется концепция редюсеров и то, как они работают конкретно в Redux.В этом уроке мы изучим концепцию редюсеров и то, как они работают, особенно в приложениях React. Чтобы понять и лучше использовать Redux, необходимо иметь четкое представление о редьюсерах. Редюсеры позволяют обновлять состояние приложения с помощью действия. Это неотъемлемая часть библиотеки Redux.
Это руководство предназначено для разработчиков, которые хотят узнать больше о Redux Reducers. Понимание React и Redux будет полезным. В конце руководства вы должны лучше понять роль редукторов в Redux. Мы будем писать демо-код и приложение, чтобы лучше понять редукторы и то, как они влияют на состояние в приложении.
Что такое редуктор
Редьюсер — это чистая функция, которая принимает состояние приложения и действия в качестве аргументов и возвращает новое состояние. Например, редуктор аутентификации может принимать начальное состояние приложения в виде пустого объекта и действия, которое сообщает ему, что пользователь вошел в систему, и возвращает новое состояние приложения с вошедшим в систему пользователем.
Чистые функции — это функции, не имеющие побочных эффектов и возвращающие одинаковые результаты при передаче одинаковых аргументов.
Ниже приведен пример чистой функции:
const add = (x, y) => x + y; add(2, 5);
Приведенный выше пример возвращает значение на основе входных данных, если вы передадите 2
и 5
, вы всегда получите 7
, если это тот же вход, ничто другое не влияет на получаемый вами результат, это пример чистой функции.
Ниже приведен пример функции редуктора, которая принимает состояние и действие.
const initialState = {}; const cartReducer = (state = initialState, action) => { // Do something here }
Давайте определим два параметра, которые принимает редюсер: state
и action
.
Состояние
Состояние — это данные, с которыми работает ваш компонент (компоненты) — оно содержит данные, которые требуются компоненту, и определяет, что компонент отображает. Как только объект state
изменяется, компонент перерисовывается. Если состоянием приложения управляет Redux, то изменения состояния происходят в редюсере.
Действие
Действие — это объект, который содержит полезную информацию. Это единственный источник информации для магазина Redux, который нужно обновлять. Редукторы обновляют хранилище на основе значения action.type
. Здесь мы определим action.type
как ADD_TO_CART
.
Согласно официальной документации Redux, действия — это единственные вещи, которые запускают изменения в приложении Redux, они содержат полезную нагрузку для изменений в магазине приложений. Действия — это объекты JavaScript, которые сообщают Redux тип действия, которое необходимо выполнить, обычно они определяются как функции, подобные приведенной ниже:
const action = { type: 'ADD_TO_CART', payload: { product: 'margarine', quantity: 4 } }
Приведенный выше код представляет собой типичное значение payload
, которое содержит то, что отправляет пользователь, и будет использоваться для обновления состояния приложения. Как вы можете видеть выше, объект действия содержит тип действия и объект полезной нагрузки, которые необходимы для выполнения этого конкретного действия.
Обновление состояния с помощью редукторов
Чтобы показать, как работают редукторы, давайте посмотрим на числовой счетчик ниже:
const increaseAction = { type: 'INCREASE', }; const decreaseAction = { type: 'DECREASE' }; const countReducer = (state = 0, action) => { switch(action.type){ case INCREASE: return state + 1; case DECREASE : return state -1; default: return state; } };
В приведенном выше коде increaseAction
действие и decreaseAction
действие — это действия, используемые в редьюсере для определения того, до какого state
будет обновлено состояние. Далее у нас есть функция-редуктор с именем countReducer
, которая принимает action
и начальное state
, значение которого равно 0
. Если значение action.type
равно INCREASE
, мы возвращаем новое состояние, которое увеличивается на 1, иначе, если оно равно DECREASE
, возвращается новое состояние, которое уменьшается на 1. В случаях, когда ни одно из этих условий не подразумевается, мы возвращаем state
.
Обновление состояния с помощью редукторов: оператор распространения
Состояние нельзя изменить напрямую, чтобы создать или обновить состояние, мы можем использовать оператор распространения JavaScript, чтобы убедиться, что мы не меняем значение состояния напрямую, а вместо этого возвращаем новый объект, который содержит переданное ему состояние и полезная нагрузка пользователя.
const contactAction = { type: 'GET_CONTACT', payload: ['0801234567', '0901234567'] }; const initialState = { contacts: [], contact: {}, }; export default function (state = initialState, action) { switch (action.type) { case GET_CONTACTS: return { ...state, contacts: action.payload, }; default: return state; }
В приведенном выше коде мы используем оператор распространения, чтобы убедиться, что мы не меняем значение состояния напрямую, таким образом мы можем вернуть новый объект, который заполнен переданным ему состоянием и полезной нагрузкой, отправленной Пользователь. Используя оператор распространения, мы можем убедиться, что состояние остается прежним, поскольку мы добавляем в него все новые элементы, а также заменяем поле контактов в состоянии, если оно присутствовало ранее.
Redux Reducers в действии — демонстрация
Чтобы лучше понять Redux Reducers и то, как они работают, мы будем реализовывать простое приложение для поиска информации о фильмах, код и рабочую версию можно найти здесь, на Codesandbox. Чтобы начать, перейдите в свой терминал и инициализируйте реагирующее приложение, используя команду ниже:
create-react-app movie-detail-finder
После инициализации нашего проекта давайте установим пакеты, которые нам понадобятся для нашего приложения.
npm i axios reactstrap react-redux redux redux-thunk
После того, как пакеты установлены, давайте запустим наш сервер разработки с помощью команды:
npm start
Приведенная выше команда должна запустить наш сервер разработки проекта в нашем браузере. Затем давайте откроем наш проект в выбранном нами текстовом редакторе, внутри папки src
нашего проекта удалим следующие файлы: App.css
, App.test.js
, serviceWorker.js
и setupTests.js
. Далее давайте удалим весь код, который ссылается на удаленные файлы в нашем App.js
В этом проекте мы будем использовать Open Movie Database API для получения информации о фильмах, контента и изображений для нашего приложения, вот ссылка на API, вам нужно зарегистрироваться и получить ключи доступа, чтобы использовать его для этого. application. Когда вы закончите, давайте продолжим работу над нашим приложением, создав компоненты.
Создание компонентов приложения
Во-первых, внутри нашей папки src
в каталоге нашего проекта создайте папку с именем components и внутри папки давайте создадим две папки с именами Movie
и Searchbar
, наш компонент должен выглядеть так, как показано на рисунке ниже:
Создание компонента фильма
Давайте создадим компонент Movies
, который определит структуру сведений о фильме, которые мы будем получать из нашего API. Для этого в папке Movies
нашего компонента создайте новый файл Movie.js
, затем создайте компонент на основе класса для результатов API, давайте сделаем это ниже.
import React, { Component } from 'react'; import { Card, CardImg, CardText, CardBody, ListGroup, ListGroupItem, Badge } from 'reactstrap'; import styles from './Movie.module.css'; class Movie extends Component{ render(){ if(this.props.movie){ return ( <div className={styles.Movie}> <h3 className="text-center my-4"> Movie Name: {this.props.movie.Title} </h3> <Card className="text-primary bg-dark"> <CardImg className={styles.Img} top src={this.props.movie.Poster} alt={this.props.movie.Title}/> <CardBody> <ListGroup className="bg-dark"> <ListGroupItem> <Badge color="primary">Actors:</Badge> {this.props.movie.Actors} </ListGroupItem> <ListGroupItem> <Badge color="primary">Genre:</Badge> {this.props.movie.Genre} </ListGroupItem> <ListGroupItem> <Badge color="primary">Year:</Badge> {this.props.movie.Year} </ListGroupItem> <ListGroupItem> <Badge color="primary">Writer(s):</Badge> {this.props.movie.Writer} </ListGroupItem> <ListGroupItem> <Badge color="primary">IMDB Rating:</Badge> {this.props.movie.imdbRating}/10 </ListGroupItem> </ListGroup> <CardText className="mt-3 text-white"> <Badge color="secondary">Plot:</Badge> {this.props.movie.Plot} </CardText> </CardBody> </Card> </div> ) } return null } } export default Movie;
В приведенном выше коде Использование компонентов из пакета reactstrap
вы можете ознакомиться с документацией здесь. Мы создали компонент Card, который включает в себя название фильма, изображение, жанр, актера, год, автора фильма, рейтинг и сюжет. Чтобы упростить передачу данных из этого компонента, мы создали данные в качестве реквизита для других компонентов. Далее давайте Searchbar
наш компонент панели поиска.
Создание нашего компонента панели поиска
Searchbar
компонент панели поиска будет иметь панель поиска и компонент кнопки для поиска компонентов фильма, давайте сделаем это ниже:
import React from 'react'; import styles from './Searchbar.module.css'; import { connect } from 'react-redux'; import { fetchMovie } from '../../actions'; import Movie from '../Movie/Movie'; class Searchbar extends React.Component{ render(){ return( <div className={styles.Form}> <div> <form onSubmit={this.formHandler}> <input type="text" placeholder="Movie Title" onChange={e => this.setState({title: e.target.value})} value={this.state.title}/> <button type="submit">Search</button> </form> </div> <Movie movie={this.props.movie}/> </div> ) } }
В приведенном выше коде мы импортируем connect
из react-redux
, которое используется для подключения компонента React к хранилищу Redux, предоставляет компоненту информацию из хранилища, а также предоставляет функции, используемые для отправки действий в хранилище. Затем мы импортировали компонент Movie
и функцию fetchMovie
из действий.
Затем у нас есть тег формы с полем ввода для ввода названий наших фильмов, используя хук setState
из React, мы добавили событие onChange
и значение, которое установит состояние title
в значение, введенное в поле ввода. У нас есть тег button
для поиска названий фильмов, и с помощью импортированного нами компонента Movie
мы передали свойства компонента в качестве props
результата поиска.
Далее нам нужно написать функцию для отправки названия нашего фильма в API, чтобы отправить нам результаты, нам также нужно установить начальное состояние приложения. давайте сделаем это ниже.
class Searchbar extends React.Component{ state = { title: '' } formHandler = (event) => { event.preventDefault(); this.props.fetchMovie(this.state.title); this.setState({title: ''}); }
Здесь мы устанавливаем начальное состояние приложения на пустые строки, мы создали функцию formHandler
, которая принимает параметр события и передает функцию fetchMovie
из действия и устанавливает заголовок в качестве нового состояния приложения. Чтобы завершить наше приложение, давайте экспортируем этот компонент, используя свойство connect из react-redux
, для этого мы будем использовать свойство mapToStateProps
, чтобы выбрать часть данных, которые потребуются нашему компоненту, вы можете узнать больше о mapToStateProps
здесь.
const mapStateToProps = (state) => { return { movie: state.movie } } export default connect(mapStateToProps, { fetchMovie })(Searchbar)
Давайте добавим стили в нашу форму, создав файл Searchbar.module.css
и добавив стили ниже:
.Form{ margin: 3rem auto; width: 80%; height: 100%; } input{ display: block; height: 45px; border: none; width: 100%; border-radius: 0.5rem; outline: none; padding: 0 1rem; } input:focus, select:focus{ border: 2px rgb(16, 204, 179) solid; } .Form button{ display: block; background: rgb(16, 204, 179); padding: 0.7rem; border-radius: 0.5rem; width: 20%; margin-top: 0.7rem; color: #FFF; border: none; text-decoration: none; transition: all 0.5s; } button:hover{ opacity: 0.6; } @media(max-width: 700px){ input{ height: 40px; padding: 0 1rem; } .Form button{ width: 40%; padding: 0.6rem; } }
После того, как мы сделали все вышеперечисленное, наш компонент панели поиска должен выглядеть примерно так, как показано на рисунке ниже:
Создание действий для приложения
В этом компоненте мы будем настраивать действия Redux для нашего приложения. Во-первых, внутри каталога src
создайте папку с именем actions
, а внутри папки мы создадим файл index.js
. Здесь мы создадим функцию fetchMovie
, которая принимает параметр title и извлекает фильм из API с помощью Axios. Сделаем это ниже:
import axios from 'axios'; export const fetchMovie = (title) => async (dispatch) => { const response = await axios.get( `https://cors-anywhere.herokuapp.com/https://www.omdbapi.com/?t=${title}&apikey=APIKEY`); dispatch({ type: 'FETCH_MOVIE', payload: response.data }) }
В приведенном выше коде мы импортировали axios
и создали функцию fetchMovie
, которая принимает параметр title
с помощью async/await, чтобы мы могли отправить запрос на сервер API. У нас есть функция dispatch
, которая отправляет в Redux переданный ему объект действия. Из того, что у нас есть выше, мы отправляем действие с типом FETCH_MOVIE
и полезную нагрузку, содержащую ответ, который мы получили от API.
ПРИМЕЧАНИЕ . apikey
-ключ в запросе будет заменен на ваш собственный apikey
после регистрации на OmdbAPI .
Создание редукторов приложений
В этом разделе мы собираемся создать редукторы для нашего приложения.
const fetchMovieReducer = (state = null, action) => { switch(action.type){ case 'FETCH_MOVIE': return action.payload; default: return state; } } const rootReducer = (state, action) => { return { movie: fetchMovieReducer(state, action) } } export default rootReducer;
В приведенном выше коде мы создали fetchMovieReducer
, который принимает состояние по умолчанию, равное null
, и параметр action
, используя оператор переключения, для случая FETCH_MOVIE
мы вернем значение action.payload
, которое является фильмом, полученным из API. Если действие, которое мы пытались выполнить, не находится в редюсере, мы возвращаем состояние по умолчанию.
Затем мы создали функцию rootReducer
, которая принимает текущее состояние и действие в качестве входных данных и возвращает fetchMovieReducer
.
Собираем вместе
В этом разделе мы закончим наше приложение, создав хранилище избыточности в index.js
, давайте сделаем это ниже:
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import App from './App'; import 'bootstrap/dist/css/bootstrap.min.css'; import './index.css'; import reducers from './reducers'; const store = createStore(reducers, applyMiddleware(thunk)) ReactDOM.render( <Provider store={store}> <> <App/> </> </Provider>, document.getElementById('root') )
В приведенном выше коде мы создали store
приложений с помощью метода createStore
, передав созданный редюсер и промежуточное ПО. Промежуточные программы — это надстройки, которые позволяют нам расширять функциональные возможности Redux. Здесь мы используем промежуточное ПО Redux Thunk с помощью applyMiddleware
. Промежуточное ПО Redux Thunk необходимо нашему магазину для выполнения асинхронных обновлений. Это необходимо, потому что по умолчанию Redux синхронно обновляет хранилище.
Чтобы убедиться, что наше приложение знает точное хранилище, которое нужно использовать, мы обернули наше приложение компонентом Provider
и передали хранилище в качестве реквизита, делая это, другие компоненты в нашем приложении могут подключаться и обмениваться информацией с хранилищем.
Давайте добавим немного стиля в наш файл index.css
.
*{ margin: 0; padding: 0; box-sizing: border-box; } body{ background: rgb(15, 10, 34); color: #FFF; height: 100vh; max-width: 100%; }
Визуализация и тестирование средства поиска деталей фильма
В этом разделе мы собираемся завершить наше приложение, отобразив наше приложение в нашем App.js
Для этого давайте создадим компонент на основе класса с именем App
и инициализируем нашу панель поиска и поле ввода.
import React from 'react'; import Searchbar from './components/Searchbar/Searchbar'; import styles from './App.module.css'; class App extends React.Component{ render(){ return( <div className={styles.App}> <h1 className={styles.Title}>Movies Search App</h1> <Searchbar/> </div> ) } } export default App;
Здесь мы создали компонент на основе класса App с h1
, который говорит «Приложение для поиска фильмов», и добавили наш компонент Searchbar
. Наше приложение должно выглядеть как на картинке ниже:
Рабочая демоверсия доступна на Codesandbox.
Заключение
Редюсеры являются важной частью управления состоянием Redux, с помощью редукторов мы можем писать чистые функции для обновления определенных областей наших приложений Redux без побочных эффектов. Мы изучили основы редукторов Redux, их использование и основную концепцию редукторов, состояния и аргументов.
Вы можете пойти дальше, ознакомившись с документацией по редукторам Redux здесь. Вы можете пойти дальше и построить больше на редюсерах Redux, дайте мне знать, что вы строите.
Ресурсы
- Документация React-Redux
- Редукс-документация
- функция
connect()
-
applyMiddleware
функцию