【山竹记账前端-vue】1.搭建前端项目


大纲链接 §

[toc]


Vue-架构

1. 使用 oh-my-env 开发环境 ⇧

快速启动开发环境 ⇧

  • 下载 oh-my-env-1
  • 使用 VSCode 打开目录
    • 预装 Docker Desktop 加速镜像已配置
    • 预装插件 Dev Container
    • 已开启 wsl 等
    • 详见 搭建Docker开发环境
  • Reopen in Container 等加载完毕,可关闭当前终端窗口
  • 另外打开终端 默认使用 zsh
  • 使用 nvm install 22 最新版
    • 单独安装 pnpm:
    • 首先 npm install --global corepack@latest
    • 然后 corepack enable
    • 最后 corepack prepare pnpm@11.27.0 --activate
  • 工作目录 ~/repos 在这个目录中创建项目目录
  • 根目录 ~ 即 /root
    • 放置 .ssh 目录
    • 放置 .bashrc 文件
    • 放置 .gitconfig 文件

快速配置开发环境 ⇧

锁死 npm 版本号

  • 运行 npm config set save-prefix='' 去除 ^

配置 ssh,快速使用 github 项目

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 现代 SSH 密钥的首选推荐
ssh-keygen -t ed25519 -C "your_email@example.com"
cat ~/.ssh/id_ed25519.pub
# 将显示的文本输入github的ssh秘钥配置
# 添加host列表
ssh-keyscan github.com >> /root/.ssh/known_hosts
# 验证
ssh -T git@github.com
############################################################
############################################################
# 传统 SSH 密钥
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
cat ~/.ssh/id_rsa.pub
# 其他步骤一致

配置 git,快速使用 github 项目

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
git config --global user.name xmasuhai
git config --global user.email xmasuhai@163.com
# 设置 git push 的默认行为
git config --global push.default simple
# 禁止对包含中文等非 ASCII 字符的文件路径进行引号转义
git config --global core.quotepath false
# 将默认的文本编辑程序设置为 VS Code
git config --global core.editor "code --wait"
# 控制换行符的自动转换
git config --global core.autocrlf input
# 设置通过 git init 创建的新仓库的默认第一个主分支名称为 master
git config --global init.defaultBranch master
# 禁用忽略大小写
git config --global core.ignorecase false
# 启用长文件名称
git config --system core.longpaths true
# 查看配置
git config --list
git config --global --list

搭建前端框架 ⇧

  • 前端技术栈 Vite 8 + Vue 3 + Typescript 7(TSX) + VueRouter 5 + Pinia
  • 开发环境:全程使用 我的开发环境 oh-my-env-1
    • node@19.9.0 (npm v8.19.2) (使用nvm use system开启)
    • pnpm@6.32.3(推荐代替npm 和 yarn)
    • 使用淘宝 npm 源加速 npm config set registry=https://registry.npmmirror.com
    • 不推荐npm@8.19.2(备用)、yarn@1.22.22(备用)

后续升级记录(20260920)

  • node@22.23.2 (npm@10.9.8)
    • nvm alias system 22.23.2
    • nvm alias default system
  • pnpm@11.27.0

创建前端项目 ⇧

  • 运行 cd ~/repos
  • 运行 nvm use system 或者升级到稳定版 nvm use 22
  • 创建项目,以下命令二选一:
    • pnpm create vite@8.3.0 mangosteen-fe-0 --template vue-ts
    • pnpm create vite@8.3.0 根据脚手架提示选择选项
    • 项目名称:mangosteen-fe-0
    • 框架:Vue
    • 框架变体-Vue官方启动器:Official Vue Starter
    • 是否使用Vite 8:Yes
    • 是否使用 TypeScript 语法 Yes
    • 选择要包含的功能
      • JSX 支持
      • Router(单页面应用开发)
      • Pinia(状态管理)
      • Vitest(单元测试)
      • 端到端测试
      • Linter(错误预防)
      • Prettier(代码格式化)
    • 端到端测试框架:Cypress
    • 试验特性:
      • 使用 Oxfmt 替代 Prettier
      • 使用 typescript-native-bridge(tsgo)替代 TypeScript
    • 包管理器:pnpm
  • 按照提示执行后续命令
    • 推荐将 cd mangosteen-fe-0 改为 code mangosteen-fe-0 使用 VSCode 打开

按照提示执行后续命令 项目初始化完成

