<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

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

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

          理解高潮 MVP

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

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

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

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

          開始之前

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

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

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

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

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

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

          看透本質(zhì)

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

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

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

          類似下面這張冰山圖:

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

          了解上下文

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

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

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

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

          一個一個使用 Button 的例子長什么樣子:


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

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

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

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

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

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

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

          • 高潮 MVP:跑通一個 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,同時使用了 CSS BEM 框架 CSS Render[5],同時包管理工具使用 pnpm

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

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

          • src:這個是主要放組件庫相關(guān)的組件代碼,以及導(dǎo)出一些國際化、樣式、主題定制相關(guān)的內(nèi)容,一般是一個開源項(xiàng)目的核心開發(fā)目錄
          • scripts:一些運(yùn)行代碼、構(gòu)建、發(fā)版相關(guān)的腳本邏輯
          • theme :則為 NaiveUI 內(nèi)置的默認(rèn)主題,類似這種組件庫一般都允許用戶自定義主題,整個 NaiveUI 各個組件在使用各種 UI 屬性時都是遵從這套主題進(jìn)行設(shè)置的,也就是可以修改 theme 里面的內(nèi)容,或者自己完全自定義一套主題
          • .github.husky 等都是一些配置,無需過多關(guān)注,可以直接加入到你的 MVP 模板工程里
          • playground 是用于此時代碼在各種環(huán)境下運(yùn)行的支持代碼,如 SSR 等
          • demo 則是引入 src 相關(guān)內(nèi)容用于展示組件實(shí)際效果的網(wǎng)站例子,實(shí)際上對于 NaiveUI 也就是我們之前看到的文檔官網(wǎng)
          • 其他的如 builddesign-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),用于了解整個想法發(fā)展上下文的 CHANGELOG.xx.md ,還有我們之前提到的用于跑通代碼的 CONTRIBUTING 貢獻(xiàn)指南。

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

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

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

          • 針對目錄的來說

            • .github.huskyplaygroundscripts 這種我們可以不要,我們只需要測試最基礎(chǔ)的環(huán)境,以及在開發(fā)時可以跑通即可
            • theme 這種只是整個 NaiveUI 遵循的設(shè)計(jì)體系,在其他部分會遵循這個體系,但是不會直接引用,所以我們也可以不要
            • 這樣我們只剩下 demosrc ,而更近一步,我們可以把 demo 做到 src 里面,整個 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)然引入這兩個到我們初始的高潮 MVP 項(xiàng)目里也不礙事

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

          • 構(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)容對吧?同時也非常易懂。

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

          跑通流程

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






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






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

          找到核心入口

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

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

          • 前置的 import 依賴

          • 定義組件 defineComponent

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

          而上圖代碼中的所有和 TS 定義相關(guān)的內(nèi)容我們都是不需要的,所以可以刪除 ButtonPropsNativeButtonPropsMergedPropsXButton 這些類型定義相關(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?組件在整個全局設(shè)計(jì)系統(tǒng)中的對應(yīng)樣式
          ????const?{?mergedClsPrefixRef,?NConfigProvider?}?=?useConfig(props)
          ????const?themeRef?=?useTheme(...)
          ????const?rtlEnabledRef?=?useRtl(...)
          ????
          ?????//?將自身狀態(tài)、全局狀態(tài)相關(guān)的主題樣式、各個?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.colorthis.ghostthis.textthis.cssVars ,所以我們的核心就是理解這些主題是如何定制的,包含哪些變量和依賴,這些變量和依賴是如何影響 Button 可以承載不同樣式和功能的。

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

          • 我們只需要看一個獨(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)入:這個我們只需要查詢對應(yīng)庫的文檔就可了解對于 API 的作用
          • 直接依賴于自身項(xiàng)目的其他相對路徑導(dǎo)入:這個我們就需要繼續(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)容的代碼拷貝過去。

          打個比方,對于非葉子依賴 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)行上述的依賴分析,直至收斂,最后我們會得到一個如下的文件組織圖:

          .
          ├──?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

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

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

          抽絲剝繭

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

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

          核心的 useTheme 鉤子是干嘛的:

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

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

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

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

          寫在最后

          相信大家在看皮湯的這篇源碼閱讀文章之前,應(yīng)該也看過各種大牛的源碼解讀文章,但是相信每個人都有自己比較獨(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)用這個技巧去看其他的源碼,如 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)典電子書,省去你篩選以及下載的時間
          • 后臺回復(fù):不一樣的前端,自動獲取精選優(yōu)質(zhì)前端文章。
          • 后臺回復(fù):算法,自動獲取精選算法文章。另外也可關(guān)注我的另外一個專注算法的公眾號力扣加加
          • 后臺回復(fù):每日一薦,自動獲取我為大家總結(jié)的每日一薦月刊,內(nèi)含精品文章,實(shí)用技巧,高效工具等等


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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人人摸人人操人人奶 | 婬荡的寡妇一区二区三区 | 国产精品国产亚洲精品看不 | 国产成人MV | 人人操人人摸人人透 |