1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        6.7K Star 的知名開源項(xiàng)目源碼,該怎么看?

        共 13435字,需瀏覽 27分鐘

         ·

        2021-12-01 11:17

        源碼解讀??讀懂開源??精選好文

        大家好,我是皮湯。最近程序員巴士學(xué)習(xí)交流群里有小伙伴想要了解一下如何看源碼,正好最近有一點(diǎn)心得感悟,之前也寫過一篇實(shí)際跑通 NaiveUI 源碼的文章:尤大都推薦的組件庫是如何開發(fā)出來的?[1] 源碼的經(jīng)驗(yàn),來給大家分享一下。

        心理認(rèn)知要到位

        首先要認(rèn)識到,看源碼是一個(gè)開始比較枯燥、同時(shí)時(shí)間跨度相對比較長的一個(gè)過程。所以看源碼的第一步是找到自己想要了解領(lǐng)域、或者自己所在業(yè)務(wù)領(lǐng)域高度相關(guān)的項(xiàng)目,并且在這個(gè)領(lǐng)域比較出名,且維護(hù)活躍。

        打個(gè)比方,皮湯我因?yàn)槭且幻岸?,而前端這個(gè)領(lǐng)域有很多新興的內(nèi)容,如 Unbundled 方案 Vite,新興框架 Svelte,新匯編語言 WebAssembly,CSS 工程化方案 TailwindCSS,組件庫如抖音很火的開源庫 Semi Design[2]、或者社區(qū)比較火的 Vue3 組件庫 NaiveUI 等。

        而皮湯我一直對組件庫、CSS 方向比較癡迷,且是組內(nèi)最近負(fù)責(zé)前端工程化 CSS 方面基建的負(fù)責(zé)人之一,所以讓我去研究一個(gè)組件庫的源碼,如 NaiveUI,那么我是很有興趣、動力和理由的,而這也是驅(qū)動你啃下一個(gè)源碼的核心驅(qū)動力之一。

        理解高潮 MVP

        其次我們看源碼要有一定的技巧,復(fù)雜如 React[3],可以算作一個(gè)簡單的操作系統(tǒng)了,如果你上來通過比較簡單粗暴的從代碼入口開始,一路打斷點(diǎn)了解源碼,那你再怎么堅(jiān)持也會想吐的。

        那這里的技巧是什么呢?就像我們互聯(lián)網(wǎng)創(chuàng)業(yè)一樣,如果你有一個(gè)大而全的點(diǎn)子,但是你的第一步肯定不是找一個(gè)空曠的屋子,備好半年的糧食,準(zhǔn)備幾臺電腦,然后高強(qiáng)度開發(fā)幾個(gè)月,然后祈求一問世就驚艷世人。一個(gè)是這種情況非常少見,二個(gè)是你也得有堅(jiān)持幾個(gè)月的資本和耐心。

        而在創(chuàng)業(yè)領(lǐng)域一個(gè)比較知名且流傳深遠(yuǎn)的技巧就是 MVP,即最小可行性產(chǎn)品,你需要先做出一個(gè)非常小的,剛好能夠使用以及能夠測試你想法的產(chǎn)品,然后快速推向市場,接收用戶反饋,接著跟進(jìn)用戶反饋,不斷迭代產(chǎn)品,滿足用戶需求,直至達(dá)到 PMF(產(chǎn)品與市場匹配),這個(gè)時(shí)候你基本上就可以找投資,進(jìn)行規(guī)?;?,然后就是融資、去納斯達(dá)克敲鐘。

        所以對應(yīng)到我們看源碼這個(gè)領(lǐng)域,第二個(gè)需要注意的,就是你需要找到一個(gè)開源項(xiàng)目能跑起來的最小 MVP,去除其他繁雜的依賴,最最核心的流程與機(jī)制。這個(gè)能夠幫助你理解項(xiàng)目核心的 MVP,我稱之為 高潮 MVP -- 即如果你能夠跑通一個(gè)項(xiàng)目這樣的 MVP,那么你內(nèi)心會異常幸福,感覺自己成就慢慢,興奮達(dá)到了高潮,而接下來其他內(nèi)容、分支基本上就是復(fù)用這套 MVP,往上面添磚加瓦,補(bǔ)齊一些兼容細(xì)節(jié)等。

        開始之前

        那么對于 NaiveUI 來說,它的高潮 MVP 是什么呢?

        我們首先打開它的官網(wǎng):

        點(diǎn)開開始使用:

        作為一個(gè)組件庫來說,它一般需要談?wù)撟约旱膬r(jià)值觀、設(shè)計(jì)原則、定制方式,ICON 圖標(biāo)相關(guān)的內(nèi)容,但是這對于你搞懂這份源碼其實(shí)沒有多大幫助,所以需要略去這些干擾項(xiàng)。

        讓我們再來看看它的源碼倉庫:

        確保這個(gè)庫使用何種語言編寫,這樣你在看源碼之前可以先衡量你當(dāng)前的知識儲備是否能夠支撐你看懂這份源碼,當(dāng)然如果你沒有對應(yīng)的支持儲備,但是又堅(jiān)持想要看這份源碼,那么你首先應(yīng)該考慮根據(jù)它使用的語言,提前進(jìn)行語言學(xué)習(xí)儲備。

        看透本質(zhì)

        讓我們回到 NaiveUI 的官網(wǎng):

        可以看到,對于一個(gè) “組件庫” 來說,實(shí)際上最最基礎(chǔ)的其實(shí)就是 “組件”,而組成 “組件” 的背后則需要一系列更加基礎(chǔ)的元素,如顏色、間距、邊框、背景、字體等。

        那么我們的目標(biāo)是不是很明確了呢?把一個(gè) “按鈕” 組件拿下,理解能夠完整使用到這樣一個(gè)按鈕背后所需要的所有必須的流程、知識、細(xì)節(jié),那么針對其他的組件,基本上 90 % 的邏輯可以復(fù)用,只需要理解剩余 10% 特定功能需求就可以搞懂。

        類似下面這張冰山圖:

        冰山之下就屬于那 90%,我們基于一個(gè)看似簡單的 “按鈕” 組件,來梳理整個(gè)組件庫的核心流程,就可以幫助我們快速、精準(zhǔn)的搞懂整份源碼,所以我們的高潮 MVP 就是搞懂一個(gè) “按鈕” 組件的全流程。

        了解上下文

        理解我們的高潮 MVP 目標(biāo)是什么了之后,接下來就是帶著這個(gè)目標(biāo)先詳細(xì)讀一下文檔中關(guān)于 Button 的所有相關(guān)說明,可以看到這個(gè)按鈕包含如下內(nèi)容:

        通過右側(cè)的目錄,了解到一個(gè)按鈕首先會有基礎(chǔ)內(nèi)容,包含 default、primary、info、success、warningerror 這幾類,然后需要處理:

        • 邊框相關(guān):虛線
        • 尺寸相關(guān):尺寸、行政
        • 顏色:自定義顏色
        • 狀態(tài):文本、禁用、加載中
        • 事件

        上述表明了這個(gè) Button 可以達(dá)到的效果,可以完成的操作,了解之后,接著可以了解按鈕相關(guān)的使用 API,通過 API 以及可以達(dá)到的效果,我們大致可以理解這個(gè)按鈕接收的輸入和輸出有哪些。

        一個(gè)一個(gè)使用 Button 的例子長什么樣子:


        了解如何開啟項(xiàng)目

        通常開源項(xiàng)目比較方便的一點(diǎn)是它會有詳細(xì)的文檔,同時(shí)它非??释胸暙I(xiàn)者加入,所以會有完善的 貢獻(xiàn)指南,比如 NaiveUI 的貢獻(xiàn)指南[4]如下:

        通過貢獻(xiàn)指南,你能夠了解如何安裝依賴、處理一些啟動項(xiàng)目的問題,能夠把項(xiàng)目跑起來進(jìn)行調(diào)試,這通常是你了解整個(gè)代碼運(yùn)行過程的初次體驗(yàn)。

        理解目標(biāo)項(xiàng)目的項(xiàng)目結(jié)構(gòu)

        通常你到這個(gè)步驟時(shí),你應(yīng)該需要知道如下內(nèi)容:

        • 你已經(jīng)理解了你的目標(biāo),高潮 MVP 是什么
        • 你理解了你目標(biāo)內(nèi)容作為一個(gè)功能特性,它的輸入和輸出是什么
        • 你理解此項(xiàng)目的技術(shù)棧是什么,如何把項(xiàng)目跑起來

        對應(yīng)到 NaiveUI 我們的這三點(diǎn)分別如下:

        • 高潮 MVP:跑通一個(gè) Button 并能夠使用,保有和現(xiàn)有 Button 一樣的特性,接收一樣的輸入,產(chǎn)生一樣的輸出
        • Button 包含邊框、尺寸、顏色、狀態(tài)、事件等相關(guān)的內(nèi)容,輸入這些參數(shù),產(chǎn)出對應(yīng)條件下的輸出
        • 項(xiàng)目的技術(shù)棧是 Vue3、TypeScript,構(gòu)建工具是 Vite,同時(shí)使用了 CSS BEM 框架 CSS Render[5],同時(shí)包管理工具使用 pnpm

        理解這三點(diǎn)之后,接下來我們就需要對照著源碼來理解一下整份文件目錄,了解各個(gè)目錄之前的依賴關(guān)系,見下圖。

        我們可以先了解一下大致每個(gè)文件夾是干什么的:

        • src:這個(gè)是主要放組件庫相關(guān)的組件代碼,以及導(dǎo)出一些國際化、樣式、主題定制相關(guān)的內(nèi)容,一般是一個(gè)開源項(xiàng)目的核心開發(fā)目錄
        • scripts:一些運(yùn)行代碼、構(gòu)建、發(fā)版相關(guān)的腳本邏輯
        • theme :則為 NaiveUI 內(nèi)置的默認(rèn)主題,類似這種組件庫一般都允許用戶自定義主題,整個(gè) NaiveUI 各個(gè)組件在使用各種 UI 屬性時(shí)都是遵從這套主題進(jìn)行設(shè)置的,也就是可以修改 theme 里面的內(nèi)容,或者自己完全自定義一套主題
        • .github 、.husky 等都是一些配置,無需過多關(guān)注,可以直接加入到你的 MVP 模板工程里
        • playground 是用于此時(shí)代碼在各種環(huán)境下運(yùn)行的支持代碼,如 SSR 等
        • demo 則是引入 src 相關(guān)內(nèi)容用于展示組件實(shí)際效果的網(wǎng)站例子,實(shí)際上對于 NaiveUI 也就是我們之前看到的文檔官網(wǎng)
        • 其他的如 build 、design-notes 等是構(gòu)建產(chǎn)物,或者一些主題設(shè)計(jì)的筆記等,基本上不屬于本次源碼需要閱讀的部門,看興趣的同學(xué)可以看看

        然后就是一些用于各種工程化配置的文件如:

        • .prettierrc :Prettier 相關(guān)
        • .gitignore :Git 相關(guān)
        • .eslintrc.js :ESLint 相關(guān)
        • babel.config.js :Babel 相關(guān)
        • jest.config.js :Jest 測試相關(guān)
        • postcss.config.js :處理 CSS 相關(guān)
        • tsconfig.xx.json 處理 TypeScript 相關(guān)
        • vite.config.js :Vite 構(gòu)建工具相關(guān)的配置

        以及一些和項(xiàng)目強(qiáng)相關(guān),用于了解整個(gè)想法發(fā)展上下文的 CHANGELOG.xx.md ,還有我們之前提到的用于跑通代碼的 CONTRIBUTING 貢獻(xiàn)指南。

        有點(diǎn)看懵了。??

        創(chuàng)建你的高潮 MVP 項(xiàng)目

        了解了整個(gè) NaiveUI 的項(xiàng)目目錄結(jié)構(gòu)之后,我們就可以著手創(chuàng)建我們的高潮 MVP 項(xiàng)目了,但在這之前我們可以再進(jìn)行一波簡化,即我們有些內(nèi)容可以不要:

        • 針對目錄的來說

          • .github 、.huskyplayground 、scripts 這種我們可以不要,我們只需要測試最基礎(chǔ)的環(huán)境,以及在開發(fā)時(shí)可以跑通即可
          • theme 這種只是整個(gè) NaiveUI 遵循的設(shè)計(jì)體系,在其他部分會遵循這個(gè)體系,但是不會直接引用,所以我們也可以不要
          • 這樣我們只剩下 demosrc ,而更近一步,我們可以把 demo 做到 src 里面,整個(gè) src 我們將其職責(zé)變?yōu)楦叱?MVP 網(wǎng)站入口,然后原剩下的 src 下面的代碼則用于導(dǎo)入到 src 入口文件里面使用
        • 針對配置文件來說:

          • 測試相關(guān)的,Jest 等我們并不需要
          • TypeScript 相關(guān)的,我們后續(xù)可以迭代,不用引入不必要的復(fù)雜度以及類型體操
          • ESLint 和 Prettier 等我們也可以不需要,依賴于編輯器默認(rèn)的格式化就可,當(dāng)然引入這兩個(gè)到我們初始的高潮 MVP 項(xiàng)目里也不礙事

        經(jīng)過簡化之后,我們的高潮 MVP 項(xiàng)目就只需要如下幾個(gè)文件了:

        • 構(gòu)建項(xiàng)目和提供開發(fā)服務(wù)器的 Vite 相關(guān)內(nèi)容:vite.config.js
        • 用于提供語法轉(zhuǎn)譯的 babel.config.js
        • 項(xiàng)目依賴文件 package.json
        • 用于跑通項(xiàng)目的主要代碼 src 以及 index.html 入口模板

        目錄結(jié)構(gòu)如下:

        .

        ├──?babel.config.js

        ├──?index.html

        ├──?node_modules

        ├──?package.json

        ├──?public

        ├──?src

        ├──?vite.config.js

        └──?yarn.lock

        很精簡,沒有多余繁雜的內(nèi)容對吧?同時(shí)也非常易懂。

        這些剩下要創(chuàng)建的文件內(nèi)容,從 NaiveUI 的工程目錄里面 Copy 過來,然后安裝對應(yīng)的依賴即可。

        跑通流程

        當(dāng)我們根據(jù)源碼庫創(chuàng)建了我們的高潮 MVP 項(xiàng)目之后,現(xiàn)在應(yīng)該可以跑起來了,只不過內(nèi)容只是一個(gè)簡單的 Button,因?yàn)闉榱丝焖倥芷饋眄?xiàng)目,我們的入口文件 src/App.vue 會如下:






        而對應(yīng)的 src/components/TButton.vue 如下:






        接下來我們就嘗試一遍了解 NaiveUI 的代碼,一遍將這些主干代碼遷移到我們的高潮 MVP 項(xiàng)目中來,然后確保遷移過程中能夠持續(xù)跑起來,雖然我們可能會遇到有時(shí)候一個(gè)依賴需要大量的前置依賴,所以需要遷移一大段代碼才能將項(xiàng)目跑起來。

        找到核心入口

        我們要完成一個(gè) Button 的所有前置依賴,只需要去到 NaiveUI 對應(yīng)的工程目錄文件里面,找到 Button 對應(yīng)的代碼,如下:

        其實(shí)解析一下組件文件的代碼,就是下面幾部分:

        • 前置的 import 依賴

        • 定義組件 defineComponent

          • 組件里面處理 props 傳入與使用、自身狀態(tài)的定義與使用
          • 模板代碼
        • 導(dǎo)出組件

        而上圖代碼中的所有和 TS 定義相關(guān)的內(nèi)容我們都是不需要的,所以可以刪除 ButtonPropsNativeButtonProps 、MergedPropsXButton 這些類型定義相關(guān)的內(nèi)容。

        而導(dǎo)入部分涉及到類型定義相關(guān)的我們也可以刪除掉:

        import?type?{?ThemeProps?}?from?'../../_mixins'

        import?type?{?BaseWaveRef?}?from?'../../_internal'

        import?type?{?ExtractPublicPropTypes,?MaybeArray?}?from?'../../_utils'

        import?type?{?ButtonTheme?}?from?'../styles'

        import?type?{?Type,?Size?}?from?'./interface'

        刪除完這些無關(guān)的代碼之后,我們的代碼還剩下那些內(nèi)容呢?

        導(dǎo)入依賴部分:

        import?{
        ??h,
        ??ref,
        ??computed,
        ??inject,
        ??nextTick,
        ??defineComponent,
        ??PropType,
        ??renderSlot,
        ??CSSProperties,
        ??ButtonHTMLAttributes
        }?from?'vue'
        import?{?useMemo?}?from?'vooks'
        import?{?createHoverColor,?createPressedColor?}?from?'../../_utils/color/index'
        import?{?useConfig,?useFormItem,?useTheme?}?from?'../../_mixins'
        import?{
        ??NFadeInExpandTransition,
        ??NIconSwitchTransition,
        ??NBaseLoading,
        ??NBaseWave
        }?from?'../../_internal'
        import?{?call,?createKey?}?from?'../../_utils'
        import?{?buttonLight?}?from?'../styles'
        import?{?buttonGroupInjectionKey?}?from?'./ButtonGroup'
        import?style?from?'./styles/button.cssr'
        import?useRtl?from?'../../_mixins/use-rtl'

        組件聲明部分:

        const?Button?=?defineComponent({
        ??name:?'Button',
        ??props:?buttonProps,
        ??setup(props)?{
        ????//?定義組件狀態(tài)
        ????const?selfRef?=?refnull>(null)
        ????const?waveRef?=?refnull>(null)
        ????const?enterPressedRef?=?ref(false)
        ????
        ????//?使用?Props?或注入全局狀態(tài)
        ????const?NButtonGroup?=?inject(buttonGroupInjectionKey,?{})
        ????const?{?mergedSizeRef?}?=?useFormItem(...)
        ????const?mergedFocusableRef?=?computed(()?=>?{...})
        ????
        ????//?定義組件事件處理
        ????const?handleMouseDown?=?(e:?MouseEvent):?void?=>?{...}
        ????const?handleClick?=?(e:?MouseEvent):?void?=>?{...}
        ????const?handleKeyUp?=?(e:?KeyboardEvent):?void?=>?{...}
        ????const?handleKeyDown?=?(e:?KeyboardEvent):?void?=>?{...}
        ????const?handleBlur?=?():?void?=>?{...}
        ????
        ????//?處理組件的主題,獲取該?Button?組件在整個(gè)全局設(shè)計(jì)系統(tǒng)中的對應(yīng)樣式
        ????const?{?mergedClsPrefixRef,?NConfigProvider?}?=?useConfig(props)
        ????const?themeRef?=?useTheme(...)
        ????const?rtlEnabledRef?=?useRtl(...)
        ????
        ?????//?將自身狀態(tài)、全局狀態(tài)相關(guān)的主題樣式、各個(gè)?CSS?屬性的值、事件相關(guān)的內(nèi)容處理之后返回給模板使用
        ?????return?{
        ??????selfRef,
        ??????waveRef,
        ??????mergedClsPrefix:?mergedClsPrefixRef,
        ??????mergedFocusable:?mergedFocusableRef,
        ??????mergedSize:?mergedSizeRef,
        ??????showBorder:?showBorderRef,
        ??????enterPressed:?enterPressedRef,
        ??????rtlEnabled:?rtlEnabledRef,
        ??????handleMouseDown,
        ??????handleKeyDown,
        ??????handleBlur,
        ??????handleKeyUp,
        ??????handleClick,
        ??????customColorCssVars:?computed(()?=>?{...}),
        ??????cssVars:?computed(()?=>?{...})
        ????}
        ???},
        ???render()?{
        ???//?處理各種組件相關(guān)的樣式渲染、事件處理相關(guān)的內(nèi)容,這里的樣式渲染對應(yīng)著在文檔里提到的?Button?可以呈現(xiàn)的狀態(tài)和能處理的操作
        ????const?{?$slots,?mergedClsPrefix,?tag:?Component?}?=?this
        ????return?(
        ??????<Component
        ????????ref="selfRef"
        ????????class={[
        ??????????`${mergedClsPrefix}-button`,
        ??????????`${mergedClsPrefix}-button--${this.type}-type`,
        ??????????{
        ????????????[`${mergedClsPrefix}-button--rtl`]:?this.rtlEnabled,
        ????????????[`${mergedClsPrefix}-button--disabled`]:?this.disabled,
        ????????????[`${mergedClsPrefix}-button--block`]:?this.block,
        ????????????[`${mergedClsPrefix}-button--pressed`]:?this.enterPressed,
        ????????????[`${mergedClsPrefix}-button--dashed`]:?!this.text?&&?this.dashed,
        ????????????[`${mergedClsPrefix}-button--color`]:?this.color,
        ????????????[`${mergedClsPrefix}-button--ghost`]:?this.ghost?//?required?for?button?group?border?collapse
        ??????????}
        ????????]}
        ????????tabindex={this.mergedFocusable???0?:?-1}
        ????????type={this.attrType}
        ????????style={this.cssVars?as?CSSProperties}
        ????????disabled={this.disabled}
        ????????onClick={this.handleClick}
        ????????onBlur={this.handleBlur}
        ????????onMousedown={this.handleMouseDown}
        ????????onKeyup={this.handleKeyUp}
        ????????onKeydown={this.handleKeyDown}
        ??????>

        ????????{$slots.default?&&?this.iconPlacement?===?'right'???(
        ??????????<div?class={`${mergedClsPrefix}-button__content`}>{$slots}div>

        ????????)?:?null}
        ????????<NFadeInExpandTransition>NFadeInExpandTransition>
        ????????{$slots.default?&&?this.iconPlacement?===?'left'???(
        ??????????<span?class={`${mergedClsPrefix}-button__content`}>{$slots}span>
        ????????)?:?null}
        ????????{!this.text???(
        ??????????<NBaseWave?ref="waveRef"?clsPrefix={mergedClsPrefix}?/>
        ????????)?:?null}
        ????????{this.showBorder???(?...)}
        ????????{this.showBorder???(...)}
        ?????Component>
        ???)
        ??}
        })

        進(jìn)一步簡化代碼

        從上述還剩下的代碼,我們可以看到,其實(shí)對于理解組件庫來說,我們其實(shí)絕大部分內(nèi)容是在做定制主題,然后如果根據(jù)各種傳入的 props,展示不同的主題的工作,所以你會看到 Button 組件里充斥著大量的 CSS 變量,如 this.color 、this.ghostthis.text 、this.cssVars ,所以我們的核心就是理解這些主題是如何定制的,包含哪些變量和依賴,這些變量和依賴是如何影響 Button 可以承載不同樣式和功能的。

        所以上述代碼中,有一些內(nèi)容其實(shí)我們就可以刪掉了:

        • 我們只需要看一個(gè)獨(dú)立的 Button 是如何運(yùn)作的,所以 NButtonGroup 部分,按鈕組部分就可以不要了
        • 我們也不需要處理一些獨(dú)特的適配,如 RTL(從右向左排版)等

        所以我們需要近一步刪除這些代碼:

        import?{?buttonGroupInjectionKey?}?from?'./ButtonGroup'

        import?useRtl?from?'../../_mixins/use-rtl'



        const?NButtonGroup?=?inject(buttonGroupInjectionKey,?{})

        以及其他使用到 buttonGroup 相關(guān)的內(nèi)容。

        理解輸入

        通過上一步,我們基本上去除了所有無關(guān)的內(nèi)容,達(dá)到了我們最終高潮 MVP 項(xiàng)目里需要的 Button 的所有的、最精簡的內(nèi)容,也就是說我們核心入口代碼自身和依賴的部分已經(jīng)確定了,那么接下來就需要處理全部的輸入,以及刪除這些輸入中相關(guān)的依賴與 Button 處理無關(guān)的邏輯。

        我們可以看到 Button 主要有如下一種輸入:

        • 文件頂部的 import 輸入
        • 使用鉤子 useFormItem 、或全局狀態(tài)注入 inject(...) 相關(guān)的輸入

        我們可以看到,import 相關(guān)的輸入主要分為兩類:

        • 某些庫,如 vue 的導(dǎo)入:這個(gè)我們只需要查詢對應(yīng)庫的文檔就可了解對于 API 的作用
        • 直接依賴于自身項(xiàng)目的其他相對路徑導(dǎo)入:這個(gè)我們就需要繼續(xù)探究 NaiveUI 源碼庫的其他部分

        而鉤子 useFormItem 、或全局狀態(tài)注入 inject(...) 相關(guān)的輸入則也依賴于 import 里自身項(xiàng)目的其他相對路徑引入。

        我們需要順著如下的這些依賴,進(jìn)行依賴分析:

        import?{?createHoverColor,?createPressedColor?}?from?'../../_utils/color/index'

        import?{?useConfig,?useFormItem,?useTheme?}?from?'../../_mixins'

        import?{

        ??NFadeInExpandTransition,

        ??NIconSwitchTransition,

        ??NBaseLoading,

        ??NBaseWave

        }?from?'../../_internal'

        import?{?call,?createKey?}?from?'../../_utils'

        import?{?buttonLight?}?from?'../styles'

        import?{?buttonGroupInjectionKey?}?from?'./ButtonGroup'

        import?style?from?'./styles/button.cssr'

        這些依賴?yán)锩嬗行┳约罕揪褪侨~子依賴,并無其它依賴,如:

        import?{?createHoverColor,?createPressedColor?}?from?"../../_utils/color/index";



        //?其中某幾項(xiàng)

        import?{?useFormItem?}?from?"../../_mixins";



        //?下面的某幾項(xiàng)

        import?{

        ??NFadeInExpandTransition,

        ??NIconSwitchTransition,

        }?from?"../../_internal";



        import?{?call,?createKey,?getSlot,?flatten?}?from?"../../_utils";

        這些葉子依賴可以直接對照著原倉庫建立對應(yīng)的目錄結(jié)構(gòu)和文件命名,然后把代碼拷貝過來。

        對于那些非葉子依賴,我們需要再下一番功夫繼續(xù)解析其依賴,重復(fù)之前的兩項(xiàng)操作:

        • 刪除 TS 或者其他和 Button 不相干的代碼和依賴
        • 尋找其依賴的依賴,繼續(xù)上面的過程

        最后就是對照著源碼的目錄結(jié)構(gòu)創(chuàng)建一樣的結(jié)構(gòu),將處理完無關(guān)內(nèi)容的代碼拷貝過去。

        打個(gè)比方,對于非葉子依賴 style

        import?style?from?"./styles/button.cssr.js";

        我們需要去到對應(yīng)的文件下,查看其依賴:

        import?{?c,?cB,?cE,?cM,?cNotM?}?from?'../../../_utils/cssr'

        import?fadeInWidthExpandTransition?from?'../../../_styles/transitions/fade-in-width-expand.cssr'

        import?iconSwitchTransition?from?'../../../_styles/transitions/icon-switch.cssr'

        發(fā)現(xiàn)其依賴了用于進(jìn)行 BEM 規(guī)范定義的 cssr 庫(自建)、以及處理動畫的一些 fadeInWidthExpandTransitioniconSwitchTransition 依賴,那么接著要繼續(xù)進(jìn)入這些依賴,如:

        import?{?c,?cB,?cE,?cM,?cNotM?}?from?'../../../_utils/cssr'

        它的依賴如下:

        ?/*?eslint-disable?@typescript-eslint/restrict-template-expressions?*/

        import?CSSRender,?{?CNode,?CProperties?}?from?'css-render'

        import?BEMPlugin?from?'@css-render/plugin-bem'

        發(fā)現(xiàn)沒有其他再需要繼續(xù)遞歸尋找的依賴了,都是引入的第三方庫,那么就可以去查閱一下對應(yīng)的第三方庫的文檔,了解 API 的含義即可。

        如此往復(fù)進(jìn)行上述的依賴分析,直至收斂,最后我們會得到一個(gè)如下的文件組織圖:

        .
        ├──?App.vue
        ├──?_internal
        │???├──?fade-in-expand-transition
        │???│???├──?index.js
        │???│???└──?src
        │???│???????└──?FadeInExpandTransition.jsx
        │???├──?icon
        │???│???├──?index.js
        │???│???└──?src
        │???│???????├──?Icon.jsx
        │???│???????└──?styles
        │???│???????????└──?index.cssr.js
        │???├──?icon-switch-transition
        │???│???├──?index.js
        │???│???└──?src
        │???│???????└──?IconSwitchTransition.jsx
        │???├──?index.js
        │???├──?loading
        │???│???├──?index.js
        │???│???└──?src
        │???│???????├──?Loading.jsx
        │???│???????└──?styles
        │???│???????????└──?index.cssr.js
        │???└──?wave
        │???????├──?index.js
        │???????└──?src
        │???????????├──?Wave.jsx
        │???????????└──?styles
        │???????????????└──?index.cssr.js
        ├──?_mixins
        │???├──?index.js
        │???├──?use-config.js
        │???├──?use-form-item.js
        │???├──?use-style.js
        │???└──?use-theme.js
        ├──?_styles
        │???├──?common
        │???│???├──?_common.js
        │???│???├──?index.js
        │???│???└──?light.js
        │???├──?global
        │???│???└──?index.cssr.js
        │???└──?transitions
        │???????├──?fade-in-width-expand.cssr.js
        │???????└──?icon-switch.cssr.js
        ├──?_utils
        │???├──?color
        │???│???└──?index.js
        │???├──?cssr
        │???│???├──?create-key.js
        │???│???└──?index.js
        │???├──?index.js
        │???├──?naive
        │???│???├──?index.js
        │???│???└──?warn.js
        │???└──?vue
        │???????├──?call.js
        │???????├──?flatten.js
        │???????├──?get-slot.js
        │???????└──?index.js
        ├──?assets
        │???└──?logo.png
        ├──?button
        │???├──?src
        │???│???├──?Button.jsx
        │???│???└──?styles
        │???│???????└──?button.cssr.js
        │???└──?styles
        │???????├──?_common.js
        │???????├──?index.js
        │???????└──?light.js
        ├──?components
        │???└──?Button.jsx
        ├──?config-provider
        │???└──?src
        │???????└──?ConfigProvider.js
        └──?main.js

        32?directories,?45?files

        一個(gè)簡單的 Button 竟然要包含 45 個(gè)文件,32個(gè)目錄來進(jìn)行支撐,我們基本上可以確定組件庫中 90% 的內(nèi)容是共通的,只需要理解了一個(gè) Button 需要的所有底層依賴和設(shè)計(jì)理念,理解這個(gè)組件庫只需要再努力一步,了解剩下 10 % 的各組件特殊設(shè)計(jì),就可以弄懂整個(gè)組件庫的源碼。

        上述核心整理的一個(gè) Button 的全部依賴代碼可以進(jìn)入我的 Github 倉庫查閱:https://github.com/pftom/naive-app。

        抽絲剝繭

        當(dāng)我們能夠拿到一個(gè) Button 能夠完美運(yùn)行背后所需要的所有 “必要” 和 “最簡” 的依賴之后,我們就可以邊運(yùn)行這個(gè)項(xiàng)目,邊通過查閱資料,畫思維導(dǎo)圖理解這份最簡必要代碼了。

        我們首先把代碼跑起來,然后逐層理解代碼邏輯,如前置的幾個(gè)鉤子函數(shù)是干嘛的:

        核心的 useTheme 鉤子是干嘛的:

        用戶自定義相關(guān)的鉤子函數(shù)又是干嘛的,它包含哪些 CSS 變量:

        Vue3 組件里面的 setup 返回值有哪些:

        最終用于渲染的 render 函數(shù)邏輯是干嘛的:

        通過查閱 Vue3 文檔、梳理整個(gè)代碼流程,然后了解各個(gè)分支是如何運(yùn)作的,我們就能慢慢理解 Button 組件是如何跑起來的。得益于我們進(jìn)行了代碼的最精簡化處理,所以整個(gè)看代碼的流程雖然會慢一點(diǎn),但是整體需要理解的內(nèi)容相比之前我們拿到一整份源碼,幾百上千個(gè)文件來一股腦從入口開始打斷點(diǎn)調(diào)試會好很多。

        寫在最后

        相信大家在看皮湯的這篇源碼閱讀文章之前,應(yīng)該也看過各種大牛的源碼解讀文章,但是相信每個(gè)人都有自己比較獨(dú)特的看源碼技巧,雖然我這里是拿如何看懂 NaiveUI 的源碼舉例子,但是相信所有看源碼的過程都是如此,遵循如下步驟:

        • 樹立好的心理認(rèn)知
        • 理解高潮 MVP,又包含定位源碼最小可行性代碼需要的內(nèi)容,在看源碼之前先梳理結(jié)構(gòu),確保 MVP 能夠跑起來
        • 然后再在最小、最核心的源碼上進(jìn)行打斷點(diǎn)、畫思維導(dǎo)圖、查閱文檔等方式幫助自己啃下源碼

        這是皮湯在看 Vite、NaiveUI 源碼過程中總結(jié)出來的經(jīng)驗(yàn),相信能夠?yàn)榕腔苍诳丛创a路上卻沒有方法的同學(xué)提供一點(diǎn)指引,你完全可以應(yīng)用這個(gè)技巧去看其他的源碼,如 Webpack?qiankun?Ant Design?或者抖音最近發(fā)布的 Semi Design。共勉 ??

        參考資料

        [1]

        尤大都推薦的組件庫是如何開發(fā)出來的?: https://mp.weixin.qq.com/s?__biz=MzkxMjI3OTA3NQ==&mid=2247485296&idx=1&sn=61b6de490f9d437215cadde9502d75df&chksm=c10e143cf6799d2afe13b5aecebc2b6608bd0fab3e61149b80910ce87757ceb2219651ed9a07&token=586597170&lang=zh_CN#rd

        [2]

        Semi Design: https://github.com/DouyinFE/semi-design

        [3]

        React: https://github.com/facebook/react

        [4]

        貢獻(xiàn)指南: https://github.com/TuSimple/naive-ui/blob/main/CONTRIBUTING.md

        [5]

        CSS Render: https://github.com/07akioni/css-render

        - END -


        • 后臺回復(fù):typescript,獲取我寫的 typescript 系列文章,絕對精品
        • 后臺回復(fù):電子書,自動獲取我為大家整理的大量經(jīng)典電子書,省去你篩選以及下載的時(shí)間
        • 后臺回復(fù):不一樣的前端,自動獲取精選優(yōu)質(zhì)前端文章。
        • 后臺回復(fù):算法,自動獲取精選算法文章。另外也可關(guān)注我的另外一個(gè)專注算法的公眾號力扣加加。
        • 后臺回復(fù):每日一薦,自動獲取我為大家總結(jié)的每日一薦月刊,內(nèi)含精品文章,實(shí)用技巧,高效工具等等


        瀏覽 105
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            日日射夜夜操 | tickling挠裸乳尿孔网站 | 成人高清无码视频在观看 | 亚洲丁香花色 | 久久久久9999 | 91麻豆精品国产自产在线观看一区 | 77777精品成人免费A片 | 亚洲无码在线视频观看 | 男人添女人下面高潮视频 | 操老女人屄视频 |