1
2
3
4
5
code mangosteen-fe-0
   pnpm install
   pnpm format
   pnpm dev
git init && git add -A && git commit -m "initial commit"

安装完毕

1
2
3
4
5
6
pnpm i
#...
Packages are hard linked from the content-addressable store to the virtual store.
  Content-addressable store is at: ~/.pnpm-store/v11
  Virtual store is at:             node_modules/.pnpm
Progress: resolved 624, reused 273, downloaded 284, added 557, done
  • 虚拟软链接地址 node_modules/.pnpm
  • 实际安装地址 .pnpm-store/v11
  • 可以节省磁盘空间

添加版本固定配置(文件) ⇧

  • pnpm config set save-prefix='' 锁定版本;还需要在 package.json 中手动去除 ^,全局替换
    • 手动维护依赖版本号更新,也可以安装相关插件提示最新可用lts版本
  • .nvmrc 运行命令 echo "22.23.2" >> .nvmrc 锁定版本;

在 package.json 中锁定 Node.js 版本 在 package.json 中添加配置

1
2
3
4
5
6
{
  "engines": {
    "node": ">=22.23.2",
    "pnpm": ">=11.27.0"
  }
}

配置 .npmrc(适用于 npm / pnpm),在项目根目录下创建或编辑 .npmrc 文件,增加以下配置:

  • echo 'engine-strict=true' >> .npmrc

    1
    
    engine-strict=true
  • 让限制对 npm 生效:默认情况下 engines 限制只对 Yarn/Pnpm 强生效或仅作警告

  • 如果使用 npm,需要在项目根目录下创建 .npmrc 文件才会开启严格检查

锁定包管理器

1
2
3
4
5
6
# 确保团队开启了 Corepack
corepack enable
# 本地全局默认配置
corepack prepare pnpm@11.27.0 --activate
# 生成项目配置 packageManager 字段,防止误用其他包管理器
corepack use pnpm@11.27.0

实现硬拦截,配合 preinstall 脚本,防止误用其他包管理器,在 package.json 中添加配置

1
2
3
4
5
{
  "scripts": {
    "preinstall": "npx only-allow pnpm"
  }
}
  • 仅可使用 pnpm 安装;运行都可

可选:修改 ~/.bashrc 中的命令别名,将其他包管理器都命名为 pnpm

1
2
alias npm='pnpm'
alias yarn='pnpm'
  • 之后如果需要使用 npm 时,只需要运行 \npm --version,添加一个反斜杠即可

写到 README.md


参考


Vite 项目在 VSCode 中根目录别名配置 @ ⇧

在 webstorm 中默认读取 js/ts.config.json 中配置的别名,VSCode 需要额外配置别名

  • 按 Ctrl + Shift + P 搜索 settings
  • 打开用户编辑 settings.json
  • 添加以下字段配置

    1
    2
    3
    4
    
    {
    "js/ts.preferences.importModuleSpecifier": "non-relative",
    "js/ts.preferences.importModuleSpecifierEnding": "minimal"
    }
  • 或者按 Ctrl + Shift + P 董凯配置面板,搜索 importModule

  • 将如图两项分别设置为 non-relative 和 minimal

  • Prefers a non-relative import based on the baseUr1 or paths configured in your jsconfig.json tsconfig.json

  • 过时的答案,丼可以根据新的提示修改 Always use alias for automatic imports

alias-1 alias-2

添加路径别名 @,配置 vite.config.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { fileURLToPath, URL } from 'node:url'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueJsx({
      transformOn: true,
      mergeProps: true,
    }),
    vueDevTools(),
  ],
  server: {
    host: true
  },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    }
  }
})

添加路径别名 @,配置 tsconfig.app.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "target": "es2023",
    "lib": [
      "ES2023",
      "DOM"
    ],
    "types": [
      "vite/client"
    ],
    "allowArbitraryExtensions": true,
    "skipLibCheck": true,
    "jsx": "preserve",
    "jsxImportSource": "vue",
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "erasableSyntaxOnly": true,
    "noFallthroughCasesInSwitch": true,
    "paths": {
      "@/*": [
        "./src/*"
      ]
    }
  },
  "include": [
    "src",
    "env.d.ts",
    "src/**/*",
    "src/**/*.vue"
  ],
  "exclude": ["src/**/__tests__/*"]
}
  • 部分配置继承了 node_modules/@vue/tsconfig/tsconfig.json

2. dev、build 和 preview ⇧

