像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文件。当然在实践当中会遇到更复杂的情况,列如目标元素并非单一元素的改变,而是整个区域的变化,又或者控制目标元素的变化的好几个状态值同时变化,带来性能问题等其实并不用太过担心,这些问题是新的挑战,相信我们在不断的实践当中慢慢一步步客户这些挑战,注:大家可以在这个列子的基础上去增加复杂度,列如,新增多个目标元素,让目标元素的某个属性同时由几个状态值控制。