首頁 > 軟體

Ant Design 的Bug修復範例詳解

2022-10-30 14:01:19

引言

我在工作中大量使用Ant Design,它為我省去了很多重複勞動,所以有空的時候,我也會為Ant Design做一些微小的貢獻。

本文詳細描述了我處理一個Ant Design的Bug全過程,文章內容較多但難度並不高,新手同學看完也可以嘗試為Ant Design添磚加瓦!

第一步、選擇要修復的Bug

Ant Design有不少Bug,你可以直接存取它的Issues,挑一個簡單的問題嘗試解決,需要注意的是,並不是所以Issue都是Bug,經過管理員確認過的Bug一般會打上Bug的標記。

這次我處理的問題是#37165

第二步、準備工作

Fork ant-design倉庫到自己的Github,回到自己的Github,將剛剛Fork的倉庫克隆到本地,然後基於master分支新建一個bugfix分支,接著安裝依賴,啟動專案,然後就可以在開發環境存取Ant Design官網上所有的Demo,方便偵錯。

npm install && npm run start

第三步、問題排查

回到問題#37165,它的表現是:一個DatePicker,一個Input, 使用者用滑鼠選中Input的內容進行復制時經常會滑到DatePicker,會觸發日曆控制元件輸入框的focus事件。

這種操作是比較常見的,問題簡化完了就是:只要在日曆控制元件上觸發mouseUp就會開啟日曆控制元件。

檢視官網Demo發現確實存在這個問題,第一反應是日曆控制元件的輸入框繫結了onMouseUp事件,檢視對應的DatePicker元件原始碼,定位到檔案ant-design/components/date-picker/generatePicker/generateSinglePicker.tsx第121~156行

(基於antd@4.23.4版本,不同版本行數可能不同):

import RCPicker from 'rc-picker';
// 中間程式碼省略……
<RCPicker<DateType>
  ref={innerRef}
  placeholder={getPlaceholder(mergedPicker, locale, placeholder)}
  suffixIcon={suffixNode}
  dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
  dropdownClassName={popupClassName || dropdownClassName}
  clearIcon={<CloseCircleFilled />}
  prevIcon={<span className={`${prefixCls}-prev-icon`} />}
  nextIcon={<span className={`${prefixCls}-next-icon`} />}
  superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
  superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
  allowClear
  transitionName={`${rootPrefixCls}-slide-up`}
  {...additionalProps}
  {...restProps}
  {...additionalOverrideProps}
  locale={locale!.lang}
  className={classNames(
    {
      [`${prefixCls}-${mergedSize}`]: mergedSize,
      [`${prefixCls}-borderless`]: !bordered,
    },
    getStatusClassNames(
      prefixCls as string,
      getMergedStatus(contextStatus, customStatus),
      hasFeedback,
    ),
    className,
  )}
  prefixCls={prefixCls}
  getPopupContainer={customizeGetPopupContainer || getPopupContainer}
  generateConfig={generateConfig}
  components={Components}
  direction={direction}
  disabled={mergedDisabled}
/>

以上原始碼沒有傳入onMouseUp,將解構賦值prop的{...restProps}註釋掉,檢視本地Demo,問題依然存在,說明restProps也沒有傳入onMouseUp

由於DatePicker元件是基於RCPicker封裝的,接下來繼續深入RCPicker元件,看看onMouseUp是不是在RCPicker裡面繫結的。

rc-pickerreact-components元件庫中的日曆元件,Ant Design大部分元件都是在react-components基礎上封裝的。

檢視rc-picker原始碼,rc-picker預設匯出的是Picker元件:

import Picker from './Picker';
//...
export default Picker;

定位到picker/src/Picker.tsx,搜尋字串"onMouseUp",發現在日曆控制元件輸入框的父級div繫結了onMouseUp事件,對應的回撥函數在287行(基於rc-picker@2.6.10):

// 輸入框父級元素 L530~L555
<div
  ref={containerRef}
  className={classNames(prefixCls, className, {
    [`${prefixCls}-disabled`]: disabled,
    [`${prefixCls}-focused`]: focused,
    [`${prefixCls}-rtl`]: direction === 'rtl',
  })}
  style={style}
  onMouseDown={onMouseDown}
  // 看這裡
  onMouseUp={onInternalMouseUp}
  onMouseEnter={onMouseEnter}
  onMouseLeave={onMouseLeave}
  onContextMenu={onContextMenu}
  // 順便關注一下這裡,後文會說
  onClick={onClick}