运行开发 pnpm dev

  • 开启一个 server 在 Local: http://localhost:5174
    • 这个端口是容器 Linux 的端口
    • VSCode 做了端口自动转发
    • 在宿主机(Windows)中也可访问这个端口
  • 注意需要添加 --host 参数,才能转发成功

    1
    2
    3
    4
    5
    6
    
    {
    "scripts": {
    "dev": "vite --host",
    "preview": "vite preview --host"
    }
    }

运行构建

  • 运行 pnpm build
  • 遇坑(官方已修复,现无需处理)pnpm build 过程中报第三方打包错误 node_modules/.pnpm/...
    • 处理过程就是报报错信息复制到google/ai中,尝试提示解决方法
    • 在 tsconfig.json 添加 "skipLibCheck": true 到 "compilerOptions" 字段中,可利用 自动补全查看
    • 跳过依赖库代码检查,只需要检查项目代码即可

预览项目

  • pnpm preview --host
  • 查看dist目录是否能正常运行
  • 大约等价于
    • pnpm - http-server
    • http-server -p 4173 dist

3. 修改 build path 部署到 GitHub ⇧

把打包完的代码 dist 部署到 GitHub 或服务器时必须配置 build path

  • 配置规则见 vite文档-开发或生产环境服务的公共基础路径
  • 在 vite.config.js 里添加 base: '/' 或 base: '/reponame/' 等

    • '/reponame/' 为github中仓库名称

      1
      2
      3
      4
      5
      6
      7
      8
      
      import { defineConfig } from 'vite'
      import vue from '@vitejs/plugin-vue'
      
      // https://vitejs.dev/config/
      export default defineConfig({
      base: '/mangosteen/',
      plugins: [vue()],
      })
      
  • 重新打包到 pnpm build

仅是本地预览,直接 base: '/' 就可以了

  • 但是上传 github 会有中间的项目名,base中间写仓库名称即可
  • 之后部署章节中会另外说明配属的配置,可用 node 获取环境变量
  • 目前暂时用不到

提交完开始手动部署 git init;git add .;git commit -m 'first commit'

  • git相关配置见文章 搭建docker开发环境,配置git/github,创建.ssh 密钥
  • 发布到 GitHub
    • 创建github仓库
    • 推到远程仓库
  • 手动开启 GitHub Pages(手动部署可选)
    • 在仓库 settings 中配置 Pages
    • 选择 Branch 为 master,点击 Save
    • 如果直接上传 dist 目录到源代码同一项目仓库(不推荐),则需要在 GitHub Pages 中设置路径 /project-name/dist/
    • 并且需要在 vite.config.ts 中配置 base: '/project-name/dist/',修改完重新 pnpm build
    • 可以查看 index.html 中资源路径是否已包含 /project-name/dist/
    • 重新上传 dist
    • 另外单独创建网页发布仓库,项目源代码仓库和网页仓库分开:mangosteen-xxx-1-publish
    • 注意修改 vite.config.ts 中配置 base: '/mangosteen-xxx-1-publish/',修改完重新 pnpm build
    • base 中的目录 必须与仓库名称相同
    • 路径写入项目描述 http://xxx.github.io/project-name/index.html

使用脚本部署

  • 项目目录下创建 bin/deploy_to_github.sh

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    #!/usr/bin/env bash
    rm -rf dist
    pnpm build
    cd dist
    git init
    git add .
    git commit -m deploy
    git remote add origin git@github.com:xxxx/mangosteen-xxx-1-xx.git
    git push -f origin master:master
    cd -
  • 运行 sh bin/deploy_to_github.sh

  • 添加可执行权限 chmod +x bin/deploy_to_github.sh,以后可以直接运行 bin/deploy_to_github.sh

之后会直接部署到服务器,可以删掉暂时这个 base 配置


