React and Vue divide an application into different components. The state of one component may affect another component. With the increasing complexity of the project, the communication between components is a headache
Me: Qiao to Ma TTE! Is the increasing complexity of the project making state management difficult?
- There may be many factors causing a model change: server response, cache data, local data, UI state, user events… How do we deal with these States?
- Multiple components depend on the same state. How to update and synchronize the state of each component?
That’s redux.
When it comes to Redux, it’s amazing. She’s here to solve this problem, so students who don’t know need to study Redux carefully.
It’s on. It’s on
In order to follow the new front-end technology and enhance personal competitiveness, we must understand some principles and be a smart coder who knows why. In the future, soft girls will show off their skills and hard core will be confident.
State management in my mind
In fact, the young brick movers at the bottom of the front-end society don’t have the state in mind. I think Redux is very good and I appreciate it very much
First of all, flattery on the surface: the concept and the move of “lying trough” are powerful; in fact, they really feel powerful.
If I can achieve one by myself, am I just the same? yes!
Now, suppose I have implemented this state manager, how should I use it, what posture is cool, it’s worth thinking about
Create a module first
const app = {
namespace: 'app',
state: {
num: 0
},
actions: {
change (state, value) {
this.setState({
num: value
})
}
}
}
Then create a store
const store = createStore({
modules: [app]
})
This store is our overall state. You can compare it to a Redux store, but please don’t say it’s copied.
Then the store should also expose the methods of obtaining and modifying data
API that store should contain
const actions = store.mapActions({
change: 'app/change'
})
const states = store.mapStates({
num: 'app/num'
})
Here, everything is taken for granted, the usage is simple and clear, once suspected that he was a genius.
const app = {
namespace: 'app',
state: {
num: 0
},
actions: {
change (state, value) {
this.setState({
num: value
})
}
}
}
const { mapActions, mapStates } = createStore({
modules: [app]
})
const actions = store.mapActions({
change: 'app/change'
})
const states = store.mapStates({
num: 'app/num'
})
actions.change(1)
states.num // 1
Create logic
All of a sudden, it seems that there is no direction. Faced with the fake code examples, I feel frustrated that I can no longer use CC and CV. This kind of feeling is like being lovelorn.
No way, life will continue, vaguely remember the teachers said: in the face of suffering, simplify, one by one break
So you list a few implementation steps:
- Store is a class
- You need to install the app modules. Note that there may be multiple modules
- Modules are divided by a namespace
- Implement mapactions
- Implementing mapstates
- Drink a glass of water and greet the goddess you have admired for a long time on wechat to live a delicate life
The implementation of store
class Store {
constructor (options) {
this._state = {}
this._modules = {}
this.setModules(options.modules)
}
setModules (modules) {
[].concat(modules).forEach(m => {
let ns = m.namespace
let _m = new Module(m, this)
this._module[ns] = _m
this._state[ns] = _m.state
})
}
}
function createStore (options) {
return new Store(options)
}
You find that the logic of store can be simplified by simple combination, so you plan to implement oneModule
Class to facilitate expansion
Implementation of module class
class Module {
constructor (m, _store) {
this._store = _store
this.state = m.state
this.actions = m.actions
this.namespace = m.namespace
}
setState (state) {
Object.assign(this.state, state)
}
}
Now, the structure of the store can be sorted out, but the methods (actions) and states (States) have not been exposed, so now we need to implement mapactions and mapstate.
The necessity of dispatch
Think of the previous mapactions call:
const actions = store.mapActions({
change: 'app/change'
})
Is it OK to just find and return the corresponding actions of the module? It’s too low, and the middleware of Redux is enhanced and expanded by modifying dispath. It’s too powerful to use for reference.
Therefore, mapactions should be the encapsulation implementation of dispatch;
Implementation of dispatch
class Store {
// ...
dispatch = (path, ...args) => {
let { ns, key } = normalizePath(path);
ns = this._module[ns]
if (!ns) { return }
let action = ns.actions[key]
return action.call(ns, ns.state,...args)
}
}
function normalizePath (path) {
const [ns, key] = path.split('/')
return { ns, key }
}
Implementation of mapactions and mapstates
All the methods returned by mapactions should encapsulate the dispatch, so that all the methods go through the dispatch, which makes it extremely convenient for us to add Middleware in the future.
class {
// ...
mapActions = map => {
let res = {}
forEachValue(map, (path, fkey) => {
let fn = (...args) => {
this.dispatch(path, ...args)
}
res[fkey] = fn
})
return res
}
mapStates = map => {
let res= {}
forEachValue(map, (path, fkey) => {
const { ns, key } = normalizePath(path);
const m = this._module[ns]
res[fkey] = m.state[key]
})
return res
}
}
function forEachValue (obj, fn) {
Object.keys(obj).forEach(key => fn(obj[key], key))
}
Mapstates is implemented conveniently
Here, you look at the last evil spirit on the list, smile and think: technology changes the world, changes yourself, and you feel like a winner in life.
Just the great initiative to give you full courage, you happy to open wechat, skilled to open the dialog with the goddess, carefully sent a sentence: in?
Review
Goddess may be busy again. You are going to test your code
const actions = mapActions({
change: 'app/change'
})
const states = mapStates({
num: 'app/num'
})
change(1)
console.log(states.num) // 0
console.log(store._store._state.app.state.num) // 1
const add = val => actions.change(states.num + 1)
const reudce = val => actions.change(states.mum - 1)
Problem with code found:
- States do not respond to changes
- Mapactions are too limited; the above actions should support the following optimizations
const actions = store.mapActions({
change: 'app/change',
add (dispatch) {
dispatch('app/change', states.val + 1)
},
reduce (dispatch) {
dispatch('app/change', states.val - 1)
}
})
Proxy the value returned by mapstates to the state of the module
class Store {
// ...
mapStates = map => {
let res= {}
forEachValue(map, (path, fkey) => {
const { ns, key } = normalizePath(path);
const m = this._module[ns]
if (!m) { return }
proxyGetter(res, fkey, m.state, key)
})
return res
}
}
function proxyGetter (target, key, source, sourceKey) {
sharedPropertyDefinition.get = function () {
return source[sourceKey]
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
Modify mapactions to support expansion
class Store {
// ...
mapActions = map => {
let res = {}
forEachValue(map, (path, fkey) => {
let fn
if (typeof path === 'function') {
fn = (...args) => {
path(this.dispatch, ...args)
}
} else {
fn = (...args) => {
this.dispatch(path, ...args)
}
}
res[fkey] = fn
})
return res
}
}
So far, the front-end state manager function has been basically realized. You can
Go here to see her simple usage and source code;
See simple examples of undo and redo
She has been able to meet her own use, but she still has limitations and shortcomings
- The unpredictability of dispath to setstate
For example:
const app = {
namespace: 'app',
state: { num: 0 },
actions: {
async getNumer () {
// waiting...
this.setState({ num: 'xxx' })
}
}
}
As you can see, if action is an asynchronous method, then we don’t knowsetState
When it will be called.
- There is no error handling logic in the code
- Module does not support multi-level modules, like vuex;
However, you can define it by using a namespace
const app = { namespace: 'app' }
const app = { namespace: 'app:user' }
const app = { namespace: 'app:system' }
It also flattens the data, doesn’t it?
- Calling other action in action of module
const other = {
namespace: 'other',
actions: {
foo () {
this.dispatch('app/getNumber')
}
}
}