保姆级教程-从0开始搭建一个规范的React+Typescript+Vite+Unocss项目

2,551 阅读12分钟

前言

在工作时,我们往往使用公司脚手架搭建项目或者直接Copy旧项目配置,很少能有机会自己去搭建一个项目。 私底下想做点东西,用creat-react-app或者vite创建一个项目,就发现很不习惯,因为需要装一大堆的配置。

因此为了更好的摸鱼🐟节省时间,顺便接触下工程化的东西,我试着去建一个标准化的React+Ts+vite项目。

这篇文章就是搭建的过程,采用的版本都是最新的,所以不用太担心出现版本相关的问题。

命令行操作单独列出来了,方便不想配置全部只想CV部分的同学😀,欢迎提供意见和建议!

image.png

image.png

新建一个文件夹,开始项目搭建

mkdir template-ts-react
cd template-ts-react

包管理工具

项目采用pnpm作为包管理工具,pnpm的依赖包被存在在统一的位置,可以节约磁盘空间。即使是不同版本的依赖,也仅有版本之间不同的文件被存储起来。依赖的安装速度也更快。

npm install -g pnpm
pnpm init

(可选) 将pnpm切换成tabao镜像

# 查看pnpm源
pnpm get registry
# 切换tabao镜像
pnpm config set registry https://registry.npmmirror.com
pnpm install

# 还原
pnpm config set registry https://registry.npmjs.org

这样设置源也有一些问题,自己用用到还算好,但如果碰到日常开发使用公司的私有源就有点麻烦。

使用镜像源管理工具nrm(NPM registry manager),能很好解决这个问题。

pnpm i -g nrm
nrm use taobao
# 添加私有源
nrm add <registry> <url>
nrm del <registry>
# 查看源
nrm ls

image.png

🐱切换源之后pnpm全局安装会出现 ERR_PNPM_REGISTRIES_MISMATCH问题

image.png

按提示重新安装下好了
pnpm i -g
pnpm i -g pnpm

vite

Vite原生支持ESM和Typescript,支持自动热更新,构建速度也比webpack快。除了陈年老项目实在想不出不用的理由。。。

pnpm i vite -D

在项目根目录新建一个index.html,为作为入口文件。

# 会开启一个本地服务器,就可以直接打开这个index.html页面
pnpm vite

vite v1版本使用Koa开启本地服务器,v2及以上版本使用connect中间件的形式, live-server也是使用connect中间件的形式

image.png

根目录下新建下一个vite.config.ts,安装下@vitejs/plugin-react,开启HMR特性.

pnpm i @vitejs/plugin-react -D
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  base: './',
  plugins: [react()],
})

引入React 和 Typescript

React核心代码位于React包中,虚拟DOM相关代码在React的Reconciler包中,使用虚拟DOM对接不同的宿主环境,调用对应的API,就能实现多平台的渲染能力。

image.png

在浏览器和Nodejs宿主环境使用ReactDOM。

pnpm i react react-dom

这里以版本的形式列出了部分React的变化,标记红色粗体部分在后续的操作中会接触到 image.png

并发模式

在项目中使用React第一步就是需要先创建一个React root,用于在浏览器DOM中显示元素,React v16和v17版本创建的项目有些是使用ReactDOM.render(<App />, document.getElementById('root'));,v18则全面开启并发模式(ConCurrent),实现并发更新.

差异如下:

image.png

// src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'

import App from './App'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)
// src/App.tsx
function App() {
  return <div>Hello World</div>
}

export default App

新的JSX转换方式

浏览器本身并不支持JSX,v17之前的旧版本JSX需要借助babel-plugin-transform-react-jsx转化成React.createElement,因此即便没使用React也需要显示引入React.

转换后的结果如下:

import React from 'react';

function App() {
  return React.createElement('h1', null, 'Hello world');
}

得益于v17版本引入的全新的JSX转换,即使无需引入React也能够使用JSX.

// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';

function App() {
  return _jsx('h1', { children: 'Hello world' });
}

Typescript

继续开发时就会发现ts不支持JSX的语法,还需外要额外配置下ts.

