6-7K Star 的知名開源項目源碼,該怎麼看?
大家好,我是皮湯。最近程序員巴士學習交流羣裏有小夥伴想要了解一下如何看源碼,正好最近有一點心得感悟,之前也寫過一篇實際跑通 NaiveUI 源碼的文章:尤大都推薦的組件庫是如何開發出來的?[1] 源碼的經驗,來給大家分享一下。
心理認知要到位
首先要認識到,看源碼是一個開始比較枯燥、同時時間跨度相對比較長的一個過程。所以看源碼的第一步是找到自己想要了解領域、或者自己所在業務領域高度相關的項目,並且在這個領域比較出名,且維護活躍。
打個比方,皮湯我因爲是一名前端,而前端這個領域有很多新興的內容,如 Unbundled 方案 Vite,新興框架 Svelte,新彙編語言 WebAssembly,CSS 工程化方案 TailwindCSS,組件庫如抖音很火的開源庫 Semi Design[2]、或者社區比較火的 Vue3 組件庫 NaiveUI 等。
而皮湯我一直對組件庫、CSS 方向比較癡迷,且是組內最近負責前端工程化 CSS 方面基建的負責人之一,所以讓我去研究一個組件庫的源碼,如 NaiveUI,那麼我是很有興趣、動力和理由的,而這也是驅動你啃下一個源碼的核心驅動力之一。
理解高潮 MVP
其次我們看源碼要有一定的技巧,複雜如 React[3],可以算作一個簡單的操作系統了,如果你上來通過比較簡單粗暴的從代碼入口開始,一路打斷點了解源碼,那你再怎麼堅持也會想吐的。
那這裏的技巧是什麼呢?就像我們互聯網創業一樣,如果你有一個大而全的點子,但是你的第一步肯定不是找一個空曠的屋子,備好半年的糧食,準備幾臺電腦,然後高強度開發幾個月,然後祈求一問世就驚豔世人。一個是這種情況非常少見,二個是你也得有堅持幾個月的資本和耐心。
而在創業領域一個比較知名且流傳深遠的技巧就是 MVP,即最小可行性產品,你需要先做出一個非常小的,剛好能夠使用以及能夠測試你想法的產品,然後快速推向市場,接收用戶反饋,接着跟進用戶反饋,不斷迭代產品,滿足用戶需求,直至達到 PMF(產品與市場匹配),這個時候你基本上就可以找投資,進行規模化,然後就是融資、去納斯達克敲鐘。
所以對應到我們看源碼這個領域,第二個需要注意的,就是你需要找到一個開源項目能跑起來的最小 MVP,去除其他繁雜的依賴,最最核心的流程與機制。這個能夠幫助你理解項目核心的 MVP,我稱之爲 高潮 MVP -- 即如果你能夠跑通一個項目這樣的 MVP,那麼你內心會異常幸福,感覺自己成就慢慢,興奮達到了高潮,而接下來其他內容、分支基本上就是複用這套 MVP,往上面添磚加瓦,補齊一些兼容細節等。
開始之前
那麼對於 NaiveUI 來說,它的高潮 MVP 是什麼呢?
我們首先打開它的官網:
點開開始使用:
作爲一個組件庫來說,它一般需要談論自己的價值觀、設計原則、定製方式,ICON 圖標相關的內容,但是這對於你搞懂這份源碼其實沒有多大幫助,所以需要略去這些干擾項。
讓我們再來看看它的源碼倉庫:
確保這個庫使用何種語言編寫,這樣你在看源碼之前可以先衡量你當前的知識儲備是否能夠支撐你看懂這份源碼,當然如果你沒有對應的支持儲備,但是又堅持想要看這份源碼,那麼你首先應該考慮根據它使用的語言,提前進行語言學習儲備。
看透本質
讓我們回到 NaiveUI 的官網:
可以看到,對於一個 “組件庫” 來說,實際上最最基礎的其實就是 “組件”,而組成 “組件” 的背後則需要一系列更加基礎的元素,如顏色、間距、邊框、背景、字體等。
那麼我們的目標是不是很明確了呢?把一個 “按鈕” 組件拿下,理解能夠完整使用到這樣一個按鈕背後所需要的所有必須的流程、知識、細節,那麼針對其他的組件,基本上 90 % 的邏輯可以複用,只需要理解剩餘 10% 特定功能需求就可以搞懂。
類似下面這張冰山圖:
冰山之下就屬於那 90%,我們基於一個看似簡單的 “按鈕” 組件,來梳理整個組件庫的核心流程,就可以幫助我們快速、精準的搞懂整份源碼,所以我們的高潮 MVP 就是搞懂一個 “按鈕” 組件的全流程。
瞭解上下文
理解我們的高潮 MVP 目標是什麼了之後,接下來就是帶着這個目標先詳細讀一下文檔中關於 Button 的所有相關說明,可以看到這個按鈕包含如下內容:
通過右側的目錄,瞭解到一個按鈕首先會有基礎內容,包含 default
、primary
、info
、success
、warning
和 error
這幾類,然後需要處理:
-
邊框相關:虛線
-
尺寸相關:尺寸、行政
-
顏色:自定義顏色
-
狀態:文本、禁用、加載中
-
事件
上述表明了這個 Button 可以達到的效果,可以完成的操作,瞭解之後,接着可以瞭解按鈕相關的使用 API,通過 API 以及可以達到的效果,我們大致可以理解這個按鈕接收的輸入和輸出有哪些。
一個一個使用 Button 的例子長什麼樣子:
<template>
<n-space>
<n-button>Default</n-button>
<n-button type="primary">Primary</n-button>
<n-button type="info">Info</n-button>
<n-button type="success">Success</n-button>
<n-button type="warning">Warning</n-button>
<n-button type="error">Error</n-button>
</n-space>
</template>
瞭解如何開啓項目
通常開源項目比較方便的一點是它會有詳細的文檔,同時它非常渴望有貢獻者加入,所以會有完善的 貢獻指南,比如 NaiveUI 的貢獻指南 [4] 如下:
通過貢獻指南,你能夠了解如何安裝依賴、處理一些啓動項目的問題,能夠把項目跑起來進行調試,這通常是你瞭解整個代碼運行過程的初次體驗。
理解目標項目的項目結構
通常你到這個步驟時,你應該需要知道如下內容:
-
你已經理解了你的目標,高潮 MVP 是什麼
-
你理解了你目標內容作爲一個功能特性,它的輸入和輸出是什麼
-
你理解此項目的技術棧是什麼,如何把項目跑起來
對應到 NaiveUI 我們的這三點分別如下:
-
高潮 MVP:跑通一個 Button 並能夠使用,保有和現有 Button 一樣的特性,接收一樣的輸入,產生一樣的輸出
-
Button 包含邊框、尺寸、顏色、狀態、事件等相關的內容,輸入這些參數,產出對應條件下的輸出
-
項目的技術棧是 Vue3、TypeScript,構建工具是 Vite,同時使用了 CSS BEM 框架 CSS Render[5],同時包管理工具使用 pnpm
理解這三點之後,接下來我們就需要對照着源碼來理解一下整份文件目錄,瞭解各個目錄之前的依賴關係,見下圖。
我們可以先了解一下大致每個文件夾是幹什麼的:
-
src
:這個是主要放組件庫相關的組件代碼,以及導出一些國際化、樣式、主題定製相關的內容,一般是一個開源項目的核心開發目錄 -
scripts
:一些運行代碼、構建、發版相關的腳本邏輯 -
theme
:則爲 NaiveUI 內置的默認主題,類似這種組件庫一般都允許用戶自定義主題,整個 NaiveUI 各個組件在使用各種 UI 屬性時都是遵從這套主題進行設置的,也就是可以修改 theme 裏面的內容,或者自己完全自定義一套主題 -
.github
、.husky
等都是一些配置,無需過多關注,可以直接加入到你的 MVP 模板工程裏 -
playground
是用於此時代碼在各種環境下運行的支持代碼,如 SSR 等 -
demo
則是引入src
相關內容用於展示組件實際效果的網站例子,實際上對於 NaiveUI 也就是我們之前看到的文檔官網 -
其他的如
build
、design-notes
等是構建產物,或者一些主題設計的筆記等,基本上不屬於本次源碼需要閱讀的部門,看興趣的同學可以看看
然後就是一些用於各種工程化配置的文件如:
-
.prettierrc
:Prettier 相關 -
.gitignore
:Git 相關 -
.eslintrc.js
:ESLint 相關 -
babel.config.js
:Babel 相關 -
jest.config.js
:Jest 測試相關 -
postcss.config.js
:處理 CSS 相關 -
tsconfig.xx.json
處理 TypeScript 相關 -
vite.config.js
:Vite 構建工具相關的配置
以及一些和項目強相關,用於瞭解整個想法發展上下文的 CHANGELOG.xx.md
,還有我們之前提到的用於跑通代碼的 CONTRIBUTING
貢獻指南。
有點看懵了。🤯
創建你的高潮 MVP 項目
瞭解了整個 NaiveUI 的項目目錄結構之後,我們就可以着手創建我們的高潮 MVP 項目了,但在這之前我們可以再進行一波簡化,即我們有些內容可以不要:
-
針對目錄的來說
-
.github
、.husky
、playground
、scripts
這種我們可以不要,我們只需要測試最基礎的環境,以及在開發時可以跑通即可 -
theme
這種只是整個 NaiveUI 遵循的設計體系,在其他部分會遵循這個體系,但是不會直接引用,所以我們也可以不要 -
這樣我們只剩下
demo
和src
,而更近一步,我們可以把 demo 做到 src 裏面,整個 src 我們將其職責變爲高潮 MVP 網站入口,然後原剩下的 src 下面的代碼則用於導入到 src 入口文件裏面使用 -
針對配置文件來說:
-
測試相關的,Jest 等我們並不需要
-
TypeScript 相關的,我們後續可以迭代,不用引入不必要的複雜度以及類型體操
-
ESLint 和 Prettier 等我們也可以不需要,依賴於編輯器默認的格式化就可,當然引入這兩個到我們初始的高潮 MVP 項目裏也不礙事
經過簡化之後,我們的高潮 MVP 項目就只需要如下幾個文件了:
-
構建項目和提供開發服務器的 Vite 相關內容:
vite.config.js
-
用於提供語法轉譯的
babel.config.js
-
項目依賴文件
package.json
-
用於跑通項目的主要代碼
src
以及index.html
入口模板
目錄結構如下:
.
├── babel.config.js
├── index.html
├── node_modules
├── package.json
├── public
├── src
├── vite.config.js
└── yarn.lock
很精簡,沒有多餘繁雜的內容對吧?同時也非常易懂。
這些剩下要創建的文件內容,從 NaiveUI 的工程目錄裏面 Copy 過來,然後安裝對應的依賴即可。
跑通流程
當我們根據源碼庫創建了我們的高潮 MVP 項目之後,現在應該可以跑起來了,只不過內容只是一個簡單的 Button,因爲爲了快速跑起來項目,我們的入口文件 src/App.vue
會如下:
<template>
<t-button>hello tuture</t-button>
</template>
<script>
import { defineComponent } from "vue";
import { TButton } from "./components";
export default defineComponent({
name: "App",
components: {
TButton,
},
});
</script>
而對應的 src/components/TButton.vue
如下:
<template>
<button>{$slots.default}</button>
</template>
<script>
import { defineComponent } from "vue";
import { TButton } from "./components";
export default defineComponent({
name: "Button"
});
</script>
接下來我們就嘗試一遍瞭解 NaiveUI 的代碼,一遍將這些主幹代碼遷移到我們的高潮 MVP 項目中來,然後確保遷移過程中能夠持續跑起來,雖然我們可能會遇到有時候一個依賴需要大量的前置依賴,所以需要遷移一大段代碼才能將項目跑起來。
找到核心入口
我們要完成一個 Button 的所有前置依賴,只需要去到 NaiveUI 對應的工程目錄文件裏面,找到 Button 對應的代碼,如下:
其實解析一下組件文件的代碼,就是下面幾部分:
-
前置的 import 依賴
-
定義組件
defineComponent
-
組件裏面處理 props 傳入與使用、自身狀態的定義與使用
-
模板代碼
-
導出組件
而上圖代碼中的所有和 TS 定義相關的內容我們都是不需要的,所以可以刪除 ButtonProps
、NativeButtonProps
、MergedProps
、XButton
這些類型定義相關的內容。
而導入部分涉及到類型定義相關的我們也可以刪除掉:
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'
刪除完這些無關的代碼之後,我們的代碼還剩下那些內容呢?
導入依賴部分:
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) {
// 定義組件狀態
const selfRef = ref<HTMLElement | null>(null)
const waveRef = ref<BaseWaveRef | null>(null)
const enterPressedRef = ref(false)
// 使用 Props 或注入全局狀態
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 組件在整個全局設計系統中的對應樣式
const { mergedClsPrefixRef, NConfigProvider } = useConfig(props)
const themeRef = useTheme(...)
const rtlEnabledRef = useRtl(...)
// 將自身狀態、全局狀態相關的主題樣式、各個 CSS 屬性的值、事件相關的內容處理之後返回給模板使用
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() {
// 處理各種組件相關的樣式渲染、事件處理相關的內容,這裏的樣式渲染對應着在文檔裏提到的 Button 可以呈現的狀態和能處理的操作
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>
)
}
})
進一步簡化代碼
從上述還剩下的代碼,我們可以看到,其實對於理解組件庫來說,我們其實絕大部分內容是在做定製主題,然後如果根據各種傳入的 props,展示不同的主題的工作,所以你會看到 Button 組件裏充斥着大量的 CSS 變量,如 this.color
、this.ghost
、this.text
、this.cssVars
,所以我們的核心就是理解這些主題是如何定製的,包含哪些變量和依賴,這些變量和依賴是如何影響 Button 可以承載不同樣式和功能的。
所以上述代碼中,有一些內容其實我們就可以刪掉了:
-
我們只需要看一個獨立的 Button 是如何運作的,所以 NButtonGroup 部分,按鈕組部分就可以不要了
-
我們也不需要處理一些獨特的適配,如 RTL(從右向左排版)等
所以我們需要近一步刪除這些代碼:
import { buttonGroupInjectionKey } from './ButtonGroup'
import useRtl from '../../_mixins/use-rtl'
const NButtonGroup = inject(buttonGroupInjectionKey, {})
以及其他使用到 buttonGroup
相關的內容。
理解輸入
通過上一步,我們基本上去除了所有無關的內容,達到了我們最終高潮 MVP 項目裏需要的 Button 的所有的、最精簡的內容,也就是說我們核心入口代碼自身和依賴的部分已經確定了,那麼接下來就需要處理全部的輸入,以及刪除這些輸入中相關的依賴與 Button 處理無關的邏輯。
我們可以看到 Button 主要有如下一種輸入:
-
文件頂部的 import 輸入
-
使用鉤子
useFormItem
、或全局狀態注入inject(...)
相關的輸入
我們可以看到,import
相關的輸入主要分爲兩類:
-
某些庫,如
vue
的導入:這個我們只需要查詢對應庫的文檔就可瞭解對於 API 的作用 -
直接依賴於自身項目的其他相對路徑導入:這個我們就需要繼續探究 NaiveUI 源碼庫的其他部分
而鉤子 useFormItem
、或全局狀態注入 inject(...)
相關的輸入則也依賴於 import
裏自身項目的其他相對路徑引入。
我們需要順着如下的這些依賴,進行依賴分析:
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 { createHoverColor, createPressedColor } from "../../_utils/color/index";
// 其中某幾項
import { useFormItem } from "../../_mixins";
// 下面的某幾項
import {
NFadeInExpandTransition,
NIconSwitchTransition,
} from "../../_internal";
import { call, createKey, getSlot, flatten } from "../../_utils";
這些葉子依賴可以直接對照着原倉庫建立對應的目錄結構和文件命名,然後把代碼拷貝過來。
對於那些非葉子依賴,我們需要再下一番功夫繼續解析其依賴,重複之前的兩項操作:
-
刪除 TS 或者其他和 Button 不相干的代碼和依賴
-
尋找其依賴的依賴,繼續上面的過程
最後就是對照着源碼的目錄結構創建一樣的結構,將處理完無關內容的代碼拷貝過去。
打個比方,對於非葉子依賴 style
:
import style from "./styles/button.cssr.js";
我們需要去到對應的文件下,查看其依賴:
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'
發現其依賴了用於進行 BEM 規範定義的 cssr
庫(自建)、以及處理動畫的一些 fadeInWidthExpandTransition
和 iconSwitchTransition
依賴,那麼接着要繼續進入這些依賴,如:
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'
發現沒有其他再需要繼續遞歸尋找的依賴了,都是引入的第三方庫,那麼就可以去查閱一下對應的第三方庫的文檔,瞭解 API 的含義即可。
如此往復進行上述的依賴分析,直至收斂,最後我們會得到一個如下的文件組織圖:
.
├── 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 個目錄來進行支撐,我們基本上可以確定組件庫中 90% 的內容是共通的,只需要理解了一個 Button 需要的所有底層依賴和設計理念,理解這個組件庫只需要再努力一步,瞭解剩下 10 % 的各組件特殊設計,就可以弄懂整個組件庫的源碼。
上述核心整理的一個 Button 的全部依賴代碼可以進入我的 Github 倉庫查閱:https://github.com/pftom/naive-app。
抽絲剝繭
當我們能夠拿到一個 Button 能夠完美運行背後所需要的所有 “必要” 和 “最簡” 的依賴之後,我們就可以邊運行這個項目,邊通過查閱資料,畫思維導圖理解這份最簡必要代碼了。
我們首先把代碼跑起來,然後逐層理解代碼邏輯,如前置的幾個鉤子函數是幹嘛的:
核心的 useTheme
鉤子是幹嘛的:
用戶自定義相關的鉤子函數又是幹嘛的,它包含哪些 CSS 變量:
Vue3 組件裏面的 setup
返回值有哪些:
最終用於渲染的 render
函數邏輯是幹嘛的:
通過查閱 Vue3 文檔、梳理整個代碼流程,然後瞭解各個分支是如何運作的,我們就能慢慢理解 Button 組件是如何跑起來的。得益於我們進行了代碼的最精簡化處理,所以整個看代碼的流程雖然會慢一點,但是整體需要理解的內容相比之前我們拿到一整份源碼,幾百上千個文件來一股腦從入口開始打斷點調試會好很多。
寫在最後
相信大家在看皮湯的這篇源碼閱讀文章之前,應該也看過各種大牛的源碼解讀文章,但是相信每個人都有自己比較獨特的看源碼技巧,雖然我這裏是拿如何看懂 NaiveUI 的源碼舉例子,但是相信所有看源碼的過程都是如此,遵循如下步驟:
-
樹立好的心理認知
-
理解高潮 MVP,又包含定位源碼最小可行性代碼需要的內容,在看源碼之前先梳理結構,確保 MVP 能夠跑起來
-
然後再在最小、最核心的源碼上進行打斷點、畫思維導圖、查閱文檔等方式幫助自己啃下源碼
這是皮湯在看 Vite、NaiveUI 源碼過程中總結出來的經驗,相信能夠爲徘徊在看源碼路上卻沒有方法的同學提供一點指引,你完全可以應用這個技巧去看其他的源碼,如 Webpack?qiankun?Ant Design?或者抖音最近發佈的 Semi Design。共勉 💪
參考資料
[1]
尤大都推薦的組件庫是如何開發出來的?: 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]
貢獻指南: https://github.com/TuSimple/naive-ui/blob/main/CONTRIBUTING.md
[5]
CSS Render: https://github.com/07akioni/css-render
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/XgQqypxGlljWesFU4UcvyA