- 被人詬病的Form
- Form的原理
- Vue版Form的進化史
本文適合React、Vue開發者閱讀,10分鐘不夠?那就再加10分鐘。
被人詬病的Form
antd被人吐槽最多的除了彩蛋之外,那應該就是Form表單了。如果需要使用Form自帶的收集校驗功能,需要使用Form.create()包裝元件,每一個需要收集的值還需要getFieldDecorator進行註冊。官方文件大量的讓人眼花繚亂的API,大概率沒有多少人讀完了整個文件,即便讀完了,大概率也記不住。
寫這篇文章不是為了吐槽Form表單,當然我也並沒有更好的優化Form表單的方案,本文的目的是希望大家能夠通過本文了解Form表單的本質,更好的使用的Form表單。
Form的原理
網路上有一些原始碼分析的文章,個人覺得收益比不高,逐條過api式的講解甚是無趣。一句廢話概括原理:Form.create建立一個具有註冊、收集、校驗功能的”例項”。
我們把這句話分成幾個關鍵詞逐一分析:Form.create建立例項、註冊、收集、校驗 四個關鍵詞
Form.create建立”例項”:
例項?為什麼不是元件。Form.create的核心能力是建立例項this.props.form,並不是建立元件。這個例項提供一系列的方法,如註冊、收集、校驗
那為什麼要包裝元件呢?包裝元件的目的是為了更新元件,僅此而已。
你應該知道所有需要該例項幫助你進行收集校驗的元件,必須要通過getFieldDecorator進行修飾,一旦經過getFieldDecorator的修飾,那麼該元件的值將完全由該例項管理。元件的更新需要元件所在上下文處執行render,我們知道元件的更新有兩種方式:1. 父元件更新了 2. 自身狀態改變了
所以進一步講,包裝元件的目的就是為了被包裝元件的父元件更新,一旦被getFieldDecorator修飾過的元件觸發onChange事件,便會觸發這個父元件的的更新(forceUpdate),從而促使被包裝元件的render。如:Form.create()(A) A就是我們所說的被包裝元件
註冊(getFieldDecorator):
getFieldDecorator的目的是為了把需要收集的資料在例項中進行註冊,並把註冊的值同步到被getFieldDecorator修飾的元件B上。所以元件B不能夠在通過value賦值,元件B的狀態將全部由getFieldDecorator託管。
收集、校驗
收集校驗就更簡單了,你可以認為收集校驗就是這個例項提供的幾個方法而已。
丟棄Form.create
如果Form.create的核心能力是建立”例項”,是不是意味著可以不用Form.create包裹元件呢?答:是的,如果把更新元件的副能力解決掉。恰好Ant Design Vue就是這麼去做的。
Vue版Form的進化史
起初我們使用了和React版一致的寫法,必須使用Form.create包裹元件,但vue推崇的template語法很難再去使用,你不得不去在Vue中使用JSX,遭到了使用者的各種吐槽。然後我們進行了改版,將Form.create放在了Form中去執行,通過回撥的方式將Form.create建立的示例傳遞回來:
<
a-form :autoFormCreate="form =>
this.form = form">
...<
/a-form>
複製程式碼
註冊通過a-form-item新增對應屬性來劫持子元素進行註冊。
<
a-form-item fieldDecoratorId="name" :fieldDecoratorOptions="{
rules: [{
required: true, message: 'xxxx !'
}]
}">
<
a-input/>
<
/a-form-item>
複製程式碼
這樣一種設計他有很大的問題:
- form不能及時拿到,我們應該在元件render之前拿到form例項
- 通過a-form-item劫持子元素有很大的限制,每一個a-form-item下只能註冊一個,當然這個問題不大,我們可以在提供一個a-form-control專門用來註冊元件,O__O “…巢狀好深。
最終方案:
例項:
既然Form.create的主要能力是建立”例項”,我們可以暫時拋開元件,先解決構建例項的問題,
createForm(options = {
}) {
return new Vue(Form.create(options));
}複製程式碼
我們在元件上提供一個靜態方法createForm
來建立這個示例,那麼有了這個和元件沒有任何關係的方法,就可以隨時建立”例項”,同一個元件中也可以同時擁有多個”例項”。核心能力有了,但沒有副能力也是不行的,就像沒有了四肢的大腦,有心無力。前面講了,元件的更新需要元件所在上下文處執行render,那麼問題就簡單了,我們只需要把當前元件的上下文傳遞給這個”例項”,當註冊到例項的元件需要更新時,直接呼叫context.$forceUpdate()
即可。程式碼如下:
createForm(context, options = {
}) {
return new Vue(Form.create({
...options, templateContext: context
})());
}複製程式碼
註冊:
直接新增一個元件a-form-control專門用來劫持元件並註冊是一個不錯的選擇,但是我不想讓元件巢狀太深,所以我們還是使用a-form-item進行劫持元件,為了能夠區分需要劫持的哪些元件,我們使用指令進行標記並傳值,之所以使用指令是因為我們不應該為一個需要註冊的元件傳遞一個不相關的屬性,如果傳遞一個未經宣告的屬性,則該屬性會被掛載到dom上,如果要宣告屬性,就必須對自定義表單控制元件新增額外約束。而使用指令進行標記和傳值不會存在這類問題。
<
a-input v-decorator="[ 'note', {rules: [{
required: true, message: 'Please input your note!'
}]
} ]"/>
複製程式碼
校驗收集和React版沒有區別,都只是”例項”的方法。
為什麼不支援雙向繫結
嚴格來說並不是完全不支援,如果你不需要Form的自動收集、校驗功能,是可以使用雙向繫結的。雙向繫結在某些業務場景下的確可以節省很多程式碼,但對於某些情況下又給我們帶來了不必要的麻煩。舉一個很簡單也很常見的栗子:在系統中同一份資料被多處元件(包含可編輯的Form)使用是常有的事情,我們在表單中改變這份資料,同時資料的改變同步到各個相關元件中,非常easy的完成了需求。但很多時候我們希望表單資料改變後並不需要及時的同步到其它元件中,而是當使用者點選確定按鈕後才將資料同步,我們就不得不將這份資料進行復制甚至是深複製來滿足需求,甚是蛋疼。
而如果使用ant-design-vue單項資料流的方式,資料之間的流向就變得非常清晰,表單就像一個獨立的沙盒,不管沙盒中的資料如何變化,都不會影響到沙盒的外部,而沙盒通過相關API方法和外部進行互動。
最後,10分鐘精(wo)通(shi)不(biao)存(ti)在(dang)的,但希望大家能夠通過本文對antd的Form有一個進一步的認知,Form依然還有很多的功能需要大家自己去探索,在這就不一一展開了,我想也沒有必要展開。如果大家有更好的方案也歡迎提issue提pr,一起探討,將ant-design-vue打造成世界第二好用的Vue UI元件庫。誰是第一好用的?你問我?那當然也是ant-design-vue,且不接受任何異議,就是那麼自信,那麼臭不要臉。
最後的最後,給團隊微信公眾號打個廣告,微信搜尋“一點大資料技術團隊”關注公眾號,你沒看錯,就是大資料,如果你對大資料感興趣,歡迎關注該公眾號,我們每月會從團隊內部篩選出兩篇左右的高質量原創文章。