antd組件使用進階及踩過的坑

更多我對Antd的使用及思考,請參考:antd-doddlecss

扯點犢子

一晃眼,兩個月過去了,本身從一家不大不小的屌絲公司跳到一家被具備純正互聯網血液的公司。從之前的圍繞jQuery、Echarts爲主技術棧開展工做,到如今以React、Antd爲主技術棧開發業務;但不是全部的業務antd都能支持,因此有時得本身動手,在antd上作一層淺封裝。
文章中提到的示例均可以在codeBox找到:codeBoxhtml

自定義表單組件

Antd的Form表單介紹一節中,提到過自定義表單控件。其實例是關於貨幣價值轉換的,以下圖所示:
image
當咱們在咱們的頁面中須要頻繁的用到某一個組合類型的組件,而Antd又不支持時,最好的作法就是對Antd組件作一層淺封裝行成一個獨立的組件,固然也可使用html 自有的表單元素進行封裝,只是這樣作出來費事,且樣式和整個頁面沒有那麼容易統一。封裝的注意事項在上面的截圖中已經一一列出,接下來將以一個實例來操做說明。前端

一個帶遠程搜索的下拉選擇組件

2018.12月更新:隨着Select組件comobox模式在新的版本中被捨棄,和autoComplete組件的出現。這個組件也進行了重寫。但總體邏輯沒有改變,主要時改變了激活彈出框和關閉彈出框的邏輯。具體可參見個人github項目:React進階
seri
這個組件的大體實現需求如上面動態圖所示。產品需求就是須要一個編輯框,這個框在用戶點擊輸入時,須要彈出一個搜索框,根據用戶的輸入遠程搜索獲取數據造成一個下拉列表,供用戶選擇。這在jquery時代,這是一個很常見的需求,也有不少的組件可選擇,但在Antd的組件庫中,沒有徹底匹配的,但有及其類似功能的,好比:jquery

image

這個組件與產品的需求契合度已經達到了80%, 可是產品說了搜索輸入框須要與編輯輸入框分開,而且有明顯的區別,ok,那就費點事,把Antd組件稍微作一下改變嘛。
image
因此簡單分解一下,須要用到Input,Icon, Select這三種組件,具體實現能夠查看SandBox上的源碼及示例。說一下本身遇到的難點:git

支持雙向綁定

<FormItem type="inline" label="員工姓名">
  {getFieldDecorator('search',{
    initialValue: { name: 'Dom' }
  })(
    <OriginSearch {...modalProps} />
  )}
</FormItem>

在Antd的Form表單組件中,若是須要作數據雙向綁定,就須要用到其提供的getFieldDecorator方法來裝飾組件,而咱們本身封裝的組件要支持這個特性的話,如最開始提到的,咱們須要使用onChange方法來觸發裝飾器值的同步。 github

在咱們爲一個組件添加了裝飾器後,能夠查看props明顯發現多了id,onChange, value三個屬性,value屬性是用於獲取咱們在initialValue設定的值的,而onChange方法是用於同步值,實現雙向綁定。因此在咱們這個組件中,當用戶從下拉框中選擇一個選項後,咱們須要調用onChange方法去同步值,代碼以下所示:瀏覽器

handleSelect(value, option) {
  const { handleSelect } = this.props;
  const { seachRes } = this.state;
  // 初始化基礎信息
  const selectValue = handleSelect(value, option, seachRes) || value;
  this.triggerChange(selectValue);
  this.setState({ value: selectValue });
  this.handleCloseSearch();
}
triggerChange(value) {
  const { onChange } = this.props;
  // 調用裝飾器方法去同步選中後的值
  onChange && onChange(value);
}

點擊組件之外的地方收起組件

這看似是個很容易實現的需求,但由於Antd全部的彈框組件都用了同一套方法,其彈框Dom樹並非掛載在Select輸入框的父節點上,而是直接掛載在Body節點上,因此想用冒泡的機制來實現就不可能了。因此就和投機用了點擊事件的節點名稱來判斷,看具體實現:antd

