成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

一個 React Form 組件的重構(gòu)思路

Betta / 1625人閱讀

摘要:本文發(fā)布于我的博客最近對團(tuán)隊(duì)內(nèi)部組件庫中的組件進(jìn)行了重構(gòu),記錄一下思考的過程。暴露對外提供整個表單狀態(tài)的方法通過在外監(jiān)聽每次觸發(fā)的事件來獲取整個的狀態(tài)。子表單數(shù)量或類型發(fā)生變化時當(dāng)下面子組件被添加或刪除時,需要及時更新的結(jié)構(gòu)。

本文發(fā)布于 我的博客

最近對團(tuán)隊(duì)內(nèi)部 React 組件庫(ne-rc)中的 Form 組件進(jìn)行了重構(gòu),記錄一下思考的過程。

一些前置定義:

名詞 定義
表單 Form 組件
子表單 嵌套在 Form 下面的類似 Input, Select 這樣的子組件

首先我們看一下,我們的對 Form 組件的需求是什么。

獲取當(dāng)前變動表單的狀態(tài)

校驗(yàn)所有必填表單是否填寫完成

對外觸發(fā)具體表單變化的方法 formFieldChange

暴露對外提供整個表單狀態(tài)的方法

提供整個表單最新狀態(tài)的方法 $Form.data

提交方法

校驗(yàn)表單是否通過校驗(yàn)

對外觸發(fā) formSubmit 方法

接著我們從重構(gòu)前和重構(gòu)后,看如何來解決這個問題。

Before 獲取當(dāng)前變動表單的狀態(tài) 如何獲取變動的子表單

React 父子通信需要通過 prop 傳遞方法,對于 Form 下面的類似與 Input 之類的子表單的變化想要通知到父級,如果不借助第三方的事件傳遞方法,那么就只能通過由父級通過 props 向 Input 傳遞 formFieldChange(假設(shè)就叫這個名字)方法,然后當(dāng)子組件變化時去調(diào)用 formFieldChange 來實(shí)現(xiàn)。

那么問題來了,什么時候去傳遞這個方法呢?

不能在具體頁面里面使用的時候再去每條表單里面注冊這個方法,那每個用到表單組件的時候就都需要給子表單進(jìn)行這樣的事件綁定,這樣太累了。

所以一開始,我選擇通過直接遞歸的遍歷 Form 下面的 children,只要發(fā)現(xiàn)這個 children 是我想要的表單類型,那么就重新克隆一個帶有 formFieldChange 的組件來替換掉原來的組件。

/**
  * 獲取 form 下面每一個表單對象,注入屬性,并收集起來
  * @param children
  * @returns {*}
  */
function getForms(children) {
  return React.Children.map(children, (el, i) => {
    if (!el) {
      return null
    }
    switch (el.type) {
      case Input:
        Forms.push(el)
        return React.cloneElement(
          el,
          {
            key: i,
            formFieldChange,
            emptyInput
          }
        )
      case Select:
        Forms.push(el)
        return React.cloneElement(
          el,
          {
            key: i,
            formFieldChange
          }
        )
      case CheckBox:
        Forms.push(el)
        return React.cloneElement(
          el,
          {
            key: i,
            formFieldChange
          }
        )
      default:
        if (el.props && el.props.children instanceof Array) {
          const children = getForms(el.props.children)
          return React.cloneElement(
            el,
            {
              key: i,
              children
            }
          )
        } else {
          return el
        }
    }
  })
}

這樣,所有的特定子組件就都可以拿到被注冊的方法。以 Input 為例,在 Input 的 onChange 方法里面去調(diào)用從父級 props 傳入的 formFieldChange 就可以通知到 Form 組件了。

收集變動表單的數(shù)據(jù)。

前一步完成后,這一步就比較簡單了,Input 在調(diào)用 formFieldChange 的時候把想要傳遞的數(shù)據(jù)作為參數(shù)傳進(jìn)去,在 Form 里面去對這個參數(shù)做處理,就可以拿到當(dāng)前變動的表單狀態(tài)數(shù)據(jù)了。

校驗(yàn)表單是否填寫完成

