Redux

вопросы после собеседования:

  • что значит задиспатчить событие
  • redux-thunk что делает и что будет если его отключить
  • bindActionCreator - в typescript ломает типизацию - как можно обойтись без него

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

Redux имеет три принципа: 1. Единственный источник истины - т.к. Redux имеет только одно хранилище для всего состояния приложения. 2. Состояние доступно только для чтения - единственный способ изменить состояние - передать экшен - объект, описывающий что произошло. Само хранилище имеет очень маленький API:

  • store.dispatch(action) - позволяет изменять состояние store
  • store.subscribe(listener) - регистрирует слущателей
  • store.getState() - позволяет получить текущее состояние store

Redux реализует состояние store не просто как дерево - а как не изменяемое дерево (immutable tree). При изменении состояния всегде получаем новый объект. Это не позволяет вернуться к старому состоянию, но зато пишется болле простой код (и легче тестировать).

3. Изменения делаются чистыми функциями - изменение состояния происходит через reducer - функция, которая обрабатывает экшены и могут вносить изменения в состояние. Reducer это чистая функция - тоясть она должна удовлетворять след.условиям:

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

Но на самом деле центральная функция Redux'а createStore сама по себе накладывает только два ограничения на то, как вы должны писать свой код: action'ы должны быть простыми объектами, и должны содержать определенный type. Все остальные ограничения накладывают Redux DevTools, React-Redux, React и Reselect, которые действительно полагаются на правильное использование иммутабельности, сериализуемости action'ов/состояния и чистых функций-reducer'ов.

Возможности для разработчиков, такие как отладка перемещением во времени, являются одними из ключевых вариантов использования Redux. Следовательно, такие ограничения как иммутабельность и сериализуемость существуют в основном для того, чтобы подобные возможности были доступны при разработке, а также для упрощения отслеживания потока данных и логики изменения. (Раздел «Введение» в документации Redux)

Action