image.png

# 安装下typescript和React的类型声明
pnpm i typescript  @types/react @types/react-dom -D
# 生成tsconfig.json
tsc -init

设置下tsconfig.json,找到"jsx": "preserve"这一项,修改值为"jsx": "react-jsx" , 此时TS的报错问题就解决了.

tsconfig.ts中添加下需要编译处理的文件列表:

  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "types/*.d.ts",
    "vite.config.ts"
  ],

对根目录的index.html做下修改,设置根结点,以模块形式引入main.tsx,就建立一个最基本的结构了.

<!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"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

执行pnpm vite就可以启动本地服务器在浏览器上可以看到渲染出的内容了

pnpm vite

为了使用方便,项目需要添加下别名.因为node本身并不支持ts,需要安装下node的类型定义.

pnpm i @types/node -D

vite.config.ts中添加alias

  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  }

实际使用别名,还需要处理ts问题.

image.png

tsconfig.json中添加下关于baseUrlpath的配置.

"baseUrl": "./",
"paths": {"@/*": ["src/*"]}

代码格式

📏 没有规范的代码格式会导致:

  • 代码格式风格不统一,要花更多的时间看代码
  • 没有统一的规范,会导致在多人协作的代码提交中会有很多的格式修改,造成不必要的消耗。非常影响排查问题

ESLint

ESLint 是一个根据方案识别并报告 ECMAScript/JavaScript 代码问题的工具,其目的是使代码风格更加一致并避免错误。

# 初始化eslint配置
npx eslint --init

就直接使用现成的ESlint标准了,不折腾了

image.png

根目录新建一个.eslintignore,里面存放Eslint不检测的文件

node_modules
.eslintrc.cjs
dist

Prettier

使用Prettier格式化代码,但是同时启用ESLint+Prettier,ESlint会先执行,Prettier后执行,导致代码格式反复横跳。

pnpm i prettier eslint-plugin-prettier eslint-config-prettier -D

Eslint和prettier的冲突处理可以参考下prettier/eslint-plugin-prettier的配置方式

根目录下新建.prettierrc.cjs,添加下Prettier配置

module.exports = {
  printWidth: 100, //单行长度
  tabWidth: 2, //缩进长度
  useTabs: false, //使用空格代替tab缩进
  semi: false, //句末使用分号
  singleQuote: true, //使用单引号
  quoteProps: 'as-needed', //仅在必需时为对象的key添加引号
  jsxSingleQuote: true, // jsx中使用单引号
  bracketSpacing: true, //在对象前后添加空格-eg: { foo: bar }
  jsxBracketSameLine: true, //多属性html标签的‘>’折行放置
  arrowParens: 'always', //单参数箭头函数参数周围使用圆括号-eg: (x) => x
  requirePragma: false, //无需顶部注释即可格式化
  insertPragma: false, //在已被preitter格式化的文件顶部加上标注
  endOfLine: 'auto', //结束行形式
  embeddedLanguageFormatting: 'auto', //对引用代码进行格式化
}

.eslintrc.js添加下规则,extends添加plugin:prettier/recommended, 关闭冲突规则。 plugins中添加prettier,使得Eslint可以通过Prettier格式化代码

  extends: ['standard-with-typescript','plugin:react/recommended','plugin:prettier/recommended'],
  plugins: ['react', 'prettier'],
  rules: {
    'react/jsx-use-react': 0, // React V17开始JSX已经不再需要引入React
    'react/react-in-jsx-scope': 0, // 同上
    'import/first': 0, // 消除绝对路径必须要在相对路径前引入,
    'no-mixed-spaces-and-tabs': 2, // 禁止空格和 tab 的混合缩进
    'no-debugger': 2, // 禁止有debugger
    'space-infix-ops': 2, // 要求操作符周围有空格
    'space-before-blocks': 2, // 要求语句块之前有空格
    '@typescript-eslint/explicit-function-return-type': 0, // 禁止函数必须要定义返回类型
  },

Stylelint

使用Stylelint规范化CSS,可以控制下CSS属性的书写顺序,看起来更加工整。

