如何做网站数据库,重庆定制网站建设,免费wordpress主题下载地址,建设工程司法解释作者 | 一只图雀来源 | 程序员巴士心理认知要到位首先要认识到#xff0c;看源码是一个开始比较枯燥、同时时间跨度相对比较长的一个过程。所以看源码的第一步是找到自己想要了解领域、或者自己所在业务领域高度相关的项目#xff0c;并且在这个领域比较出名#xff0c;且维… 作者 | 一只图雀来源 | 程序员巴士心理认知要到位首先要认识到看源码是一个开始比较枯燥、同时时间跨度相对比较长的一个过程。所以看源码的第一步是找到自己想要了解领域、或者自己所在业务领域高度相关的项目并且在这个领域比较出名且维护活跃。打个比方皮汤我因为是一名前端而前端这个领域有很多新兴的内容如 Unbundled 方案 Vite新兴框架 Svelte新汇编语言 WebAssemblyCSS 工程化方案 TailwindCSS组件库如抖音很火的开源库 Semi Design、或者社区比较火的 Vue3 组件库 NaiveUI 等。而皮汤我一直对组件库、CSS 方向比较痴迷且是组内最近负责前端工程化 CSS 方面基建的负责人之一所以让我去研究一个组件库的源码如 NaiveUI那么我是很有兴趣、动力和理由的而这也是驱动你啃下一个源码的核心驱动力之一。理解高潮 MVP其次我们看源码要有一定的技巧复杂如 React可以算作一个简单的操作系统了如果你上来通过比较简单粗暴的从代码入口开始一路打断点了解源码那你再怎么坚持也会想吐的。那这里的技巧是什么呢就像我们互联网创业一样如果你有一个大而全的点子但是你的第一步肯定不是找一个空旷的屋子备好半年的粮食准备几台电脑然后高强度开发几个月然后祈求一问世就惊艳世人。一个是这种情况非常少见二个是你也得有坚持几个月的资本和耐心。而在创业领域一个比较知名且流传深远的技巧就是 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 的例子长什么样子templaten-spacen-buttonDefault/n-buttonn-button typeprimaryPrimary/n-buttonn-button typeinfoInfo/n-buttonn-button typesuccessSuccess/n-buttonn-button typewarningWarning/n-buttonn-button typeerrorError/n-button/n-space/template了解如何开启项目通常开源项目比较方便的一点是它会有详细的文档同时它非常渴望有贡献者加入所以会有完善的 贡献指南比如 NaiveUI 的贡献指南如下通过贡献指南你能够了解如何安装依赖、处理一些启动项目的问题能够把项目跑起来进行调试这通常是你了解整个代码运行过程的初次体验。理解目标项目的项目结构通常你到这个步骤时你应该需要知道如下内容你已经理解了你的目标高潮 MVP 是什么你理解了你目标内容作为一个功能特性它的输入和输出是什么你理解此项目的技术栈是什么如何把项目跑起来对应到 NaiveUI 我们的这三点分别如下高潮 MVP跑通一个 Button 并能够使用保有和现有 Button 一样的特性接收一样的输入产生一样的输出Button 包含边框、尺寸、颜色、状态、事件等相关的内容输入这些参数产出对应条件下的输出项目的技术栈是 Vue3、TypeScript构建工具是 Vite同时使用了 CSS BEM 框架 CSS Render同时包管理工具使用 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 会如下templatet-buttonhello tuture/t-button/templatescriptimport { defineComponent } from vue;import { TButton } from ./components;export default defineComponent({name: App,components: {TButton,},});/script而对应的 src/components/TButton.vue 如下templatebutton{$slots.default}/button/templatescriptimport { 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 ../../_mixinsimport type { BaseWaveRef } from ../../_internalimport type { ExtractPublicPropTypes, MaybeArray } from ../../_utilsimport type { ButtonTheme } from ../stylesimport 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 refHTMLElement | null(null)const waveRef refBaseWaveRef | 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 } thisreturn (ComponentrefselfRefclass{[${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 refwaveRef 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 ./ButtonGroupimport useRtl from ../../_mixins/use-rtlconst NButtonGroup inject(buttonGroupInjectionKey, {})以及其他使用到 buttonGroup 相关的内容。理解输入通过上一步我们基本上去除了所有无关的内容达到了我们最终高潮 MVP 项目里需要的 Button 的所有的、最精简的内容也就是说我们核心入口代码自身和依赖的部分已经确定了那么接下来就需要处理全部的输入以及删除这些输入中相关的依赖与 Button 处理无关的逻辑。我们可以看到 Button 主要有如下一种输入文件顶部的 import 输入使用钩子 useFormItem 、或全局状态注入 inject(...) 相关的输入我们可以看到import 相关的输入主要分为两类某些库如 vue 的导入这个我们只需要查询对应库的文档就可了解对于 API 的作用直接依赖于自身项目的其他相对路径导入这个我们就需要继续探究 NaiveUI 源码库的其他部分而钩子 useFormItem 、或全局状态注入 inject(...) 相关的输入则也依赖于 import 里自身项目的其他相对路径引入。我们需要顺着如下的这些依赖进行依赖分析import { createHoverColor, createPressedColor } from ../../_utils/color/indeximport { useConfig, useFormItem, useTheme } from ../../_mixinsimport {NFadeInExpandTransition,NIconSwitchTransition,NBaseLoading,NBaseWave} from ../../_internalimport { call, createKey } from ../../_utilsimport { buttonLight } from ../stylesimport { buttonGroupInjectionKey } from ./ButtonGroupimport 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/cssrimport fadeInWidthExpandTransition from ../../../_styles/transitions/fade-in-width-expand.cssrimport 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-renderimport 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.js32 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 源码过程中总结出来的经验相信能够为徘徊在看源码路上却没有方法的同学提供一点指引你完全可以应用这个技巧去看其他的源码如 WebpackqiankunAnt Design或者抖音最近发布的 Semi Design。共勉 参考资料[1]尤大都推荐的组件库是如何开发出来的: https://mp.weixin.qq.com/s?__bizMzkxMjI3OTA3NQmid2247485296idx1sn61b6de490f9d437215cadde9502d75dfchksmc10e143cf6799d2afe13b5aecebc2b6608bd0fab3e61149b80910ce87757ceb2219651ed9a07token586597170langzh_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往期推荐移动云TeaTalk这是一场云数据库技术的深度对话
长跑11年腾讯开源的变与不变
低代码发展专访系列之一低代码平台产品的使用者都是谁
CSDN云原生Meet up深圳站与你不见不散
点分享点收藏点点赞点在看