乙個 React Form 元件的重構思路

2021-09-16 23:57:09 字數 4579 閱讀 2461

本文發布於 我的部落格

最近對團隊內部 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 引...