preface
To be honest, the state management solutions of the three front-end frameworks (ReDUx, VUex, and Service + RXJS) are without a doubt the worst in terms of development experience, which has led to a lot of redux improvements
However, they are the wheels of other people’s homes. Out of the attitude of being responsible for their own products, we do not dare to use them at will. We still try to use the original Redux, after all, Redux has been tested by numerous products, and its reliability can be guaranteed
Instead of building a wheel from scratch, we could just add a few dozen lines of code and tweak Redux a little to improve the development experience at the lowest cost
Of course, these optimizations may seem a little crude to you or not be your pain point
But that doesn’t matter, this article just provides a feasible way to optimize Redux at a minimum cost
Don’t be afraid to experiment, and don’t be afraid to tinker with code to change a design that doesn’t make sense to you
The body of the
It is officially recommended that action and Reducer be placed in different files, but this results in cumbersome file switching
According to the general file classification, we have action and Reducer files
Action and Reducer obviously correspond one by one and there is no need to divide them into different files
Take the 🌰 child of a counter
The file directory structure is as follows
- store
- action.js
- reducer.js
// action.js
export const ADD ='add'
//reducer.js
const counter ={ count: 0 }
export default function reducer(state = counter, action) {
switch (action.type) {
case 'ADD':
return {
...state,
count:state.count+1
}
break;
default:
return state
break; }}// Component use
import {ADD} from './action.js'
store.dispatch(ADD)
Copy the code
When we want to look at the component logic store.dispatch(ADD), we need to
- Open the Action file and search for the constant ADD
- Open the Reducer file and search for the constant ADD
There are two file switching operations here, which are completely unnecessary in my opinion. The above actions and reducer are essentially to change the same state (counter object). Since they are to change the same state, why not put them together with the state they want to change?
We can do this by creating a file with the name of the state counter that we need to change and putting the corresponding actions and reducer into it
The file directory structure is as follows
- store
- counter.js
Counter. Js is as follows
const initialState = {
count: 10
}
function reducer(state = counter, action) {
switch (action.type) {
case 'add':
return {
...state,
count:state.count+1
}
break;
default:
return state
break; }}Copy the code
Reducer switch writing is cumbersome
When reducer is large, switch is cumbersome
We can write a tool method to convert the Reducer Switch style to the object style and the action to the property name of the object
// Tool methods
const handleActions = ({ state, action, reducers}) = >
Object.keys(reducers)
.includes(action.type)
? reducers[action.type](state,action)
: state
//counter.js
const initialState = {
count: 10
}
const reducers = {
add(state, action) {
return{
...state,
count:state.count+1
}
},
minus(state, action) {
return{
...state,
count:state.count- 1}}},export default (state = initialState, action) => handleActions({
state,
action,
reducers,
})
Copy the code
Reducer must be a pure function and must return new references. When the object level is deep, it is cumbersome to write
We can optimize this by introducing immer, a small library of immutable data structures
The first step is to introduce immer
import produce from "immer"
Copy the code
The second step is to modify the handleActions tool function
export const handleActions = ({ state, action, reducers}) = >
Object.keys(reducers)
.includes(action.type)
? produce(state, draft => reducers[action.type](draft, action))
: state
// Add this line
produce(state, draft => reducers[action.type](draft, action))
Copy the code
And then we just say reducer
- You do not need to manually return each time
- There is no need to manually generate a new reference
The following
/ / before modification
const reducers = {
add(state, action) {
return{
...state,
count:state.count+1
}
},
minus(state, action) {
return{
...state,
count:state.count- 1}}},/ / after transforming
const reducers = {
add(state, action) {
state.count++
},
minus(state, action) {
state.count--
},
}
Copy the code
Just to explain a little bit about our new line of code
produce(state, draft => reducers[action.type](draft, action))
Copy the code
This involves the use of immer
The first argument to produce is the object you want to manipulate, in this case, operation state
The second argument is a function that immer passes as an argument a copy of the object that you operate on without affecting the original object
So the function we wrote in the reducers object is equivalent to writing the second parameter of Produce, and in the handleActions utility function, we have also returned through the ternary expression, This allows you to implement the reducers object without having to manually return functions
Adding a namespace
When the project gets big, we may have naming conflicts in the actions we write. The solution is to use the current file name as the namespace
For example, we have the following directory structure
- store
- counter.js
One day we will need to add a todo-list module
- store
- counter.js
- todoList.js
To prevent naming conflicts between counter and actions in todoList, we can modify the handleActions tool function
import produce from "immer"
const getKey = (str, flag) = > {
const i = str.indexOf(flag)
return str.substring(i + 1, str.length + 1)}export const handleActions = ({ state, action, reducers, namespace = ' ' }) = >
Object.keys(reducers)
.map(key= > namespace + '/' + key)
.includes(action.type)
? produce(state, draft => reducers[getKey(action.type, '/')](draft, action))
: state
export default (state = initialState, action) => handleActions({
namespace: 'counter'.// Add a namespace
state,
action,
reducers,
})
Copy the code
Components used like this
store.dispatch('counter/add')// Add method for counter module
store.dispatch('todoList/add')//todoList module add method
Copy the code
The sample code
Points to be optimized
- Actions are based on strings, the editor can’t do smart hints and are prone to spelling errors
- Support for ts
To be continued…