前面我們收集了每一條變動表單的數(shù)據(jù)。但是要判斷當(dāng)前 Form 下面的表單是否填寫完成,那么首先需要知道我們有多少個需要填寫的表單,然后在 formFieldChange 的時候進(jìn)行判斷就可以了。如何來提前知道我們有多少需要填寫的 Field 呢,之前我選擇的是通過在使用 Form 的時候先初始化一個包含所有表單初始化狀態(tài)的數(shù)據(jù)。

export default class Form extends React.Component {
  constructor(props) {
    super(props)
    this.Forms = []
    this.formState = Object.assign({}, {
      isComplete: false,
      isValidate: false,
      errorMsg: "",
      data: {}
    }, this.props.formState)
  }

  static propTypes = {
    onChange: PropTypes.func,
    onSubmit: PropTypes.func,
    formState: PropTypes.object
  }
?// 初始化一個類似這樣的對象傳遞給 Form
formState: {
  data: {
    realName: {},
    cityId: {},
    email: {},
    relativeName: {},
    relativePhone: {},
    companyName: {}
  }
},

這樣就很粗暴的解決了這個問題,但是這中間存在很多問題。

因?yàn)橄薅颂囟ǖ慕M件類型(Input,Select,CheckBox),導(dǎo)致不利于擴(kuò)展,如果在開發(fā)過程遇到其他類型的比如自定義的子表單,那么 Form 就沒法對這個自定義子表單進(jìn)行數(shù)據(jù)收集,解決起來比較麻煩。

所以就在考慮另一個種實(shí)現(xiàn)方式, Form 只去收集一個特定條件下的組件,只要這個組件滿足了這個條件,并實(shí)現(xiàn)了對應(yīng)的接口,那么 Form 就都可以去收集處理。這樣也就大大挺高了適用性。

暴露對外提供整個表單狀態(tài)的方法

通過在外監(jiān)聽每次 Form 觸發(fā)的 onChange 事件來獲取整個 Form 的狀態(tài)。

提交方法 檢驗(yàn)表單是否通過校驗(yàn)

已經(jīng)有了整個 Form 的數(shù)據(jù)對象,做校驗(yàn)并不是什么困難。通過校驗(yàn)的時候調(diào)用 formSubmit 方法,沒有通過校驗(yàn)的時候?qū)ν獍彦e誤信息添加到 Form 的 state 上去。

對外觸發(fā) formSubmit 方法

當(dāng)表單通過校驗(yàn)的時候,對外觸發(fā) formSubmit 方法,把要提交的數(shù)據(jù)作為 formSubmit 的參數(shù)傳遞給外面。

After

前面是之前寫的 Form 組件的一些思路,在實(shí)際使用中也基本能滿足業(yè)務(wù)需求。

但是整個 Form 的可拓展性比較差,無法很好的接入其他自定義的組件。所以萌生了重寫的想法。

對于重寫的這個 Form,我的想法是:首先一定要方便使用,不需要一大堆的起始工作;其次就是可拓展性要強(qiáng),除了自己已經(jīng)提供的內(nèi)在 Input,Select 等能夠接入 Form 外,對于其他的業(yè)務(wù)中的特殊需求需要接入 Form 的時候,只要這個組件實(shí)現(xiàn)了特定的接口就可以了很方便的接入,而不需要大量的去修改組件內(nèi)部的代碼。

重構(gòu)主要集中在上面需求 1 里面的內(nèi)容,也就是:__獲取當(dāng)前變動表單的狀態(tài)__

獲取當(dāng)前表單的狀態(tài)分解下來有一下幾點(diǎn):

獲取所有需要收集的子表單 formFields

初始化 Form state

表單下面子表單數(shù)量或類型發(fā)生變化時更新 1 里面創(chuàng)建的 formFields

子表單內(nèi)部狀態(tài)發(fā)生變化時通知到父表單

獲取當(dāng)前變動表單的狀態(tài) 獲取所有需要的子表單

同樣通過遞歸遍歷 children 來獲取需要收集的子表單,通過子表單的 type.name 命名規(guī)則是否符合我們的定義來決定是否要進(jìn)行收集。
直接來看代碼:

