导语:在日常开发中,有时候会在项目中引入 ts 来解决一些 js 的问题,下面就简单介绍一下如何使用 vue3+ts+router+pinia 来搭建一个项目。
vue3 目前是常用的 vue 版本,提供了组合式 API 以及一些新的功能和特性;ts 这种类型编程语言可以在编译时通过静态分析检测出很多常见错误,减少了生产环境中的运行时错误,改善了开发体验和效率;vue-router 也更新到了 4 版本,pinia 则是最新退出的替代 vuex 的一个 vue 官方状态管理库;下面就结合以上提到的技术栈组合来简单创建一个项目。
下面打开 cmd 或其他命令行,输入以下命令创建一个 vite 项目。
这里我选择使用pnpm
来创建。
pnpm create vite
包括项目名称、选择框架、js 语言等。
√ Project name: ... tslx
√ Select a framework: » Vue
√ Select a variant: » TypeScript
根据以下步骤来安装基本的依赖和运行项目。
cd tslx
pnpm install
pnpm run dev
创建好项目后,接下来安装一些必备的依赖包。
这个是必须的,路由管理。
pnpm i vue-router -S
这个是必须的,主要是使用 sass 写项目样式表。
pnpm i sass -S
这个是必须的,主要是 http 请求数据。
pnpm i axios -S
这个是可选的,主要是提供状态管理。
pnpm i pinia -D
这个是可选的,主要是提供状态管理的持久化存储。
pnpm i pinia-plugin-persist -D
这个是可选的,自动导入依赖插件。
pnpm i unplugin-auto-import -D
这个是可选的,主要是可定制化 CSS 样式。
pnpm i normalize.css -D
这个是可选的,一个简洁、灵活的 JavaScript 事件订阅和发布库。
pnpm i mitt -D
这是可选的,一个支持 vue3 语法的插件。
pnpm i @volar-plugins/vetur -D
这个是可选的,支持基于 Vite 构建的 Vue 项目。
pnpm i @vitejs/plugin-vue -D
这个是可选的,主要是解决模块的声明问题。
pnpm i @types/node -D
这个是可选的,主要是 node 的 path 模块。
pnpm i path -D
这个是可选的,主要是应用模块统计。
pnpm i rollup-plugin-visualizer -D
这个是可选的,主要是多语言配置。
pnpm i vue-i18n -D
好了,以上就是日常开发项目常用的一些依赖包。
下面配置一下vite.config.ts
文件和main.ts
文件以及其他需要配置的文件。
打开vite.config.ts
文件,主要是添加插件配置,文件路径别名配置,css 全局样式配置,服务端端口及代理配置。
import AutoImport from "unplugin-auto-import/vite";
import vetur from "@volar-plugins/vetur";
import path from "path";
import { visualizer } from "rollup-plugin-visualizer";
在plugins
中添加以下配置。
//...
{
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: () => {
return false;
},
},
},
}),
vetur,
AutoImport({
dts: "src/auto-import.d.ts",
include: [
/\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
/\.vue$/,
/\.vue\?vue/, // .vue
],
imports: [
"vue",
"vue-router",
{
from: "vue-router",
imports: ["RouteLocationRaw"],
type: true,
},
],
}),
visualizer({
emitFile: false,
filename: "stats.html",
open: true,
}),
];
}
// ...
在src
文件夹下面新建三个文件,可以选择去掉assets
文件夹。
types
:主要是存放 ts 声明等内容;styles
:主要是存放全局样式文件;apis
:主要是存放全局方法文件;下面是配置方法。
//...
{
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"@c": path.resolve(__dirname, "./src/components"),
"@t": path.resolve(__dirname, "./src/types"),
"@s": path.resolve(__dirname, "./src/styles"),
"@a": path.resolve(__dirname, "./src/apis"),
},
}
}
//...
下面设置 css 全局样式配置,在刚刚创建的styles
文件夹下面创建一个global.scss
的文件。
// ...
{
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "./src/styles/global.scss";',
javascriptEnabled: true,
},
},
},
}
//...
最后就是一个服务端的配置,包括端口,自动打开网页,跨域接口设置。
//...
{
server: {
host: "0.0.0.0",
port: 6060,
open: true,
proxy: {
"/api": {
autoRewrite: true,
target: "http://127.0.0.1:9999",
changeOrigin: true,
ws: true,
},
},
},
}
//...
配置好以后,重启一下服务就生效了。
paths
路径在tsconfig.json
中新增一个paths
属性并配置如下。
{
"paths": {
"@/*": ["src/*"],
"@c/*": ["src/components/*"],
"@t/*": ["src/types/*"],
"@s/*": ["src/styles/*"],
"@a/*": ["src/apis/*"]
}
}
完整的tsconfig.json
内容:
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Node",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }],
"paths": {
"@/*": ["src/*"],
"@c/*": ["src/components/*"],
"@t/*": ["src/types/*"],
"@s/*": ["src/styles/*"],
"@a/*": ["src/apis/*"]
}
}
完整的tsconfig.node.json
的内容:
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
main.ts
文件修改为以下内容,这样方便后面挂载全局组件,方法和插件等内容。
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.mount("#app");
打开main.ts
会看到找不到模块“./App.vue”或其相应的类型声明的报错,下面就在src
下面新建一个 vue 声明文件global.d.ts
。
// ./src/global.d.ts
declare module "*.vue" {
import type { DefineComponent } from "vue";
const vueComponent: DefineComponent<{}, {}, any>;
export default vueComponent;
}
这个可以让 ts 识别 vue 组件类型声明。
下面简单的配置一个路由文件,在src
下面新建一个router
文件夹,并创建一个index.ts
文件。
// ./src/router/index.ts
// 导入依赖
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
// 配置routes
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "Home",
component: () => import("@c/home.vue"),
children: [],
},
{
path: "/404",
name: "NotFound",
component: () => import("@c/404.vue"),
meta: {
title: "404",
auth: false,
},
},
{
path: "/:pathMatch(.*)",
redirect: "/404",
},
];
// 配置router
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from) {
console.log(to, from);
return {
left: 0,
top: 0,
};
},
});
// 配置钩子
router.beforeEach((to, from, next) => {
console.log(to, from);
// ...
next();
});
router.afterEach((to, from) => {
console.log(to, from);
// window.scrollTo(0, 0);
});
// 导出路由
export default router;
main.ts
文件。//...
import router from "./router";
// ...
app.use(router);
//...
在src
下面新建一个store
的文件夹,里面新建一个index.ts
、types.ts
以及user.ts
的文件。
index.ts
文件这个文件主要是放置基础的配置,包括插件,持久化存储。
import { createPinia } from "pinia";
import piniaPluginPerisit from "pinia-plugin-persist";
// 全局设置
export const pinia = createPinia();
pinia.use(piniaPluginPerisit);
export default pinia;
在配置的时候,pinia-plugin-persist
插件可能会报错,原因是找不到模块声明。
可以采取以下方法解决:
types
目录下新建一个pinia-plugin-persist.d.ts
声明文件。内容为:
declare module "pinia-plugin-persist";
types.ts
文件这个文件主要是放置类型变量。
const enum NAMES {
user = "USER",
}
export default NAMES;
enum
可能会报错,Parsing error: The keyword 'enum' is reserved
, enum 是 Javascript 为未来特性保留的关键字,我们不应该使用它,属于eslint
检查错误。
可以采取以下方法解决:
安装三个插件eslint-plugin-vue
、@typescript-eslint/parser
、@typescript-eslint/eslint-plugin
;
pnpm i eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
修改项目根目录下的.eslintrc.json
配置文件;
{
"env": {
"browser": true,
"es2021": true,
"node": true,
"vue/setup-compiler-macros": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended",
"prettier",
"plugin:@typescript-eslint/recommended"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"parser": "@typescript-eslint/parser"
},
"plugins": ["vue", "prettier"],
"rules": {
"semi": ["warn", "never"]
},
"settings": {}
}
重启一下项目就可以了。
user.ts
文件这个文件主要是用户的一些状态信息。
import { defineStore } from "pinia";
import NAMES from "./types";
import { User } from "../types/interface";
// 用户
const user = defineStore(NAMES.user, {
state: () => {
return {
userInfo: {
id: 1,
name: "mark",
},
};
},
getters: {
getUserInfo(state) {
return state.userInfo;
},
},
actions: {
saveUser(user: User) {
this.userInfo = user;
},
},
});
export default user;
上面简单做了个interface
的接口定义。
// ./src/types/interface.ts
// 用户信息
export interface User {
id: number;
name: string;
}
main.ts
文件。//...
import pinia from "./store";
// ...
app.use(pinia);
//...
在src
文件夹下面新建locale
文件夹,用了存放vue-i18n
配置信息。
包括zhCn.ts
、zhHk.ts
、en.ts
、lang.ts
、index.ts
等文件。
const zhCn = {
home: "首页",
index: "主页",
list: "列表",
info: "信息",
welcome: "欢迎光临",
};
export default zhCn;
const zhHk = {
home: "首頁",
index: "主頁",
list: "清單",
info: "資訊",
welcome: "歡迎光臨",
};
export default zhHk;
// 英文
const en = {
home: "Home",
index: "Index",
list: "List",
info: "Info",
welcome: "Welcome",
};
export default en;
index.ts
lang.ts
import zhCn from "./zhCn";
import zhHk from "./zhHk";
import en from "./en";
export default {
zhCn,
zhHk,
en,
};
index.ts
import { createI18n } from "vue-i18n";
import messages from "./lang";
const i18n: any = createI18n({
locale: localStorage.getItem("lang") || "zhCn",
globalInjection: true,
legacy: false,
messages,
});
export default i18n;
tsconfig.json
中添加类型{
"compilerOptions": {
// ...
"types": ["vue-i18n"]
}
// ...
}
在main.ts
中引入vue-i18n
。
// ...
import i18n from "./locale/index";
// ...
app.config.globalProperties.$i18n = i18n;
// ...
app.use(i18n);
接下来就可以在组件或者其他 ts 文件中使用了。
在styles
文件夹中新建一个font.scss
和reset.css
样式文件,然后导入main.ts
文件中即可实现。
// ./src/main.ts
import "@s/reset.css";
import "@s/font.scss";
在apis
文件夹中新建一个http.ts
文件,封装请求方法。
// ./src/apis/http.ts
import axios from "axios";
// 创建axios实例
const http = axios.create({
baseURL: "/",
timeout: 30000,
headers: {
"Content-Type": "application/json",
},
});
// 请求拦截
http.interceptors.request.use(
(config) => {
config.headers.version = "v1";
return config;
},
(err) => {
return Promise.reject(err);
}
);
// 响应拦截
http.interceptors.response.use(
(res) => {
let data = res.data;
return data;
},
(err) => {
return Promise.reject(err);
}
);
export default http;
在apis
文件夹中新建一个util.ts
文件,封装请求方法。
// ./src/apis/util.ts
// 全局方法
import { AllAny } from "../types/interface";
const sum = (a: number, b: number): number => a + b;
const util: AllAny = {
sum,
};
export default util;
这里我又定义了util
的接口。
// 任意对象类型
export interface AllAny {
[propsName: string]: any;
}
下面介绍以下如何配置mitt
。
在apis
文件夹下面新建一个mitts.ts
文件。
// ./src/apis/mitts.ts
import mitt, { Emitter } from "mitt";
import { MittEvents } from "../types/interface";
const mitts: Emitter<MittEvents> = mitt<MittEvents>();
export default mitts;
记得在interface.ts
声明以下类型。
// mitt类型
export type MittEvents = {
[propsName: string]: any;
};
main.ts
文件// ./src/main.ts
// ...
import http from "./apis/http";
import util from "./apis/util";
import mitts from "./apis/mitts";
// ...
app.config.globalProperties.$http = http;
app.config.globalProperties.$util = util;
app.config.globalProperties.$mitts = mitts;
// ...
在项目创建,安装依赖,配置全局环境结束后,写一个简单的组件案例。
下面是home.vue
组件中的一些小案例,可以练习一下。
<p>
<span>{{ sayHi }}span>
p>
<p>
<button type="button" @click="changeName">改变姓名button>
p>
<p>
<button type="button" @click="goNotFound">到404button>
p>
<p>
<button type="button" @click="getData">请求接口button>
p>
<p>接口数据:{{ msg }}p>
<p>
<input v-model="sumInfo.num1" type="number" name="num1" id="num1" placeholder="数字1" />+
<input v-model="sumInfo.num2" type="number" name="num2" id="num2" placeholder="数字2" />= {{
sumInfo.sum }}
p>
<p>
<button type="button" @click="getSum">计算和button>
p>
<p>
<button type="button" @click="sendMsg">发送消息button>
p>
import { reactive, ref, watch, computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import userStore from "../store/user";
import { Store } from "pinia";
import { useCurrentInstance } from "../types/util";
import { AllAny } from "../types/interface";
// 组件路由
const route = useRoute();
const router = useRouter();
console.log("route:", route, router);
// 用户状态
const user: Store = userStore();
console.log("store:", user);
// 组件数据
const msg: Ref = ref("");
console.log("data:", msg);
const info = reactive({
id: 1,
name: "mark",
});
console.log("data:", info);
const sumInfo = reactive({
num1: 0,
num2: 0,
sum: 0,
});
console.log("sum:", sumInfo);
// 组件监听
watch(
() => [info.name],
(val: string[]) => {
console.log("watch:", val);
}
);
// 组件计算
const sayHi = computed(() => {
return `Hi,${info.name}!`;
});
// 组件当前实例
const { proxy } = useCurrentInstance();
console.log("proxy:", proxy);
// 改变姓名
function changeName() {
info.name = "jack";
}
// 到404
function goNotFound() {
router.push("/404");
}
// 请求接口
async function getData() {
let data: AllAny = await proxy.$http.get("/todos/1");
msg.value = data;
console.log("http:", data);
}
// 计算和
function getSum() {
let sum: number = proxy.$util.sum(sumInfo.num1, sumInfo.num2);
sumInfo.sum = sum;
}
// 发送消息
function sendMsg() {
proxy.$mitts.emit("user", "mark");
}
import i18n from "../locale/index";
const trans = i18n.global.t;
trans("home");
import { useI18n } from "vue-i18n";
const { t } = useI18n();
console.log(t("welcome"));
以上就是如何从零搭建一个 vue3+ts5+vue-router4+pinia2 的项目方法。