1.状态管理到底是什么?有什么用?
状态管理说白了就是一个 全局变量,var obj = {} 这样的东西。
使用全局变量的缺点:
无法追踪数据的更改情况,任意的操作都可导致数据变量,无法所追踪数据的变化过程,大型应用中不太容易定位bug
状态管理:
可以追踪到数据变化的全局变量。
使用场景:
在多页面中,状态管理应用不大,因为这个变量仅仅存与内存中,当跳转页面时,数据就会清除。当然,如果对状态进行存储(比如sessionStroge),倒也可以实现状态的复用,但是想想,每次数据都需存储,相比较sessionStroge api级别的存储,状态管理会更复杂一些,带来应用的复杂性。
在单页面中,跳转系统转化为路由系统,路由跳转无需熟悉页面,保存到内存中的状态数据生命周期更长,使用也更加广泛。但依然要注意在web端,刷新操作的频率,刷新页面会使的数据全部丢失,所以需在代码中完成丢失情况的处理,使的代码更加健壮性。
2.那如何追踪一个全局变量的数据变化?
如果我们有使用vuex的经验,我们知道在vuex里的数据,无法通过store.state.a = 1这样的赋值操作完成。因为这样,无法追踪到数据的变化。
问题:追踪一个全局变量的数据变化?
方法1:Object.defineProperty();这个应该很熟悉,vue双向绑定就是通过这里的setter getter实现的。
方法2:Proxy ES6新语法,vue3的底层双向绑定会使用它。(本文我们以Proxy做demo)
学习Proxy,请点击
3.在写一个简易的状态管理,需要我们准备什么?
前提:本文只讨论如何简易状态管理,所以性能方面不会考虑,也就是说不会使用Vnode diff算法等知识。
查看上面的图,有几个问题我们需要考虑:
1.只要state发生变化,view层自动更新?
我们增加个订阅-发布器, 每个需要state的view订阅数据变化,当数据发生变化时,发布这个变化,state也会跟着变化。
2.这个指令是什么?是vuex中的actions mutations吗?
我们模拟vuex实现,所以认为指定就是这两个,actions是异步操作,mutation是同步操作。
4.代码实现:
本文代码大部分来自
本文是在上文中总结学习写出的。
本文目录结构如下:
1.订阅-发布模式 js/lib/pubsub.jsexport default class PubSub{ constructor(){ this.events = {}; } // 订阅 subscribe(type,callback){ if(!this.events.hasOwnProperty(type)){ this.events[type] = []; } // 返回该type下订阅了多少个回掉函数 // push返回的是当前数组的length return this.events[type].push(callback); } // 发布 publish(type,data={}){ if(!this.events.hasOwnProperty(type)){ return []; } return this.events[type].map((callback)=>{ callback(data) }) }}复制代码
2.实现状态管理的关键 js/store/store.jsimport pubSub from '../lib/pubsub.js'export default class Store { // 默认参数 /* * 以vuex的写法举例 * new Vuex.store({ * state:{ * * }, * mutations:{}, * actions:{} * }) * */ constructor(params) { let self = this; self.mutations = params.mutations ? params.mutations : {}; self.actions = params.actions ? params.actions : {} ; this.events = new pubSub(); self.state = new Proxy((params.state || {}), { set(target,key,value){ target[key] = value; console.log(`stateChange: ${key}: ${value}`); // 当数据变化时,进行发布操作 self.events.publish('stateChange',self.state); // 如果数据不是有mutation方式改变,则发出警告 if(self.status !== 'mutation'){ console.warn(`数据应该用mutation方式提交`); } self.status = 'resting'; return true; } }); self.status = 'resting'; } /* * dispatch():提交action,action是一个异步方法 * */ dispatch(actionType,payload){ let self = this; if(typeof self.actions[actionType] !== 'function'){ console.log(`action ${actionType} 不存在`); return false; } console.groupCollapsed(`ACTION: ${actionType}`); self.status = 'action'; self.actions[actionType](self,payload); console.groupEnd(); return true; } commit(mutataionType,payload){ let self = this; if(typeof self.mutations[mutataionType] !== 'function'){ console.log(`Mutation "${mutataionType}" 不存在`); return false; } console.log(`Mutation: ${mutataionType}`) self.status = 'mutation'; let newState = self.mutations[mutataionType](self.state, payload); self.state = Object.assign(self.state,newState); return true; }}复制代码
实例化Store js/store/index.jsimport Store from './store.js'export default new Store({ state:{ items:[ 1,2 ] }, mutations:{ addItem(state,payload){ state.items.push(payload); return state; }, clearItem(state,payload){ state.items.splice(payload.index,1); return state; } }, actions:{ addItem(context,payload){ context.commit('addItem',payload) }, clearItem(context,payload){ context.commit('clearItem',payload) } }})复制代码
view层:js/lib/component.jsimport Store from '../store/store.js'export default class Component{ constructor(props={}) { let self = this; this.render = this.render || function () { } // 关键:这个是通用的组件类,可对需要使用state的组件,进行数据订阅。 if(props.store instanceof Store){ props.store.events.subscribe('stateChange',self.render.bind(self)) } if(props.hasOwnProperty('el')){ self.el = props.el; } }}复制代码
组件类:List js/components/List.jsimport component from '../lib/component.js'import store from '../store/index.js'export default class List extends component{ constructor() { super({ store, el: document.querySelector('.js-items') }) } render(){ let self = this; if(store.state.items.length === 0) { self.el.innerHTML = `You've done nothing yet 😢
`; return; } self.el.innerHTML = `
- ${store.state.items.map(item => { return `
- ${item} ` }).join('')}
Count组件 js/components/Count.jsimport store from '../store/index.js'import component from '../lib/component.js'export default class Count extends component{ constructor() { super({ store, el:document.querySelector('.js-count') }) } render(){ let suffix = store.state.items.length !== 1 ? 's' : ''; let emoji = store.state.items.length > 0 ? '🙌' : '😢'; this.el.innerHTML = ` You've done ${store.state.items.length} thing${suffix} today ${emoji} `; }}复制代码
主函数:main.jsimport store from './store/index.js';import Count from './components/Count.js';import List from './components/List.js';const formElement = document.querySelector('.js-form');const inputElement = document.querySelector('#new-item-field');formElement.addEventListener('submit', evt => { evt.preventDefault(); let value = inputElement.value.trim(); if(value.length) { store.dispatch('addItem', value); inputElement.value = ''; inputElement.focus(); }});const countInstance = new Count();const listInstance = new List();countInstance.render();listInstance.render();复制代码
入口html:Vanilla State Management 复制代码 Done list
A list of things that you have achieved today
Note: The data isn't stored, so it will disappear if you reload!
What you've done
5.总结
以上,也就实现了一下简易的状态管理,其实状态管理就是将{a:1}的数据修改都通过指定的指令去修改,可以追踪到数据变化,所以不要把状态管理想的多么复杂,多么高大上。时刻牢记,所谓状态管理就是一个全局对象,只是这个对象的属性变化要经过规定一套流程。
如果你不喜欢状态管理,而且项目不大的情况下,可以不用。sessionstorage这些也是很好的选择。