4. 项目目录结构 ⇧

  • Linux 中运行 tree -I "node_modules|target|.git|.vscode"

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    
    .
    ├── README.md               # 项目自述文件(说明文档、运行与部署指南)
    ├── dist                    # 打包构建输出目录(执行 build 命令后生成的静态资源,用于生产环境部署)
    ├── env.d.ts                # TypeScript 环境变量声明文件(让 TS 识别 .vue 文件和 import.meta.env 的类型)
    ├── eslint.config.ts        # ESLint 配置文件(定义代码代码规范、统一团队代码风格)
    ├── index.html              # 项目的入口 HTML 文件(Vite 应用的挂载主页)
    ├── package.json            # 项目元数据及依赖管理文件(定义脚本、开发依赖、生产依赖等)
    ├── pnpm-lock.yaml          # pnpm 依赖锁定文件(确保团队成员安装完全相同的依赖版本)
    ├── pnpm-workspace.yaml     # pnpm 工作区配置文件(用于多包管理/Monorepo 架构,定义子项目路径)
    ├── public                  # 静态资源公共目录(存放不需要 Webpack/Vite 编译的资源,如网页图标 favicon.ico)
    ├── script                  # 自定义脚本目录
    │   └── deploy_to_github.sh # 自动化部署脚本(用于一键将项目打包并推送到 GitHub Pages 等平台)
    ├── src                     # 核心源代码目录
    │   ├── App.tsx             # 根组件(所有页面的外壳,页面入口)
    │   ├── main.ts             # 项目核心入口文件(初始化 Vue 实例、挂载路由、状态管理等插件)
    │   ├── pages               # 页面组件
    │   ├── const               # 通用常量
    │   ├── modules             # 模块化业务逻辑
    │   ├── components          # 通用组件
    │   ├── shared              # 共用文件
    │   ├── utils               # 通用工具函数
    │   ├── styles              # 全局或公共样式
    │   ├── assets              # 静态资源:矢量图或位图
    │   ├── apis                # 公共接口
    │   ├── hooks               # 钩子文件
    │   ├── types               # 通用类型
    │   ├── router              # 路由配置目录
    │   │   └── index.ts        # 路由核心配置文件(定义页面路径与组件的映射关系)
    │   └── stores              # Pinia 状态管理目录(全局数据共享)
    │       └── counter.ts      # 计数器状态示例文件(演示如何使用 Pinia 管理全局状态)
    ├── tsconfig.app.json       # 针对前端应用代码(src 目录)的 TypeScript 编译配置文件
    ├── tsconfig.json           # TypeScript 主配置文件(作为引用其它子配置的根配置)
    ├── tsconfig.node.json      # 针对构建工具环境(如 vite.config.ts)的 TypeScript 编译配置文件
    └── vite.config.ts          # Vite 构建工具配置文件(配置插件、别名、代理、打包规则等)
    
    # 模块化业务逻辑
    modules/
    └── order/                         # 以“订单模块”为例
    ├── docs/                      # 给人员和ai阅读的文档
    ├── api.ts                     # [可选] 仅属于该模块的后端接口 订单相关的请求函数(如 getOrderList)
    ├── components/                # 仅属于该模块的私有业务组件
    ├── styles/                    # 仅属于该模块的私有业务样式
    ├── hooks                      # 仅属于该模块的组合式逻辑(解耦 View 层)
    │   └── useXxx.ts              # 封装逻辑
    ├── useOrderStore | stores     # [可选] 该模块独享的全局状态管理
    ├── types/                     # 该模块涉及的 TypeScript 类型定义
    ├── utils/                     # 仅属于该模块的内部工具函数
    └── views/                     # 该模块的路由页面入口
        ├── OrderList.vue          # 订单列表页
        └── OrderDetail.vue        # 订单详情页
  • 添加空文件夹占位文件 .keep


5. Template V.S. TSX ⇧

两者可共存

Template ⇧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCounterStore } from './stores/counter'

const { count } = storeToRefs(useCounterStore())
const { increment } = useCounterStore()
</script>

<template>
  <h1>count {{ count }}</h1>
  <button @click="increment">+1</button>
</template>

<style scoped lang="scss"></style>

TSX ⇧

默认脚手架选项已选择配置插件 @vitejs/plugin-vue-jsx

  • 或者之后再安装 pnpm i -D @vitejs/plugin-vue-jsx

在 vite.config.ts 中添加插件配置,添加两个选项:事件对象形式切换、自动合并属性绑定子组件根元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  base: '/mangosteen-fe-0-publish/',
  plugins: [
    vue(),
    vueJsx({
      transformOn: true,
      mergeProps: true,
    }),
  ],
})
  • 重新运行 pnpm dev

注意 Vue 的 JSX 转换方式与 React 中 JSX 的转换方式不同,因此不能在 Vue 应用中使用 React 的 JSX 转换

  • 与 React JSX 语法的一些明显区别包括:
    • 可以使用 HTML attributes 比如 class 和 for 作为 props - 不需要使用 className 或 htmlFor。
    • 传递子元素给组件 (比如 slots) 的 方式不同(具名插槽函数)。
  • 使用 TSX 语法时,确保在 tsconfig.json 中配置了 "jsx": "preserve",这样的 TypeScript 就能保证 Vue JSX 语法转换过程中的完整性

