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.im/post/5b7635…
本文是在上文中總結學習寫出的。
本文目錄結構如下:
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這些也是很好的選擇。