- Scotland team
- Author: Dao Kuan Peng
preface
Today we bring you a piece of REdux middleware: 👏 RC-Redux-Model, which provides you ✍️ with a more comfortable way to write data state management, so that you can develop with simplicity and elegance; Internal generated action, just remember an action, can change any state value, convenient and concise, thus releasing your ⌨️ CV key ~
Source repository address: RC-redux-model, if you feel good, ask ✨
background
Everyone knows about Redux and agrees with the way it streams data (you wouldn’t use it if you didn’t), but there are pros and cons to everything.
Redux doesn’t have any “pain points” per se, because redux only supports synchronous operations by default, leaving the user to choose to handle asynchrony and not asynchrony requests. It keeps itself pure, so to speak, leaving all the dirty work to others.
So my pain point was: how do I handle asynchronous requests, for which I used Redux-Saga to solve the asynchronous problem
But using Redux + Redux-Saga, I found that it made me do more [repetitive] work (working my way up to CV engineer) because of the verbose boilerplate code in our projects.
An 🌰 : To make an asynchronous request to get user information, I need to create sagas/user.js, reducers/user.js, actions/user.js, and to unify const management, I will also have a const/user.js and then switch back and forth between these files.
Should split files be the default specification?
// const/user.js
const FETCH_USER_INFO = 'FETCH_USER_INFO'
const FETCH_USER_INFO_SUCCESS = 'FETCH_USER_INFO_SUCCESS'
Copy the code
// actions/user.js
export function fetchUserInfo(params, callback) {
return {
type: FETCH_USER_INFO,
params,
callback,
}
}
Copy the code
// sagas/user.js
function* fetchUserInfoSaga({ params, callback }) {
const res = yield call(fetch.callAPI, {
actionName: FETCH_USER_INFO,
params,
})
if (res.code === 0) {
yield put({
type: FETCH_USER_INFO_SUCCESS,
data: res.data,
})
callback && callback()
} else {
throw res.msg
}
}
Copy the code
// reducers/user.js
function userReducer(state, action) {
switch (action.type) {
case FETCH_USER_INFO_SUCCESS:
return Immutable.set(state, 'userInfo', action.data)
}
}
Copy the code
Yes, such boilerplate code is simply CV operation, only need a copy, modify the name, personally, this will make me not focused enough, decentralized management of const, action, saga, reducer a set of processes, need to constantly jump ideas.
And the number of files will be more, I really do not like such a tedious process, is there a good framework to help me finish all these things?
dva
The following is the introduction of DVA official website:
Data flow solutions based on Redux and Redux-Saga let you write all your actions, states, effects, reducers, etc. in one Model file and then build in react-Router and FETCH to simplify the development experience.
It’s based on redux + Redux-Saga, but as you write it, you write it all in a model file, and it does some processing for you; Second, it is a framework, not a library. Does this mean that I need to determine whether the architecture of the project is DVA before the project starts? If I want to change to dVA in the middle of the development, does it not make sense to introduce DVA?
Or if I’m just doing demos and writing small personal projects, but I want to write dVA in the same way as the data state management model, would it be cumbersome to introduce DVA?
Looking back, my starting point is: Is to solve the tedious repetitive work, store files, state type and assign the wrong question, therefore, for people like me, provides a write state management a more comfortable way of writing, in most cases compatible with the original project, only need to install this package, will introduce a set of data management solutions, to write concise and comfortable, Happy happy masturbation code, not fragrant?
And here comes the RC-Redux-Model
rc-redux-model
To be clear: the RC-Redux-Model is middleware that provides a more concise and convenient way to manage data state.
Reference dVA data flow scheme, write all actions, reducer, state in a model file, read the source code of Redux-Thunk, internal implementation of a middleware, while providing the default action, Call this action to directly modify the state of any value, convenient and concise, let you cannot help but say WC
features
- Lightweight and concise, write data management with write
dva
So comfortable - The asynchronous request is handled by the user and internally supports the Call method, which can be forwarded by calling the provided method, which returns a Promise
- reference
redux-thunk
Internal implementation of independent middleware, all actions are asynchronous actions - Provide default action, call this action, can modify any state value, solve your repetitive writing action, reducers problem
- built-in
seamless-immutable
, just turn on the configuration and make your data immutable - Default detection for non-standard assignments and type errors, making your data more robust
The installation
npm install --save rc-redux-model
Copy the code
Related instructions
How to define a Model and automatically register actions and reducers?
Each model must contain namespace, state, and action and reducers without being written. To enable IMMUTABLE, openSeamlessImmutable = true is required
export default {
namespace: '[your model.namespace]'.state: {
testA: ' '.testB: false.testC: [].testD: {},}},Copy the code
Rc-redux-model will automatically register an action for each state field that changes the state, freeing the ⌨️ CV key on your keyboard, for example:
state: {
userName: 'oldValue'
}
Copy the code
Set ${stateName}. If you set stateName to userName, you will automatically register the action setuserName
action: {
setuserName: ({ dispatch, getState, commit, call, currentAction }) => {}
}
Copy the code
You can change state simply by calling this action in the component (📢 is not recommended, setStore is recommended).
this.props.dispatch({
type: 'userModel/setuserName'.payload: {
userName: 'newValue',}})Copy the code
The problem is that when there are many values in state (e.g., dozens of values), the user automatically registers dozens of actions. Does the user need to remember the corresponding action of each state? This must be extremely unreasonable, so the first step is to provide a default action that changes all the state values…
The problem with this is that if only one action is provided, then the action.type where all State values are changed will not be visible in the redux-devtools-extension (because it is the same action), and eventually, Again, a default action is provided, which is forwarded to the corresponding action according to the payload-key provided by the user.
✨ provides a unified default action for external users. Forwards real actions based on internal keys
this.props.dispatch({
type: '[model.namespace]/setStore'.payload: {
key: [model.state.key] // The state key you want to change
values: [your values] // The value you want to modify}})Copy the code
🌟 All actions that change state are sent via setStore. You don’t have to worry about finding them in Redux DevTools. This action will only forward the corresponding action based on your key
How do I send an action?
An action consists of type and payload. The name of type is [model.namespace/actionName].
Namespace = appModel, actionName = fetchUserList
const action = {
type: 'appModel/fetchUserList',}// Initiate the action
this.props.dispatch(action)
Copy the code
Note that each action is a function, which is the same as an asynchronous action. If you don’t understand, go to 👉
Who handles asynchronous requests?
In model.action, each action is a function and its callback argument is:
- Dispatch: Store provides an API that you can call
dispatch
Continue to distribute actions - GetState: API provided by the Store to get the latest state
- CurrentAction: The current you
this.props.dispatch
Action, you can get it heretype
和payload
- Call: forwards the request for you, using the Promise package, but you can write your own asynchronous logic
- Commit: Receives an Action, which is used to dispatch the action to the reducers to modify the state value
You can handle asynchrony yourself and change the state value by calling the default [model.namespace/setStore] action
How do I get the state value in the component?
Note that the RC-Redux-Model is middleware and, for the most part, compatible with your existing projects, so get state the same way you would get state in components
In general, our projects will install the React-Redux library and use Connect to get the values on the state.
class appComponent extends React.Component {
componentDidMount() {
// Initiate action to change the loading state to true
this.props.dispatch({
type: 'appModel/fetchLoadingStatus'.payload: {
loadingStatus: true,}})}render() {
const { loadingStatus } = this.props.appModel
console.log(loadingStatus) // true}}const mapStateToProps = (state) = > {
return {
appModel: state.appModel,
reportTaskInfo: state.reportModel.taskInfo, // Other model values}}export default connect(mapStateToProps)(appComponent)
Copy the code
If, unfortunately, you don’t have a react-Redux installed in your project, you’ll have to import the store in each component and get the state value via store.getState()
The downside of this approach is that you need to make sure your state is up to date, meaning that after you change the state value, you need to retrieve the latest value from store.getState(), which can be a bit cumbersome
import store from '@your_folder/store' // This store is the store you generated using the Redux.createstore API
class appComponent extends React.Component {
constructor() {
this.appState = store.getState()['appModel']}}Copy the code
Is data Immutable?
In functional programming languages, data is immutable. Once all data is created, it cannot change its value. If it is changed, it can only create a new data. If you have read redux’s source code, you will know why it is necessary to return a new state every time. If you have not heard of redux’s source code, you can read the article 👉
The rC-redux-model configuration parameter openSeamlessImmutable is set to false if your state is immutable. If this configuration is not set in the Model, an error will be reported!!
/ / use seamless - immutable
import Immutable from 'seamless-immutable'
export default {
namespace: 'appModel'.state: Immutable({
username: ' ',}).openSeamlessImmutable: true.// This configuration must be enabled
}
Copy the code
Type correctness?
Inevitably, sometimes you define the type of a value in model.state, but change it to another type when you change it, for example:
export default {
namespace: 'userModel'.state: {
name: ' '.// Define name as string}},Copy the code
But when you modify the state value, you do pass a non-string value
this.props.dispatch({
type: 'userModel/setStore'.payload: {
key: 'name'.values: {}, // Where name becomes object}})Copy the code
The rC-redux-model compares the type of state[key] with the type of the payload. If the type is not the same, an error message is displayed
Change the value of state if the value is already defined in state
export default {
namespace: 'userModel'.state: {
name: ' '.// Only name exists in state}},Copy the code
Now you want to change the value of another property in state
this.props.dispatch({
type: 'userModel/setStore'.payload: {
key: 'age'.values: 18.// We want to change the age property}})Copy the code
Extremely unreasonable, since you did not declare this property in state, the RC-redux-model will do it for you by default
use
If in doubt, see the instructions below ~ and for how to use it in your project, 👉 can be clicked here
Provide default actions without additional action/reducers
Originally, we needed to define the Action in reducers if we wanted to change the state value, but now the RC-Redux-Model provides the default action for modification, so we only need to define the state value in the Model
export default {
namespace: 'appModel'.state: {
value1: ' ',}}Copy the code
In the page, you only need to call the default [Model.namespace /setStore] to modify the value of the state, delightfully, you do not need to write a lot of repeated modify state code in action and reducers
this.props.dispatch({
type: 'appModel/setStore'.payload: {
key: 'value1'.values: 'appModel_state_value1',}})Copy the code
Complex and realistic examples
- Create a new model folder and add userModel.js to this folder
// model/userModel.js
import adapter from '@common/adapter'
const userModel = {
namespace: 'userModel'.openSeamlessImmutable: false.state: {
classId: ' '.studentList: [].userInfo: {
name: 'PDK',}},action: {
// Demo: Initiate an asynchronous request and modify the loading state of global.model. After the asynchronous request, modify the reducers
// This asynchronous logic can be handled by itself. If call is used, then it will be forwarded by a Promise wrapped layer
fetchUserInfo: async ({ dispatch, call }) => {
// Set Loading in globalModel to true before requesting
dispatch({
type: 'globalModel/changeLoadingStatus'.payload: true,})let res = await call(adapter.callAPI, params)
if (res.code === 0) {
dispatch({
type: 'userModel/setStore'.payload: {
key: 'userInfo'.values: res.data,
},
})
// The request ends. Set Loading in globalModel to false
dispatch({
type: 'globalModel/changeLoadingStatus'.payload: false})},return res
},
},
}
export default userModel
Copy the code
- Aggregate all the Models, and notice that you are exporting an array
// model/index.js
import userModel from './userModel'
export default [userModel]
Copy the code
- Working with Models, registering middleware
// createStore.js
import { createStore, applyMiddleware, combineReducers } from 'redux'
import models from './models'
import RcReduxModel from 'rc-redux-model'
const reduxModel = new RcReduxModel(models)
const reducerList = combineReducers(reduxModel.reducers)
return createStore(reducerList, applyMiddleware(reduxModel.thunk))
Copy the code
- Called in a component
class MyComponents extends React.PureComponent {
componentDidMount() {
// Demo: Initiate an asynchronous request and modify the loading state of global.model. After the asynchronous request, modify the reducers
// Specify the request in model.action, which supports the Promise
this.props
.dispatch({
type: 'userModel/fetchUserInfo',
})
.then((res) = > {
console.log(res)
})
.catch((err) = > {
console.log(err)
})
// demo1: Call the automatically generated default action and directly modify the value of state.userInfo (recommended)
this.props.dispatch({
type: 'userModel/setStore'.payload: {
key: 'userInfo'.values: {
name: 'setStore_name',}}})// demo2: Call the automatically generated default action and directly modify the value of state.classId (recommended)
this.props.dispatch({
type: 'userModel/setStore'.payload: {
key: 'classId'.values: 'sugarTeam2020',},})}}Copy the code
hooks ?
Rc-redux-model in hooks: “What, one is the React feature, one is the middleware for Redux, conflict?”
It is recommended that hooks do all the business logic, including asynchronous requests, etc., call Dispatch only to change state, so your model file is extremely clean, just write namespace and state, You don’t need to write the Actions and reducers, use the default provided [model.namespace/setStore]
API
Each model receives five attributes, as shown below
parameter | instructions | type | The default value |
---|---|---|---|
namespace | Must, and only | string | – |
state | Data state, must | object | {} |
action | Action, not required | object | – |
reducers | Reducer, not necessary | object | – |
openSeamlessImmutable | Whether to enable Immutable is optional | boolean | false |
The default Action provided
@desc Register to generate the default action @summary usagethis.props.dispatch({
type: '[model.namespace]/setStore'.payload: {
key: [model.state.key]
values: [your values]
}
})
Copy the code
Related articles
- rc-redux-model
- why rc-redux-model and what’s rc-redux-model
- Rc-redux-model from 0 to 1