General steps for using react-redux
- Provider, as the provider of the top-level global state, needs to pass a parameter, the global state store
import { Provider } from 'react-redux'; <Provider store={ store }></Provider>
- Stores are created by the createStore function and need to pass the reducer pure function as a parameter
import { createStore, combineReducers } from 'redux'; const rootReducer = combineReducers({ reducer }); const store = createStore(rootReducer);
- Reducr takes two more parameters, state and action, and returns a new state based on different actions
const reducer = (state, action) => { switch (action) { default: return state; } };
- Now you can use redux in your components
- mapStateToProps receives a global state as a parameter, returns an object with properties as props to the component
{ props.reducer }; const mapStateToProps = (state) => ({ reducer: state.reducer })
- mapDispatchToProps receives dispatch as a parameter and returns the object's property as a method that calls it to update, passing either the object (which can trigger dispatch only once) or the function (which can trigger any number of times, and of course an asynchronous operation can)
props.action(); const mapDispatchToProps = (dispatch) => ({ action: () => dispatch(action) })
- ActionCreator is used to generate parameters passed to dispatch, that is, action objects are usually returned in actionCreator
// Return Object const actionCreator = function(data) { return { type: ACTION_TYPE, data } } // Use props.action(someData); const mapDispatchToProps = (dispatch) => ({ action: (data) => dispatch(actionCreator(data)) })
TypeScript Writing
The above code executes well in the js environment. Change the suffix of the file name to.ts directly, without surprise it will prompt an error...
For example:
// action.ts // error: parameter "data" implicitly has "any" type export const CHANGE_NAME = 'CHANGE_NAME'; export const changeName = (data) => ({ type: CHANGE_NAME, data }); // reducer.ts // error: The parameter'action'implicitly has an'any' type import { CHANGE_NAME } from './action'; const initialState = { name: 'lixiao' }; export default (state = initialState, action) => { switch (action.type) { case CHANGE_NAME: retutn Object.assign({}, state, { name: action.data }); default: return state; } }; // rootReducer.ts import { combineReducers } from 'redux'; import home from './pages/Home/reducer'; export default combineReducers({ home }); // Home.tsx // error: The parameter'state'implicitly has an'any' type // The parameter'dispatch'implicitly has an'any' type // The parameter "data" implicitly has an "any" type const mapStatetoProps = (state) => ({ name: state.home.name }); const mapDispatchToProps = (dispatch) => ({ changeName: (data) => dispatch(changeName(data)), })
ts is compiled statically, in part to avoid such operations as taking attributes at will.For example, operations such as state.home in mapStateToProps need to tell ts which attributes of the state in the parameter are preferable, and similarly, what attributes are preferable for each reducer after splitting, they need to be explicitly told to ts (it's not very friendly to use any type).Next, type declarations are made for the variables that prompt errors.
actionCreator
data in a parameter is used as a load for this type of action, and it is acceptable to set the variable type to any
// action.ts export const CHANGE_NAME = 'CHANGE_NAME'; export const changeName = (data: any) => ({ type: CHANGE_NAME, data });
reducer
Reducr receives state and action parameters. Actions usually have an action type attribute, type, and action payload data, which can be unified throughout the application. While the state here is accessed in other components connected to redux, it is necessary to define an interface to specify which properties of the state can be accessed.
// global.d.ts declare interface IAction { type: string; data: any; } // reducer.ts import { CHANGE_NAME } from './action'; // This interface convention has properties accessible to the child reducer export interface IHomeState { name: string; } const initialState: IHomeState = { name: 'lixiao' }; export default (state = initialState, action: IAction): IHomeState => { switch (action.type) { case CHANGE_NAME: return Object.assign({}, state, { name: action.data }); default: return state; } };
rootReducer
As the top-level state, it is often used in subcomponents to access the state in the child reducer, which attributes can be retrieved by the state in the child reducer and which sub-states can be retrieved by the top-level state.
// rootReducer.ts import { combineReducers } from 'redux'; import home, { IHomeState } from './pages/Home/reducer'; // Each additional child reducer synchronizes updates in this interface export interface IStore { home: IHomeState } export default combineReducers({ home });
Using redux in components
// Home.tsx import { Dispatch } from 'redux'; import { IStore } from '../../rootReducer'; // props type of component interface IProps { name: string; // The changeName and actionCreator are not the same here, so the return value type is not an object changeName(data: any): void; } const mapStatetoProps = (state: IStore) => ({ name: state.home.name }); // Dispatch receives parameters as objects const mapDispatchToProps = (dispatch: Dispatch) => ({ changeName: (data: any) => dispatch(changeName(data)), })
redux-thunk
In the previous example, an action is passed to dispatch and a new state is returned from the reducer function, which is synchronous, similar to a mutation in vuex.redux-thunk is a solution for handling asynchronous actions.
Asynchronous action
A common scenario is when dispatch is triggered after an ajax request is successfully sent.The simplest way to do this is to encapsulate the operation as a function:
const asyncAction = () => { dispatch({ type, data }); ajax().then(res => dispatch(res)); }
What if this operation needs to be called in more than one place?Copying and pasting this function is also possible, but it is better to use a middleware such as redux-thunk to generate an action in actionCreator that contains asynchronous operations.
redux-thunk usage
- Pass in redux-thunk middleware when creating store
// index.tsx import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; const store = createStore(rootReducer, applyMiddleware(thunk));
- actionCreator returns a function in which you can do anything, trigger dispatch es any number of times, including asynchronous operations.
// action.ts import { Dispatch } from 'redux'; export const CHANGE_NAME = 'CHANGE_NAME'; export const changeName = (data: any) => ({ type: CHANGE_NAME, data }); export const changeNameAsync = (data?: any) => (dispatch: Dispatch) => { dispatch(changeName('loading')); fetch('/api').then(res => res.json()).then(res => dispatch(changeName(res.data))); }
- Use the action of this return function in the component
// Home.tsx import { changeName, changeNameAsync } from './action'; // Dispatch can pass in objects and functions, but you can't simply use the Dispatch type here const mapDispatchToProps = (dispatch: any) => ({ changeName: (data: any) => dispatch(changeName(data)), changeNameAsync: () => dispatch(changeNameAsync()) }); // You can also use bindActionCreators const mapDispatchToProps = (dispatch: Dispatch) => (bindActionCreators({ changeName, changeNameAsync }, dispatch))
redux-thunk implementation process
The difference between a synchronous action and an asynchronous action is that a dispatch object is operated synchronously, while an asynchronous operation is a function of dispatch, which can contain any operation such as asynchronous, dispatch synchronous action, and so on.
createStore
Base Version
Before redux-thunk was introduced, store s were generated in the following way
const store = createrStore(reducer, initialState)
The second parameter, initialState, is the initial state, optional
// Two parameters function createStore(reducer, preloadedState) { let currentReducer = reducer; let currentState = preloadedState; // More important function dispatch function dispatch(action) { // This is why you can also initialize state in reducer // You can also find that the preloadedState parameter here takes precedence over the initialState in reducer currentState = currentReducer(currentState, action); } // When the page just opens, call createStore, execute dispatch again, and update the currentState // So there's an action of type @@INIT in the redux development tool dispatch({ type: ActionTypes.INIT }); }
Advanced Version
With the introduction of redux-thunk, the way store s are generated has also changed
const store = createrStore(reducer, initialState, enhancer)
The third parameter is a function, so let's see what happens inside createStore when the third parameter is passed in
function createStore(reducer, preloadedState, enhancer) { // Since the second parameter is optional, some code is required to detect the number and type of parameters // ts is convenient... // The detection passed before it was return ed. return enhancer(createStore)(reducer, preloadedState) }
Do a code conversion
const store = createStore(rootReducer, applyMiddleware(thunk)); // enhancer is applyMiddleware(thunk) // preloadedState is undefined // Code Conversion const store = applyMiddleware(thunk)(createStore)(rootReducer);
applyMiddleware
import compose from './compose'; export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
Next to code swapping
const store = applyMiddleware(thunk)(createStore)(rootReducer); // Equivalent to executing the following sequence of codes const store = createStore(rootReducer) // This store is in the base version let dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) } const middlewareAPI = { getState: store.getState, // dispatch: (rootReducer) => dispatch(rootReducer) dispatch: dispatch } const chain = [thunk].map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) // This is the store that eventually returns return { ...store, dispatch }
Take another look at what thunk (middleware API), compose did
thunk
// After redux-thunk/src/index.js simplification const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState); } return next(action); };
compose
function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args) => a(b(...args))) }
Multiple middleware passed in, which in turn takes a parameter; only one middleware returns this function directly
How dispatch was expanded
const store = createStore(rootReducer) // const chain = [thunk].map(middleware => middleware(middlewareAPI)) // const chain = [thunk(middlewareAPI)]; const chain = [(next => action => { if (typeof action === 'function') { return action(dispatch, store.getState); } return next(action); })] // dispatch = compose(...chain)(store.dispatch) dispatch = action => { if (typeof action === 'function') { return action(dispatch, store.getState); } return store.dispatch(action); }
When a parameter passed to dispatch is either a function or a base version, call the reducer function directly to update the state; when a function is passed in, execute the contents of the function body and pass dispatch in, and within this function you can use dispatch to do what you want.
summary
Most functions are provided by redux, and closures are used more often, (params1) => (params2) => {someFunction (params1, params2)} which can be used a lot and seem dizzy, but it is also the function that is executed to pass in parameters once.