Vue3.2+Vite+Typescript 开发(一)
项目创建
两种项目创建方式现在都是基于 vite
vue3 - 官方推荐 create vue
create-vue 是一个官方的 Vue 项目脚手架工具,用来快速创建 Vue 3 项目。默认情况下,它使用 Vite 作为构建工具
pnpm create vue@latest |
✔ Project name: … <your-project-name> |
// 如果不确定是否要开启某个功能,你可以直接按下回车键选择 No。在项目被创建后,通过以下步骤安装依赖并启动开发服务器: |
Vite を使う理由
問題点
ES モジュールがブラウザーで利用できるようになるまで、開発者はモジュール化された JavaScript を生成するネイティブの仕組みを持っていませんでした。これは、私たちが「バンドル」のコンセプトに慣れ親しんでいる理由でもあります: すなわち、ブラウザーで実行可能なようにソースモジュールをクロール、処理し、連結するツールを使用しています。
時を経て webpack や Rollup、Parcel のようなツールが登場し、フロントエンド開発者の開発体験は大きく向上されました。
しかしながら、大規模なアプリケーションが作られるようになってくると、取り扱う JavaScript の量は劇的に増加しました。大規模プロジェクトでは、数千ものモジュールが含まれることも珍しくありません。JavaScript ベースのツールを使用していては、いずれパフォーマンスのボトルネックにぶつかります: 開発サーバーを起動するのにやたらと長く待つこともあります(数分かかることさえ!)。また、Hot Module Replacement(HMR)を利用していても、ファイル編集がブラウザーに反映されるまで数秒かかることもあります。フィードバックの遅さが継続することは、開発者の生産性や幸福度に大きな影響を与える可能性があります。
Vite では新しいエコシステムの進歩を活用し、これらの問題を解決することに取り組んでいます: ブラウザーのネイティブ ES モジュールや、ネイティブにコンパイルされる言語で書かれた先進的な JavaScript ツールの利用です。
现实问题
在浏览器支持 ES 模块之前,JavaScript 并没有提供原生机制让开发者以模块化的方式进行开发。这也正是我们对 “打包” 这个概念熟悉的原因:使用工具抓取、处理并将我们的源码模块串联成可以在浏览器中运行的文件。
时过境迁,我们见证了诸如 webpack、Rollup 和 Parcel 等工具的变迁,它们极大地改善了前端开发者的开发体验。
然而,当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。基于 JavaScript 开发的工具就会开始遇到性能瓶颈:通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用模块热替换(HMR),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。
Vite 旨在利用生态系统中的新进展解决上述问题:浏览器开始原生支持 ES 模块,且越来越多 JavaScript 工具使用编译型语言编写。
遅いサーバー起動
開発サーバーがコールドスタートするとき、バンドラーベースのビルドセットアップは、アプリケーション全体を提供する前に、アプリケーション全体を隅々までクロールしてビルドする必要があります。
Vite はまず最初にアプリケーションのモジュールを 2 つのカテゴリーに分割することで、開発サーバーの起動時間を改善します: 依存関係とソースコードです。
依存関係の大部分は開発中あまり変更されないプレーンな JavaScript です。巨大な依存関係の中には、処理コストが極めて高いものがあります(例: 100 ものモジュールを持つコンポーネントライブラリー)。依存関係は、様々なモジュール形式で出力されることがあります(例: ESM または CommonJS)。
Vite は、esbuild を使用して依存関係の事前バンドルを行います。esbuild は Go 言語によって開発されており、依存関係の事前バンドルは、JavaScript ベースよりも 10 倍から 100 倍高速です。
ソースコードには変換を必要とするプレーンな JavaScript ではないものが含まれることがよくあり、頻繁に編集されます(例: JSX、CSS や Vue/Svelte コンポーネント)。また、全てのソースコードを同時に読み込む必要はありません(例: ルーティングによるコード分割)。
Vite は、ネイティブ ESM を行使してソースコードを提供します。ブラウザーは、実質的にバンドラーの仕事の一部を引き受けます: Vite はブラウザーのリクエストに応じて、ソースコードを変換し提供するのみになります。条件で囲われている動的インポートのコードは、現在の画面で使われる場合のみ処理されます。
バンドルベースの開発サーバーentry···routeroutemodulemodulemodulemodule···BundleServer ready
ネイティブ ESM ベースの開発サーバーentry···routeroutemodulemodulemodulemodule···Server ready動的インポート (コード分割点)HTTP リクエスト
遅い更新速度
バンドラーベースのビルドセットアップでファイルが編集されたとき、全てのバンドルを再構築することが非効率なことは明白です: 更新スピードはアプリケーションのサイズに応じて線形的に低下してしまうからです。
バンドラーの中には開発サーバーがメモリー上でバンドルを実行し、ファイルが変更されたときにモジュールグラフの一部のみ無効化して再処理を行うものも存在しますが、それでもバンドル全体を再構築して、ウェブページをリロードしなければなりません。バンドルの再構築にはコストがかかりますし、ページがリロードされるとアプリケーションの現在の状態は消えてしまいます。そのため、幾つかのバンドラーは HMR(ホットモジュールリプレースメント)をサポートしています: これにより、ページの変更に関係のない部分には影響を与えることなく、モジュールを「ホットリプレース」することができます。これは開発者体験を大きく改善します。- しかしながら、実際には HMR でもアプリケーションが大きくなるにつれ更新速度が著しく悪化することが分かってきました。
Vite では、HMR をネイティブ ESM 上で行います。ファイルが編集されたとき、Vite は編集されたモジュールと最も近い HMR バウンダリ間のつながりを正確に無効化することで(大抵はモジュール本体だけです)、HMR による更新はアプリケーションのサイズに関係なく一貫して高速で実行されます。
また、Vite は HTTP ヘッダーを活用して、フルページのリロードも高速化します(ここでも、ブラウザーにはもっと働いてもらいます): ソースコードモジュールのリクエストでは 304 Not Modified
を利用して条件が作成されます。そして、依存モジュールのリクエストでは、一度キャッシュされたものが再びサーバーにヒットしないよう、Cache-Control: max-age=31536000,immutable
を利用して積極的にキャッシュされます。
超高速な Vite を一度体験してしまうと、バンドルでの開発にまた耐えられるかはとても疑わしいです。
プロダクションではバンドルする理由
ネイティブ ESM が広くサポートされるようになっても、バンドルされていない ESM をプロダクション用にリリースすることは非効率です(HTTP/2 を利用していても)。これは、ネットワークのラウンドトリップの増加がネストされたインポートによって引き起こされるためです。プロダクションでは最適化されたローディングパフォーマンスを得るために、ツリーシェイキングや遅延読み込み、(キャッシュ改善のための)共通コード分割などの技術を用いつつバンドルを行うことは、より良いことです。
開発サーバーとプロダクションビルド間で、最適化された出力と一貫した動作を確保することは容易なことではありません。そのため、Vite にはあらかじめ調整されたビルドコマンドが用意されており、これには従来の常識を破る多くのパフォーマンス最適化が施されています。
なぜ esbuild でバンドルしないのか?
Vite は 開発環境で一部の依存関係を事前バンドルするために esbuild を活用していますが、本番ビルドのためのバンドラーとしては esbuild を利用しません。
Vite の現在のプラグイン API は、esbuild
をバンドラーとして使用することと互換性がありません。esbuild
の方が速いにもかかわらず、Vite は Rollup の柔軟なプラグイン API とインフラストラクチャーを採用し、エコシステムでの成功に大きく貢献しました。当面は、Rollup の方がパフォーマンスと柔軟性のトレードオフに優れていると考えています。
Rollup はパフォーマンスの向上にも取り組んでおり、v4 でパーサーを SWC に切り替えました。 そして、Rolldown と呼ばれる Rollup の Rust ポートを構築する取り組みが進行中です。 Rolldown の準備が完了すると、Vite の Rollup と esbuild の両方が置き換えられ、ビルドのパフォーマンスが大幅に向上し、開発とビルドの間の不一致が解消されます。 詳細については、Evan You の ViteConf 2023 基調講演 をご覧ください。
什么是TypeScript?
TypeScript 是具有类型语法的 JaveScript - 点击查看详情
示例1.
type Result = "pass" | "fail" |
示例 2.
const hello = names =>{ |
示例 3.
// 函数参数类型 |
基础概念.
// 基础类型: 字符串, 数字, 布尔 |
初始化项目
create vue 基于vite 内置了 ts 依赖 可忽略这个步骤 - 点击查看详情
// 0. 初始化项目,新建package.json |
git协作
点击查看详情
我们在开发大型项目时,会同时开发多个需求
git 提供了 branch(分支)功能 - 本质为指向 commit 节点对象的一个指针,当我们新建分支时,实际上是新建了一个指针并指向了当前 commit
分支操作
git branch
:列出本地分支。git branch <branch-name>
:创建新分支。git checkout <branch-name>
:切换到指定分支。git checkout -b <branch-name>
:创建并切换到新分支。git merge <branch-name>
:合并其他分支到当前分支。git branch -d <branch-name>
:删除本地分支。
大型项目,往往需要跟其他人一起协作
- git 分支合并
将一个分支上的代码合并进当前分支
git fetch origin
是用于从远程仓库获取最新的提交到本地的命令。具体含义如下:
git fetch
:从远程仓库中抓取(fetch)所有分支的更新,获取远程仓库中的最新提交和引用(tags、branches 等),但不会修改本地工作目录,也不会自动合并这些更新。origin
:是默认远程仓库的名称,表示你想要从origin
这个远程仓库获取更新。通常,origin
是你在git clone
时默认创建的远程仓库别名。
- 使用场景
git fetch origin
常用于以下场景:
- 你想了解远程仓库的最新变化,但暂时不想合并这些更改到当前分支中。
- 配合其他命令(如
git merge
或git rebase
)来手动处理从远程分支获取的更新。
- 命令工作流程
- 执行
git fetch origin
,Git 会连接到origin
远程仓库并下载所有分支的最新提交和引用。 - 这些更新会保存到你本地仓库的远程分支中,例如
origin/main
,而不会影响你当前的分支或工作目录。
- 初始化和克隆仓库
git init
:初始化一个新的 Git 仓库。git clone <repository-url>
:克隆远程仓库到本地。
- 查看仓库状态
git status
:查看当前分支的状态,显示已修改但未提交的文件。git log
:查看提交历史。git diff
:显示未提交的变更。
- 分支操作
git branch
:列出本地分支。git branch <branch-name>
:创建新分支。git checkout <branch-name>
:切换到指定分支。git checkout -b <branch-name>
:创建并切换到新分支。git merge <branch-name>
:合并其他分支到当前分支。git branch -d <branch-name>
:删除本地分支。
- 提交和推送代码
git add <file>
:添加文件到暂存区。git add .
:添加所有修改的文件到暂存区。git commit -m "<message>"
:提交暂存区中的文件,附带提交信息。git push
:将本地分支推送到远程仓库。git push origin <branch-name>
:将指定分支推送到远程仓库。
- 更新和拉取代码
git pull
:从远程仓库拉取最新的更改,并合并到当前分支。git fetch
:拉取远程仓库的最新内容但不合并,通常与git merge
一起使用。
- 冲突解决
git mergetool
:使用外部工具来帮助解决合并冲突。git diff --base <file>
:查看冲突文件的基础版本。git add <file>
:在解决冲突后,使用该命令标记冲突已解决。
- 远程仓库管理
git remote -v
:查看当前配置的远程仓库。git remote add <name> <url>
:添加新的远程仓库。git remote rm <name>
:删除指定的远程仓库。
- 回滚和撤销
git reset --hard <commit-hash>
:将当前分支重置到指定提交。git revert <commit-hash>
:撤销指定提交,并生成一个新的提交来记录此次撤销。git reset HEAD <file>
:将文件从暂存区移除,但保留修改。
- 协作工作流相关
git stash
:临时保存当前工作区的修改,用于切换分支或拉取代码时避免冲突。git stash pop
:恢复保存的工作区修改。git rebase <branch-name>
:将当前分支变基到指定分支之上。
- 标签
git tag <tag-name>
:为当前提交打标签。git push origin <tag-name>
:将标签推送到远程仓库。
git hooks
点击查看详情
什么是 git hooks
Git hooks
是 Git 提供的一种机制,允许在 Git 仓库中的特定事件发生时执行自定义脚本。它可以帮助开发者在进行一些操作时触发自动化流程,例如提交代码前自动检查代码风格、提交后通知某个服务等。
Git hooks 的作用
Git hooks 是本地钩子,它们与代码仓库一起存在,并在特定的 Git 事件(如提交、推送、合并等)触发时自动执行,典型的应用包括:
- 提交前代码检查:在提交代码之前自动运行测试或检查代码风格。
- 提交信息规范化:确保提交信息遵循一定的格式。
- 自动部署:在推送代码到远程仓库后,自动触发部署脚本。
Git hooks 的分类
Git hooks 分为两大类:
- 客户端钩子(Client-Side Hooks):主要在本地开发阶段触发,影响的是开发者的本地仓库行为。例如,提交前的检查、提交后的动作。
- 服务端钩子(Server-Side Hooks):主要在服务器端 Git 仓库中触发,影响的是推送到远程仓库的操作。例如,禁止推送不符合规定的提交或自动化部署等。
常用的 Git hooks
Git hook 是存放在 .git/hooks/
目录下的一组脚本,以下是一些常用的钩子事件:
- pre-commit
- 触发时机:在执行
git commit
前触发。 - 用途:可以用来检查代码格式、运行测试或阻止提交不符合规范的代码。
- commit-msg
- 触发时机:在提交信息编辑完毕后执行。
- 用途:用来检查提交信息是否符合规范,如确保每次提交信息都有相应的 issue 编号或规定格式。
- pre-push
- 触发时机:在执行
git push
前触发。 - 用途:可以用来阻止推送某些特定分支、运行测试等。
- post-commit
- 触发时机:在提交完成后触发。
- 用途:可以用于记录提交日志、发送通知或触发 CI(持续集成)服务。
- pre-receive
- 触发时机:在服务端接收到推送请求但未处理前触发。
- 用途:在服务器端用于验证推送的代码,如确保符合公司标准,或防止特定分支的推送。
- post-receive
- 触发时机:在服务器端接受推送并处理后触发。
- 用途:常用于触发自动化部署、发送推送通知等。
// install |
为什么需要 commitlint?
产生大量的 commit 版本后,良好的 commit 信息可帮我们更好的回顾或回退版本
如何规范?
提供一套规范来约束项目的 commit 信息;
type(scope?): subject
type(scope?): subject
是一种规范化的 Git 提交信息格式,通常用于 Conventional Commits 规范。这种格式有助于标准化提交信息,明确每次提交的意图。下面简述各部分的含义:- type (必填)
type
表示提交的类型,用于描述这次提交的目的或性质。常见的type
包括:feat
:新增功能(feature)。fix
:修复 bug。docs
:文档变更。style
:代码格式变更(不影响代码逻辑的变动,如空格、格式等)。refactor
:代码重构(不包括新增功能或修复 bug 的变动)。test
:添加或修改测试。chore
:非代码逻辑的变动(如构建任务、工具配置等)。
- scope (可选)
scope
用来标明本次提交影响的模块或功能的范围。通常是项目中的某个特定功能、模块或子系统,帮助团队成员明确该提交的影响范围。- 例子:
feat(user)
: 影响用户模块。fix(auth)
: 影响身份认证模块。
注意:
scope
是可选的,如果不需要指定具体的模块范围,可以省略。- subject (必填)
subject
是对本次提交的简要描述,用于说明提交内容。需要注意的是:- 使用简洁明了的语言。
- 应该以动词开头,通常是祈使句形式,如 “add”,”fix”,”update” 等。
- 不要以大写字母开头,且不要以句号结尾。
例子:
feat(auth): add login functionality
(新增了登录功能)。fix(user): resolve issue with profile picture upload
(修复了头像上传的问题)。
// 那当然需要先安装下 |
- chmod:这是改变文件权限的命令。
- a+x:表示给所有用户(
a
是 “all” 的意思,包括用户本人、用户组和其他用户)添加执行权限(+x
表示增加执行权限)。 - .husky/commit-msg:这是文件路径,表示你要更改权限的文件。这个文件是
husky
钩子脚本,用来检查提交信息(commit message)。
作用
这条命令的作用是让 .husky/commit-msg
文件可以被执行。commit-msg
是 husky
钩子,用来在 Git 提交时自动检查提交信息是否符合规范。让这个文件具有执行权限后,Git 在提交时可以调用并运行这个脚本。
一般在设置 husky
钩子时,需要给相应的钩子文件(如 commit-msg
、pre-commit
等)赋予执行权限,否则这些钩子无法运行。
commit-msg
#!/usr/bin/env sh |
pre-commit
#!/usr/bin/env sh |
rem适配 && 路径别名
点击查看详情
/src/utils/rem.ts
// 基准大小 |
tsconfig.app.json 文件中需要配置别名路径
{ |
eslint,prettier
代码规范标准
在运行代码前,发现语法错误和潜在bug
允许定制自己的代码规范
ESLint规则的三个等级:off、warn、error
ESlint
扩展会优先去查找项目根目录中的eslint.config.js
配置文件,并且包括配置文件所提到的ESlint
插件,也就是npm
依赖包,是的没错,ESlint
扩展本身所需的ESlint
版本和ESlint
插件,都是来自于node_modules
,你可以试着把这个目录删了,vscode
中的ESlint
扩展就会报错,无法运行。
但你启用vscode
中的ESlint
扩展之后,并不会对所有文件生效,你还需要配置ESlint
扩展的设置来对所需的文件启用校验。
这里建议为每个项目单独添加vscode独有的设置,也就是项目根目录中创建一个.vscode
目录,里面放置一个settings.json
文件,这样vscode
就会优先读取该设置:
// .vscode/settings.json |
这样一来,配置中所提到的文件格式,都会被ESlint
扩展识别。
到这里,ESlint基本算是可以正常使用了,でも,ちょうど待ってください,prettier
がありますね。
用于检测代码中的格式问题
ESLint 是侧重对项目代码质量的把控,而Prettier更侧重于统一项目的代码风格
因此,两者配合一起使用,比较好。
ESLint9之后,原有的配置文件已经被弃用
在我还不知道如何从0开始配置ESlint的时候,ESlint已经更新到9.x了。
总而言之,从ESlint9.x开始,在使用create vue 创建项目后,如果选中了eslint,那么项目中生成的文件为 eslint.config.js(ESNext)或者eslint.config.mjs(CommonJS)命名的配置文件。
// 依赖包安装 |
import eslint from '@eslint/js' |
ESLint Stylistic 是一个社区维护的开源项目,专注于为 JavaScript 和 TypeScript 提供风格和格式化相关的 ESLint 规则。该项目由 ESLint 和 typescript-eslint 团队发起,旨在将原本在核心规则中被弃用的风格和格式化规则独立出来,以便社区能够更好地维护这些规则。
该项目的主要编程语言是 TypeScript,同时也包含一些 JavaScript 代码。
规则冲突问题 - 在使用 ESLint Stylistic 时,可能会遇到与现有 ESLint 规则冲突的情况,导致代码检查失败。
eslint-plugin-vue & vite-plugin-eslint vite-plugin-eslint 作用: 这个插件将 ESLint 集成到 Vite 构建过程中。它可以在开发时自动检查你的代码,并在代码出现问题时给出即时反馈。 功能 : eslint-plugin-vue 作用: 这是一个专门为 Vue.js 提供的 ESLint 插件,提供 Vue 相关的 lint 规则。 功能 : 总结 关系与互补性 使用建议 总的来说,合理配置这两个插件将有助于提升你的开发体验和代码质量,不会导致冲突。 eslint-plugin-vue & vite-plugin-eslint - 着重介绍俩插件
vite-plugin-eslint
和 eslint-plugin-vue
是两个与 ESLint 相关的插件,分别用于 Vite 项目和 Vue 项目的代码质量检查。vite-plugin-eslint
是用于将 ESLint 集成到 Vite 开发流程中的插件,确保在开发过程中实时检查代码质量。eslint-plugin-vue
则是专门针对 Vue.js 的 lint 规则插件,帮助开发者编写符合 Vue 最佳实践的代码。两者结合使用,可以有效提升 Vue 项目的代码质量和开发体验。vite-plugin-eslint
和 eslint-plugin-vue
之间并不冲突,它们实际上是互补的工具,可以一起使用,以实现更好的代码质量管理。vite-plugin-eslint
来实时检查代码,同时在 ESLint 配置中引入 eslint-plugin-vue
,以确保 Vue 组件代码符合最佳实践。eslint-plugin-vue
,并按照项目需求启用或禁用特定规则。vite-plugin-eslint
,不需要在构建时重复运行 ESLint,除非你有特定需求。
解决步骤:
检查现有规则:首先,检查你的 .eslintrc.json 文件中是否已经存在与 ESLint Stylistic 规则冲突的配置。
禁用冲突规则:如果发现冲突规则,可以在配置文件中禁用这些规则。例如,如果你已经配置了 indent 规则,可以禁用它:
{ |
逐步应用规则:建议逐步应用 ESLint Stylistic 的规则,避免一次性引入过多规则导致冲突。
自定义规则问题 - 新手可能希望自定义 ESLint Stylistic 的规则,但不知道如何正确配置。
解决步骤:
了解规则配置:首先,阅读 ESLint Stylistic 的官方文档,了解每个规则的配置选项。
自定义规则:在 .eslintrc.json 文件中,根据文档中的说明自定义规则。
{ |
测试配置:在应用自定义规则后,运行 ESLint 检查代码,确保配置正确无误。
通过以上步骤,新手可以更好地理解和使用 ESLint Stylistic 项目,避免常见问题。
————————————————
vueuse 工具集 & pinia 独立维护 & vant 3
开发环境初始化
VueUse 是基于 组合式 API 的实用函数集合。
// 安装 |
Vant 3UI:轻量的移动端组件库
在基于 Rsbuild、Vite、webpack 或 vue-cli 的项目中使用 Vant 时,可以使用 unplugin-vue-components 插件,它可以自动引入组件。
Vant 官方基于 unplugin-vue-components
提供了自动导入样式的解析器 @vant/auto-import-resolver,两者可以配合使用。
相比于常规用法,这种方式可以按需引入组件的 CSS 样式,从而减少一部分代码体积,但使用起来会变得繁琐一些。如果业务对 CSS 的体积要求不是特别极致,我们推荐使用更简便的常规用法。
// 当然是先安装好 |
请求拦截器
点击查看详情
src目录下创建utils/request.ts
import axios from 'axios'
import { showToast } from 'vant'
const baseURL = '/api'
const service = axios.create({
baseURL,
timeout: 10000
})
// 发起请求之前的拦截器
service.interceptors.request.use(
config => {
const token = window.localStorage.getItem('token')
if (token) {
// config.headers = {
// 'x-access-token': token
// }
// 直接在 headers 对象上添加属性
config.headers['x-access-token'] = token
}
return config
},
error => Promise.reject(error)
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (response.status !== 200) {
return Promise.reject(new Error(res.success || 'Error'))
} else {
if (res.code == 200) {
return res.result || res.data
} else {
showToast(res.message)
}
}
},
error => {
return Promise.reject(error)
}
)
// 导出后在 api 文件中引入
export default service
快速创建文件结构-路由规则
点击查看详情
项目页面结构
views |
// 先创建文件夹 |
// vue-router 当然是先安装一下 |
两款插件
vite-plugin-pages 404页面的手动配置&动态路由
404路由配置
vite-plugin-pages
插件在自动生成路由时,并不会直接处理 404 页面。若要添加 404 页面支持,需结合 vue-router
的一些配置。以下是详细步骤来设置 404 页面。
- 创建 404 页面组件
在 src/pages
目录中创建 404.vue
文件,并编写 404 页面内容:
<!-- src/pages/404.vue --> |
- 安装并配置
vite-plugin-pages
确保已安装 vite-plugin-pages
,并配置 vite.config.js
:
pnpm install vite-plugin-pages -D |
在 vite.config.js
中导入插件并添加配置:
// vite.config.js |
- 配置
vue-router
捕获所有未知路由
vite-plugin-pages
会自动生成 routes
配置,我们可以使用这些生成的路由,并手动添加 404 页面配置。在 src/main.js
或 src/main.ts
中完成以下设置:
// src/main.js 或 src/main.ts |
- 可选配置:重定向到首页或其他页面
如果希望未匹配的路径重定向到首页或其他页面,而不是显示 404 页面,可以将 404.vue
替换为首页或指定页面。例如:
const routes = [ |
- 可选配置:在生产环境中部署时处理 404
如果使用 GitHub Pages、Netlify 等平台部署,静态网站通常会出现直接访问 404 页面 URL 时无法找到页面的情况。这时可以根据部署平台的不同,分别进行配置:
Netlify:在根目录下创建 _redirects
文件,添加如下配置:
/* /index.html 200 |
Vercel:Vercel 自动支持单页应用,默认不需要额外配置。
GitHub Pages:在 404.html
中添加 JavaScript 重定向代码:
<script type="text/javascript"> |
总结
配置 404 页面时需完成以下步骤:
- 创建
404.vue
页面。 - 使用
vite-plugin-pages
自动生成路由。 - 在
vue-router
中手动添加通配符路由/:pathMatch(.*)*
指向404.vue
组件。 - (可选)根据部署平台的需求配置生产环境的 404 页面处理。
动态路由
动态路由参数
如果你需要接收动态参数,如 things_id
和 receive_id
,你需要确保你的文件名包含正确的参数名称,例如:
src/pages/ |
或者使用 MessageTalk.vue
文件的嵌套路由:
src/pages/ |
vite-plugin-pages
会自动解析这些动态参数并将它们映射到路由中。
unplugin-vue-router
和 vite-plugin-pages
都是为 Vue 开发的文件系统路由插件,主要用于简化路由配置。但它们在特性和实现方式上有所不同。以下是两者的简要对比:
vite-plugin-pages vs unplugin-vue-router
最好还是使用后者,因为...
可以直接在页面中使用 definePage
来定义 meta
,并且 不会 报错,因为 unplugin-vue-router
提供了类型支持和自动引入 definePage
的功能。正确配置后,definePage
会在页面中自动识别,无需额外导入。以下是确保配置无误的步骤:
- 确保
vite.config.ts
配置了unplugin-vue-router
在 vite.config.ts
中确保插件已经正确配置,通常设置如下:
// vite.config.ts |
- 添加
unplugin-vue-router
的自动生成文件
unplugin-vue-router
会自动生成路由文件,并包含类型声明。添加好插件后,首次启动项目时它会生成类型声明文件。这些文件通常位于 src/typed-router.d.ts
中,可以提供完整的路由类型支持。
- 在页面组件中使用
definePage
定义meta
成功配置后,definePage
函数会在页面组件中自动识别,你可以直接定义 meta
,例如:
// src/pages/Home.vue |
- 验证和使用
meta
信息
在主布局或根组件中,可以通过 route.meta.showTabBar
访问并根据需要使用 meta
信息。例如:
<!-- App.vue --> |
这样配置后,definePage
应该不会报错,meta
信息也可以直接在页面中定义并在组件中读取,实现动态显示组件的功能。
方法:通过 meta
字段配置路由
vite-plugin-pages
本身并不直接支持 definePageMeta
,这个 API 是 Nuxt.js
的功能,而在 Vue 3 项目中并没有官方直接的 definePageMeta
功能。因此,以下是实现类似效果的替代方法:
vite-plugin-pages - 可尝试通过 meta 字段配置路由
使用 vite-plugin-pages
时,可以在 vite.config.ts
文件中对路由进行扩展,设置 meta
字段,用于单个页面的额外配置。
步骤 1:配置 vite-plugin-pages
添加 meta
在 vite.config.ts
中,通过 extendRoute
为指定页面添加 meta
字段:
// vite.config.ts |
步骤 2:在页面组件中读取 meta
信息
在页面组件中使用 useRoute
读取路由 meta
信息:
// src/pages/YourPage.vue |
注意事项
- 如果需要在多个页面中复用
meta
字段,可在extendRoute
中批量设置条件。 - 此方法兼容 Vue 3 的组合式 API,并与
vite-plugin-pages
配置无冲突。
- 功能对比
特性 | vite-plugin-pages | unplugin-vue-router |
---|---|---|
路由自动化 | 根据文件系统自动生成路由 | 根据文件系统自动生成路由 |
文件名映射 | 支持文件名到路由的直接映射 | 支持文件名到路由的直接映射 |
动态路由支持 | 支持动态路由命名(如 [id].vue ) | 支持动态路由命名(如 [id].vue ) |
嵌套路由 | 支持基于目录结构的嵌套路由 | 支持基于目录结构的嵌套路由 |
404 页面支持 | 需要手动配置 404 页面 | 可自动处理 404 页面 |
全局导航守卫 | 不支持 | 支持全局导航守卫 |
懒加载支持 | 自动支持懒加载 | 自动支持懒加载 |
TypeScript 支持 | 原生支持,需配置类型声明 | 原生支持,需配置类型声明 |
构建时路由生成 | 构建时生成路由,支持虚拟模块 | 构建时生成路由,支持虚拟模块 |
- 使用场景
场景 | 适合的插件 |
---|---|
小型项目 | vite-plugin-pages |
快速开发与原型设计 | vite-plugin-pages |
简单的路由管理 | vite-plugin-pages |
复杂的路由需求 | unplugin-vue-router |
需要权限管理和导航守卫 | unplugin-vue-router |
大型项目 | unplugin-vue-router |
高级路由配置 | unplugin-vue-router |
- 优缺点
vite-plugin-pages
优点:
- 快速上手:可以快速生成路由配置,节省时间。
- 减少冗余:不需要手动维护路由数组,文件结构直接映射路由。
- 易于维护:通过修改文件名和结构来轻松管理路由。
缺点:
- 灵活性有限:对于复杂需求,可能需要手动调整。
- 不支持所有高级功能:如全局导航守卫等功能需额外实现。
unplugin-vue-router
优点:
- 灵活性高:支持多种复杂路由配置,包括嵌套路由、命名路由、动态路由等。
- 强大的功能:支持全局导航守卫、懒加载等高级功能。
- 广泛使用:作为广泛支持的插件,文档和社区支持丰富。
缺点:
- 配置复杂:对于初学者,路由配置可能较为繁琐。
- 手动维护:需要手动维护路由数组,增加了工作量。
- 集成方式
vite-plugin-pages & unplugin-vue-router
安装插件:
// vite-plugin-pages
pnpm install vite-plugin-pages -D
// unplugin-vue-router
pnpm install unplugin-vue-router -D配置 vite.config.js:
// vite-plugin-pages
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Pages from 'vite-plugin-pages';
export default defineConfig({
plugins: [
vue(),
Pages({
// 指定路由文件的目录
dirs: 'src/views',
// 可以自定义路由生成规则
extensions: ['vue'], // 仅识别 .vue 文件
exclude: ['**/components/**'] // 排除特定目录
})
],
});
// unplugin-vue-router
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import UnpluginVueRouter from 'unplugin-vue-router/vite';
export default defineConfig({
plugins: [
vue(),
UnpluginVueRouter({
routesFolder: 'src/pages',
}),
],
});
使用
// vite-plugin-pages
import { createRouter, createWebHistory } from 'vue-router';
import generatedRoutes from 'virtual:generated-pages';
const router = createRouter({
history: createWebHistory(),
routes: generatedRoutes,
});
// unplugin-vue-router
import { createRouter, createWebHistory } from 'vue-router';
import { routes } from 'unplugin-vue-router';
const router = createRouter({
history: createWebHistory(),
routes,
});
接受路由参数的三种方式
点击查看详情
最佳实践中,使用 useRoute
为什么推荐 useRoute
?
- 语义化:
useRoute
是 Vue 3 提供的组合式 API,它直接说明你是在获取当前路由的参数。使用useRoute
可以让代码更具可读性和可维护性。 - 响应式:
useRoute
返回的是一个响应式对象,当路由参数发生变化时,组件会自动重新渲染,这对于动态路由特别有用。 - 简洁:在组件内部,直接使用
useRoute
获取路由参数是最简洁的方式。
假设 receive_id.vue
组件位于路径 /message/:things_id/:receive_id
下,可以在组件中按如下方式获取动态路由参数:
<script setup lang="ts"> |
router.currentRoute.value 获取动态路由参数
虽然 router.currentRoute.value
也可以用来获取动态路由参数,但它的使用场景相对较少。通常这种方式更多见于非组件上下文,或者你需要对路由做更细致的控制时(比如在 Vuex、全局状态管理等地方访问路由信息)。
示例代码:
<script setup lang="ts"> |
使用场景:
- 如果你需要手动访问路由实例,或者在非组件环境(如 Vuex、服务模块等)中获取路由参数时,
router.currentRoute.value
会更适用。 - 需要直接操作或获取路由实例的情况下,适合使用这种方式。
defineProps 也可以获取路由参数
defineProps
主要用于从父组件获取数据或 props,而 vite-plugin-pages 会将动态路由参数自动作为 props 传递给组件。如果你通过 vite-plugin-pages
的自动路由系统来配置路由,并且路由参数会自动作为 props 传递给组件,那么你可以直接使用 defineProps
来获取。
根据项目实践,如果使用了 vite-plugin-pages 插件,不使用 defineProps 接受路由参数的话,控制台会给与相应提示!
示例代码:
<script setup lang="ts"> |
使用场景:
- 这种方式只适用于
vite-plugin-pages
等插件自动处理的场景。 - 如果路由参数会自动作为组件的 props 传递,那么使用
defineProps
是最简洁的方式。 - 不适合动态路由和非插件自动处理的情况。
组件封装 - 实用技巧
这里需要区分,有些组件例如 tabbar,需要写在项目根组件当中
否则,在 vant ui 框架中,会导致内部调用起冲突
另外,如果需要,可在 app.vue 根组件中,针对 tabbar 加上一个条件判断,例如:
<script setup lang="ts"></script> |
配置
// 自动导入配置 settings.json |
Vue VSCode Snippets 是一个 Visual Studio Code 插件,提供了一组 Vue.js 的代码片段,能够帮助开发者快速编写 Vue 组件和其他常用代码结构。这个插件非常有助于提高开发效率,特别是在使用 Vue 3 的项目中。
名字+icon
api 请求前的类型声明
点击查看步骤和技巧
基本概念 - 如果一个东西长得想鸭子,可以像鸭子一样嘎嘎叫,那就认为它是鸭子
// interface |
步骤 1:定义请求参数的接口
首先,定义 API 请求参数的接口,以确保传入的参数符合要求。
// 请求参数类型定义 |
步骤 2:定义 API 响应数据的接口
根据 API 返回的 JSON 结构定义接口,确保响应的数据类型安全:
// 返回参数的定义 |
步骤 3:封装 API 请求函数
在 Vue 项目中,通常使用 axios
发送请求。封装一个通用的请求方法,将参数和响应数据类型应用到请求中。
export function login(data: LoginData): Promise<LoginResponse> { |
步骤 4:在 Vue 组件中使用 API 请求函数
在组件中调用该 API 函数时,使用 async/await
处理异步请求,并确保响应数据符合预期类型。
// 根据数据的实际使用方式,ts 会倒过来自动推断 api 返回的数据类型,并给出声明建议 |
技巧:
- 类型推导:尽量在 API 请求时显式定义接口,有助于类型推导,避免类型不匹配。
- 分层设计:将 API 请求函数与业务逻辑分离,便于维护和测试。
- 类型安全:使用接口描述请求参数和响应数据,确保数据符合预期。
- 错误处理:在组件中进行错误捕获,以便向用户反馈错误信息。
- 可选字段:对于返回结果中的非必需字段(如
msg
),可用?
标记为可选。
即 - 部分类型定义
允许只定义 ApiResponse
中需要的字段,而不必包含整个返回对象的所有字段。这种方式称为“部分类型定义”,它有助于简化代码,尤其在返回数据结构复杂的情况下。
但,虽不必包含返回对象的所有字段,但对于需要使用的返回数据,必须在 interface 中声明,如暂时无法确定其类型,可暂时将类型留空,将来在组件中根据实际的使用方法,ts 会自动未我们进行类型推断
ts编译器可以从上下文中推断出变量的结构和字段类型,它会根据你的使用模式自动推断出一个更准确的类型。
// 例如,本项目中未在 /api/user.ts 中的 records |
但是,在组件中,根据对返回数据的实际调用方式,ts 会帮我们推断出合适的类型声明
// /src/views/login/PrivatePolicy.vue |
state & defineProps 中的数据类型声明
点击查看详情
/src/stores/task.ts
在 Vue 3 + TypeScript 项目中,通常建议对store.state
中的数据进行类型声明,以便充分利用 TypeScript 的类型检查和代码提示功能。这不仅可以提高代码的可读性和可维护性,还有助于避免潜在的错误。
同样的,也是遵循按需声明的原则,即只声明将来会在组件中渲染的数据即可。
import { defineStore } from 'pinia' |
父组件中,在子组件标签中,直接使用冒号(v-bind)增加动态属性
// 对于 向子组件传递的数据,像以往那样,需要在组合式 api 当中声明其数据类型 |
然后子组件中,像这样
// 首先对传递进来的数据类型进行声明 |
# 进一步实际测试发现,使用 defineProps 接受父组件的数据时 |
defineProps 泛型参数 <{ messageList }>:
- 泛型定义了
props
的类型,帮助 TypeScript 提供更好的类型推断。 - 在这个例子中,泛型
{ messageList }
表示组件的props
中应该有一个名为messageList
的属性。 - 未指定 messageList 的具体类型,所以默认会是
any
类型。
使用 defineProps 接收路由参数
// 通过 vite-plugin-pages 插件,路由参数(如 id)会自动作为 props 传递给组件 |
provide / inject 依赖注入 - 父组件向子组件提供方法时的类型声明
点击查看详情
在 Vue 中,父组件通过 provide
提供的函数在子组件中调用时,子组件不会自动继承该函数的类型声明。这是因为 TypeScript 的类型系统是静态的,类型信息并不会随着函数的传递而自动传播。以下是一些相关的要点:
- 提供与注入
- 父组件使用
provide
提供数据或函数,子组件使用inject
注入这些数据或函数。虽然父组件提供了函数,但 TypeScript 仍需要明确这些函数在子组件中的类型,以确保调用时的类型安全。
- 父组件使用
- 类型声明的重要性
- 当子组件调用从父组件提供的函数时,TypeScript 不会自动推断出该函数的参数和返回值类型。子组件必须显式地声明这些类型,以确保在调用时符合预期的类型。这是因为 TypeScript 只在定义时检查类型,而不是在运行时。
- 接口或类型的使用
- 为了提高代码的可维护性和可读性,通常会在父组件中定义一个接口(如
PopupContext
),描述提供的函数的类型。子组件在使用inject
时,可以将这个接口作为类型断言,从而明确调用该函数时应遵循的类型约定。
- 为了提高代码的可维护性和可读性,通常会在父组件中定义一个接口(如
示例
在使用 provide 时,进行类型检查,以使其只能传入特定类型的数据
在使用 inject 时,能够获取数据类型
假设在父组件中定义了一个函数并提供了类型:
// 父组件定义方法
const closeCitySwitch = (name: string) => {
if (name) {
store.setCityValue(name)
}
state.citySwitch = false
}
# 通过'popup'向子组件暴露这个方法
provide('popup', {
closeCitySwitch
})在子组件中,需要显式地声明其类型:
// 子组件中,需要再次声明一个函数的类型
interface PopupContext {
closeCitySwitch: (cityName?: string) => void
}
// 注入父组件暴露出来的 'popup' 函数,并进行类型断言
const popupContext = inject('popup') as PopupContext | null
// 检查 popupContext 是否存在
if (!popupContext) {
throw new Error('popup is not provided')
}
const { closeCitySwitch } = popupContext // 解构出 closeCitySwitch,在子组件调用总结
在 TypeScript 中,尽管父组件通过
provide
提供了函数,子组件仍然需要显式声明类型。这并不是因为语言的局限性,而是为了确保类型的明确性和代码的可维护性。这种方式使得代码更健壮,开发者能够快速了解和检查函数的使用方式。
实战中的使用技巧
provide
可以直接将父组件中定义的接口类型实例传递出去,这样子组件在 inject
时也可以利用同样的类型接口。这里是一个完整示例:
示例:父组件中定义并提供接口实例
// 定义接口 |
子组件中接收并使用
在子组件中使用 inject
时,可以直接将 inject
的类型声明为 UserContext
,确保代码安全并享受类型推导的好处。
// 在子组件中 inject 并指定类型 |
工作原理:
- 父组件通过
provide
将userContext
作为类型为UserContext
的对象传出。 - 子组件通过
inject<UserContext>
使用该类型,使 TypeScript 知道userContext
的属性和方法类型。
好处:
这种方式的好处在于:
- 子组件直接继承了父组件的类型定义,避免重复声明。
- IDE 和 TypeScript 提供的自动补全和类型检查功能在子组件中也能正常工作。
这种做法让父子组件在 provide
/inject
的类型上保持一致,确保了良好的代码可维护性和类型安全性。
symbol 处理
为什么使用 Symbol
?
避免命名冲突:
- 如果你的应用中有多个
provide
使用了相同的键,可能会导致冲突。 Symbol
生成的是唯一的标识符,能有效避免这种问题。
const MyKey = Symbol('MyUniqueKey');
app.provide(MyKey, someValue);- 如果你的应用中有多个
代码安全性:
- 使用字符串作为键时,可能被无意间覆盖。
Symbol
键能降低被误用或覆盖的风险。
提升代码可维护性:
- 使用
Symbol
时,键通常会用常量声明,使代码更加规范。
- 使用
是否必须使用 Symbol
?
不是必须的。
- 如果你只是在一个简单的上下文中使用(如没有深层嵌套或复杂依赖),字符串键是完全可以的。
// 提供一个值
app.provide('myKey', someValue);
// 子组件注入
const injectedValue = inject('myKey');推荐使用 Symbol 的场景:
- 大型应用或复杂组件树: 有很多依赖注入的场景。
- 第三方库开发: 避免用户无意中覆盖注入的值。
- 共享依赖: 不同的开发者团队可能会定义相同的字符串键。
使用示例
使用字符串键
// 父组件 |
使用 Symbol 键
// 定义唯一的 Symbol |
结论
- 简单应用:可以直接使用字符串。
- 复杂或潜在冲突场景:建议使用
Symbol
,更安全且维护性更好。
ps: 中大型项目或多人协作
- 推荐将 Symbol 键集中管理:新建一个专门的文件存放所有的
Symbol
键,可以避免键重复、命名混乱和跨模块依赖问题。 - 文件结构建议:
- 将所有
Symbol
键集中定义在一个文件中,例如keys.js
或injectionKeys.js
。 - 根据功能模块,适当分层管理。
- 将所有
数据的本地化存储
基于 pinia defineStore
首先,基于 pinia 的 defineStore 创建一个独立的 store 模块
// 本项目实战中 /src/stores/task.ts |
组件中,通过定义请求函数拿到返回结果之后,通过 store 暴露出来的方法,更新 state 中的数据
// 组件中发起 api 请求,拿到数据 |
前面这些步骤做好了之后,即可以在 actions 中,轻松的实现本地化存储
// 例如,本项目实战中 /src/stores/task.ts |
state 中:虽然实时更新了数据,但,当页面刷新后,state 会优先从本地取先前存进去的数据。
state: () => { |
下拉刷新和滚动加载
借助 vant ui 中的 组件实现这一功能 - 点击查看详情
// 页面结构如下 |
底部tabbar - 向下滑动时的隐藏效果设计与实现
点击查看详情
<script setup lang="ts"> |
ps : 需要在 env.d.ts 文件中进行node类型声明
/// <reference types="vite/client" /> |
onMounted
和 onUnmounted
是 Vue 3 中的生命周期钩子,分别用于在组件挂载(即插入 DOM)和卸载(即从 DOM 中移除)时执行特定的操作。
在组件挂载时添加事件监听器(比如监听滚动事件)并在组件卸载时移除它们,此时的 onMounted
和 onUnmounted
是必须的。否则,当页面跳转或组件销毁时,这些事件监听器就会留下来,可能会导致内存泄漏或不必要的性能开销。
如果你不需要做类似的初始化或清理操作,那么可以删去 onMounted
和 onUnmounted
,不影响功能。
但,根据最佳实践,肯定是要保留的啦!
常见报错
点击查看详情
- 类型“AxiosResponse”上不存在属性“errCode”。ts-plugin(2339)
// 根据 chatgpt 最佳实践 |
-无法找到模块 vite-plugin-eslint 插件中的 TS 声明文件
关于在 vite.config.ts 中引入 vite-plugin-eslint 插件,出现如下报错:
无法找到模块 vite-plugin-eslint 插件中的 TS 声明文件,隐式含有 “any” 类型。
我们可以得到在该插件中是含有 TS 声明文件的,vite-plugin-eslint/dist/index.d.ts,但是由于 TypeScript 的变更,导致新版本的 typescript 与依赖包中 package.json 指明 TS 声明文件位置的 types 配置项不匹配,最终导致新版本的 TypeScript 找不到 vite-plugin-eslint 插件中的 TS 声明文件。
在新版的 TypeScript 中,已经不再使用 package.json 文件中根结构中的 types 字段指明 TS 声明文件位置,而是在 exprots 中相应的导入方式中添加 typs 字段指明 TS 声明文件位置。
我这里的解决方法是</span>[1]vite-plugin-eslint 插件报错参考资料</span></span>,在 node_modules 中找到 vite-plugin-eslint 插件的 package.json 文件,将其中的 exports 字段修改为如下内容,让新版的 TypeScript 在使用 import 导入时能够找到 vite-plugin-eslint 插件中的 TS 声明文件。
"exports": { |
修改完成后,重启 ide 就不报错了。
到这里,项目的创建和最基础的开发配置已经完成了,后面继续整理项目的具体业务逻辑实现!