componentDidUpdate(prevProps, prevState) {
  const { isShowSearch } = this.state;
  const bodyNode = document.querySelector("body");
  if (isShowSearch !== prevState.isShowSearch) {
    // 狀態切換的時候才改變狀態
    if (isShowSearch) {
      document
        .querySelector(".js-origin-search .ant-select-search__field")
        .focus();
      bodyNode.addEventListener("click", this.handleChangeVisible);
    } else {
      bodyNode.removeEventListener("click", this.handleChangeVisible);
    }
  }
}
handleChangeVisible(event) {
  const { isShowSearch } = this.state;
  event = event || window.event;
  const elem = event.target;
  let inComponentClick = false;
  // 當搜索框框被打開時,點擊空白處搜索框收起;因爲antd的下拉列表是掛載在body下,而非搜索框節點下的某一子節點,因此
  // 沒法採用阻止冒泡的方式來避免body下的click事件被響應,因此只有靠判斷被點擊的節點類,來判斷body的click事件是否響應
  if (
    (this.searchInputElement && this.searchInputElement.contains(elem)) ||
    elem.className.indexOf("ant-select-dropdown") !== -1
  ) {
    inComponentClick = true;
  }
  // 當點擊事件爲非下拉列表選中事件,切搜索框爲展開時,觸發搜索框收起方法;
  !inComponentClick && isShowSearch && this.handleCloseSearch();
}

雖然這只是一次很簡單的封裝,但其包含的知識點仍是很是多的。本身還封裝過日期多選,日期選擇增長至今,地址地區聯合選擇器這種,從實現上其實都是一個思路,在這一個SandBox項目中都能看到。app

組件奇特使用方式(持續更新)

動態更新表單組件Required參數,但驗證沒有同步

加入如今有這樣一個需求,用戶須要選擇本身的性別(男,女,其餘),當用戶選擇其餘時,下面說明項由非必填變爲必填。這看似是一個很簡單的需求,在用戶選擇其餘時,將isRequired變量變爲true就好了,看起來好像,大概,貌似成功了。可是本身在antd組件的應用上,發現,當你把isRequired置爲true,label標籤前面會加上一個*,但這只是一個假象,當你填上數據再刪除時,antd組件這時並不會自動驗證,並觸發提示詞顯示。可是,這些都沒有。所謂的isRequired置爲true,並無達到真正想要的效果,測試代碼以下,我猜想antd的這個機制和他的initValue更新機制類似,只有在組件初始化的時候會設置一次初始值,後面都是組件內部state參數進行狀態切換,原覺得用resetFields能夠解決,最後發現不能。可是巧的方法沒有,不表明笨辦法也沒有。dom

<FormItem
  label="性別"
  labelCol={{ span: 5 }}
  wrapperCol={{ span: 12 }}
>
  {getFieldDecorator('gender', {
    rules: [{ required: true, message: 'Please select your gender!' }],
  })(
    <Select
      placeholder="Select a option and change input text above"
      onChange={this.handleSelectChange}
    >
      <Option value="male">male</Option>
      <Option value="female">female</Option>
    </Select>
  )}
</FormItem>
<FormItem
  label="說明"
  labelCol={{ span: 5 }}
  wrapperCol={{ span: 12 }}
>
  {getFieldDecorator('note', {
    rules: [{ required: isRequired, message: 'Please input your note!' }],
  })(
    <Input />
  )}
</FormItem>

最後想出的最好的解決辦法就是動態銷燬從新掛載這個組件,咱們能夠經過動態設定key值,來保證狀態的同步。其實這是從新渲染了一個新的組件來替換。

<FormItem
  label="說明"
  key={isRequired}
  labelCol={{ span: 5 }}
  wrapperCol={{ span: 12 }}
>

2019.01.06日更:
上面的問題,在本身使用3.9的版本上沒有再出現了,可是好像又出現了一個更大的表單動態校驗問題。看下圖,需求和上面差很少:
2303313742-5c317658312cd_articlex
當選擇啓用時,緣由必填。其餘時,變爲非必填。如今出現的狀況時,必填時觸發錯誤提醒,但將狀態置爲禁用時,label的必填屬性雖然被重置了,但錯誤提醒仍然存在,有些奇怪。
基於上面的現象,我去基友社區搜了一下Issuse,果真時存在的:form動態校驗問題,幸運的時,antd大佬也給出瞭解決方案:使用form.validateFields([key], { force: true })來解決