这里以less为例子,使用Stylelint需要安装对应的less相关的库

pnpm i less stylelint stylelint-config-standard-less postcss-less -D

可以根据项目和习惯自定义CSS属性的属性顺序,也可以直接使用社区方案

  1. 自定义CSS属性的属性顺序
pnpm i stylelint-order -D

.stylelintrc.cjs按照下面代码进行配置,order/properties-order中存放指定属性的前后顺序

module.exports = {
  extends: ['stylelint-config-standard-less'],
  overrides: [{ files: ['**/*.less'], customSyntax: 'postcss-less' }],
  plugins: ['stylelint-order'],
  rules: {
    'order/order': ['custom-properties', 'declarations'],
    'order/properties-order': ['width', 'height'],
  },
}

image.png

  1. 使用社区方案 在awesome-stylelint上有其他已经配置好的方案,开盒即用

这里就以stylelint-config-recess-order为例

pnpm i stylelint-config-recess-order -D
module.exports = {
  extends: ['stylelint-config-standard-less', 'stylelint-config-recess-order'],
  overrides: [{ files: ['**/*.less'], customSyntax: 'postcss-less' }],
  plugins: [],
  rules: {},
}

环境配置

环境变量

针对开发/生产环境项目需要制定不同的配置,根目录下新建.env,.env.development,.env.production

  • .env:所有环境都需要用到的环境变量
  • .env.development: 开发环境需要用到的环境
  • .env.production: 生产环境需要用到的环境

vite会将对应的环境变量注入到import.meta.env里去。vite会拦截非VITE开头,就不会注入到import中去.因此环境变量的命名必须以VITE_开头,

这里就以.env.development为例子

# 接口路径
VITE_BASE_API = '/api'
# 开发环境地址前缀
VITE_PUBLIC_PATH = '/'
# 密钥
VITE_AK = 'AK1'

在types目录下添加vite-env.d.ts,声明下 vite 环境变量的类型

/// <reference types="vite/client" />

