前言
从开始做前端这份工作,一直是使用现成的组件库,这么多年过去了,还一直停留在只会使用的阶段。那么组件库的原理是什么呢?可以自己实现一个组件库么
vite环境搭建
使用 NPM:
$ npm create vite@latest
使用 Yarn:
$ yarn create vite
使用 PNPM:
$ pnpm create vite
然后按照提示操作即可!
第一次选择react,第二次选择typescript
pnpm run dev就可以启动项目
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root">
Hello 酷狗
</div>
</body>
</html>
更改一下文本内容
建立example文件夹,用来进行测试,删掉src文件夹,建立packages文件夹,用来写组件
第一个组件button
我们先来练练手,不要慌,一点点来 创建一个Button文件夹
packages/Button/Button.tsx
import classNames from 'classnames';
import React, {PropsWithChildren} from 'react';
export type ButtonProps = {
className?: string;
};
const Button: React.FC<PropsWithChildren<ButtonProps>> = ({ className, ...props }) => {
return <button className={classNames('my-button', className)} {...props} />;
};
export default Button;
然后再写一下css
packages/Button/styles/index.less
.my-button {
background : #fff;
border : 1px solid #e4e4e4;
padding : 8px 12px;
border-radius: 4px;
cursor : pointer;
transition : all 300ms;
&:hover {
background: #f3f3f3;
}
}
packages/Button/index.tsx 然后把类型,组件,css都导出去
import Button from './Button';
export type { ButtonProps } from './Button';
export default Button;
import './styles/index.less';
为了方便全局引入,我们在package下面建立一个index.tsx文件
packages/index.tsx
export { default as Button } from './Button';
export type { ButtonProps } from './Button';
然后我们在example文件夹中引入我们写的button,看一下样式是否正常
example/App.tsx
import React from 'react';
import { Button } from '../packages/index';
function App() {
return (
<div className="App">
<h1>Vite + React 创建组件库</h1>
<section>
<Button>Click</Button>
</section>
</div>
);
}
export default App;
这就是我们的button, 现在基本完成了80%了
css上色
每一套组件库都有自己完善的样式系统,如果我们自己写这个样式系统,那么需要很长的时间,所以为了简单一些,我们使用现成的样式系统,来快速完成我们的组件,每个人的精力都是有限的,我们应该站在巨人的肩膀上,把有限的精力放在主要的事情上
所以在这里我们使用然叔推荐的样式系统UnoCSS
除此以外UnoCSS还可以有更强的可定制性和易用性。
- 完全可定制 - 没有核心实用程序,所有功能都通过预设提供。
- 无需解析、无需 AST、无需扫描,它是即时的(比 Windi CSS 或 Tailwind JIT 快 200 倍)
- ~3.5kb min+gzip - 零依赖和浏览器友好。
- 快捷方式 - 动态别名实用程序。
- 属性模式 - 在属性中分组实用程序
- 纯 CSS 图标 - 使用任何图标作为单个类。
- 检查器 - 以交互方式检查和调试。
- CSS-in-JS 运行时版本。
- CSS Scoping。
- CSS 代码拆分 - 为 MPA 提供最小的 CSS。
- 库友好 - 随你的组件库提供原子样式并安全地限定范围。
而且对vite也很友好的支持,所以我们就喜欢这样的库
引入UnoCSS
安装 UnoCSS 库。
pnpm i -D unocss@"0.44.5"
因为unocss是按需引入样式的,就是如果你的样式是变量形式的,他是不支持的,为了解决这个问题,UnoCSS 提供了安全列表选项。也就是说,把样式定义中变量的取值添加到 Safelist 中去。这样 UnoCSS 就会根据 Safelist 生成样式了。
考虑到后续会在 Safelist 中添加大量配置,所以我们将 UnoCSS 配置拆成一个新的 ts 模块,然后引用到 vite.config.ts 中。
config/unocss.ts
import { presetUno, presetAttributify, presetIcons } from "unocss";
import Unocss from "unocss/vite";
const colors = [
"white",
"black",
"gray",
"red",
"yellow",
"green",
"blue",
"indigo",
"purple",
"pink",
];
const safelist = [
...colors.map((v) => `bg-${v}-500`),
...colors.map((v) => `hover:bg-${v}-700`),
];
export default () =>
Unocss({
safelist,
presets: [presetUno(), presetAttributify(), presetIcons()],
});
vite.config.ts
export default defineConfig({
plugins: [
Unocss(),
],
});
在组件内部引入unocss
packages/Button/Button.tsx
import classNames from 'classnames'
import React, { PropsWithChildren } from 'react'
import 'uno.css'
export type ButtonProps = {
className?: string
color: string
}
const Button: React.FC<PropsWithChildren<ButtonProps>> = ({ ...props }) => {
return (
<button
className={classNames(
`
py-2
px-4
font-semibold
rounded-lg
shadow-md
text-white
bg-${props.color}-500
hover:bg-${props.color}-700
border-none
cursor-pointer
m-1
`,
props.className
)}
{...props}
/>
)
}
export default Button
此时我们五彩的button就做好了
规范化
配置eslint
执行安装命令
pnpm add eslint -D
复制代码
执行eslint初始化命令
pnpm eslint --init
复制代码
依次初始化选项
(1) How would you like to use ESLint?
选择:To check syntax and find problems
(2) What type of modules does your project use?
选择:JavaScript modules (import/export)
(3) Which framework does your project use?
选择:Vue.js
(4) Does your project use TypeScript?
选择:Yes
(5) Where does your code run?
选择:Browser
(6) What format do you want your config file to be in?
选择:JavaScript
(7) Would you like to install them now?
选择:Yes
(8) Which package manager do you want to use?
选择:pnpm
这些依赖安装完成以后,会生成.eslintrc.cjs文件
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],
"overrides": [
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
}
}
我们打开.eslintrc.cjs文件,会发现有报错
此时我们要在env中添加‘node’: true
然后在package.json中添加添加lint命令,用来进行eslint检查
"lint": "eslint . --ext .tsx,.js,.ts --fix"
然后我们执行如下命令
pnpm lint
发现错误都是我们打完包后中的文件的错误,这个包其实不用检查的,所以我们要忽略这个包
此时我们创建文件.eslintignore用来指定需要忽略eslint检查的文件
.eslintignore
# 打包文件
dist
# 依赖包
node_modules
# 文档
docs
# 测试文件
example
此时我们再次执行pnpm lint
Warning: React version not specified in eslint-plugin-react settings. See https://github.com/jsx-eslint/eslint-plugin-react#configuration .
发现这个报错
说我们没有在eslint-plugin-react中执行react版本
这个时候,我们需要在.eslintrc.cjs中添加如下config
"settings": {
"react": {
"version": "detect" // React version. "detect" automatically picks the version you have installed.
}
}
使用detect值他会自动监测react版本
此时我们再执行pnpm lint发现没有问题了
配合vscode的eslint插件使用
想想如果我们写一个代码就要lint一遍,那么效率太低了,所以我们需要借助vscode的eslint插件, 每保存一次,那么就自动lint一下,对代码进行修复
.vscode/settings.json
{
// 开启自动修复
"editor.codeActionsOnSave": {
"source.fixAll": false,
"source.fixAll.eslint": true
}
}
配置prettier
执行安装命令
pnpm add prettier -D
复制代码
在根目录下新建.prettierrc.js
module.exports = {
// 一行的字符数,如果超过会进行换行,默认为80
printWidth: 80,
// 一个tab代表几个空格数,默认为80
tabWidth: 2,
// 是否使用tab进行缩进,默认为false,表示用空格进行缩减
useTabs: false,
// 字符串是否使用单引号,默认为false,使用双引号
singleQuote: true,
// 行位是否使用分号,默认为true
semi: false,
// 是否使用尾逗号,有三个可选值"<none|es5|all>"
trailingComma: "none",
// 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
bracketSpacing: true
}
运行该命令,会将我们项目中的文件都格式化一遍
安装vscode的prettier-code format插件
用于在代码保存的时候格式化文件
.vscode/settings.json
{
// 开启自动修复
"editor.codeActionsOnSave": {
"source.fixAll": false,
"source.fixAll.eslint": true
},
// 保存的时候自动格式化
"editor.formatOnSave": true,
// 默认格式化工具选择prettier
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
解决eslint和prettier的冲突
在理想的状态下,eslint与prettier应该各司其职。eslint负责我们的代码质量,prettier负责我们的代码格式。但是在使用的过程中会发现,由于我们开启了自动化的eslint修复与自动化的根据prettier来格式化代码。所以我们已保存代码,会出现屏幕闪一起后又恢复到了报错的状态。
这其中的根本原因就是eslint有部分规则与prettier冲突了,所以保存的时候显示运行了eslint的修复命令,然后再运行prettier格式化,所以就会出现屏幕闪一下然后又恢复到报错的现象。这时候你可以检查一下是否存在冲突的规则。
查阅资料会发现,社区已经为我们提供了一个非常成熟的方案,即eslint-config-prettier + eslint-plugin-prettier。
- eslint-plugin-prettier: 基于 prettier 代码风格的 eslint 规则,即eslint使用pretter规则来格式化代码。
- eslint-config-prettier: 禁用所有与格式相关的 eslint 规则,解决 prettier 与 eslint 规则冲突,确保将其放在 extends 队列最后,这样它将覆盖其他配置
安装依赖
pnpm add eslint-config-prettier eslint-plugin-prettier -D
复制代码
在 .eslintrc.json中extends的最后添加一个配置
{
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:@typescript-eslint/recommended',
+ // 新增,必须放在最后面
+ 'plugin:prettier/recommended'
],
}
复制代码
最后重启vscode,你会发现冲突消失了,eslint与prettier也按照我们预想的各司其职了。
配置husky
husky主要是在代码提交的时候,进行eslint检测,防止不符合规范的代码提交到了代码库
husky是一个管理git hook的工具,git hook是我们在提交代码的时候会触发的钩子
安装依赖
pnpm add husky -D
在package.json中添加如下命令
"prepare": "husky install"
该命令会在pnpm install之后运行,这样其他克隆该项目的同学就在装包的时候就会自动执行该命令来安装husky。这里我们就不重新执行pnpm install了,直接执行pnpm prepare,这个时候你会发现多了一个.husky目录。
然后使用husky命令添加pre-commit钩子
pnpm husky add .husky/pre-commit "pnpm lint && pnpm format"
现在当我们执行git commit的时候就会执行pnpm lint与pnpm format,当这两条命令出现报错,就不会提交成功。以此来保证提交代码的质量和格式。