大家好,我是皮汤。最近程序员巴士学习交换群里有小伙伴想要理解一下如何看源码,正好最近有一点心得感悟,之前也写过一篇理论跑通 NaiveUI 源码的文章:尤大都举荐的组件库是如何开发进去的? 源码的教训,来给大家分享一下。
心理认知要到位
首先要意识到,看源码是一个开始比拟干燥、同时时间跨度绝对比拟长的一个过程。所以看源码的第一步是找到本人想要理解畛域、或者本人所在业务畛域高度相干的我的项目,并且在这个畛域比拟闻名,且保护沉闷。
打个比方,皮汤我因为是一名前端,而前端这个畛域有很多新兴的内容,如 Unbundled 计划 Vite,新兴框架 Svelte,新汇编语言 WebAssembly,CSS 工程化计划 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 的例子长什么样子:
<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 的奉献指南如下:
通过奉献指南,你可能理解如何装置依赖、解决一些启动我的项目的问题,可能把我的项目跑起来进行调试,这通常是你理解整个代码运行过程的首次体验。
了解指标我的项目的我的项目构造
通常你到这个步骤时,你应该须要晓得如下内容:
- 你曾经了解了你的指标,低潮 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
会如下:
<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.js32 directories, 45 files
一个简略的 Button 居然要蕴含 45 个文件,32个目录来进行撑持,咱们基本上能够确定组件库中 90% 的内容是共通的,只须要了解了一个 Button 须要的所有底层依赖和设计理念,了解这个组件库只须要再致力一步,理解剩下 10 % 的各组件非凡设计,就能够弄懂整个组件库的源码。
上述外围整顿的一个 Button 的全副依赖代码能够进入我的 Github 仓库查阅:https://github.com/pftom/naiv...。
抽丝剥茧
当咱们可能拿到一个 Button 可能完满运行背地所须要的所有 “必要” 和 “最简” 的依赖之后,咱们就能够边运行这个我的项目,边通过查阅材料,画思维导图了解这份最简必要代码了。
咱们首先把代码跑起来,而后逐层了解代码逻辑,如前置的几个钩子函数是干嘛的:
外围的 useTheme
钩子是干嘛的:
用户自定义相干的钩子函数又是干嘛的,它蕴含哪些 CSS 变量:
Vue3 组件外面的 setup
返回值有哪些:
最终用于渲染的 render
函数逻辑是干嘛的:
通过查阅 Vue3 文档、梳理整个代码流程,而后理解各个分支是如何运作的,咱们就能缓缓了解 Button 组件是如何跑起来的。得益于咱们进行了代码的最精简化解决,所以整个看代码的流程尽管会慢一点,然而整体须要了解的内容相比之前咱们拿到一整份源码,几百上千个文件来一股脑从入口开始打断点调试会好很多。
写在最初
置信大家在看皮汤的这篇源码阅读文章之前,应该也看过各种大牛的源码解读文章,然而置信每个人都有本人比拟独特的看源码技巧,尽管我这里是拿如何看懂 NaiveUI 的源码举例子,然而置信所有看源码的过程都是如此,遵循如下步骤:
- 建立好的心理认知
- 了解低潮 MVP,又蕴含定位源码最小可行性代码须要的内容,在看源码之前先梳理构造,确保 MVP 可能跑起来
- 而后再在最小、最外围的源码上进行打断点、画思维导图、查阅文档等形式帮忙本人啃下源码
这是皮汤在看 Vite、NaiveUI 源码过程中总结进去的教训,置信可能为彷徨在看源码路上却没有办法的同学提供一点指引,你齐全能够利用这个技巧去看其余的源码,如 Webpack?qiankun?Ant Design?或者抖音最近公布的 Semi Design。共勉
❤️/ 感激反对 /
以上便是本次分享的全部内容,心愿对你有所帮忙^_^
喜爱的话别忘了 分享、点赞、珍藏 三连哦~
欢送关注公众号 程序员巴士,来自字节、虾皮、招银的三端兄弟,分享编程教训、技术干货与职业规划,助你少走弯路进大厂。