collectFormField = (children) => {

  const handleFieldChange = this.handleFieldChange

  // 簡單粗暴,在 Form 更新的時候直接清空上一次保存的 formFields,全量更新,
  // 避免 formFields 內(nèi)容或者數(shù)量發(fā)生變化時 this.formFields 數(shù)據(jù)不正確的問題
  const FormFields = this.formFields = []

  function getChildList(children) {
    return React.Children.map(children, (el, i) => {
      // 只要 Name 以 _Field 開頭,就認(rèn)為是需要 From 管理的組件
      if (!el || el === null) return null

      const reg = /^_Field/
      const childName = el.type && el.type.name
      if (reg.test(childName)) {
        FormFields.push(el)
        return React.cloneElement(el, {
          key: i,
          handleFieldChange
        })
      } else {
        if (el.props && el.props.children) {
          const children = getChildList(el.props.children)
          return React.cloneElement(el, {
            key: i,
            children
          })
        } else {
          return el
        }
      }
    })
  }

只要組件的 class name 以 _Field 開頭,就把它收集起來,并傳入 handleFieldChange 方法,這樣當(dāng)一個自定義組件接入的時候,只需要在外面包一層,并把 class 的命名為以 _Field 開頭的格式就可以被 Form 收集管理了。

接入組件里面需要做的就是,在合適的時機(jī)調(diào)用 handleFieldChange 方法,并把要傳遞的數(shù)據(jù)作為參數(shù)傳遞出來就可以了。

為什么一定要執(zhí)迷不悟的使用遍歷這種低效的方式去收集呢,其實(shí)都是為了組件上使用的方便。這樣就不需要每次在引用的時候在對子表單做什么操作了。

初始化 Form state

上一步拿到了所有的子表單,然后通過調(diào)用 initialFormDataStructure 拿來初始化 Form 的 state.data 的結(jié)構(gòu),同時通知到外面 Form 發(fā)生了變化。

子表單數(shù)量或類型發(fā)生變化時

當(dāng) Form 下面子組件被添加或刪除時,需要及時更新 Form Data 的結(jié)構(gòu)。通過調(diào)用 updateFormDataStructure
把新增的或者修改的子表單更新到最新,并通知到外面 Form 發(fā)生了變化。

子表單內(nèi)部狀態(tài)發(fā)生變化時

在第一步收集子表單的時候就已經(jīng)把 handleFieldChange 注入到了子表單組件里面,所以子表單來決定調(diào)用的時機(jī)。當(dāng) handleFieldChange 被調(diào)用的時候,首先對 Form state 進(jìn)行更新,然后外通知子表單發(fā)生了變化,同時通知外面 Form 發(fā)生了變化。

這樣看起來整個流程就走通了,但實(shí)際上存在很多問題。

首先由于 setState 是一個異步的過程,只有在 render 后才能獲取到最新的 state. 這就導(dǎo)致,在一個生命周期循環(huán)內(nèi)如果我多次調(diào)用了 setState ,那么兩次調(diào)用之間對 state 的讀取很可能是不準(zhǔn)確的。(有關(guān)生命周期的詳細(xì)內(nèi)容可以看這篇文章:https://www.w3ctech.com/topic...)

所以我創(chuàng)建了一個臨時變量 currentState 來存放當(dāng)前狀態(tài)下最新的 state,每次 setState 的時候都對其進(jìn)行更新。

另一個問題是當(dāng) Form 發(fā)生變化的時候,updateFormDataStructure 調(diào)用的過于頻繁。其實(shí)只有在子表單的數(shù)量或者類型發(fā)生變化時才需要更新 Form state 的結(jié)構(gòu)。而直接去對比子表單的類型是否發(fā)生變化也是意見開銷很大操作,所以選擇另一種折中方式。通過給 Form 當(dāng)前的狀態(tài)打標(biāo),將 Form 可能處于的狀態(tài)都標(biāo)識出來:

const?STATUS?=?{
??Init:?"Init",
??Normal:?"Normal",
??FieldChange:?"FieldChange",
??UpdateFormDataStructure:?"UpdateFormDataStructure",
??Submit:?"Submit"
}

這樣,只有在 Form 的 STATUS 處于 Normal 的時候才對其進(jìn)行 updateFormDataStructure 操作。這樣就可以省去很多次渲染以及無效的對外觸發(fā)的 FormChange 事件。

提交和對外暴露 Form 狀態(tài)的方法和之前基本一致,這樣整個對 Form 的重構(gòu)就算完成了,具體項(xiàng)目中使用體驗(yàn)還不錯 O(∩_∩)O

Form 組件地址: https://github.com/NE-LOAN-FED/NE-Component/tree/master/src/Form

最后,如果看文章的你有什么更好的想法,請告訴我?。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/82085.html

相關(guān)文章

  • Ant Design Pro - 實(shí)踐React Hooks - 頁面

    摘要:背景目前是社區(qū)最炙手可熱的新技術(shù),我們準(zhǔn)備追一下熱度,在當(dāng)前的項(xiàng)目中實(shí)踐一下技術(shù)。我們的項(xiàng)目使用的腳手架是,初步想法是把現(xiàn)有的一個有狀態(tài)頁面組件重構(gòu)成函數(shù)組件。存放表單值的狀態(tài)是聲明在列表組件,傳給表單組件。 背景 React Hooks目前是React社區(qū)最炙手可熱的新技術(shù),我們準(zhǔn)備追一下熱度,在當(dāng)前的項(xiàng)目中實(shí)踐一下Hooks技術(shù)。 我們的項(xiàng)目使用的腳手架是Ant Design P...

    wangbjun 評論0 收藏0
  • 更快助你弄懂React-高階組件

    摘要:等價于是一個返回函數(shù)的函數(shù)就是個高階函數(shù)返回的函數(shù)就是一個高階組件,該高階組件返回一個與關(guān)聯(lián)起來的新組件的也是一樣的總結(jié)一下高階組件是對代碼進(jìn)行更高層次重構(gòu)的好方法,如果你想精簡你的和生命周期方法,那么高階組件可以幫助你提取出可重用的函數(shù)。 談到react,我們第一個想到的應(yīng)該是組件,在react的眼中可真的是萬物皆組件。就連我們獲取數(shù)據(jù)用到的axios也可以用組件來表示...比如,我...

    qylost 評論0 收藏0
  • 更快助你弄懂React-高階組件

    摘要:等價于是一個返回函數(shù)的函數(shù)就是個高階函數(shù)返回的函數(shù)就是一個高階組件,該高階組件返回一個與關(guān)聯(lián)起來的新組件的也是一樣的總結(jié)一下高階組件是對代碼進(jìn)行更高層次重構(gòu)的好方法,如果你想精簡你的和生命周期方法,那么高階組件可以幫助你提取出可重用的函數(shù)。 談到react,我們第一個想到的應(yīng)該是組件,在react的眼中可真的是萬物皆組件。就連我們獲取數(shù)據(jù)用到的axios也可以用組件來表示...比如,我...

    libxd 評論0 收藏0
  • 更快助你弄懂React-高階組件

    摘要:等價于是一個返回函數(shù)的函數(shù)就是個高階函數(shù)返回的函數(shù)就是一個高階組件,該高階組件返回一個與關(guān)聯(lián)起來的新組件的也是一樣的總結(jié)一下高階組件是對代碼進(jìn)行更高層次重構(gòu)的好方法,如果你想精簡你的和生命周期方法,那么高階組件可以幫助你提取出可重用的函數(shù)。 談到react,我們第一個想到的應(yīng)該是組件,在react的眼中可真的是萬物皆組件。就連我們獲取數(shù)據(jù)用到的axios也可以用組件來表示...比如,我...

    dreamGong 評論0 收藏0
  • 更快助你弄懂React-高階組件

    摘要:等價于是一個返回函數(shù)的函數(shù)就是個高階函數(shù)返回的函數(shù)就是一個高階組件,該高階組件返回一個與關(guān)聯(lián)起來的新組件的也是一樣的總結(jié)一下高階組件是對代碼進(jìn)行更高層次重構(gòu)的好方法,如果你想精簡你的和生命周期方法,那么高階組件可以幫助你提取出可重用的函數(shù)。 談到react,我們第一個想到的應(yīng)該是組件,在react的眼中可真的是萬物皆組件。就連我們獲取數(shù)據(jù)用到的axios也可以用組件來表示...比如,我...

    xushaojieaaa 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<