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,请点击 www.jianshu.com/p/34f0e6abe…
3.在写一个简易的状态管理,需要我们准备什么?
前提:本文只讨论如何简易状态管理,所以性能方面不会考虑,也就是说不会使用Vnode diff算法等知识。
查看上面的图,有几个问题我们需要考虑:
1.只要state发生变化,view层自动更新?
我们增加个订阅-发布器, 每个需要state的view订阅数据变化,当数据发生变化时,发布这个变化,state也会跟着变化。
2.这个指令是什么?是vuex中的actions mutations吗?
我们模拟vuex实现,所以认为指定就是这两个,actions是异步操作,mutation是同步操作。
4.代码实现:
本文代码大部分来自juejin.cn/post/684490…
本文是在上文中总结学习写出的。
本文目录结构如下:
1.订阅-发布模式 js/lib/pubsub.js
export 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.js
import 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.js
import 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.js
import 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.js
import 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 = `<p class="no-items">You've done nothing yet 😢
</p>`;
return;
}
self.el.innerHTML = `
<ul class="app__items">
${store.state.items.map(item => {
return `
<li>${item}<button aria-label="Delete this item">×</button></li>
`
}).join('')}
</ul>
`;
self.el.querySelectorAll('button').forEach((button, index) => {
button.addEventListener('click', () => {
store.dispatch('clearItem', { index });
});
});
}
}Count组件 js/components/Count.js
import 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 = `
<small>You've done</small>
${store.state.items.length}
<small>thing${suffix} today ${emoji}</small>
`;
}
}
主函数:main.js
import 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:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="stylesheet" href="css/global.css" />
<!--<link rel="stylesheet" media="all" href="https://rawgit.com/hankchizljaw/boilerform/master/dist/css/boilerform.min.css?v=1.1.1" />-->
<title>Vanilla State Management</title>
</head>
<body>
<main>
<header class="intro">
<h1 class="intro__heading">Done list</h1>
<p class="intro__summary">A list of things that you have achieved today</p>
<p class="intro__summary"><b>Note:</b> The data isn't stored, so it will disappear if you reload!</p>
</header>
<section class="app">
<section class="app__input">
<h2 class="app__heading">What you've done</h2>
<div class="js-items" aria-live="polite" aria-label="A list of items you have done"></div>
<form class=" new-item boilerform js-form">
<div class="boilerform">
<!-- Form styles from the https://boilerform.design boilerplate -->
<label for="new-item-field" class="new-item__label c-label ">Add a new item</label>
<input type="text" class=" new-item__details c-input-field " id="new-item-field" autocomplete="off" />
<button class=" c-button new-item__button ">Save</button>
</div>
</form>
</section>
<aside class="app__status">
<p role="status" class="visually-hidden">You have done <span class="js-status">1 thing</span> today!</p>
<div class="app__decor js-count">
<small>You've done</small>
<span>1</span>
<small>things today 😢</small>
</div>
</aside>
</section>
</main>
<script type="module" src="js/main.js"></script>
</body>
</html>5.总结
以上,也就实现了一下简易的状态管理,其实状态管理就是将{a:1}的数据修改都通过指定的指令去修改,可以追踪到数据变化,所以不要把状态管理想的多么复杂,多么高大上。时刻牢记,所谓状态管理就是一个全局对象,只是这个对象的属性变化要经过规定一套流程。
如果你不喜欢状态管理,而且项目不大的情况下,可以不用。sessionstorage这些也是很好的选择。