JSX 类型推断 tsconfig.json

1
2
3
4
5
6
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "vue"
  }
}
  • webstorm 中注意自动识别正确的 typescript 语言解释器

使用 defineComponent 的 setup 方法写 TSX 语法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import { defineComponent  } from 'vue'
import {storeToRefs} from 'pinia'
import {useCounterStore} from '@/stores/counter.ts'

// 用在组件具名导入
export const AppTsxDemo = defineComponent({
  setup(/*props, ctx*/) {
    const { count } = storeToRefs(useCounterStore())
    const { increment } = useCounterStore()

    return () => <>
      <h1>count {count.value}</h1>
      <button onClick={increment}>+ 1</button>
    </>
  },
})

// 可用在路由懒加载的默认导入
export default AppTsxDemo

参考


函数式组件写法作为备用 ⇧

可用于无状态组件:接收 props,返回 vnodes

  • 在渲染过程中不会创建组件实例 (也就是说,没有 this)
  • 也不会触发常规的组件生命周期钩子
  • 禁用属性继承 FComponent.inheritAttrs = false

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    
    import type { SetupContext, FunctionalComponent } from 'vue'
    type FComponentProps = {
    message: string
    // ...
    }
    type Events = {
    sendMessage(message: string): void
    }
    
    // 具名函数式组件
    export function FComponent(
    props: FComponentProps,
    ctx: SetupContext<Events>
    ) {
    // const { slots, emit, attrs } = ctx
    return (
    <button
      type="button"
      onClick={() => {
        ctx.emit('sendMessage', props.message)
        Welcome1stPage?.emits['sendMessage']?.('msg child self')
      }}>
      {props.message}
    </button>
    )
    }
    // 具名函数式组件的属性和事件,必须后赋值
    FComponent.props = {
    message: {
    type: String,
    required: true
    }
    }
    FComponent.emits = {
    sendMessage: (value: unknown) => {console.log('FComponent.emits', value,)}
    }
    // 防止警告,用于DevTools等
    FComponent.displayName = 'FComponent'
    
    // 匿名函数式组件
    export const AnonymousFC: FunctionalComponent<FComponentProps, Events> = (
    props,
    context
    ) => {
    return (
    <button 
      type="button" 
      onClick={
        () => {
          context.emit('sendMessage', props.message)
          Welcome1stPage?.emits['sendMessage']?.('msg child self')
        }
      }>
      外部数据:{props.message}
    </button>
    )
    }
    
    AnonymousFC.props = {
    message: {
    type: String,
    required: true
    }
    }
    AnonymousFC.emits = {
    sendMessage: (value) => {console.log('FComponent.emits', value,)}
    }
    AnonymousFC.displayName = 'AnonymousFC'
    
    // 父组件使用
    function Parent() {
    return (
    <FComponent
      message="msg from parent"
      onSendMessage={($event: string) => {console.log('parent', $event)}}
    />
    )
    }
    

defineComponent的函数语法 Vue@3.3+ ⇧

  • 第一个参数是一个组件选项对象 setup: ComponentOptions['setup']
  • defineComponent函数实际上在运行时没有任何操作,仅用于提供类型推导
  • 为了能让该返回值在 TSX 中用作标签时提供类型推导支持

    1
    2
    3
    
    const Foo = defineComponent(/* ... */)
    // 返回类型中可提取出一个组件的实例类型
    type FooInstance = InstanceType<typeof Foo>
    

与组合式 API 和渲染函数或 JSX 一起使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import { ref,} from 'vue'

const Comp = defineComponent(
  (props) => {
    // 就像在 <script setup> 中一样使用组合式 API
    const count = ref(0)

    return () => {
      // 渲染函数h或 JSX
      return <div>count.value</div>
    }
  },
  // 其他选项,例如声明 props 和 emits。
  {
    props: {
      /* ... */
    }
  }
)

支持泛型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const Comp = defineComponent(
  <T extends string | number>(
    props: { msg: T; list: T[] }
  ) => {
    // 就像在 <script setup> 中一样使用组合式 API
    const count = ref(0)

    return () => {
      // 渲染函数h或 JSX
      return <div>{count.value}</div>
    }
  },
  // 目前仍然需要手动声明运行时的 props
  {
    props: ['msg', 'list'],
  }
)

