Re從零開始的UI庫編寫生活之表單

構想

表單是一個組件庫中必不可少的一部分,但不管是表單的輸入輸出仍是校驗,都很是容易與業務代碼有至關緊密的耦合。這就會致使不少的問題,好比組件常常不能很好地適應需求,須要經歷複雜的樣式功能調整,調試起來很麻煩等等。css

這個時候就會心想若是有一套能很好地與業務邏輯劃清界限,與校驗邏輯解耦,簡潔易用的表單組件就行了。html

咱們知道表單是一種與業務關聯程度很高的組件,因此咱們理應拈輕怕重,僅僅去關心view層,將校驗邏輯單獨抽離出來封裝成一個工具類,供開發者更加有條理地整理表單邏輯,留給開發者更多靈活處理的空間,將業務部分徹底交由開發者去處理。前端

總的來講就是要力求將表單組件從業務中解耦出來,同時又要方便易用,使業務邏輯清晰,權衡好這一點很是關鍵。react

想要的效果

表單的輸入輸出和校驗徹底由開發者去自由控制,表單做爲view層的組件只須要對輸入輸出的數據做出正確響應,而且這些交互效果徹底使用css去編寫。webpack

開始設計

首要問題

全由css去寫交互效果會遇到一個不可避免的問題,那就是咱們應該怎樣去保存組件的狀態呢? 若是使用 css 的hover僞類效果能夠將所保存下來的狀態瞬時響應出來。但若是想將狀態持久地展現出來呢?git

js觸發器

從js的角度去看實際上是很是簡單的一件事,無非就是將一個組件的一個狀態保存到一個變量裏,而後再加一個判斷就能夠將狀態正確地響應出來。github

//僞代碼
const clicked = false
someComponent.onclick=function(){
    if(clicked){
        someComponent.className.add('fadeOut')
        clicked = false
    }else{
        someComponent.className.add('fadeIn')
        clicked = true
    }
}
複製代碼

css觸發器

咱們要千方百計在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。後端

純css開發的表單組件

完整版 _input.scss

image
image

表單工具-將經常使用的邏輯封裝起來

還記得在構想中定下的目標嗎?咱們雖然已經將表單組件抽離在view層中,讓開發者使用表單組件的靈活性大大提升了,但付出的代價倒是開發者須要額外編寫的邏輯變多了,這意味着開發效率的下降。因此咱們要找到一個新的平衡點,對錶單經常使用的操做進行適當的封裝,幫助開發者整理表單的業務邏輯,其中最重要的就是表單的校驗邏輯。

表單校驗類(Validator)

這個校驗類的設計參考自《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
複製代碼

以React爲例的使用方式

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>
        )
    }
}
複製代碼

到目前爲止,咱們已經肯定好應該怎樣去使用這個校驗類了。

image

那如何實現的呢?

核心變量

data //用戶傳入的須要校驗的數據
config //用戶傳入的配置
result //校驗輸出的結果
types //用於保存不一樣的校驗邏輯
複製代碼

核心方法

思路很簡單,只要將用戶輸入的數據與用戶配置的校驗邏輯進行一個判斷就ok了,一會兒就能歸納出來。

具體須要作的是

  1. 遍歷用戶輸入的數據
  2. 找到數據所對應的校驗器名稱
  3. 調用校驗器去校驗數據
  4. 輸出結果
// 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: '不能爲空'
};
複製代碼

完整版 Validator.jsx

結束

觸發器的結構能夠放心使用,理論上能處理任何相似的地方。篇幅有限,不少地方只寫了原理,更多有趣的實踐盡在SluckyUI中。哈哈,若是你有更多的奇思妙想歡迎多多交流,目前SluckyUI還有不少須要完善的地方,正在持續更新中。

image

image

從零開始系列傳送門

相關文章
相關標籤/搜索