简化Redux使用步骤, 及所需要的声明文件.
解决:
- 太多的样板文件、模版代码(
action、reducer
) - 复杂的配置 , 处理中间件
state
更新麻烦 , 自行处理返回新的对象- 自带默认配置,中间件:
redux-thunk\redux-devtools-extension
安装
npm i @reduxjs/toolkit
示例
基本使用
configureStore
对比之前的代码,不再使用redux
的createSotre\applyMiddleware
方法 . 传参由redux/toolkit
内部处理.
// import {createStore,applyMiddleware} from 'redux'
import {configureStore,getDefaultMiddleware} from '@reduxjs/toolkit'
import createSagaMiddleware from 'redux-saga'
// middlewares
import {logger} from './middlewares/logger'
// sagas
import rootSaga from './sagas'
import reducer from './reducers'
const sagaMiddleware = createSagaMiddleware()
// init
// const store = createStore(reducer,applyMiddleware(sagaMiddleware,logger))
const store = configureStore({reducer,middleware:[sagaMiddleware,logger,...getDefaultMiddleware()]})
// 之后运行saga
sagaMiddleware.run(rootSaga)
export default store
createReducer
对比以前的使用,在处理action时,reducer需要返回一个新的拷贝的state,不能直接修改;现在可以了🤒
import {createReducer} from '@reduxjs/toolkit'
const INIT_STATE = {
name:'admin',
age:23,
address:'江苏省 南京市'
}
/**
*
export function UserInfo(state=INIT_STATE,action) {
const {type,data} = action
switch(type){
case 'updateName':
return {
...state,
name:data,
}
case 'updateAge':
return {
...state,
age:data,
}
default:
return state
}
}
*/
export const UserInfo = createReducer(INIT_STATE,{
updateName:(state,action)=>{
state.name = action.data
},
updateAge:(state,action)=>{
state.age = action.data
}
})
示例中
action.data
系自定义格式,现在已习惯命名为payload
, 在使用createAction
后只能使用action.payload
获取数据.
示例中为mapAction写法,还有一个builder=>builder.addCase()
写法,细节移步API
// builder.addCase() 写法
export const UserInfo = createReducer(INIT_STATE,builder=>{
builder.addCase(updateName,(state,action)=>{
state.name = action.payload
})
.addCase(updateAge.type,(state,action)=>{
state.age = action.payload
})
})
createAction
按照之前的编写应用 , 及时不使用这个创建action creator
,程序依然正常运行.
使用之前的逻辑,自定义action creator :
// action creator
function updateName(name) {
return {
type:'updateName',
data:name,
}
}
// ...
// 视图更新数据的 触发的action
return (<div style={{border:'1px solid #fff'}}>
<Input onChange={e=>setName(e.target.value)} onPressEnter={e=>dispatch(updateName(name))} />
</div>)
使用createAction
之后
import {createAction} from '@reduxjs/toolkit'
// action creator
// function updateName(name) {
// return {
// type:'updateName',
// data:name,
// }
// }
const updateName = createAction('updateName')
应用时, 拿不到数据 , 因为createAction
默认的数据格式为payload
, 改一下reducer中的数据取值 .
如果需要更改格式,则需要传递第二个参数callback
const updateName = createAction('updateName',(content)=>{
// API 约定了格式,payload 可自定数据
// 详细查看API部分
return {
payload:{
data:content,
},
meta:{},
error:{},
}
})
和createReducer
一起使用
export const updateName = createAction('updateName')
export const updateAge = createAction('updateAge')
export const UserInfo = createReducer(INIT_STATE,{
[updateName]:(state,action)=>{
state.name = action.payload
},
[updateAge.type]:(state,action)=>{
state.age = action.payload
}
})
被createAction
创建的可直接作为action type使用 , 它的.toString
被改写,同.type
createSlice
此为高阶函数,由createAction
和createReducer
组合实现.通过配置的方式.内部处理调用.
import {createSlice} from '@reduxjs/toolkit'
const UserInfoModel = createSlice({
name:"userInfo",
initialState:INIT_STATE,
reducers:{
updateName(state,action){
state.name = action.payload
},
updateAge(state,action){
state.age = action.payload
}
}
})
// 获取reducer 、 action
const {actions,reducer} = UserInfoModel
// 导出action
export const {updateName,updateAge} = actions
// 导出reducer
export const UserInfo = reducer
按功能、模块划分 , 都只有一个文件 . 不需要写type 枚举定义
异步使用
内置了redux-thunk
处理异步 , 足够解决绝大部分的问题. 还有其他中间件比如:redux-saga
、redux-observable
createAsyncThunk
异步请求处理三种状态的action :pending\fulfilled\rejected
这三种状态的action自动触发, 防止外部手动调用,则使用属性extraReducers
, 则不会生成对外的的action creator .
// 异步请求数据
export const fetchUserInfo = createAsyncThunk('users/fetchUserInfo',async (userId,thunkAPI)=>{
const res =await new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve({
name:'hboot',
age:26,
address:'江苏省 南京市 栖霞区'
})
},5000)
})
return res
})
// createSlice
const UserInfoModel = createSlice({
name:"userInfo",
initialState:INIT_STATE,
reducers:{
updateName(state,action){
state.name = action.payload
},
updateAge(state,action){
state.age = action.payload
}
},
extraReducers:{
[fetchUserInfo.fulfilled]:(state,action)=>{
return action.payload
}
}
})
数据管理
通常用来管理列表数据, 内置了常用的数据操作方法.
可以对比和之前示例代码中的不同之处.
/**
* 获取文章数据列表
*/
// const INIT_STATE = {
// id:'',
// title:'', // 文章标题
// tags:[], // 分类
// content:'', // 文章内容
// author:'', // 作者信息
// createTime:'' , // 创建时间
// status:'' , // 状态:0 失败 1 成功 2 开始请求
// comments:[],
// }
// createEntityAdapter
const articleModel = createEntityAdapter({
selectId:(article)=>article.id
})
const ArticleInfoModel = createSlice({
name:"articleInfo",
initialState:articleModel.getInitialState(),
reducers:{
getArticleSuccess(state,action){
articleModel.setAll(state,action.payload)
},
clearArticleInfo(state){
return articleModel.getInitialState()
},
updateArticleInfoById(state,action){
articleModel.updateOne(state,action.payload)
}
}
})
const {actions,reducer} = ArticleInfoModel
// actions
export const {getArticleSuccess,clearArticleInfo,updateArticleInfoById} = actions
// reducer
export const ArticleInfo = reducer
则在UI组件获取数据时 , {ids,entities}
// ...
const { ids, entities } = article;
return ids.map((id) => {
const item = entities[id];
return (
<>
<p>
{item.title} - {item.author}
</p>
<main>
<div>
{item.tags.map((tag) => (
<Tag key={tag}>{tag}</Tag>
))}
</div>
<TextArea defaultValue={item.content} onPressEnter={(e) => updateArticleById({...item,content:e.target.value})} />
</main>
<footer>{item.createTime}</footer>
</>
);
});
// ...
API
Store
基础设置
configureStore({reducer:fn|{},middleware:fn|[],devTools:boolean|{},preloadedState:any,enhancers:[]|fn})
代替creatStore
参数:
reducer
接受单个reducer
或者一个对象(内部调用了combineReducers()
合并)middleware?
中间件配置,内部通过applyMiddleware
;接受一个回调函数(getDefaultMiddleware)
获取默认配置的中间件自定义设置;devTools?
是否开启redux浏览器扩展;接受对象配置默认的设置参数preloadedState?
初始化默认的state
;如果是rootReducer
是合并的,则需要对象键值映射.enhancers?
自定义增强redux store ;
getDefaultMiddleware
返回默认的中间件配置 ; 检测不可变数据变化和检测无法序列化的类型的数据
const middleware = [thunk, immutableStateInvariant, serializableStateInvariant]
生产环境下只有thunk
reducer
&action
createReducer(initState,fn|Map Object)
创建reducer
的实用函数 , 有两种使用方式处理reducer
逻辑
fn:(builder)=>builder.addCase(type,(state,action)=>)
addCase(string|actionCreator,fn)
精准匹配单个action处理; 先与其他两个方法调用.addMatcher(matcher,fn)
模式匹配,处理匹配到的所有actionaddDefaultCase(fn)
未匹配到的action处理;最后调用
(initState,mapActions,mapMatchers,defaultCaseReducer)
mapActions:{[action type]:reducer}
处理单个actionmapMatchers:[{matcher,reducer}]
模糊匹配,处理所有匹配到的actiondefaultCaseReducer:reducer
处理未匹配时的调用
createAction(type,prepareAction)
.type
返回当前的action type.match(action)
用于匹配是否符合改action
创建action creator
函数, 该函数的.toString()
方法被改造返回type
;
type:any
可以是任意类型(包括不可序列化数据类型)prepareAction(content:any)
可自定义action内容; 返回值为一个对象,包含:payload
相当于action.payload
的内容,meta
额外的action信息error
异常信息
createSlice(name,initialState,reducers,extraReducers?)
.name
.reducer
.actions
.caseReducer
createAction
和createReducer
的高阶实现. 提供参数, 内部调用自动创建action type 以及 reducer
name
用于分割state 命名前缀.initialState
用于初始化的值.reducers:{type:fn(state,action)|{reducer,prepare}}
键值对关系映射 , 键用来生成action type ; 值用来生成 reducer ;extraReducers:fn|map object
执行额外的action 响应的reducer;不会合并到返回值的.actions
中(builder)=>{builder.addCase().addMatcher().addDefaultCase()}
{[action type]:(state,action)=>}
createAsyncThunk(type:string,fn:payloadCallback,{condition,dispatchConditionRejection})
处理一个异步的action type,回掉函数为一个promise
; 三种状态,pending\fulfilled\rejected
-
type
action 名称 -
fn(content:any,thunkAPI)
thunkAPI 为调用redux-thunk
所有配置属性,还有其他额外的参数dispatch
redux 的dispatch 方法getState
获取redux的所有state数据的方法extra
thunk中间件额外的数据requestId
自动生成的唯一的请求序列IDsignal
标签当前请求是否需要被取消rejectWithValue
用于默认reject响应返回的数据.
-
{condition,dispatchConditionRejection}
可取消当前的异步action 的回调函数;触发rejected
状态action
unwrapResult
解析请求响应结果, 捕获请求自身的错误信息,分发reject action
createEntiryAdapter({selectId:(entity)=>,sortComparer:(a,b)=>)})
.addOne(state,entity)
添加一个数据.addMany(state,entities:[]|entityObj:{id:entity})
.setAll(state,entities:[]|entityObj:{id:entity})
替换所有已经存在的数据.removeOne(state,id)
移除一个.removeMany(state,ids:[])
移除多个.updateOne(state,entity)
更新一个.updateMany(state,entitis<entity>:[])
更新多个.upsertOne(state,entity)
存在则更新(浅比较更新);不存在进行添加.upsertMany(state,entities:[]|entityObj:{id:entity})
更新多个,不存在则进行添加.getInitialState(state,{option:any})
获取一个新的初始数据.getSelectors()
返回用于查询数据的select 函数;selectIds
返回ids:[]
数据selectEntities
返回entities:{[id]:entity}
selectAll
映射成数组格式列表;和ids顺序保持一致selectTotal
返回所有的数据总数selectById(state,id)
返回数据实体数据中对应id 的数据.
处理某个实体对象的数据状态维护函数 . 比如列表数据的管理、深层次嵌套解析;提供了诸如新增、删除、更新快捷操作.
该模式下的reducer返回的数据格式为{ids:[],entities:{}}
如果你的数据嵌套比较深, 可组合使用normalizr ; 当然如果项目中已经加入了 TS , 就不需要了.
使用工具函数
createSelector
用于复杂数据计算的缓存;数据为发生变化,不会重复计算,直接返回值.(缺点:如果不是触发action的更新reducer操作
,则不能更新)
createDraftSafeSelector
不同于createSelector
,他可以检测到内部的数据更改,并重新计算.
builder.addMatcher()
模式匹配时, 内置的功能函数
.isAllof()
匹配给定的所有ation.isAnyOf()
至少匹配一次给定的action.isAsyncThunkAction(...action)
检测给定的action是否符合AsyncThunk
.isPending()
给定异步ation是否是请求状态.isFulfilled()
给定的异步action是否是完成状态.isRejected()
给定的异步action 是否是拒绝状态.isRejectedWithValue()
是否使用了rejectedWithValue 的值
current()
获取到当前state变更后的及时数据
感觉有一种要替代合并
redux
的意味 . 内置了好多redux
的方法,以及自身的数据状态管理逻辑.