declare interface ImportMetaEnv {
  readonly VITE_BASE_API: string
  readonly VITE_PUBLIC_PATH: string
  readonly VITE_AK: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

调整tsconfig.ts中的属性,否则调用import.meta.env会提示类型问题.

"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler" 

在APP中打印import.meta.env,就能打印出development的环境变量. DEV字段为true说明运行在开发环境,PROD为true说明运行在生产环境. image.png

请求

工作中最常用的请求库还是axios,配置简单也不复杂.

pnpm i axios

src/utils目录下新建一个service.ts,进行axios的配置

import axios from 'axios'

const request = axios.create({
  baseURL: import.meta.env.VITE_BASE_API, // 域名配置,可添加变量配置文件定义
  headers: {
    Authorization: `Bearer ${token}`,// token从Cookie中获取
    'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
  },
  timeout: 5000, // 请求超时时间
})

//请求拦截
request.interceptors.request.use(
  (config) => config,
  (err) => Promise.reject(err.response),
)

// 响应拦截
request.interceptors.response.use(
  (response) => {
    // 有些情况下接口未必是RESTful风格,C相关的接口返回异常时状态码会小于0
    if (response.status !== 200) return Promise.reject(response.data)
    // 一般会和后端约定一些code,分别进行处理,这里直接返回了不做处理
    return response.data
  },
  (err) => Promise.reject(err.response)
)

export default request

cookie操作一般都是使用js-cookie进行封装.在src/utils目录下新建一个cookie.ts简单封装下token操作.

import Cookies from "js-cookie"

const SYS_TOKEN = 'SYS_TOKEN'

export const getToken = () => {
  return Cookies.get(SYS_TOKEN)
}
export const setToken = (token: string) => {
  Cookies.set(SYS_TOKEN, token)
}
export const removeToken = () => {
  Cookies.remove(SYS_TOKEN)
}

一般来说,同一个项目的接口风格相对固定。在types目录下添加api.d.ts存放请求数据类型声明

interface ApiResData<T> {
  code: number
  data: T
  message: string
}

我个人习惯把请求封装好,这样直接调用也方便维护,存放在src/api目录下,按照不同的模块进行分类.

以登录页面为例子,调用loginApi进行登录,请求获取的数据也能有类型提示.

image.png

开发环境下需要配置下proxy处理跨域问题,生产环境下使用nginx.

vite.config.ts中配置下server相关属性.

server: {
  /** 设置 host: true 才可以使用 Network 的形式,以 IP 访问项目 */
  host: true, // host: "0.0.0.0"
  open: false,
  /** 跨域设置允许 */
  cors: true,
  /** 端口被占用时,是否直接退出 */
  strictPort: false,
  proxy: {
    "/api": {
      target: "对应的url",
      /** 是否允许跨域 */
      changeOrigin: true
    }
  },
},

规范化提交

Commitizen

没有明确的提交规范会导致commit message非常随意,在查找定位问题和处理合并冲突就会很头疼了😂。 使用Commitizen规范下commit message。

# 初始化下git
git init

# 安装配置Commitizen
pnpm install commitizen -D
commitizen init cz-conventional-changelog --pnpm --save-dev --save-exact

# 后续就可以使用commitizen
git cz

image.png

husky

🐶 husky是一个增强的 git hook 工具,可以在 git hook 的各个阶段执行我们在package.json中配置好的script。

# 安装husky
pnpm dlx husky-init
pnpm install

代码检验

在commit之前,执行lint进行代码校验 在package.json中添加下lint指令,使用eslint使用自动修复在src目录下ts和tsx文件

"scripts": {
    "lint": "eslint --fix --ext .ts,.tsx src"
},
npx husky add .husky/pre-commit "pnpm run lint"

提交信息检验

(可选) 在husky中添加prepare-commit-msg

npx husky add .husky/prepare-commit-msg "exec < /dev/tty && npx cz --hook || true"

这样使用git commit会自动进入到commitizen,但是我不建议这样操作.

改变 git commit 命令原有的行为,失去像 git commit -m "chore: ..."快速提交的方式.即便输入符合规范的commit message信息,也会跳转到commitizen补全信息,而且这个命令行交互效果体验很差. image.png 但使用git commit提交当前无法限制提交的message,因此额外添加了commitlint进行判断.使用commitlint工具对git commit提交的message信息进行检验.

pnpm i @commitlint/config-conventional @commitlint/cli -D

在package.json中添加

  "scripts": {
    "commitlint": "commitlint --config commitlint.config.cjs -e -V"
  },

配置下husky的commit-msg

npx husky add .husky/commit-msg 'pnpm run commitlint'

配置下commitlint的配置文件commitlint.config.cjs

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'revert', 'build'],
    ],
    'type-case': [0],
    'type-empty': [0],
    'scope-empty': [0],
    'scope-case': [0],
    'subject-full-stop': [0, 'never'],
    'subject-case': [0, 'never'],
    'header-max-length': [0, 'always', 72],
  },
}

现在既能够支持使用git cz方式提交commit,也能使用git commit按照规范快速提交.

样式检验

  "scripts": {
    "stylelint": "stylelint \"src/**/*.less\" --fix"
  },
npx husky add .husky/pre-commit 'pnpm run stylelint'

原子化CSS

原子化 CSS 是一种将样式表拆分为最小单元的 CSS 设计方法,它倾向于小巧且用途单一的 class,并且会以视觉效果进行命名,将所有样式都分解为一些独立的类名,每个类名只包含一个属性和对应的值,通过组合这些类名来实现样式的复用.

使用原子化CSS可以先看一下大佬的文章重新构想原子化 CSS (antfu.me)

对于一些没有啥历史包袱的项目上,我觉得引入原子化css是一个不错的选择,不需要太纠结class命名和冗杂重复的css属性,也能有效减少了打包体积.

尤其在是想快速搭建一个组件库项目的情况下,使用原子化CSS也能完成样式的定制,而不用额外搭建一套CSS子系统.

这里就选择Anthony Fu大佬的UnoCSS

