Js系列一十三:一個簡單的狀態管理庫
像vue或者react等在做中大型專案的時候,如果運用自身的方法來管理資料狀態,最終會導致程式碼變得臃腫難以維護及複用,因此他們都會運用相應的狀態管理庫的協助管理資料狀態,如,vuex,redux,mobix,flux等。今天我們不是講如何運用一個狀態管理庫,而是運用js模組化開發的思想來寫一個簡單的狀態管理庫,從而讓大家瞭解狀態管理庫的實現思路及js模組化開發的思想。
注:為了專注於具體的功能實現,本示列程式碼是執行在create-react-app生成的配置環境中。本節由於專注于思想的過程所以對樣式有所忽略。最終的樣式為下圖:
通過show/hide實現方框的顯示與隱藏,可以輸入改變背景色,border顏色,通過width,height動態改變元素寬高。
實現過程如下。(清空原本create-react-app的index.js檔案)最終結構如下
一,在src目錄下的store目錄中新建一個狀態管理模組,並且命名為state.js。首先我們建立一個狀態樹,在整個專案中狀態樹是唯一的。我們把所有的狀態名與狀態值通過key-value的形式儲存在狀態中
const store={};
根據需要再狀態樹中儲存狀態,狀態樹的大概形式如下:
store = {
show:0,
backgroundColor:'#ccc',
width:200,
height:200,
.........................
};
我們都瞭解閉包如果需要再其他模組中操作store,那麼久需要對外提供對應的介面。根據本示列的需要我們列出大概用到的方法,之後可根據需要再進行新增。
(1),registerState://往狀態樹中放入新的值
(2),getState://獲取某一狀態值
(3),getStore:或者整個狀態樹
(4),setState:修改書樹中的某一狀態值
示列如下:
//給store新增一個狀態
export const registerState = (status,value)=>{
if(store[status]){
throw new Error('該狀態已經存在');
return;
}
store[status] = value;
return value;
}
//獲取整個狀態樹
export const getStore = ()=>{
return store;
}
//獲取某個狀態值;
export const getState = (status)=>{
return store[status];
}
//設定某個狀態的值
export const setState = (status,value)=>{
store[status]=value;
dispatch(status,value);
return value
}
//此處只是為了學習模組化開發的思想,請勿直接將程式碼用於實踐。
二,當通過互動改變狀態值時,我們期待ui也能夠發生相應的變化,ui的變化可能比較簡單,也可能很複雜。所以為了維護ui的變化,我們將ui的變化用函式封裝起來,並與對應的狀態值對應,這樣當狀態值改變時呼叫對應的ui函式就能夠實現介面的時時變動了。因此出了store來儲存狀態值之外,還需要一個events物件來儲存ui函式。
const events={};
狀態值與ui函式的對應關係如下
通過相同的狀態名可以訪問對應的狀態值與函式,因此我們也提供幾個操作events的方法
到此一個簡單的狀態管理模組就完成了,接下來我們看看如何運用它。
三,註冊狀態值模組
我們需要管理的狀態很多,當然可以在每一個用到這些狀態值得模組中各自注冊。而通常我們可以使用一個單獨的模組來註冊。當然為了避免忘記最好寫上註釋。註冊的方式就是利用狀態管理模組當中定義的registerState的方法來完成
四,函式功能模組
每一個專案都有這樣的模組,我們可以把一些封裝好的方法都放入這個模組中去。如zhe在專案當中我們都會遇到這樣的需求比如請某數的最大值,獲取url的欄位值,對時間格式按需進行處理等就可以將這些封裝到模組中,在使用時引入即可。在此示列中我們只封裝一個獲取元素樣式的函式,如下:
出此之外,也可以引入lodash這樣的工具庫,lodash是一個具有一致介面,模組化,高效能的工具函式,它封裝了很多常用的工具函式,在實踐中非常有用
五,目標元素
目標元素會設計ui改變的額元素,之前在建立狀態管理模組時已經提到了,我們需要將ui改變的動作封裝為函式,並儲存到events中,這個操作就選擇在目標元素中來完成。首先在public/index.html中寫入一個div元素
此目標元素為一個正方形的元素,我們將通過控制按鈕來改變它的顯示與隱藏,邊框,背景,長,寬,因此該模組的主要功能就是根據註冊的狀態變數,繫結ui函式的具體程式碼如下:
import {bind} from '../store/state';
import {getStyle} from '../utils/utils';
import '../store/registerState';
const div=document.querySelector('.target');
//control hide or show;
bind('show',value=>{
if(value===1){
div.classList.add('hide');
}
if(value===0){
div.classList.remove('hide')
}
})
//change background color;
bind('backgroundColor',value=>{
div.style.backgroundColor=value;
})
//change border color;
bind('borderColor',value=>{
const width = parseInt(getStyle(div,'borderWidth'));
if(width===0){
div.style.border='2px solid #ccc';
}
div.style.borderColor = value;
})
//change Width height;
bind('width',value=>{
div.style.width= `${value}px`;
})
bind('height',value=>{
div.style.height= `${value}px`;
})
六:控制模組
我們通過按鈕,input框,或者滑塊等不同的方式來改變狀態值,因此控制模組將會是一個比較複雜的模組。為了更好的組織程式碼,一個可讀性和可維護性都很強的方式是將整個控制模組拆分為很多小模組,每一個小模組只完成一個狀態值的控制操作。因此根據需求我們建立對應的控制模組,首先在public/index.html中新增相應的html程式碼。
<div class="target"></div>
<div class="control_wrap">
<div><button class="show">show/hide</button></div>
<div>
<input class='bgcolor_input' type='text' placeholder="input background color">
<button class="bgcolor">sure</button>
</div>
<div>
<input class='bdcolor_input' type='text' placeholder="input boder color">
<button class="bdcolor">sure</button>
</div>
<div>
<span>width</span>
<button class="width_reduce">-5</button>
<button class="width_add">+5</button>
</div>
<div>
<span>hight</span>
<button class="height_reduce">--</button>
<input type="text" class="height_input" readonlys>
<button class="height_add">++</button>
</div>
</div>
在src的目錄下新建一個js檔案,該資料夾中全部用來存放控制模組,然後我們依次編寫控制模組的程式碼即可
(1),控制元素的顯示與隱藏
(2),控制目標元素背景色的模組
(3),控制目標元素邊框顏色的模組
(4),控制目標元素寬度的模組
(5),控制目標元素高度的模組
最後將所有的模組整合起來
在構建工具當中,當我們引入一個資料夾當中模組。那麼就相當於預設引入該檔案下名為index.js的模組,因此如下兩種方式是等價的。
import './js'
//等價於
import './js/index';
到此如果你一直跟著著的話,我們已經能通過點選事件時,僅僅對狀態值做了改變,而沒有考慮ui的變化。在以前的開發經驗當中,要向改變一個元素的某個屬性,一般來說會有相應的狀態值的變化,並且還有對應的ui操作,這裡的做法好像不太一樣。其實這裡利用了一個列子,帶大家嘗試一下分層開發的思維,在這個列子紅,我們將狀態控制設定為控制層,而ui變化設定為view層,我們只需要再目標元素中,將view層的變化封裝好,然後利用狀態管理的模組機制,在控制層只要考慮值得變化即可。這樣處理之後,我們的開發重心就從考慮整個介面的變化,轉移到了僅僅考慮狀態值的變化,這樣的好處是,極大的簡化了我們在實現需求過程當中所需要考慮的問題,在未來進階學習中,大家還可能會大量的接觸這樣的開發思路
七:拼合模組
在src目錄下的index.js檔案中,可以通過import將所需要的模組拼合起來
至此,我們需要的專案就已經全部完成了,如果你一直在動手實踐,相信你已經能夠看到專案的最終效果了。
專案小結:
模組化開發思路,實際上是通過視覺元素,功能性測試等原則,將程式碼劃分為一個個擁有獨立功能的模組。我們通過es6的modules語法,按需將這些模組組合起來,並藉助構建根據打包成我們熟悉的js檔案。當然在實踐當中會遇到更復雜的情況,列如目標元素並非單一元素的改變,而是整個區域的變化,又或者控制目標元素的變化的好幾個狀態值同時變化,帶來效能問題等其實並不用太過擔心,這些問題是新的挑戰,相信我們在不斷的實踐當中慢慢一步步客戶這些挑戰,注:大家可以在這個列子的基礎上去增加複雜度,列如,新增多個目標元素,讓目標元素的某個屬性同時由幾個狀態值控制。
專案程式碼地址:https://github.com/GoodHy/js-