>
  <div
    className={classNames(`${prefixCls}-input`, {
      [`${prefixCls}-input-placeholder`]: !!hoverValue,
    })}
    ref={inputDivRef}
  >
    {inputNode}
    {suffixNode}
    {clearNode}
  </div>
</div>
// onMouseUp回撥 L287~L296
const onInternalMouseUp: React.MouseEventHandler<HTMLDivElement> = (...args) => {
  if (onMouseUp) {
    onMouseUp(...args);
  }
  // 問題就在這裡
  if (inputRef.current) {
    inputRef.current.focus();
    triggerOpen(true);
  }
};

就在函數onInternalMouseUp中,除了觸發onMouseUp,還觸發了輸入框的focus事件!

if (inputRef.current) {
  inputRef.current.focus();
  triggerOpen(true);
}

此時,應該弄清楚一件事:當時為什麼要寫這幾行程式碼?

最好的途徑就是看看當時的commit message,為了方便後續偵錯,還是將rc-picker倉庫克隆到本地:先Fork,再新建分支,然後安裝依賴,啟動專案。

PS:VSCode安裝GitLens外掛,即可檢視每一行程式碼的commit記錄。

從這幾行程式碼對應的commit瞭解到,這幾行程式碼是為了解決Ant Design的日曆控制元件點選輸入框右側的icon沒有彈出日曆面板的問題。

知道它的作用了,已經成功了一半!看看要怎麼修復?

第四步、問題修復

到這一步,建議先到對應的Issue下面留言說明正在處理中,避免其他開發者再花時間修復同一個問題。

接著,要解決2個問題:

  • 點選日曆控制元件輸入框右側的icon需要開啟日曆面板
  • 不要用mouseUp事件來開啟,想想其它實現方式

答案顯而易見了,那就是改成在onClick裡觸發,因為icon節點同樣被輸入框的父級包裹,icon的click事件必然會冒泡到父級,從而觸發父級的onClick。

檢視以上原始碼發現,輸入框的父級已經有一個onClick屬性,只需要將以上的4行程式碼挪到onClick的回撥即可,點選這裡檢視詳細修改。

修改完成,檢視本地Demo,問題已經解決了,勝利在望!

第五步、單元測試

原來的commit寫了對應的單元測試,我們修改了它的實現,同時也需要修改對應的單元測試:

// picker/tests/picker.spec.tsx L620~631
it('Picker should open when click inside', () => {
  const onClick = jest.fn();
  const wrapper = mount(<MomentPicker onClick={onClick} />);
  const inputElement = wrapper.find('input').instance() as any as HTMLInputElement;
  inputElement.focus = jest.fn();
  wrapper.find('.rc-picker').simulate('click');
  expect(inputElement.focus).toHaveBeenCalled();
  expect(wrapper.isOpen()).toBeTruthy();
  expect(onClick).toHaveBeenCalled();
});

修改完成之後,完整地跑一遍單元測試:

npm run test

確保所有單元測試都通過

Test Suites: 8 passed, 8 total
Tests:       294 passed, 294 total
Snapshots:   11 passed, 11 total
Time:        15.643s

程式碼部分到這裡就算圓滿完成了!

第六步、提交Pull Request

這一步,是本文最簡單的一步了!

提交程式碼,push到遠端倉庫,開啟倉庫頁面,會看到頁面上出現了一個【compare & pull request】的入口,點選即可向Ant Design建立合併請求,輸入修復的Issue連結(這個很重要,到時候Issue頁面會自動關聯你的此次的pull request)和修復思路或理由,方便管理員review。

最後,等待管理員review,沒問題的話管理員會合並分支,然後釋出新版本,程式碼最終會應用到數十萬專案中,Bug修復大功告成!

總結

最後,總結一下,第一次提交PR,建議選擇簡單的Issue,重要的是先將整個流程熟悉一遍,不要一開始就挑戰複雜的問題,如果花了很長時間卻解決不了問題,自信心受挫,可能就不想繼續下去了。

以上就是Ant Design 的Bug修復範例詳解的詳細內容,更多關於Ant Design Bug修復的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com