# 引入unocss和@iconify-json/mdi图标
pnpm i -D unocss @iconify-json/mdi
# rem转换成px
pnpm i -D @unocss/preset-rem-to-px
# 支持在JSX/TSX中valueless attributify写法
pnpm i -D @unocss/transformer-attributify-jsx
#如果项目中没有使用其他的CSS框架,可以引入unocss的样式重置
pnpm i @unocss/reset

在项目入口文件中引入Unocss

import React from 'react'
import ReactDOM from 'react-dom/client'

import '@unocss/reset/normalize.css'
import 'uno.css'

import App from './App'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

根目录下新建一个uno.config.ts文件存放Unocss设置

// uno.config.ts
import { defineConfig,presetUno,presetAttributify,presetIcons,presetTypography,presetWebFonts,transformerDirectives,transformerVariantGroup } from 'unocss'
import presetRemToPx from '@unocss/preset-rem-to-px'
import transformerAttributifyJsx from '@unocss/transformer-attributify-jsx'

export default defineConfig({
  presets: [
    //将rem单位转换成px
    presetRemToPx(),
    // 默认预设
    presetUno(),
    // 支持attributify mode,简单说就是为了避免样式写太长难维护,能将py-2 px-2这种相关属性整合起来写成p="y-2 x-4"
    presetAttributify(),
    // 图标异步导入按需加载
    presetIcons({
      collections: {
        carbon: () => import('@iconify-json/mdi').then((i) => i.icons),
      },
    }),
    presetTypography(),
    presetWebFonts(),
  ],
  transformers: [transformerAttributifyJsx(),transformerDirectives(), transformerVariantGroup()],
})

很多情况下组件都根据属性值动态生成样式,但UnoCSS默认是按需生成方式,在 class属性中使用变量是无法分析变量的取值的.还需要额外添加安全列表safelist.

import UnoCSS from 'unocss/vite'
import { presetUno, presetAttributify, presetIcons } from 'unocss'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

const colors = ['white','black','gray','red','yellow','green','blue','indigo','purple','pink',]

const icon = ['search', 'edit', 'check', 'message', 'star-off', 'delete', 'add', 'share']

const safelist = [
  ...colors.map((v) => `bg-${v}-500`),
  ...colors.map((v) => `hover:bg-${v}-700`),
  ...icon.map((v) => `i-mdi-${v}`),
]

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    UnoCSS({
      safelist,
      presets: [presetUno(), presetAttributify(), presetIcons()],
    }),
    react(),
  ],
  build: {
    // 编译时独立输出css
    cssCodeSplit: true,
  },
})

这里实现一个简易的Button组件,来检验一下效果

import { FC, PropsWithChildren, ButtonHTMLAttributes, AnchorHTMLAttributes } from 'react'

type tColor = 'black' | 'gray' | 'red' | 'yellow' | 'green' | 'blue' | 'indigo' | 'purple' | 'pink'

interface IButtonProps {
  color: tColor
  icon?: string
  link?: boolean
}

type NativeButtonProps = IButtonProps & ButtonHTMLAttributes<HTMLButtonElement>
type AnchorButtonProps = IButtonProps & AnchorHTMLAttributes<HTMLAnchorElement>
type ButtonProps = Partial<NativeButtonProps | AnchorButtonProps>

const Button: FC<PropsWithChildren<ButtonProps>> = (props) => {
  const { color = 'blue', icon, link, children, ...restProps } = props
  if (link) {
    return <a {...(restProps as AnchorHTMLAttributes<HTMLAnchorElement>)}>{children}</a>
  } else {
    return (
      <button
        className={`
  py-2 
  px-4 
  font-semibold 
  rounded-lg 
  shadow-md 
  text-white 
  bg-${color}-500 
  hover:bg-${color}-700 
  border-none 
  cursor-pointer 
  m-1
  `}
        {...(restProps as ButtonHTMLAttributes<HTMLButtonElement>)}>
        {icon && <i className={`i-mdi-${icon} p-2`}></i>}
        {children}
      </button>
    )
  }
}

export default Button

调用一下Button组件,可以看到Button效果已经能正常显示了.

