表单是一个组件库中必不可少的一部分,但不管是表单的输入输出仍是校验,都很是容易与业务代码有至关紧密的耦合。这就会致使不少的问题,好比组件常常不能很好地适应需求,须要经历复杂的样式功能调整,调试起来很麻烦等等。css
这个时候就会心想若是有一套能很好地与业务逻辑划清界限,与校验逻辑解耦,简洁易用的表单组件就行了。html
咱们知道表单是一种与业务关联程度很高的组件,因此咱们理应拈轻怕重,仅仅去关心view层,将校验逻辑单独抽离出来封装成一个工具类,供开发者更加有条理地整理表单逻辑,留给开发者更多灵活处理的空间,将业务部分彻底交由开发者去处理。前端
总的来讲就是要力求将表单组件从业务中解耦出来,同时又要方便易用,使业务逻辑清晰,权衡好这一点很是关键。react
表单的输入输出和校验彻底由开发者去自由控制,表单做为view层的组件只须要对输入输出的数据做出正确响应,而且这些交互效果彻底使用css去编写。webpack
全由css去写交互效果会遇到一个不可避免的问题,那就是咱们应该怎样去保存组件的状态呢? 若是使用 css 的hover伪类效果能够将所保存下来的状态瞬时响应出来。但若是想将状态持久地展现出来呢?git
从js的角度去看实际上是很是简单的一件事,无非就是将一个组件的一个状态保存到一个变量里,而后再加一个判断就能够将状态正确地响应出来。github
//伪代码 const clicked = false someComponent.onclick=function(){ if(clicked){ someComponent.className.add('fadeOut') clicked = false }else{ someComponent.className.add('fadeIn') clicked = true } } 复制代码
咱们要千方百计在css中构造一种结构去储存状态,这个触发器的核心就是浏览器的checkbox组件,不知细心的你有没有曾经想过,其实浏览器原生的checkbox组件是可以对用户是否checked做出不一样响应的,这就意味着这个浏览器原生组件中自带的checked属性就像变量同样标记着这个组件的状态,偏偏好咱们能用css中的:checked
伪类去‘监听’这个‘变量’,在监听的同时用相邻兄弟选择器+
去处理后续响应,这样咱们的css触发器就呼之欲出了。web
<!--trigger.html 一个简单的触发器--> ... <input type="checkbox" id="trigger"/> <div class="content">响应内容</div> 复制代码
//trigger.css .content{ color:red; } #trigger:checked + .content{ color:blue; } 复制代码
:checked
伪类以及+
相邻选择器能够保存两种状态,使用这种方案的兼容性最好,适用全部相似状况。:checked
伪类,~
,+
兄弟选择器能够最多保存3种状态,但这种方法只能适用于一部分状况(这里如何保存3种状态的魔法会在后面的系列会讲到)。是否是以为就这样触发器的应用范围有些局限,由于咱们没法保证它的样式。放心,还有一个很神奇的标签能够帮触发器脱胎换骨,label标签的for属性能将对应id的input组件关联起来,这样咱们就能够利用这个特性巧妙地将触发器的控制部分与响应部分区分开。sql
<!--trigger.html 一个完整的触发器--> <!-- 表面的触发部分 --> <label class="AnyStyleWhatYouLike" for="trigger">控制器</label> ... <!-- 实际的响应部分 --> <div class="trigger-container"> <input type="checkbox" id="trigger" style="display:none"/> <div class="content">响应内容</div> </div> 复制代码
//trigger.css .content{ color:red; } #trigger:checked + .content{ color:blue; } 复制代码
使用这种触发器结构就能设计出不少以往不敢想象的组件了,这些表单组件现已集成在SluckUI中,到SluckUI的表单标签中就能看到完整的Demo。后端
还记得在构想中定下的目标吗?咱们虽然已经将表单组件抽离在view层中,让开发者使用表单组件的灵活性大大提升了,但付出的代价倒是开发者须要额外编写的逻辑变多了,这意味着开发效率的下降。因此咱们要找到一个新的平衡点,对表单经常使用的操做进行适当的封装,帮助开发者整理表单的业务逻辑,其中最重要的就是表单的校验逻辑。
这个校验类的设计参考自《JavaScript设计模式》一书,使用配置模式。目标是经过简单的配置便可达到表单校验的目的。
首先咱们先想象一下在实际使用中怎样才能使表单代码简洁明了,从使用方式入手能帮助咱们构建出这个类的蓝图。
在进行校验以前应该先配置好相应的信息,一般这一步会在构造函数中完成。
this.Validator = new Validator() //初始化校验类 //配置须要校验的字段 this.Validator.config = { list: ['isEmpty','isArrayEmpty'], //检测空值和空数组 id: ['isEmpty','isInt'], //检测空值和整数 name: ['isEmpty'] //检测空值 }; 复制代码
在提交数据时,应该要对想要校验的数据进行判断,isSubmit方法会返回一个boolean值来判断结果是否符合预期
... if(this.Validator.isSubmit({ list:[1,2,3], id:456, name:'asdf' })){ //do some... } ... 复制代码
在调用完isSubmit方法以后,可使用formatRes('youKey')
获得校验的结果,在须要的地方给出相应的提示。
this.Validator.formatRes('list') //return string 复制代码
import React, { Component } from 'react' importal { Validator ,http } form 'slucky'; export default class Register extends Component { constructor(){ this.state={ name:'', email:'', password:'' } this.Validator = new Validator() //初始化校验类 Validator.types.isEmptyTest = { validate(value) { return value !== ''; }, instruction: '不为空自定义校验' }; //配置须要校验的字段 this.Validator.config = { name: ['isEmpty','isEmptyTest'], email: ['isEmpty'], password: ['isEmpty'] }; } handelClickSubmit=()=>{ const {name, email, password} = this.state //isSubmit只检测 if(this.Validator.isSubmit(this.state)){ //发送表单 http.post({ name, email, password }) } //更新校验信息 this.forceUpdate(); } render() { const { res } = this.state return ( <div> name: <input type="text" onChange={(e)=>{this.setState({name:e.target.value})}}/> {this.Validator.formatRes('name')} email: <input type="text" onChange={(e)=>{this.setState({email:e.target.value})}}/> {this.Validator.formatRes('email')} password: <input type="text" onChange={(e)=>{this.setState({password:e.target.value})}}/> {this.Validator.formatRes('password')} <button onClick={this.handelClickSubmit}></button> </div> ) } } 复制代码
到目前为止,咱们已经肯定好应该怎样去使用这个校验类了。
data //用户传入的须要校验的数据
config //用户传入的配置
result //校验输出的结果
types //用于保存不一样的校验逻辑
复制代码
思路很简单,只要将用户输入的数据与用户配置的校验逻辑进行一个判断就ok了,一会儿就能归纳出来。
具体须要作的是
// Validator.jsx class Validator{ constructor() { this.config = {} this.result = {} this.data = {} } ... // 校验用户传入的数据 validate(data) { this.data = data; this.result = {}; //遍历用户传入的数据 for (const item in data) { if (data.hasOwnProperty(item)) { const val = data[item]; //给出判断结果 const res = this.validateItem(item, val); if (res) { this.result[item] = res; } } } return this.result; } //判断用户数据是否符合校验器的预期 validateItem(item, val) { const checkerList = this.config[item]; if (!checkerList) { return false; } const result = { key: item, isValid: true, message: [] }; // 一个字段的校验器能够有多个,遍历为某个字段配置的校验器 for (let index = 0; index < checkerList.length; index++) { const checkerName = checkerList[index] const isValid = Validator.types[checkerName].validate; // 在字段校验非法时给出错误信息 if (!isValid.call(this, val)) { const instruction = Validator.types[checkerName].instruction; result.isValid = false; result .message .push(instruction); } } return result; } // 判断表单是否符合提交条件 isSubmit(data = undefined) { data && this.validate(data); for (const item in this.data) { if (this.result[item] !== undefined && !this.result[item].isValid) { return false; } } return true; } ... } // 校验器,用于保存不一样的校验逻辑 Validator.types = {} // 自定义校验器能够在类初始化完成后添加 Validator.types.isEmpty = { validate(value) { return value !== ''; }, instruction: '不能为空' }; 复制代码
触发器的结构能够放心使用,理论上能处理任何相似的地方。篇幅有限,不少地方只写了原理,更多有趣的实践尽在SluckyUI中。哈哈,若是你有更多的奇思妙想欢迎多多交流,目前SluckyUI还有不少须要完善的地方,正在持续更新中。