Simplify the steps to use Redux and the required boilerplate.
What problems to solve:
- Too much boilerplate, template code (
The action, reducer,
) - Complex configuration, processing middleware
state
Update trouble, return new object by itself- Built-in default configurations, such as middleware:
redux-thunk\redux-devtools-extension
The installation
npm i @reduxjs/toolkit
Copy the code
The sample
The basic use
configureStore
Redux’s createSotre\applyMiddleware method is no longer used compared to the previous code. Parameter transmission is handled internally by 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()]})
// Then run saga
sagaMiddleware.run(rootSaga)
export default store
Copy the code
createReducer
Compared with previous use,reducer needs to return a new copy state when dealing with actions and cannot be directly modified. Now it’s ready: face_with_succeeds:
import {createReducer} from '@reduxjs/toolkit'
const INIT_STATE = {
name:'admin'.age:23.address:Nanjing, Jiangsu Province
}
/** * 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
}
})
Copy the code
In this example, the action. Data format is user-defined, and it is now named payload. After using createAction, only action.
In this example, it is written as mapAction, and there is also a builder=> Builder.addCase (), the details of the move API
/ / builder. AddCase () method
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
})
})
Copy the code
createAction
Follow the previous writing application, even without using this to create action Creator, the program still runs normally.
Use the previous logic to customize Action Creator:
// action creator
function updateName(name) {
return {
type:'updateName'.data:name,
}
}
// ...
// The action triggered by the view to update data
return (<div style={{border:'1px solid #fff'}} >
<Input onChange={e= >setName(e.target.value)} onPressEnter={e=>dispatch(updateName(name))} />
</div>)
Copy the code
After using createAction
import {createAction} from '@reduxjs/toolkit'
// action creator
// function updateName(name) {
// return {
// type:'updateName',
// data:name,
/ /}
// }
const updateName = createAction('updateName')
Copy the code
The data format of createAction is payload. Modify the reducer data.
If you need to change the format, you pass the second parameter, callback
const updateName = createAction('updateName'.(content) = >{
// The API specifies the format of the data payload
// Take a closer look at the API section
return {
payload: {data:content,
},
meta: {},error: {},}})Copy the code
andcreateReducer
Used together
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
}
})
Copy the code
The createAction created by createAction can be used directly as the action type, and its.toString is overridden as.type
createSlice
This is a higher-order function implemented by a combination of createAction and createReducer. By configuration. Internal processing calls.
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
}
}
})
// Obtain reducer, action
const {actions,reducer} = UserInfoModel
/ / the action is deduced
export const {updateName,updateAge} = actions
/ / export reducer
export const UserInfo = reducer
Copy the code
There is only one file for each function and module. No need to write type enumeration definitions
Asynchronous use
Redux-thunk is built in to handle asynchrony, which is enough to solve most problems. There are other middleware such as Redux-Saga and Redux-Observable
createAsyncThunk
Asynchronous requests handle three kinds of action: Pending \ depressing \ Rejected
The actions in these three states are automatically triggered to prevent manual external invocation, and the extraReducers property will not generate an external Action Creator.
// Request data asynchronously
export const fetchUserInfo = createAsyncThunk('users/fetchUserInfo'.async (userId,thunkAPI)=>{
const res =await new Promise((resolve,reject) = >{
setTimeout(() = >{
resolve({
name:'hboot'.age:26.address:'Qixia District, Nanjing City, Jiangsu Province'})},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
}
}
})
Copy the code
Data management
Commonly used to manage list data, built-in common data manipulation methods.
You can compare the differences from the previous example code.
/** * get the list of article data */
// const INIT_STATE = {
// id:'',
// title: ", // the title of the article
// tags:[], // classification
// content: ", // article content
// author: ", // author information
// createTime: ", // createTime
// status: ", // Status :0 Failed 1 Succeeded 2 Start request
// 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
Copy the code
{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>
</>
);
});
// ...
Copy the code
API
Store
infrastructure
configureStore({reducer:fn|{},middleware:fn|[],devTools:boolean|{},preloadedState:any,enhancers:[]|fn})
Instead of creatStore
Parameters:
reducer
Accepts a singlereducer
Or an object (internally calledcombineReducers()
Merged)middleware?
Middleware configuration, internal passapplyMiddleware
; Accept a callback function(getDefaultMiddleware)
Get middleware custom Settings for default configuration;devTools?
Whether to enable the Redux browser extension; Accept the default setting parameters for the object configurationpreloadedState?
Initialize the defaultstate
; If it isrootReducer
Is merged, object key-value mapping is required.enhancers?
Custom enhanced Redux Store;
getDefaultMiddleware
Returns the default middleware configuration; Detect immutable data changes and detect data of types that cannot be serialized
const middleware = [thunk, immutableStateInvariant, serializableStateInvariant]
Copy the code
Only Thunk is available in the production environment
reducer
&action
createReducer(initState,fn|Map Object)
Create a reducer utility function. There are two ways to use the Reducer logic
Parameters:
fn:(builder)=>builder.addCase(type,(state,action)=>)
addCase(string|actionCreator,fn)
Accurately match single action processing; Call with the other two methods first.addMatcher(matcher,fn)
Pattern matching, processing all matched actionsaddDefaultCase(fn)
Unmatched action processing; The last call
(initState,mapActions,mapMatchers,defaultCaseReducer)
-
MapActions :{[action type]: Reducer} Processes a single action
-
MapMatchers :[{matcher,reducer}] Fuzzy match, process all matched actions
-
Call defaultCaseReducer: reducer processing did not match
createAction(type,prepareAction)
Return value accessible attribute:
.type
Returns the current action type.match(action)
Used to match match changesaction
Create the action Creator function whose.toString() method is modified to return Type;
type:any
Can be any type (including non-serializable data types)prepareAction(content:any)
Action content can be customized. The return value is an object containing:payload
The equivalent ofaction.payload
The content of themeta
Additional action informationerror
Exception information
createSlice(name,initialState,reducers,extraReducers?)
Return value accessible attribute:
.name
.reducer
The Reducer function of the module.actions
Automatically generated Actions Creator method.caseReducer
Higher level implementation of createAction and createReducer. Provide parameters, internal calls automatically create action types and reducer
name
Used to split state naming prefixes.initialState
The value used for initialization.reducers:{type:fn(state,action)|{reducer,prepare}}
Key-value relational mapping, where keys are used to generate action Types; Value is used to generate a reducer;extraReducers:fn|map object
Execute the reducer of additional action responses; Will not be merged into the return value.actions
In the(builder)=>{builder.addCase().addMatcher().addDefaultCase()}
{[action type]:(state,action)=>}
createAsyncThunk(type:string,fn:payloadCallback,{condition,dispatchConditionRejection})
Handle an asynchronous action type that returns a promise; Pending \ depressing \ Rejected
-
Type the name of the action
-
Fn (Content :any,thunkAPI) thunkAPI all configuration attributes for calling redux-thunk, as well as other additional parameters
dispatch
Redux’s dispatch methodgetState
Method to get all state data for reduxextra
Additional data for thunk middlewarerequestId
A unique request sequence ID that is automatically generatedsignal
Label whether the current request needs to be canceledrejectWithValue
Used to default reject the data returned in response.
Copy the code
-
{condition, dispatchConditionRejection} can cancel the current asynchronous callback function of action; Trigger the Rejected state Action
UnwrapResult parses the response result of the request, captures the error message of the request itself, and distributes the Reject Action
createEntiryAdapter({selectId:(entity)=>,sortComparer:(a,b)=>)})
Return value accessible attribute:
.addOne(state,entity)
Add a data.addMany(state,entities:[]|entityObj:{id:entity})
.setAll(state,entities:[]|entityObj:{id:entity})
Replace all existing data.removeOne(state,id)
To remove a.removeMany(state,ids:[])
Remove more than one.updateOne(state,entity)
Update a.updateMany(state,entitis<entity>:[])
Update multiple.upsertOne(state,entity)
Update exists (shallow comparison update); Does not exist to add.upsertMany(state,entities:[]|entityObj:{id:entity})
Update multiple files, add them if they do not exist.getInitialState(state,{option:any})
Get a new initial data.getSelectors()
Returns the select function used to query the data;selectIds
returnids:[]
dataselectEntities
returnentities:{[id]:entity}
selectAll
Map to an array format list; Same order as IDSselectTotal
Returns the total of all dataselectById(state,id)
Returns the data corresponding to the ID in the data entity data.
Handle the data state maintenance function of an entity object. For example, the management of list data, deep nested parsing; Provides quick operations such as add, delete and update.
The data format returned by reducer in this mode is {IDS :[],entities:{}}.
If your data is deeply nested, combine normalizr; Of course, this is not necessary if TS is already in the project.
Using utility functions
createSelector
Caches for complex data calculations; If the data is changed, the reducer operation is not triggered.(Disadvantage: If the reducer operation is not triggered, the reducer operation is directly returned
, cannot be updated.)
createDraftSafeSelector
Unlike createSelector, it can detect internal data changes and recalculate.
builder.addMatcher()
Built-in function function for pattern matching
.isAllof()
Matches all of the given ation.isAnyOf()
Match a given action at least once.isAsyncThunkAction(... action)
Checks whether a given action matchesAsyncThunk
.isPending()
Given whether asynchronous ation is a request state.isFulfilled()
Whether the given asynchronous action is complete.isRejected()
Whether the given asynchronous action is rejected.isRejectedWithValue()
Whether to use the rejectedWithValue value
current()
Get the timely data after the current state change
It feels like an alternative to merging Redux. There are many redux methods built in, as well as its own data state management logic.
The resources
Redux – Toolkit official document