Ant Design 的Bug修復(fù)示例詳解
引言
我在工作中大量使用Ant Design,它為我省去了很多重復(fù)勞動(dòng),所以有空的時(shí)候,我也會(huì)為Ant Design做一些微小的貢獻(xiàn)。
本文詳細(xì)描述了我處理一個(gè)Ant Design的Bug全過(guò)程,文章內(nèi)容較多但難度并不高,新手同學(xué)看完也可以嘗試為Ant Design添磚加瓦!
第一步、選擇要修復(fù)的Bug
Ant Design有不少Bug,你可以直接訪問(wèn)它的Issues,挑一個(gè)簡(jiǎn)單的問(wèn)題嘗試解決,需要注意的是,并不是所以Issue都是Bug,經(jīng)過(guò)管理員確認(rèn)過(guò)的Bug一般會(huì)打上Bug的標(biāo)記。
這次我處理的問(wèn)題是#37165。
第二步、準(zhǔn)備工作
Fork ant-design倉(cāng)庫(kù)到自己的Github,回到自己的Github,將剛剛Fork的倉(cāng)庫(kù)克隆到本地,然后基于master分支新建一個(gè)bugfix分支,接著安裝依賴,啟動(dòng)項(xiàng)目,然后就可以在開(kāi)發(fā)環(huán)境訪問(wèn)Ant Design官網(wǎng)上所有的Demo,方便調(diào)試。
npm install && npm run start
第三步、問(wèn)題排查
回到問(wèn)題#37165,它的表現(xiàn)是:一個(gè)DatePicker,一個(gè)Input, 用戶用鼠標(biāo)選中Input的內(nèi)容進(jìn)行復(fù)制時(shí)經(jīng)常會(huì)滑到DatePicker,會(huì)觸發(fā)日歷控件輸入框的focus事件。
這種操作是比較常見(jiàn)的,問(wèn)題簡(jiǎn)化完了就是:只要在日歷控件上觸發(fā)mouseUp就會(huì)打開(kāi)日歷控件。
查看官網(wǎng)Demo發(fā)現(xiàn)確實(shí)存在這個(gè)問(wèn)題,第一反應(yīng)是日歷控件的輸入框綁定了onMouseUp事件,查看對(duì)應(yīng)的DatePicker
組件源碼,定位到文件ant-design/components/date-picker/generatePicker/generateSinglePicker.tsx
第121~156行
(基于antd@4.23.4版本,不同版本行數(shù)可能不同):
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} />
以上源碼沒(méi)有傳入onMouseUp,將解構(gòu)賦值prop的{...restProps}
注釋掉,查看本地Demo,問(wèn)題依然存在,說(shuō)明restProps
也沒(méi)有傳入onMouseUp
。
由于DatePicker
組件是基于RCPicker
封裝的,接下來(lái)繼續(xù)深入RCPicker
組件,看看onMouseUp
是不是在RCPicker
里面綁定的。
rc-picker
是react-components
組件庫(kù)中的日歷組件,Ant Design大部分組件都是在react-components
基礎(chǔ)上封裝的。
查看rc-picker源碼,rc-picker默認(rèn)導(dǎo)出的是Picker組件:
import Picker from './Picker'; //... export default Picker;
定位到picker/src/Picker.tsx
,搜索字符串"onMouseUp",發(fā)現(xiàn)在日歷控件輸入框的父級(jí)div綁定了onMouseUp事件,對(duì)應(yīng)的回調(diào)函數(shù)在287行(基于rc-picker@2.6.10):
// 輸入框父級(jí)元素 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} // 順便關(guān)注一下這里,后文會(huì)說(shuō) onClick={onClick} > <div className={classNames(`${prefixCls}-input`, { [`${prefixCls}-input-placeholder`]: !!hoverValue, })} ref={inputDivRef} > {inputNode} {suffixNode} {clearNode} </div> </div> // onMouseUp回調(diào) L287~L296 const onInternalMouseUp: React.MouseEventHandler<HTMLDivElement> = (...args) => { if (onMouseUp) { onMouseUp(...args); } // 問(wèn)題就在這里 if (inputRef.current) { inputRef.current.focus(); triggerOpen(true); } };
就在函數(shù)onInternalMouseUp中,除了觸發(fā)onMouseUp,還觸發(fā)了輸入框的focus事件!
if (inputRef.current) { inputRef.current.focus(); triggerOpen(true); }
此時(shí),應(yīng)該弄清楚一件事:當(dāng)時(shí)為什么要寫(xiě)這幾行代碼?
最好的途徑就是看看當(dāng)時(shí)的commit message,為了方便后續(xù)調(diào)試,還是將rc-picker倉(cāng)庫(kù)克隆到本地:先Fork,再新建分支,然后安裝依賴,啟動(dòng)項(xiàng)目。
PS:VSCode安裝GitLens插件,即可查看每一行代碼的commit記錄。
從這幾行代碼對(duì)應(yīng)的commit了解到,這幾行代碼是為了解決Ant Design的日歷控件點(diǎn)擊輸入框右側(cè)的icon沒(méi)有彈出日歷面板的問(wèn)題。
知道它的作用了,已經(jīng)成功了一半!看看要怎么修復(fù)?
第四步、問(wèn)題修復(fù)
到這一步,建議先到對(duì)應(yīng)的Issue下面留言說(shuō)明正在處理中,避免其他開(kāi)發(fā)者再花時(shí)間修復(fù)同一個(gè)問(wèn)題。
接著,要解決2個(gè)問(wèn)題:
- 點(diǎn)擊日歷控件輸入框右側(cè)的icon需要打開(kāi)日歷面板
- 不要用mouseUp事件來(lái)打開(kāi),想想其它實(shí)現(xiàn)方式
答案顯而易見(jiàn)了,那就是改成在onClick里觸發(fā),因?yàn)閕con節(jié)點(diǎn)同樣被輸入框的父級(jí)包裹,icon的click事件必然會(huì)冒泡到父級(jí),從而觸發(fā)父級(jí)的onClick。
查看以上源碼發(fā)現(xiàn),輸入框的父級(jí)已經(jīng)有一個(gè)onClick屬性,只需要將以上的4行代碼挪到onClick的回調(diào)即可,點(diǎn)擊這里查看詳細(xì)修改。
修改完成,查看本地Demo,問(wèn)題已經(jīng)解決了,勝利在望!
第五步、單元測(cè)試
原來(lái)的commit寫(xiě)了對(duì)應(yīng)的單元測(cè)試,我們修改了它的實(shí)現(xiàn),同時(shí)也需要修改對(duì)應(yīng)的單元測(cè)試:
// 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(); });
修改完成之后,完整地跑一遍單元測(cè)試:
npm run test
確保所有單元測(cè)試都通過(guò)
Test Suites: 8 passed, 8 total
Tests: 294 passed, 294 total
Snapshots: 11 passed, 11 total
Time: 15.643s
代碼部分到這里就算圓滿完成了!
第六步、提交Pull Request
這一步,是本文最簡(jiǎn)單的一步了!
提交代碼,push到遠(yuǎn)程倉(cāng)庫(kù),打開(kāi)倉(cāng)庫(kù)頁(yè)面,會(huì)看到頁(yè)面上出現(xiàn)了一個(gè)【compare & pull request】的入口,點(diǎn)擊即可向Ant Design創(chuàng)建合并請(qǐng)求,輸入修復(fù)的Issue鏈接(這個(gè)很重要,到時(shí)候Issue頁(yè)面會(huì)自動(dòng)關(guān)聯(lián)你的此次的pull request)和修復(fù)思路或理由,方便管理員review。
最后,等待管理員review,沒(méi)問(wèn)題的話管理員會(huì)合并分支,然后發(fā)布新版本,代碼最終會(huì)應(yīng)用到數(shù)十萬(wàn)項(xiàng)目中,Bug修復(fù)大功告成!
總結(jié)
最后,總結(jié)一下,第一次提交PR,建議選擇簡(jiǎn)單的Issue,重要的是先將整個(gè)流程熟悉一遍,不要一開(kāi)始就挑戰(zhàn)復(fù)雜的問(wèn)題,如果花了很長(zhǎng)時(shí)間卻解決不了問(wèn)題,自信心受挫,可能就不想繼續(xù)下去了。
以上就是Ant Design 的Bug修復(fù)示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Ant Design Bug修復(fù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序 監(jiān)聽(tīng)手勢(shì)滑動(dòng)切換頁(yè)面實(shí)例詳解
這篇文章主要介紹了微信小程序 監(jiān)聽(tīng)手勢(shì)滑動(dòng)切換頁(yè)面實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06JavaScript前端實(shí)用的工具函數(shù)封裝
這篇文章主要為大家介紹了JavaScript前端實(shí)用的一些工具函數(shù)的封裝,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07Canvas如何判斷點(diǎn)在形狀內(nèi)及內(nèi)置API性能詳解
這篇文章主要為大家介紹了Canvas如何判斷點(diǎn)在形狀內(nèi)及內(nèi)置API性能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03TypeScript實(shí)用技巧?Nominal?Typing名義類型詳解
這篇文章主要為大家介紹了TypeScript實(shí)用技巧?Nominal?Typing名義類型詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09微信小程序通過(guò)api接口將json數(shù)據(jù)展現(xiàn)到小程序示例
這篇文章主要介紹了微信小程序通過(guò)api接口將json數(shù)據(jù)展現(xiàn)到小程序示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01微信小程序(應(yīng)用號(hào))簡(jiǎn)單實(shí)例應(yīng)用及實(shí)例詳解
這篇文章主要介紹了微信小程序(應(yīng)用號(hào))簡(jiǎn)單實(shí)例應(yīng)用的相關(guān)資料,需要的朋友可以參考下2016-09-09