大家好,关于用户反馈网站源码分享很多朋友都还不太明白,今天小编就来为大家分享关于用户反馈页面图片的知识,希望对各位有所帮助!
大家好,我是皮汤。最近程序员巴士学习交流群里有小伙伴想要了解一下如何看源码,正好最近有一点心得感悟,之前也写过一篇实际跑通NaiveUI源码的文章:尤大都推荐的组件库是如何开发出来的?[1]源码的经验,来给大家分享一下。
心理认知要到位
首先要认识到,看源码是一个开始比较枯燥、同时时间跨度相对比较长的一个过程。所以看源码的第一步是找到自己想要了解领域、或者自己所在业务领域高度相关的项目,并且在这个领域比较出名,且维护活跃。
打个比方,皮汤我因为是一名前端,而前端这个领域有很多新兴的内容,如Unbundled方案Vite,新兴框架Svelte,新汇编语言WebAssembly,CSS工程化方案TailwindCSS,组件库如抖音很火的开源库SemiDesign[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\n<n-space>\n\n<n-button>Default</n-button>\n\n<n-buttontype=&34;>Primary</n-button>\n\n<n-buttontype=&34;>Info</n-button>\n\n<n-buttontype=&34;>Success</n-button>\n\n<n-buttontype=&34;>Warning</n-button>\n\n<n-buttontype=&34;>Error</n-button>\n\n</n-space>\n\n</template>\n
了解如何开启项目
通常开源项目比较方便的一点是它会有详细的文档,同时它非常渴望有贡献者加入,所以会有完善的贡献指南,比如NaiveUI的贡献指南[4]如下:
通过贡献指南,你能够了解如何安装依赖、处理一些启动项目的问题,能够把项目跑起来进行调试,这通常是你了解整个代码运行过程的初次体验。
理解目标项目的项目结构
通常你到这个步骤时,你应该需要知道如下内容:
你已经理解了你的目标,高潮MVP是什么你理解了你目标内容作为一个功能特性,它的输入和输出是什么你理解此项目的技术栈是什么,如何把项目跑起来
对应到NaiveUI我们的这三点分别如下:
高潮MVP:跑通一个Button并能够使用,保有和现有Button一样的特性,接收一样的输入,产生一样的输出Button包含边框、尺寸、颜色、状态、事件等相关的内容,输入这些参数,产出对应条件下的输出项目的技术栈是Vue3、TypeScript,构建工具是Vite,同时使用了CSSBEM框架CSSRender[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入口模板
目录结构如下:
.\n\n├──babel.config.js\n\n├──index.html\n\n├──node_modules\n\n├──package.json\n\n├──public\n\n├──src\n\n├──vite.config.js\n\n└──yarn.lock\n
很精简,没有多余繁杂的内容对吧?同时也非常易懂。
这些剩下要创建的文件内容,从NaiveUI的工程目录里面Copy过来,然后安装对应的依赖即可。
跑通流程
当我们根据源码库创建了我们的高潮MVP项目之后,现在应该可以跑起来了,只不过内容只是一个简单的Button,因为为了快速跑起来项目,我们的入口文件src/App.vue会如下:
<template>\n\n<t-button>hellotuture</t-button>\n\n</template>\n\n\n\n<script>\n\nimport{defineComponent}from&34;;\n\nimport{TButton}from&34;;\n\n\n\nexportdefaultdefineComponent({\n\nname:&34;,\n\ncomponents:{\n\nTButton,\n\n},\n\n});\n\n</script>\n
而对应的src/components/TButton.vue如下:
<template>\n\n<button>{$slots.default}</button>\n\n</template>\n\n\n\n<script>\n\nimport{defineComponent}from&34;;\n\nimport{TButton}from&34;;\n\n\n\nexportdefaultdefineComponent({\n\nname:&34;\n\n});\n\n</script>\n
接下来我们就尝试一遍了解NaiveUI的代码,一遍将这些主干代码迁移到我们的高潮MVP项目中来,然后确保迁移过程中能够持续跑起来,虽然我们可能会遇到有时候一个依赖需要大量的前置依赖,所以需要迁移一大段代码才能将项目跑起来。
找到核心入口
我们要完成一个Button的所有前置依赖,只需要去到NaiveUI对应的工程目录文件里面,找到Button对应的代码,如下:
其实解析一下组件文件的代码,就是下面几部分:
前置的import依赖定义组件defineComponent组件里面处理props传入与使用、自身状态的定义与使用模板代码导出组件
而上图代码中的所有和TS定义相关的内容我们都是不需要的,所以可以删除ButtonProps、NativeButtonProps、MergedProps、XButton这些类型定义相关的内容。
而导入部分涉及到类型定义相关的我们也可以删除掉:
importtype{ThemeProps}from&39;\n\nimporttype{BaseWaveRef}from&39;\n\nimporttype{ExtractPublicPropTypes,MaybeArray}from&39;\n\nimporttype{ButtonTheme}from&39;\n\nimporttype{Type,Size}from&39;\n
删除完这些无关的代码之后,我们的代码还剩下那些内容呢?
导入依赖部分:
import{\nh,\nref,\ncomputed,\ninject,\nnextTick,\ndefineComponent,\nPropType,\nrenderSlot,\nCSSProperties,\nButtonHTMLAttributes\n}from&39;\nimport{useMemo}from&39;\nimport{createHoverColor,createPressedColor}from&39;\nimport{useConfig,useFormItem,useTheme}from&39;\nimport{\nNFadeInExpandTransition,\nNIconSwitchTransition,\nNBaseLoading,\nNBaseWave\n}from&39;\nimport{call,createKey}from&39;\nimport{buttonLight}from&39;\nimport{buttonGroupInjectionKey}from&39;\nimportstylefrom&39;\nimportuseRtlfrom&39;\n
组件声明部分:
constButton=defineComponent({\nname:&39;,\nprops:buttonProps,\nsetup(props){\n//定义组件状态\nconstselfRef=ref<HTMLElement|null>(null)\nconstwaveRef=ref<BaseWaveRef|null>(null)\nconstenterPressedRef=ref(false)\n\n//使用Props或注入全局状态\nconstNButtonGroup=inject(buttonGroupInjectionKey,{})\nconst{mergedSizeRef}=useFormItem(…)\nconstmergedFocusableRef=computed(()=>{…})\n\n//定义组件事件处理\nconsthandleMouseDown=(e:MouseEvent):void=>{…}\nconsthandleClick=(e:MouseEvent):void=>{…}\nconsthandleKeyUp=(e:KeyboardEvent):void=>{…}\nconsthandleKeyDown=(e:KeyboardEvent):void=>{…}\nconsthandleBlur=():void=>{…}\n\n//处理组件的主题,获取该Button组件在整个全局设计系统中的对应样式\nconst{mergedClsPrefixRef,NConfigProvider}=useConfig(props)\nconstthemeRef=useTheme(…)\nconstrtlEnabledRef=useRtl(…)\n\n//将自身状态、全局状态相关的主题样式、各个CSS属性的值、事件相关的内容处理之后返回给模板使用\nreturn{\nselfRef,\nwaveRef,\nmergedClsPrefix:mergedClsPrefixRef,\nmergedFocusable:mergedFocusableRef,\nmergedSize:mergedSizeRef,\nshowBorder:showBorderRef,\nenterPressed:enterPressedRef,\nrtlEnabled:rtlEnabledRef,\nhandleMouseDown,\nhandleKeyDown,\nhandleBlur,\nhandleKeyUp,\nhandleClick,\ncustomColorCssVars:computed(()=>{…}),\ncssVars:computed(()=>{…})\n}\n},\nrender(){\n//处理各种组件相关的样式渲染、事件处理相关的内容,这里的样式渲染对应着在文档里提到的Button可以呈现的状态和能处理的操作\nconst{$slots,mergedClsPrefix,tag:Component}=this\nreturn(\n<Component\nref=&34;\nclass={[\n`${mergedClsPrefix}-button`,\n`${mergedClsPrefix}-button–${this.type}-type`,\n{\n[`${mergedClsPrefix}-button–rtl`]:this.rtlEnabled,\n[`${mergedClsPrefix}-button–disabled`]:this.disabled,\n[`${mergedClsPrefix}-button–block`]:this.block,\n[`${mergedClsPrefix}-button–pressed`]:this.enterPressed,\n[`${mergedClsPrefix}-button–dashed`]:!this.text&&this.dashed,\n[`${mergedClsPrefix}-button–color`]:this.color,\n[`${mergedClsPrefix}-button–ghost`]:this.ghost//requiredforbuttongroupbordercollapse\n}\n]}\ntabindex={this.mergedFocusable?0:-1}\ntype={this.attrType}\nstyle={this.cssVarsasCSSProperties}\ndisabled={this.disabled}\nonClick={this.handleClick}\nonBlur={this.handleBlur}\nonMousedown={this.handleMouseDown}\nonKeyup={this.handleKeyUp}\nonKeydown={this.handleKeyDown}\n>\n{$slots.default&&this.iconPlacement===&39;?(\n<divclass={`${mergedClsPrefix}-button__content`}>{$slots}</div>\n):null}\n<NFadeInExpandTransition></NFadeInExpandTransition>\n{$slots.default&&this.iconPlacement===&39;?(\n<spanclass={`${mergedClsPrefix}-button__content`}>{$slots}</span>\n):null}\n{!this.text?(\n<NBaseWaveref=&34;clsPrefix={mergedClsPrefix}/>\n):null}\n{this.showBorder?(…)}\n{this.showBorder?(…)}\n</Component>\n)\n}\n})\n
进一步简化代码
从上述还剩下的代码,我们可以看到,其实对于理解组件库来说,我们其实绝大部分内容是在做定制主题,然后如果根据各种传入的props,展示不同的主题的工作,所以你会看到Button组件里充斥着大量的CSS变量,如this.color、this.ghost、this.text、this.cssVars,所以我们的核心就是理解这些主题是如何定制的,包含哪些变量和依赖,这些变量和依赖是如何影响Button可以承载不同样式和功能的。
所以上述代码中,有一些内容其实我们就可以删掉了:
我们只需要看一个独立的Button是如何运作的,所以NButtonGroup部分,按钮组部分就可以不要了我们也不需要处理一些独特的适配,如RTL(从右向左排版)等
所以我们需要近一步删除这些代码:
import{buttonGroupInjectionKey}from&39;\n\nimportuseRtlfrom&39;\n\n\n\nconstNButtonGroup=inject(buttonGroupInjectionKey,{})\n
以及其他使用到buttonGroup相关的内容。
理解输入
通过上一步,我们基本上去除了所有无关的内容,达到了我们最终高潮MVP项目里需要的Button的所有的、最精简的内容,也就是说我们核心入口代码自身和依赖的部分已经确定了,那么接下来就需要处理全部的输入,以及删除这些输入中相关的依赖与Button处理无关的逻辑。
我们可以看到Button主要有如下一种输入:
文件顶部的import输入使用钩子useFormItem、或全局状态注入inject(…)相关的输入
我们可以看到,import相关的输入主要分为两类:
某些库,如vue的导入:这个我们只需要查询对应库的文档就可了解对于API的作用直接依赖于自身项目的其他相对路径导入:这个我们就需要继续探究NaiveUI源码库的其他部分
而钩子useFormItem、或全局状态注入inject(…)相关的输入则也依赖于import里自身项目的其他相对路径引入。
我们需要顺着如下的这些依赖,进行依赖分析:
import{createHoverColor,createPressedColor}from&39;\n\nimport{useConfig,useFormItem,useTheme}from&39;\n\nimport{\n\nNFadeInExpandTransition,\n\nNIconSwitchTransition,\n\nNBaseLoading,\n\nNBaseWave\n\n}from&39;\n\nimport{call,createKey}from&39;\n\nimport{buttonLight}from&39;\n\nimport{buttonGroupInjectionKey}from&39;\n\nimportstylefrom&39;\n
这些依赖里面有些自己本就是叶子依赖,并无其它依赖,如:
import{createHoverColor,createPressedColor}from&34;;\n\n\n\n//其中某几项\n\nimport{useFormItem}from&34;;\n\n\n\n//下面的某几项\n\nimport{\n\nNFadeInExpandTransition,\n\nNIconSwitchTransition,\n\n}from&34;;\n\n\n\nimport{call,createKey,getSlot,flatten}from&34;;\n
这些叶子依赖可以直接对照着原仓库建立对应的目录结构和文件命名,然后把代码拷贝过来。
对于那些非叶子依赖,我们需要再下一番功夫继续解析其依赖,重复之前的两项操作:
删除TS或者其他和Button不相干的代码和依赖寻找其依赖的依赖,继续上面的过程
最后就是对照着源码的目录结构创建一样的结构,将处理完无关内容的代码拷贝过去。
打个比方,对于非叶子依赖style:
importstylefrom&34;;\n
我们需要去到对应的文件下,查看其依赖:
import{c,cB,cE,cM,cNotM}from&39;\n\nimportfadeInWidthExpandTransitionfrom&39;\n\nimporticonSwitchTransitionfrom&39;\n
发现其依赖了用于进行BEM规范定义的cssr库(自建)、以及处理动画的一些fadeInWidthExpandTransition和iconSwitchTransition依赖,那么接着要继续进入这些依赖,如:
import{c,cB,cE,cM,cNotM}from&39;\n
它的依赖如下:
/*eslint-disable@typescript-eslint/restrict-template-expressions*/\n\nimportCSSRender,{CNode,CProperties}from&39;\n\nimportBEMPluginfrom&39;\n
发现没有其他再需要继续递归寻找的依赖了,都是引入的第三方库,那么就可以去查阅一下对应的第三方库的文档,了解API的含义即可。
如此往复进行上述的依赖分析,直至收敛,最后我们会得到一个如下的文件组织图:
.\n├──App.vue\n├──_internal\n│├──fade-in-expand-transition\n││├──index.js\n││└──src\n││└──FadeInExpandTransition.jsx\n│├──icon\n││├──index.js\n││└──src\n││├──Icon.jsx\n││└──styles\n││└──index.cssr.js\n│├──icon-switch-transition\n││├──index.js\n││└──src\n││└──IconSwitchTransition.jsx\n│├──index.js\n│├──loading\n││├──index.js\n││└──src\n││├──Loading.jsx\n││└──styles\n││└──index.cssr.js\n│└──wave\n│├──index.js\n│└──src\n│├──Wave.jsx\n│└──styles\n│└──index.cssr.js\n├──_mixins\n│├──index.js\n│├──use-config.js\n│├──use-form-item.js\n│├──use-style.js\n│└──use-theme.js\n├──_styles\n│├──common\n││├──_common.js\n││├──index.js\n││└──light.js\n│├──global\n││└──index.cssr.js\n│└──transitions\n│├──fade-in-width-expand.cssr.js\n│└──icon-switch.cssr.js\n├──_utils\n│├──color\n││└──index.js\n│├──cssr\n││├──create-key.js\n││└──index.js\n│├──index.js\n│├──naive\n││├──index.js\n││└──warn.js\n│└──vue\n│├──call.js\n│├──flatten.js\n│├──get-slot.js\n│└──index.js\n├──assets\n│└──logo.png\n├──button\n│├──src\n││├──Button.jsx\n││└──styles\n││└──button.cssr.js\n│└──styles\n│├──_common.js\n│├──index.js\n│└──light.js\n├──components\n│└──Button.jsx\n├──config-provider\n│└──src\n│└──ConfigProvider.js\n└──main.js\n\n32directories,45files\n
一个简单的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?AntDesign?或者抖音最近发布的SemiDesign。共勉
??/感谢支持/
以上便是本次分享的全部内容,希望对你有所帮助^_^
喜欢的话别忘了分享、点赞、收藏三连哦~
欢迎关注公众号程序员巴士,来自字节、虾皮、招银的三端兄弟,分享编程经验、技术干货与职业规划,助你少走弯路进大厂。
参考资料
[1]
尤大都推荐的组件库是如何开发出来的?:https://mp.weixin.qq.com/s?__biz=MzkxMjI3OTA3NQ==&mid=2247485296&idx=1&sn=61b6de490f9d437215cadde9502d75df&chksm=c10e143cf6799d2afe13b5aecebc2b6608bd0fab3e61149b80910ce87757ceb2219651ed9a07&token=586597170&lang=zh_CN#rd
[2]
SemiDesign: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]
CSSRender:https://github.com/07akioni/css-render
用户反馈网站源码分享和用户反馈页面图片的问题分享结束啦,以上的文章解决了您的问题吗?欢迎您下次再来哦!