實踐後的幡然醒悟

用了兩個多月,其實Antd本身自己沒啥坑,只是因爲咱們組如今使用的版本是2.9,但本身習慣於看3.x的版本文檔,因此屢次在一個地方徘徊很久,總覺得是本身代碼實現有問題,實際上是2.9版本尚未實現。

總結一

在Select組件上,2.9與3.x就有較大的差別:

  1. Select 的option必須帶有不一樣的Key值,且value值也不能有相同的,好比在遠程搜索加載員工列表時,就會出現同名的狀況,因此這時的value就不能只用名字,得用value加工號或則其餘值來代替。
  2. Select 的 onchange事件在3.x版本之前回調函數只有value值,沒有option回調參數。
  3. Select 的notFundContent屬性可配置結合Spin實現加載動畫,但在版本3如下,該配置對於comobox模式無效(其文檔未對這個特性(Bug)作說明)。。。
  4. Select 的 onSelect事件在3.x之後也有較大改動,其option參數包含的內容做了很大調整,在2.9版本還能夠經過option.props.index獲取選擇的索引,在3.x版本只能間接經過設置key爲index,而後經過獲取key值來獲取index;
  5. Select 組件渲染出來的下拉列表是沒有掛載在Select組件父節點上的,其是採用絕對定位,掛載在body節點上的。。。全部用父節點作篩選是沒法獲取的。

總結二

另外在表單組件自校驗validator的使用上,有一個隱藏的少有人知的使用方法是:

<FormItem {...formItemLayout} label="確認密碼">
    {
        getFieldDecorator('confirmPassword', {
            rules: [{
                    required: true,
                    message: '請再次輸入以確認新密碼',
                }, {
                    validator: this.handleConfirmPassword
                }],
        })(<Input type="password" />)
    }
</FormItem> 
handleConfirmPassword(rule, value, callback) {
    if (value && value.length < 5 ) {
        callback('長度不足');
        return;
    }
    // Note: 必須老是返回一個 callback,不然 validateFieldsAndScroll 沒法響應
    callback()
}

總結三

當咱們使用getFieldDecorator並用initialValue設定初始值時,當咱們改變組件的值時,組件表現出的值也改變了,但這個值並非initialValue設定的,其是組件內部的state值保持的,若是須要繼續用initialValue來設定組件的值,咱們須要調用resetFields方法使initialValue有效;

總結四:Table設置width無效

Antd組件我的以爲最好用的功能就是Table,其配合pagination能夠直接實現前端分頁,在有些使用場景能夠大大提升使用體驗。可是Table也有坑(其實也是css一個隱形知識點),就是有時你會發現你爲一列設置了width,可是並無鳥用。

{
  key: 'userId',
  name: '用戶ID',
  value: 'asddsddsfsfsdfsdfsdfsfsfdsfsfsfsfsfddefgervwerbvw'
  width: 80
}

就像上面的這種數據,設置了80的寬度,但最後撐開差很少是300。最後的最後,記起了又一個css屬性叫 word-break ,來歷就是在瀏覽器中,純數字或者純字母的字符串,他的顯示默認是不換行的,就算他已經超出了這一行的邊際,就是這麼叼的一個屬性。因此Table也受這個影響,因爲我要展現的內容中是純字母,縱然我設置了width,依然沒什麼鳥用。須要設置與td相關聯的table樣式,加上word-break:break-all這樣的解藥。

總結四:Select(下拉彈框類)組件頁面滾動時,下拉內容與彈出父組件分離

image
如上圖所示,外層頁面一滾動,下拉框與下拉內容就分離了,分離,離,離了。這個出現原就是由於ANTD全部的彈框都是默認掛載在body下面,而後絕對定位的。因此滾動body內容, 就會形成與彈出觸發的那個組件錯位。幸虧在3.0之後ANTD對這個bug作出了一個解決方案,就是增長了getPopupContainer屬性,可讓下拉內容掛載任何存在的dom節點上,並相對其定位。具體用例請查看官方示例

文章首發於: http://closertb.site

相關文章
相關標籤/搜索