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
  • stateUpdate 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

andcreateReducerUsed 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

Storeinfrastructure

configureStore({reducer:fn|{},middleware:fn|[],devTools:boolean|{},preloadedState:any,enhancers:[]|fn})

Instead of creatStore

Parameters:

  1. reducerAccepts a singlereducerOr an object (internally calledcombineReducers()Merged)
  2. middleware?Middleware configuration, internal passapplyMiddleware; Accept a callback function(getDefaultMiddleware)Get middleware custom Settings for default configuration;
  3. devTools?Whether to enable the Redux browser extension; Accept the default setting parameters for the object configuration
  4. preloadedState?Initialize the defaultstate; If it isrootReducerIs merged, object key-value mapping is required.
  5. 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:

  1. 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 actions
  • addDefaultCase(fn)Unmatched action processing; The last call
  1. (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:

  • .typeReturns the current action type
  • .match(action)Used to match match changesaction

Create the action Creator function whose.toString() method is modified to return Type;

  1. type:anyCan be any type (including non-serializable data types)
  2. prepareAction(content:any)Action content can be customized. The return value is an object containing:
    • payloadThe equivalent ofaction.payloadThe content of the
    • metaAdditional action information
    • errorException information

createSlice(name,initialState,reducers,extraReducers?)

Return value accessible attribute:

  • .name
  • .reducerThe Reducer function of the module
  • .actionsAutomatically generated Actions Creator method
  • .caseReducer

Higher level implementation of createAction and createReducer. Provide parameters, internal calls automatically create action types and reducer

  1. nameUsed to split state naming prefixes.
  2. initialStateThe value used for initialization.
  3. 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;
  4. extraReducers:fn|map objectExecute the reducer of additional action responses; Will not be merged into the return value.actionsIn 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

  1. Type the name of the action

  2. Fn (Content :any,thunkAPI) thunkAPI all configuration attributes for calling redux-thunk, as well as other additional parameters

    • dispatchRedux’s dispatch method
    • getStateMethod to get all state data for redux
    • extraAdditional data for thunk middleware
    • requestIdA unique request sequence ID that is automatically generated
    • signalLabel whether the current request needs to be canceled
    • rejectWithValueUsed to default reject the data returned in response.
    Copy the code
  3. {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;
    • selectIdsreturnids:[]data
    • selectEntitiesreturnentities:{[id]:entity}
    • selectAllMap to an array format list; Same order as IDS
    • selectTotalReturns the total of all data
    • selectById(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