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)])
}