本文發布於 我的部落格最近對團隊內部 react 元件庫(ne-rc)中的 form 元件進行了重構,記錄一下思考的過程。
一些前置定義:
名詞定義
表單form 元件
子表單巢狀在 form 下面的類似 input, select 這樣的子元件
首先我們看一下,我們的對 form 元件的需求是什麼。
獲取當前變動表單的狀態
暴露對外提供整個表單狀態的方法
提交方法
接著我們從重構前和重構後,看如何來解決這個問題。
react 父子通訊需要通過 prop 傳遞方法,對於 form 下面的類似與 input 之類的子表單的變化想要通知到父級,如果不借助第三方的事件傳遞方法,那麼就只能通過由父級通過 props 向 input 傳遞formfieldchange
(假設就叫這個名字)方法,然後當子元件變化時去呼叫formfieldchange
來實現。
那麼問題來了,什麼時候去傳遞這個方法呢?
不能在具體頁面裡面使用的時候再去每條表單裡面註冊這個方法,那每個用到表單元件的時候就都需要給子表單進行這樣的事件繫結,這樣太累了。
所以一開始,我選擇通過直接遞迴的遍歷 form 下面的 children,只要發現這個 children 是我想要的表單型別,那麼就重新轉殖乙個帶有formfieldchange
的元件來替換掉原來的元件。
/**
* 獲取 form 下面每乙個表單物件,注入屬性,並收集起來
* @param children
* @returns
*/function getforms(children)
switch (el.type)
)case select:
forms.push(el)
return react.cloneelement(
el,)
case checkbox:
forms.push(el)
return react.cloneelement(
el,)
default:
if (el.props && el.props.children instanceof array)
)} else }})
}
這樣,所有的特定子元件就都可以拿到被註冊的方法。以 input 為例,在 input 的onchange
方法裡面去呼叫從父級 props 傳入的formfieldchange
就可以通知到 form 元件了。
前一步完成後,這一步就比較簡單了,input 在呼叫formfieldchange
的時候把想要傳遞的資料作為引數傳進去,在 form 裡面去對這個引數做處理,就可以拿到當前變動的表單狀態資料了。
前面我們收集了每一條變動表單的資料。但是要判斷當前 form 下面的表單是否填寫完成,那麼首先需要知道我們有多少個需要填寫的表單,然後在formfieldchange
的時候進行判斷就可以了。如何來提前知道我們有多少需要填寫的 field 呢,之前我選擇的是通過在使用 form 的時候先初始化乙個包含所有表單初始化狀態的資料。
export default class form extends react.component ,
}, this.props.formstate)
} static proptypes =
// 初始化乙個類似這樣的物件傳遞給 form
formstate: ,
cityid: {},
email: {},
relativename: {},
relativephone: {},
companyname: {}
}},
這樣就很粗暴的解決了這個問題,但是這中間存在很多問題。
因為限定了特定的元件型別(input,select,checkbox),導致不利於擴充套件,如果在開發過程遇到其他型別的比如自定義的子表單,那麼 form 就沒法對這個自定義子表單進行資料收集,解決起來比較麻煩。
所以就在考慮另乙個種實現方式, form 只去收集乙個特定條件下的元件,只要這個元件滿足了這個條件,並實現了對應的介面,那麼 form 就都可以去收集處理。這樣也就大大挺高了適用性。
通過在外監聽每次 form 觸發的onchange
事件來獲取整個 form 的狀態。
已經有了整個 form 的資料物件,做校驗並不是什麼困難。通過校驗的時候呼叫formsubmit
方法,沒有通過校驗的時候對外把錯誤資訊新增到 form 的 state 上去。
當表單通過校驗的時候,對外觸發formsubmit
方法,把要提交的資料作為formsubmit
的引數傳遞給外面。
前面是之前寫的 form 元件的一些思路,在實際使用中也基本能滿足業務需求。
但是整個 form 的可拓展性比較差,無法很好的接入其他自定義的元件。所以萌生了重寫的想法。
對於重寫的這個 form,我的想法是:首先一定要方便使用,不需要一大堆的起始工作;其次就是可拓展性要強,除了自己已經提供的內在 input,select 等能夠接入 form 外,對於其他的業務中的特殊需求需要接入 form 的時候,只要這個元件實現了特定的介面就可以了很方便的接入,而不需要大量的去修改元件內部的**。
重構主要集中在上面需求 1 裡面的內容,也就是:__獲取當前變動表單的狀態__
獲取當前表單的狀態分解下來有一下幾點:
同樣通過遞迴遍歷 children 來獲取需要收集的子表單,通過子表單的 type.name 命名規則是否符合我們的定義來決定是否要進行收集。
直接來看**:
collectformfield = (children) => )
} else )
} else }})
}
只要元件的 class name 以 _field 開頭,就把它收集起來,並傳入handlefieldchange
方法,這樣當乙個自定義元件接入的時候,只需要在外麵包一層,並把 class 的命名為以 _field 開頭的格式就可以被 form 收集管理了。
接入元件裡面需要做的就是,在合適的時機呼叫handlefieldchange
方法,並把要傳遞的資料作為引數傳遞出來就可以了。
為什麼一定要執迷不悟的使用遍歷這種低效的方式去收集呢,其實都是為了元件上使用的方便。這樣就不需要每次在引用的時候在對子表單做什麼操作了。
上一步拿到了所有的子表單,然後通過呼叫initialformdatastructure
拿來初始化 form 的state.data
的結構,同時通知到外面 form 發生了變化。
當 form 下面子元件被新增或刪除時,需要及時更新 form data 的結構。通過呼叫updateformdatastructure
把新增的或者修改的子表單更新到最新,並通知到外面 form 發生了變化。
在第一步收集子表單的時候就已經把handlefieldchange
注入到了子表單元件裡面,所以子表單來決定呼叫的時機。當handlefieldchange
被呼叫的時候,首先對 formstate
進行更新,然後外通知子表單發生了變化,同時通知外面 form 發生了變化。
這樣看起來整個流程就走通了,但實際上存在很多問題。
首先由於setstate
是乙個非同步的過程,只有在render
後才能獲取到最新的state
. 這就導致,在乙個生命週期迴圈內如果我多次呼叫了setstate
,那麼兩次呼叫之間對state
的讀取很可能是不準確的。(有關生命週期的詳細內容可以看這篇文章:
所以我建立了乙個臨時變數currentstate
來存放當前狀態下最新的state
,每次setstate
的時候都對其進行更新。
另乙個問題是當 form 發生變化的時候,updateformdatastructure
呼叫的過於頻繁。其實只有在子表單的數量或者型別發生變化時才需要更新 form state 的結構。而直接去對比子表單的型別是否發生變化也是意見開銷很大操作,所以選擇另一種折中方式。通過給 form 當前的狀態打標,將 form 可能處於的狀態都標識出來:
const status =
這樣,只有在 form 的status
處於normal
的時候才對其進行updateformdatastructure
操作。這樣就可以省去很多次渲染以及無效的對外觸發的formchange
事件。
提交和對外暴露 form 狀態的方法和之前基本一致,這樣整個對 form 的重構就算完成了,具體專案中使用體驗還不錯 o(∩_∩)o
最後,如果看文章的你有什麼更好的想法,請告訴我?。
react Form元件內值的回選
1.select元件 下拉框值的回顯,一般通過initialvalue屬性賦值,如果與option的value能匹配上,就會自動回顯展示的內容 如果是非同步載入下拉選項,則需要先載入完ajax後進行賦值 匹配 回選 注意 通常專案中使用下拉的場景一般是value用作儲存id,顯示的內容為name,要...
Vue 乙個元件引用另乙個元件
有些時候需要這麼做,比如,我想在首頁載入輪播元件,但是又不想全域性註冊 因為不是每個頁面都需要輪播功能 方法1 1 template 2 div 34 testcomponent testcomponent 5div 6template 78 script 9 1.先使用import匯入你要在該元件...
乙個vue calendar的npm元件
1.基於element ui開發的vue日曆元件。更新 1.增加日期多選 selectionmode dates 事件select返回選擇日期及節點 2.增加語言切換 lang en 3.抽離css方便自定義樣式 import ele calendar dist vue calendar.css 引...