参考


6. 引入 Vue Router 5 ⇧

  • vue-router@5.3.1

相关文件初始代码 src/views/Foo.tsx 或 src/views/Bar.tsx

1
2
3
4
5
6
7
import {defineComponent} from 'vue'

export const Foo = defineComponent({
  setup() {
    return () => <div>Foo</div>
  }
})

相关文件初始代码 src/main.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router'
import {App} from '@/App.tsx'

const app = createApp(App)

app.use(createPinia())
app.use(router)

app.mount('#app')

相关文件初始代码 src/router/index.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { createRouter, createWebHistory } from 'vue-router'
import {Foo} from '@/views/Foo.tsx'
import {Bar} from '@/views/Bar.tsx'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      name: 'Foo',
      path: '/foo',
      component: Foo,
    },
    {
      name: 'Bar',
      path: '/bar',
      component: Bar,
    },
  ],
})

export default router

相关文件初始代码 src/App.tsx

1
2
3
4
5
6
7
8
import {defineComponent} from 'vue'
import {RouterView,} from 'vue-router'

export const App = defineComponent({
  setup(/*props, ctx*/) {
    return () => <RouterView />
  },
})
  • 手动切换路由 http://localhost:5173/foo -> http://localhost:5173/bar

添加导航 src/App.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import {defineComponent} from 'vue'
import {RouterLink, RouterView,} from 'vue-router'

export const App = defineComponent({
  setup(/*props, ctx*/) {
    return () => <>
      <nav>
        <ul>
          <li><RouterLink to="/foo">foo</RouterLink></li>
          <li><RouterLink to="/bar">bar</RouterLink></li>
        </ul>
      </nav>
      <main>
        <RouterView/>
      </main>
    </>
  },
})

7. 引入其他库 ⇧

  • pinia
  • @pinia/colada
  • @vueuse/core
  • axios
  • 等后续引入

8. snippet 使用代码片段汇总 ⇧

VSCode 代码片段 ⇧

  • Ctrl + Shift + P 输入 snippet,回车
  • 选择 typescriptreact.son (TypeScript JSX)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    
    {
    "React.FC": {
    "prefix": "fc",
    "description": "React Functional Component",
    "body": [
      "export const $TM_FILENAME_BASE: React.FC = () => {",
      "",
      "  return (",
      "    <div>$1</div>",
      "  )",
      "}"
    ]
    },
    "Vue Component": {
    "prefix": "vc",
    "description": "Vue Functional Component",
    "body": [
      "import { defineComponent } from 'vue'",
      "",
      "export const $TM_FILENAME_BASE = defineComponent({",
      "  setup: (/* props, context */) => {",
      "    return () => (",
      "      <div>$1</div>",
      "    )",
      "  }",
      "})"
    ]
    },
    "Vue Component With Props": {
    "prefix": "vcp",
    "description": "Vue Functional Component With Props",
    "body": [
      "import { defineComponent, type PropType } from 'vue'",
      "",
      "export const $TM_FILENAME_BASE = defineComponent({",
      "  props: {",
      "    propName: {",
      "      type: String as PropType<string>",
      "    }",
      "  },",
      "  setup: (/* props, context */) => {",
      "    return () => (",
      "      <div>$1</div>",
      "    )",
      "  }",
      "})"
    ]
    },
    "Vue Component With Props and Styles": {
    "prefix": "vcps",
    "description": "Vue Functional Component With Props and Styles",
    "body": [
      "import { defineComponent, type PropType } from 'vue'",
      "import s from './$TM_FILENAME_BASE.module.scss'",
      "",
      "export const $TM_FILENAME_BASE = defineComponent({",
      "  props: {",
      "    propName: {",
      "      type: String as PropType<string>",
      "    }",
      "  },",
      "  setup: (/* props, context */) => {",
      "    return () => (",
      "      <div class={s.wrapper}>$1</div>",
      "    )",
      "  }",
      "})"
    ]
    },
    "Import Module SCSS": {
    "prefix": "ims",
    "body": [
      "import s from './$TM_FILENAME_BASE.module.scss';"
    ]
    }
    }