<Button color='blue' icon='search'>12321</Button>

image.png

Unocss的1单位为0.25rem,大部分浏览器的html默认font-size为 16px,即 1rem = 16px. 当然其也支持直接写px.如果已经引入了@unocss/preset-rem-to-px会自动将rem转换成px.

image.png image.png

代码生成

Snippets

之前使用create-react-app搭建的项目时一直觉得那种只需要在输入几个像crf的字符,就能快速生成一段代码的方式很方便,自己搭建的项目里面肯定也要加上这样的功能。

大部分的IDE都支持自定义代码片段功能,我就以VScode为例建立一个自定义的Snippets

VScode中ctrl+shift+P搜索Snippets就能找到对应的设置。 image.png 可选择全局或者项目内设置snippets,设置后就会出现一个后缀名为code-snippets的文件。 image.png image.png Snippets有特定的语法,学习起来还要花时间,所以直接使用代码转Snippet的方案snippet-generator.app/,设置下代码和想使用的prefix前缀,cv下粘到code-snippets 文件里面就好了 image.png

hygen

在一些场景下像组件,都是有比较固定的文件结构和代码结构,直接copy旧组件还要删除多余的部分,这可太麻烦了,有时候还会带入一些bug。

既然是固定的结构那直接使用代码生成器根据指定模板生成即可。

使用hygen生成代码

pnpm i hygen -D
hygen init self

初始化之后会创建一个根目录下创建一个_templates文件夹,内部存放模板,接下来我们就创建一个名为component的模板。

hygen generator new component

模板的结构与ejs类似,前面一部分存放生成代码的位置,后面一部分就是ejs的写法,使用尖括号加百分号的标记来执行代码和插值。

---
to: src/components/<%= name %>/index.tsx
---

import React,{ FC } from "react";

interface I<%= name %>Props {
}

const <%= name %>:FC<I<%= name %>Props> = (props) => {
  return <div></div>
}

export default <%= name %>

根据这个模板可以在 src/components/指定的目录下根据模板生成指定的代码。

hygen component new Button
image.png image.png

文档

TypeDoc

📕项目开发末期后续为了更好的维护,通常要出一份文档,而写文档本身是一个相当无聊的过程。

采用TypeDoc,只要在开发过程中按规范写类型声明。能够将中的注释转换为 HTML 格式的文档或格式化的 JSON 数据。

pnpm i typedoc -D

为了测试功能,现在src/utils目录下先建立几个测试的函数和接口

然后引入到src/utils/index.ts中

npx typedoc src/utils/index.ts

会在根目录下生成一个docs的文件夹

image.png

index.html会展示项目的readme.md文件,侧边栏显示组件信息,点击就看查看详细信息.

Storybook

Storybook提供了一种可视化的方式展示组件的使用方法和效果,并支持交互式调试和快速迭代.在线调试,实时查看组件效果.

image.png
pnpm dlx storybook@latest init

根据个人的习惯,我倾向于将stories文件放到组件内部,src目录下的stories文件夹就直接删掉了

image.png

创建一个Button.stories.ts,内容如下:

import type { Meta, StoryObj } from '@storybook/react'

import Button from './index'

// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
const meta = {
  title: 'Button',
  component: Button,
  parameters: {
    // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
    layout: 'centered',
  },
  // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
  tags: ['autodocs'],
  // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
  // argTypes: {
  //   backgroundColor: { control: 'color' },
  // },
} satisfies Meta<typeof Button>

export default meta
type ButtonStory = StoryObj<typeof meta>

// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
/**
 * 默认样式
 */
export const Normal: ButtonStory = {
  args: {
    children: '默认',
  },
}

export const WithLabel: ButtonStory = {
  args: {
    icon: 'search',
    children: '默认',
  },
}

/**
 * anchor样式
 */
export const Label: ButtonStory = {
  args: {
    children: '默认',
    link: true,
    href: 'http://www.baidu.com',
  },
}

Tips:如果使用Unocss,需要在preview.ts中引入Unocss的样式文件 image.png

执行pnpm run storybook就能显示出渲染的内容的

image.png