vue3如何優雅的實現移動端登入註冊模組
前言
近期開發的移動端專案直接上了 vue3,新特性 composition api 確實帶來了全新的開發體驗.開發者在使用這些特性時可以將高耦合的狀態和方法放在一起統一管理,並能視具體情況將高度複用的邏輯程式碼單獨封裝起來,這對提升整體程式碼架構的健壯性很有幫助.
如今新啟動的每個移動端專案基本上都包含註冊登入模組,本次實踐過程中針對登入註冊中的表單控制元件做了一些經驗上的總結,通過抽離提取共性程式碼來提升程式碼的可維護性和開發效率.
接下來觀察一下美工同學提供的圖片.
註冊頁面
登入頁面
忘記密碼頁面
修改密碼頁面
通過觀察上面幾張產品圖片,可以清晰看出構成整個登入註冊模組的核心元件就是 input 輸入框.只要把輸入框元件開發完備,其他頁面直接引用就行了.
輸入框開發完了只實現了靜態頁面的展示,另外我們還要設計一套通用的資料校驗方案應用到各個頁面中的表單控制元件.
輸入框元件
從上面分析可知,輸入框元件是整個登入註冊模組的核心內容.我們先看一下輸入框元件有哪幾種 UI 形態.
形態一
左側有文字 +86,中間是輸入框,右側如果檢測到輸入框有資料輸入顯示叉叉圖示,如果沒有資料為空隱藏圖示.
形態二
左側只有一個輸入框,右側是文案.文案的內容可能是驗證碼,也可能是點選驗證碼後顯示的倒計時文案.
形態三
左側依舊只有一個輸入框,右側如果檢測到輸入框有內容顯示叉叉圖示,如果內容為空隱藏圖示.
佈局
依據上面觀察而來的現象分析,我們設計這款 input 元件時可以將其分為左中右三部分.左側可能是文案,也可能是空.中間是一個輸入框.右側可能是文案也可能是叉叉圖示.
模板內容如下:
<template> <div class="input"> <!--左側,lt是左側內容--> <span class="left-text"> {{ lt }} </span> <!--中間--> <input class="content" v-bind="$attrs" :value="value" @input="onChange" /> <!--右側,rt判端是驗證碼還是叉叉圖示--> <div v-if="rt == 'timer'" class="right-section"> {{ timerData.content }} <!--可能是'驗證碼',也可能是倒計時 --> </div> <div v-else-if="rt == 'close'" class="right-section" > <van-icon name="close" /> <!--叉叉圖示--> </div> </div> </template>
佈局上將左中右的父級設定為 display:flex,子級的三個元素全部設定成 display:inlZkGzhvNHine-block 行內塊模式,目的是為了讓左側和右側依據自身內容自適應寬度,而中間的 input 設定成 flex:1 充滿剩餘的寬度.
理論上這樣的佈局是可行的,但實踐中發現了問題.
Demo 效果圖如下:
右側持續增加寬度時,中間 input 由於預設寬度的影響導致讓右側向外溢位了,這並不是我們想要的.
解決這個問題的辦法很簡單,只需要將中間 input 的 width 設定為 0 即可,如下便達到了我們想要的效果.
v-model
外部頁面引用上述封裝的元件結構如下:
<InputForm lt="+86" <!--左側顯示+86--> rt="close" <!--右側顯示叉叉圖示--> placeholder="請輸入手機號碼" />
外部頁面建立了一個表單資料 form_data 如下,但希望能通過 v-model 的形式將 form_data 的資料與子元件輸入框的值建立雙向資料繫結.
const form_data = reactive({ number_number: '',//使用者名稱 password: '',//密碼 ppassword: '',//重複密碼 captcha: '',//驗證碼 })
在 vue3 實現 v-model 非常簡便,在父元件中使用 v-model:xx 完成繫結,這裡的 xx 對應著子元件要繫結的狀態名稱,如下所示.
<InputForm lt="+86" <!--左側顯示+86--> rt="close" <!--右側顯示叉叉圖示--> placeholder="請輸入手機號碼" v-model:value="form_data.password" />
接下來子元件裡首先宣告要繫結的屬性 value,並監聽輸入框的 oninput事件 .程式碼如下:
<template> <div class="input"> ... <input class="content" v-bind="$attrs" :value="value" @input="onChange" /> ... </div> </template> export default defineComponent({ props: { lt:String,rt: String,value: String },setup(props,context) { const onChange = (e:KeyboardEvent) => { const value = (e.target as HTMLInputElement).value; context.emit("update:value",value); }; return { onChange } } })
oninput事件 的回撥函式將獲取到的值使用 context.emit("update:value",value) 返回回去.
其中 update:value 裡前面部分 update: 為固定寫法,後面填寫要建立雙向繫結的狀態名稱.如此一來就輕易的完成了 v-model 的繫結.
資料校驗
一般來說只要頁面上涉及到表單控制元件(比如輸入框),那麼就要針對相應的值做資料校驗.如果按照原始的方法,當用戶點選按鈕,js 接受響應依次獲取每個表單項的值一一校驗.
這樣的做法當然可以實現功能,但並不高效和精簡.因為很多頁面都要做校驗,大量的校驗邏輯是重複書寫的.
我們接下來設計一套通用的校驗方案,將那些可以複用的邏輯程式碼都封裝起來,並且能夠快速的應用到每個頁面上,提升開發效率.
依註冊頁面為例,模板程式碼如下.建立四個輸入框元件:手機號,手機驗證碼,密碼和確認密碼.最後面再放置一個註冊按鈕.(為了看起來更清晰,下面的程式碼將所有 ts 型別刪除)
<Form ref="form" :rules="rules"> <InputForm lt="+86" rt="close" v-model:value="form_data.number_number" placeholder="請輸入手機號碼" propName="number_number" /> <InputForm rt="timer" v-model:value="form_data.captcha" placeholder="請輸入手機驗證碼" propName="captcha" /> <InputForm rt="close" v-model:value="form_data.password" placeholder="請輸入密碼" type="password" propName="password" /> <InputForm rt="close" v-model:value="form_data.ppassword" placeholder="請輸入確認密碼" type="password" propName="ppassword" /> <Button text="注 冊" @sub="onSubmmit" /> <!--註冊按鈕--> </Form>
在借鑑了一些其他優秀框架的表單實踐後,我們首先是在最外層增加了一個元件 Form,其次給每個輸入框元件增加了一個屬性 propName .這個屬性是配合 rules 一起使用的,rules 是手動定義的校驗規則,當它傳遞給 Form 元件後,子元件(輸入框元件)就能通過 propName 屬性拿到屬於它的校驗規則.
整體的實現思路可以從頭串聯一遍.首先是前端開發者定義好當前頁面的校驗規則 rules,並將它傳遞給 Form 元件. Form 元件接受到後會將校驗規則分發給它的每個子元件(輸入框元件).子元件拿到校驗規則後就能夠針對輸入框的值做相應的資料校驗.
當用戶點選註冊按鈕時,點選事件會獲取 Form 元件的例項,並執行它的 validate 方法,此時 Form 元件就會對它旗下的每個子元件做一輪資料校驗.一http://www.cppcns.com旦所有校驗成功了,validate 方法返回 true .存在一個校驗沒通過,validate 方法就返回 false,並彈出錯誤資訊.
註冊頁面邏輯如下:
export default defineComponent({ components: { InputForm,//輸入框 Button,//註冊按鈕 Form,//Form元件 },setup(props) { const form_data = ...; //省略 const rules = ...; //獲取最外層Form元件的例項 const form = ref(null); const onSubmmit = ()=>{ if (!form.value || !form.value.validate()) { return false; } //校驗通過了,可以請求註冊介面了 } return { form,rules,onSubmmit,form_data }; },});
定義一個變數 form,用它來獲取 Form 表單的例項.模板上 <Form ref="form" :rules="rules"> 只需要加上一個 ref 屬性就可以了.
使用者點選註冊按鈕觸發 onSubmmit 函式,因為 form 是使用 ref 建立的變數,獲取值要呼叫 .value .執行 form.value.validate() 函式,就能讓 Form 表單下面的每一個子元件開始執行校驗邏輯,如果全部通過就會返回 true,存在一個沒通過返回 false .
從上面分析可知,Form 控制元件只對外暴露一個 validate 函式,通過呼叫該函式就能知道校驗是否通過.那麼 validate 如何知道該採用什麼規則來校驗呢?所以我們要先設計一套校驗的規則 rules,把它傳給 Form 元件,那麼它內部的 validate 函式就能採用規則來執行校驗.
rules設計
rules 是一個物件,例如上述註冊頁面的 rules 定義如下:
const rules = { number_number:[{ type: 'required',msg:"請輸入正確的手機號" } "phone" ],captcha:[ { type: 'required',msg: '驗證碼不能為空' } ],password: [ { type: 'required',msg: '請輸入密碼',},{ type: 'minLength',params: 6,msg: '密碼長度不能小於6位',],ppassword:[ { type: 'custome',callback() { if (form_data.password !== form_data.ppassword) { return { flag: false,msg: '兩次輸入的密碼不一致',}; } return { flag: true,}; },] }
我們定義的 rules 是一個鍵值對形式的物件. key 對應著模板上每個輸入框元件的 propName,值是一個數組,對應著該輸入框元件要遵守的規則.
現在細緻的看下每個物件下的值的構成,值之所以組織成陣列形式,是因為這樣可以給輸入框增加多條規則.而規則對應著兩種形式,一種是物件,另外一種是字串.
字串很好理解,比如上面的 number_number 屬性,它就對應著字串 phone .這條規則的意義就是該輸入框的值要遵守手機號的規則.當然字串如果填 email,那就要當做郵箱來校驗.
規則如果為物件,那麼它包含了以下幾個屬性:
{
type,// 型別
msg,//自定義的錯誤資訊
params,//傳過來的引數值 比如 {type:'minLength',pahttp://www.cppcns.comrams:6},值最小長度不能低於6位
callback //自定義校驗函式
}
type 是校驗型別,它如果填 required,表示是必填項.如果使用者沒填,點選註冊按鈕提交時就會報出 msg 定義的錯誤資訊.
另外 type 還可以填 minLength 或者 maxLength 用來限定值的長度,那到底限定為幾位呢,可以通過 params 傳遞過去.
最後 type 還可以填 custome,那麼就是讓開發者自己來定義該輸入框的校驗邏輯函式 callback .該函式要求最後返回一個帶有 flag 屬性的物件,屬性 flag 為布林值,它會告訴校驗系統本次校驗是成功還是失敗.
Form表單
rules 被定義好後傳給 Form 元件,Form 元件需要將校驗邏輯分發給它的子元件.讓其每個子元件都負責生成自己的校驗函式.
<!-- 表單元件 --> <template> <div class="form"> <slot></slot> </div> </template> <script lang="ts"> import { ref,provide } from "vue"; export default defineComponent({ name: "Form",props:{ rules:Object },setup(props) { ...//省略 provide("rules",props.rules); // 將校驗規則分發下去 const validate = ()=>{ //向外暴露的校驗函式 } return { validate } } }) </script>
從上面結構可以看出,Form 元件模板提供了一個插槽的作用,在邏輯程式碼裡利用 provide 將校驗規則傳給後代,並向外暴露一個 validate 函式.
子元件生成校驗函式
這一次又回到了登入註冊模組的核心元件 InputForm,我們現在要給該輸入框元件新增校驗邏輯.
import { inject,onMounted } from "vue"; ... setup(props,context) { const rules = inject("rules"); const rule = rules[props.propName];// 通過propName拿到校驗規則 const useValidate = () => { const validateFn = getValidate(rule); // 獲取校驗函式 const execValidate = () => { return validateFn(props.value); //執行校驗函式並返回校驗結果 }; onMounted(() => { const Listener = inject('collectValidate'); if (Listener) { Listener(execValidate); } }); }; useValidate(); //初始化校驗邏輯 ... }
rules 結構類似如下.通過 inject 和 propName 可以拿到 Form 分發給該輸入框要執行的規則 rule .
{ captcha:[{ type: 'required',msg: '驗證碼不能為空' }],password:[{ type: 'required',}] }
再將規則 rule 傳遞給 getValidate 函式(後面會講)獲取校驗函式 validateFn .校驗函式 validateFn 傳入輸入框的值就能返回校驗結果.在這裡把 validateFn 封裝了一層賦予 execValidate 給外部使用.
在上面的程式碼中我們還看到了 onMounted 包裹的邏輯程式碼.當元件掛載完畢後,使用 inject 拿到 Form 元件傳遞下來的一個函式 Listener,並將校驗函式 execValidate 作為引數傳遞進去執行.
我們再回到下面程式碼中的 Form 元件,看一下 Listener 是一個什麼樣的函式.
setup(props) { const list = ref([]);//定義一個數組 const listener = (fn) => { list.value.push(fn); }; provide("collectValidate",listener); //將監聽函式分發下去 //驗證函式 const validate = (propName) => { const array = list.value.map((fn) => { return fn(); }); const onZkGzhvNHe = array.find((item) => { return item.flag === false; }); if (one && one.msg) { //驗證不通過 Alert(one.msg);//彈出錯誤提示 return false; } else { return true; } }; ...
從上面可以看出,Form 元件將 listener 函式分發了下去.而子元件在 onMounted 的生命週期鉤子裡,獲取到分發下來的 listener 函式,並將子元件內部定義的校驗函式 execValidate 作為引數傳遞進去執行.
這樣一來就可以確保每個子元件一旦掛載完畢就會把自己的校驗函式傳遞給 Form 元件中的 list 收集.而 Form 元件的 validate 方法只需要迴圈遍歷 list,就可以依次執行每個子元件的校驗函式.如果都校驗通過了,給外部頁面返回 true .存在一個不通過,彈出錯誤提示返回 false .
走到這裡整個校驗的流程已經打通了. Form 首先向子元件分程式設計客棧發校驗規則,子元件獲取規則生成自己的校驗函式,並且在其掛載完畢後將校驗函式再返回給 Form 收集起來.這個時候 Form 元件向外暴露的 validate 函式就可以實現針對所有表單控制元件的資料校驗.
接下來最後一步研究子元件如果通過規則來生成自己的校驗函式.
校驗
首先編寫一個管理校驗邏輯的類 Validate .程式碼如下.我們可以不斷的根據新需求擴充該類的方法,比如另外再增加 email 或者 maxLength 方法.
class Validate { constructor() {} required(data) { //校驗是否為必填 const msg = '該資訊為必填項'; //預設錯誤資訊 if (data == null || (typeof data === 'string' && data.trim() === '')) { return { flag:false,msg } } return { flag:true } } //校驗是否為手機號 phone(data) { const msg = '請填寫正確的手機號碼'; //預設錯誤資訊 const flag = /^1[3456789]\d{9}$/.test(data); return { msg,flag } } //校驗資料的最小長度 minLength(data,{ params }) { let minLength = params; //最小為幾位 if (data == null) { return { flag:false,msg:"資料不能為空" } } if (data.trim().length >= minLength) { return {flag:true}; } else { return { flag:false,msg:`資料最小長度不能小於${minLength}位` } } } }
Validate 類定義的所有方法中,第一個引數 data 是被校驗的值,第二個引數是在頁面定義每條 rule 中的規則.形如 {type: 'minLength',msg: '密碼長度不能小於6位'} .
Validate 類中每個方法最終的返回的資料結構形如 {flag:true,msg:""} .結果中 flag 就來標識校驗是否通過,msg 為錯誤資訊.
校驗類 Validate 提供了各種各樣的校驗方法,接下來運用一個單例模式生成該類的一個例項,將例項物件應用到真實的校驗場景中.
const getInstance = (function(){ let _instance; return function(){ if(_instance == null){ _instance = new Validate(); } return _instance; } })()
通過呼叫 getInstance 函式就可以得到單例的 Validate 例項物件.
輸入框元件通過給 getValidate 函式傳入一條 rule,就能返回該元件需要的校驗函式.接下來看一下 getValidate 函式是如何通過 rule 來生成校驗函式的,程式碼如下:
/** * 生成校驗函式 */ export const getValidate = (rule) => { const ob = getInstance();//獲取 Validate類 例項物件 const fn_list = []; //將所有的驗證函式收集起來 //遍歷rule陣列,根據其型別獲取Validate類中的校驗方法放到fn_list中收集起來 rule.forEach((item) => { if (typeof item === 'string') { // 字串型別 fn_list.push({ fn: ob[item],}); } else if (isRuleType(item)) { // 物件型別 fn_list.push({ //如果item.type為custome自定義型別,校驗函式直接使用callback.否則從ob例項獲取 ...item,fn: item.type === 'custome' ? item.callback : ob[item.type],}); } }); //需要返回的校驗函式 const execuate = (value) => { let flag = true,msg = ''; for (let i = 0; i < fn_list.length; i++) { const item = fn_list[i]; const result = item.fn.apply(ob,[value,item]);//item.fn對應著Validate類定義的的校驗方法 if (!result.flag) { //驗證沒有通過 flag = false; msg = item.msg ? item.msg : result.msg;//是使用預設的報錯資訊還是使用者自定義資訊 break; } } return { flag,msg,}; }; return execuate; };
rule 的資料結構形類似如下程式碼.當把 rule 傳入 getValidate 函式,它會判端是物件還是字串,隨後將其型別對應的校驗函式從 ob 例項中獲取儲存到 fn_list 中.
[ { type: 'required',msg: "請輸入電話號碼" },"phone" ]
getValidate 函式最終返回 execuate 函式,此函式也正是輸入框元件得到的校驗函式.在輸入框元件裡是可以拿到輸入框值的,如果將值傳給 execuate 方法呼叫.方法內部就會遍歷之前快取的校驗函式列表 fn_list,將值傳入每個校驗方法執行就能獲取該輸入框元件對當前值的校驗結果並返回回去.
以上校驗的邏輯也已經走通了.接下來不管是開發登入頁,忘記密碼或者修改密碼的頁面,只需要使用 Form 元件和輸入框 InputForm 元件組織頁面結構,並寫一份當前頁面的 rules 校驗規則即可.剩下的所有校驗細節和互動動作全部交給了 Form 和 InputForm 內部處理,這樣會極大的提升開發效率.
最終效果
總結
到此這篇關於vue3如何優雅的實現移動端登入註冊模組的文章就介紹到這了,更多相關vue3移動端登入註冊模組內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!