typescript.json 中状态管理文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
	"Zustand Store": {
    "prefix": "zds",
    "description": "Zustand Store",
    "body": [
      "import { create } from 'zustand'",
      "",
      "interface $2 {}",
      "",
      "export const $TM_FILENAME_BASE = create<$2>()((set, get) => ({",
      "  $1",
      "}))",
    ]
  },
	"Pinia Store": {
    "prefix": "pns",
    "description": "Zustand Store",
    "body": [
      "import { defineStore } from 'pinia'",
      "",
      "interface $2 {}",
      "",
      "export const $TM_FILENAME_BASE = defineStore('$TM_FILENAME_BAS', () => {",
      "  return {",
      "    $1",
      "  }",
      "})",
    ]
  }
}

WebStorm 模板代码 ⇧

Vue3.x TSX 文件和代码模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import {defineComponent,} from 'vue'

export const ${NAME} = defineComponent({
  name: '${NAME}',
  props: {},
  setup(/*props, ctx*/) {

    return () => (
      <>
      </>
    )
  },
})
  • 无需 components: {}, 属性配置,导入的组件直接在 TSX 中使用即可

Webstorm 实时模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import {defineComponent,} from 'vue'

export const $NAME$ = defineComponent({
  name: '$NAME$',
  props: {$VAR1$},
  setup(/*props, ctx*/) {
    $VAR3$
    return () => (
      <>
        $END$
      </>
    )
  },
})
  • 绑定系统变量,右侧的 Edit Variables(编辑变量) 按钮变成可选状态,点击
    • 在弹出的窗口中配置 $NAME$ 变量
    • 点击下拉菜单,选择或直接输入:fileNameWithoutExtension()(自动获取当前文件名,并去掉 .ts 或 .tsx 等后缀)
    • 勾选 Skip if defined(如果已定义则跳过)

带 Props 约束类型的 Vue3.x TSX 文件和代码模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {defineComponent, type PropType} from 'vue'

export const ${NAME} = defineComponent({
  name: '${NAME}',
  props: {
    /* 
      ${PROP_NAME}: {
        type: ${PROP_TYPE} as PropType<${PROP_TYPE}>,
        required: false,
        validator: (value) => true,
      }
    */
  },
  setup(/* _props, _ctx */) {

    return () => (
      <>
        ${NAME}
      </>
    )
  },
})

参考


9. 总结命令汇总 ⇧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 生成密钥
ssh-keygen -t ed25519 -C "your_email@example.com"
cat ~/.ssh/id_ed25519.pub
# 添加host列表
ssh-keyscan github.com >> /root/.ssh/known_hosts
# 验证
ssh -T git@github.com
# 配置 git
git config --global user.name xmasuhai
git config --global user.email xmasuhai@163.com
# 设置 git push 的默认行为
git config --global push.default simple
# 禁止对包含中文等非 ASCII 字符的文件路径进行引号转义
git config --global core.quotepath false
# 将默认的文本编辑程序设置为 VS Code
git config --global core.editor "code --wait"
# 控制换行符的自动转换
git config --global core.autocrlf input
# 设置通过 git init 创建的新仓库的默认第一个主分支名称为 master
git config --global init.defaultBranch master
# 禁用忽略大小写
git config --global core.ignorecase false
# 启用长文件名称
git config --system core.longpaths true
# 查看配置
git config --list
git config --global --list
# 项目相关
# 确保团队开启了 Corepack
corepack enable
# 本地全局默认配置
corepack prepare pnpm@11.27.0 --activate
pnpm create vite@8.3.0
code project_name
# 需要在项目目录中,生成项目配置 packageManager 字段,防止误用其他包管理器
corepack use pnpm@11.27.0
pnpm i
pnpm format
pnpm dev --host
pnpm build
pnpm preview --host
git init && git add -A && git commit -m "initial commit"
# 锁定版本
pnpm config set save-prefix=''
# 配置 nvmrc
echo "22.23.2" >> .nvmrc
# 配置 .npmrc
echo 'engine-strict=true' >> .npmrc
# 添加可执行权限;快速部署脚本
chmod +x bin/deploy_to_github.sh
sh bin/deploy_to_github.sh
bin/deploy_to_github.sh
# 快速使用模板初始化启动项目命令;仓库已有启动模板
# 语法:npx degit 用户名/仓库名 新项目文件夹名
npx degit xmasuhai/react-starter my-react-app

·未完待续·

参考文章

相关文章


  • 作者: Joel
  • 文章链接:
  • 版权声明
  • 非自由转载-非商用-非衍生-保持署名