Flux Standart Action (FSA) - стандарт, описывающий структуру action (литерального объекта). (https://github.com/acdlite/flux-standard-action) Basic FSA

 {
  type: 'GET',
  payload: {
   text: 'Do something'
  }
 }

Promise FSA

 {
  type: 'GET_REQUEST',
  payload: request | new Error(),
  error: true // boolean
 }

Вы можете не придерживаться FSA, но поле type - обязано быть, т.к. в redux-хранилище - присутствует проверка на type (причем не равный undefined).

Принято что action используются не на прямую а через функцию actionCreator - функция которая просто возвращает action.

 function getComment(message){
  return {
   type: 'GET_COMMIT',
   payload: message
  }
 };

Литеральный объкт принято возвращать из actionCreator-a т.к. его легко сериализовать (json), если например экшен прилетел с сервера (Сметанин)

Reducer

За изменение состояния store под действием action отвечает функция reducer:

 function reducer(state = initialState, action){
  switch(action.type){
   case 'GET_COMMENT':
    return {
     ...state,
     photo: action.photo
    }
   default:
    return state
  }
 }

initialState это состояние store по умолчанию

reducer должен удовлетворять следующим условиям:

  1. должен вернуть новое состояние
  2. если не смог обработать событие - вернуть старое состояние!!!
  3. функция должна быть "чистой"

Функция которая не мутирует(изменяет) входные данные и не зависит от внешнего состояния (например, базы данных, DOM, глобальных перемен) и обеспечивает один и тот же результат и одних и тех же входных данных называется чистой функцией.

Почему редьюсер должен быть чистой функцией и обязательно возвращать старое состояние если не смог обработать action: Вот пример кода из исходников Redux: Как видно redux сравнивает старое состояние с новым просто как объект (тот же объект или нет, не глубокое сравнение (очень затратное)) поэтому если мы мутируем старое состояние - тогда redux не будет реагировать на изменения, а если будем возвращать новое состояние даже при не удаче - redux будет постоянно перерисовывать DOM.

Обычно на каждое поле в store пишут отдельный reducer, а потом объединяют с помощью функции combineReducer():

import user from './UserReducer';
import page from './PageReducer';

export default combineReducer({
 user,
 page
})

Внимание: Имена функция конкретных редьюсеров должны совпадать с названиями полей store, которые они изменяют.

Важно помнить что при dispatch любого action-а будут вызваны все редьюсеры.

Примечание: reducer-ы вызываются дважды в приложении, один раз при создании (например когда создается store - в метод createStore - передается редьюсер) и второй раз когда вызывается экшен. Еще стоит помнить что когда вы задаете состояние по умолчанию в редьюсере может произойти ситуация - пользователь при обновлении станицы, обновляет состояния хранилища до начального.

Store

Создание store:

const store = createStore(
 reducer,                      //или combineReducer
 initialState,                 // состояние по умолчанию
 applyMiddleware(middleware)   // усилители (смотри ниже)
)

В Store лучше всего хранить нормализованные данные. Так же не стоит его захламлять данными которые можно вычислить на лету (например в selector-е). Не стоит хранить временные данные, которые нужны только для внут. функционирования компонента - store-хранилище должно быть отражением БД.

Middleware

middleware (усилители) - это функция, которая фозвращает функцию. Т.е. берет входные данные, добавляет что-то и передает дальше.

С помощью усилителей реализуют, например, логирование, обработку ошибок, кэширование и т.д.

Пример логирования

export default ping = store => next => action => {
 console.log('ping');
 return next(action);
}

благодяря методу applyMiddleware становятся доступными:

  • store - состояние нашего приложения
  • next - функция-обертка, которая позволяет продолжить выполнения цепочки
  • action - действие

Например для реализации асинхронных запросов нужно использовать redux-thunk.js - это синтаксический сахар - усилитель, который позволяет dispatch-ть не просто объект а функцию, а также получить доступ к dispatch внутри события. Мы получаем доступ к dispatch и getState внутри actionCreator

то есть благодаря thunk мы можем dispatch-ить функцию, которая первым параметром получает dispatch а вторым getState

function comment(){
 return dispatch => {
  dispatch({type: 'GET_COMMENT_REQUEST'});

  return fetch(url, method: 'GET').
          then(req => req.json()).
          then(
             result => dispatch({type: 'GET_COMMENT_SUCSESS', payload: result}),
             error => dispatch({type: 'GET_COMMENT_FAILURE', payload: error})
          );
 }
}

когда мы dispatch-им экшен с помошью thunk - это значит из actionCreator мы возвращаем функцию которая первым аргументом получает dispatch. Форма записи без использования thunk

class Action {
 constructor(private dispatch){this.dispatch = dispatch}

 getUsers = (username: string) => {
  const disp = this.dispatch;
  disp({type: 'GET_USERS_REQUEST'});

  return fetch(url).then(res => res.json())
   .then(
    (resolve) => disp({type: 'GET_USERS_SUCCESS, payload: resolve}),
    (error) => disp({type: 'GET_USERS_ERROR', payload: error})
   );
 }
}

...

const mapDispatchToProps = (dispatch) => {
 return {
  actions: new Action(dispatch);
 }
}

Сам redux-thunk:

function createMiddleware(args){
 return ({dispatch, getState}) => next => action => {
  if(typeof action === 'function'){
   return action(dispatch, setState, args);
  }

  return next(action);
 }
}

разное(свалка - нужно разобрать)

реализация кода reducer-а где нужно изменять состояния только одного объекта из массива

Source Code

function createStore(reducer) {
    var state;
    var listeners = []

    function getState() {
        return state
    }

    function subscribe(listener) {
        listeners.push(listener)
        return unsubscribe() {
            var index = listeners.indexOf(listener)
            listeners.splice(index, 1)
        }
    }

    function dispatch(action) {
        state = reducer(state, action)
        listeners.forEach(listener => listener())
    }

    dispatch({})

    return { dispatch, subscribe, getState }
}

увеличение производительности и расширяемости

  1. не использовать this.state внутри this.setState(), т.к. мы не можем быть уверены что при использовании this.state - мы получим последние данные и их ни кто не меняет.

    this.setState((prevState, currentProps) => {
    return {...prevState, age: currentProps.age}
    });
    
  2. redux-хранилище должно быть нормализованным (как в БД SQL), ни каких вложенных структур - только связь через id. Это позволяет избавится от вложенных reducer-ов, и толстых actionCreator-ов.

  3. выносить data proccessing из компонента, т.к. это сильно тормозит пере-рендеринг компонента. Лучший вариант использование selector-ов (reselect)
const userSelect = (state) => state.user;

const mapStateToProps = (state) => {
 return {
  user: userSelect(state),
  books: booksSelect(state, props)
 }
}
  1. использовать memorize (lodash или встроено в reselect)
  2. todo

отличное видео, нужно скачать и сохранить на google disk

results matching ""

    No results matching ""