Redux 学习笔记

94 阅读3分钟

Redux动机

Redux适合大型复杂的单页面应用的状态管理工具
pnpm add redux react-redux redux-logger

核心概念

state

应用全局数据的来源, 数据驱动视图的核心

action

数据发生改变动作的描述

reducer

结合state和action, 并且返回一个新的state

三大原则

单一数据源

整个应用的state被储存在一棵object tree中, 并且这个object tree 只存在于唯一一个store中

State是只读的

唯一改变state的方法就是触发action, action是一个用于描述已发生事件的普通对象

使用纯函数来执行修改State

纯函数意味着同样的输入就会有同样的输出

Redux API

createStore 创建一个Redux store 来存放应用中所有的state
combineReducers 将多个不同reducer函数作为value的object, 合并成一个最终的reducer函数
applyMiddleware 接受自定义功能的middleware来扩展Redux
compose 函数式编程中的方法, 右到左来组合执行参数
connect 将React组件与Redux链接起来
Provider 提供被connect链接起来的组件能够访问得到Store

Demo

store.js
import { createStore, compose, applyMiddleware } from 'redux'
import logger from 'redux-logger'
import { rootReducer } from './root-reducer'
const middleWares = [logger]
const composeEnhancers = compose(applyMiddleware(...middleWares))
// root-reducer
export const store = createStore(rootReducer, undefined, composeEnhancers)


root-reducer.js
import { combineReducers } from 'redux'
import { userReducer } from './user/user.reducer'
export const rootReducer = combineReducers({
  user: userReducer
})

index.js
<Provider store={store} />

import { useDispatch } from ‘react-redux’
const dispatch = useDispatch()
dispatch(setCurrentUser(user))
export const setCurrentUser = (user) => {
  createAction(USER_ACTION_TYPES.SET_CURRENT_USER, user)
}

useSelector

import { useSelector } from 'react-redux'
const currentUser = useSelector((state) => state.user.currentUser)

categories Reducer

categories.types.js
export const CATEGORIES_ACTION_TYPES = {
  SET_CATEGORIES_MAP: 'category/SET_CATEGORIES_MAP'
}

categories.action.js
export const setCategoriesMap = (categoriesMap) =>
  createAction(CATEGORIES_ACTION_TYPES.SET_CATEGORIES_MAP, categoriesMap)

categories.reducer.js
export const CATEGORIES_INITIAL_STATE = {
  categoriesMap: {}
}
export const categoriesReducer = (state = CATEGORIES_INITIAL_STATE, action) => {
  const { type, payload } = action
  switch (type) {
    case CATEGORIES_ACTION_TYPES.SET_CATEGORIES_MAP:
      return { ...state, categoriesMap: payload }
    default:
      return state
  }
}

categories.selector.js


reselect

import { createSelector } from ‘reselect’
undefined === undefined
reselect 使用闭包保存上一次的参数 lastArgs 与结果 lastResult ,只有当依赖中的某个 Redux state 发生了变化,导致前后参数比对不一致了,才会触发 selector 的再次计算。这避免了 react 组件的不必要的更新,从而达到了性能优化的效果。

const selectCategoryReducer = (state) => state.categories

export const selectCategories = createSelector(
  [selectCategoryReducer, b],
  (categoriesSlice, bSlice) => categoriesSlice.categories
)

middleware

components => middlewate => redux store

// curry
const loggerMiddleWare = (store) => (next) => (action) => {
  if (!action.type) {
    return next(action)
  }
  console.log('type', action.type)
  console.log('payload', action.payload)
  console.log('currnetState', store.getState())

  next(action)

  console.log('next state: ', store.getState())
}

redux-persist

数据持久化
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'

const persistConfig = {
  key: 'root',
  storage,
  blacklist: ['user']
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
export const store = createStore(persistedReducer, undefined, composeEnhancers)
export const persistor = persistStore(store)


index.js
import { PersistGate } from 'redux-persist/integration/react'
<PersistGate loading={null} persistor={persistor} />

redux-devtools

const composeEnhancer =
  (process.env.NODE_ENV !== 'production' &&
    window &&
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
  compose

redux-thunk

redux 异步
import thunk from 'redux-thunk'

redux-saga

基于generator
如果按照原始的redux工作流程,当组件中产生一个action后会直接触发reducer修改state,reducer又是一个纯函数,也就是不能再reducer中进行异步操作;
而往往实际中,组件中发生的action后,在进入reducer之前需要完成一个异步任务,比如发送ajax请求后拿到数据后,再进入reducer,显然原生的redux是不支持这种操作的
这个时候急需一个中间件来处理这种业务场景,目前最优雅的处理方式自然就是redux-saga

store.js
import createSagaMiddleware from ‘redux-saga’
import { rootSaga } from ‘./root-saga’
const sagaMiddleware = createSagaMiddleware()
sagaMiddleware.run(rootSaga)

root-saga.js
export function* rootSaga() {
  yield all([call(categoriesSaga)])
}
category.saga.js
import { takeLatest, all, call, put } from 'redux-saga/effects'
import { getCategoriesAndDocuments } from '../../utils/firebase/firebase.utils'
import { fetchCategoriesFailure, fetchCategoriesSuccess } from './category.action'
import { CATEGORIES_ACTION_TYPES } from './category.types'

export function* fetchCategoriesAsync() {
  try {
    const categoriesArray = yield call(getCategoriesAndDocuments, 'categories')
    yield put(fetchCategoriesSuccess(categoriesArray))
  } catch (error) {
    // dispatch
    yield put(fetchCategoriesFailure(error))
  }
}

export function* onFetchCategories() {
  yield takeLatest(CATEGORIES_ACTION_TYPES.FETCH_CATEGORIES_START, fetchCategoriesAsync)
}

export function* categoriesSaga() {
  yield all([call(onFetchCategories)])
}