2022-10-14 | 项目构建 | UNLOCK | 更新时间:2022-10-14 15:58

Vue3 项目的开始和扩展问题

Vue 3 是 Vue 当前的最新主版本。它包含了一些 Vue 2 中没有的新特性 (比如 Teleport、Suspense,以及多根元素模板)。同时它也包含了一些与 Vue 2 非兼容性的变更

使用须知

文中举例,默认 yarn 是已经安装了,如果没有安装,请先运行以下命令安装后;

npm install yarn -g

创建项目

模式一

yarn create vite || npm init vue@latest //之后根据提示即可
cd xxx
yarn add vue-router@4 || npm install vue-router@4 //安装路由插件
yarn add pinia || npm install pinia //安装状态管理 可选
yarn add @types/node -D || npm i @types/node -D //配置路径别名 可选
yarn && yarn dev

模式二

yarn create vite myapp –template vue //vue项目的默认配置

执行 npm init vue@latest 后,在创建项目的时候会提示配置项:既第三方库

√ Project name:    项目名称
√ Add TypeScript?  javascript的超集
√ Add JSX Support? JSX 支持
√ Add Vue Router for Single Page Application development? Vue路由管理
√ Add Pinia for state management?     类似Vuex的状态管理机制
√ Add Vitest for Unit Testing?        vite提供的单元测试框架
√ Add Cypress for End-to-End testing? 自动化测试框架
√ Add ESLint for code quality?        解析规范化代码
√ Add Prettier for code formatting?   ... No / Yes

配置

vite 在创建项目xxx后,可以命令进入,vite默认启动的IP是错误的,可以通过配置进行修改;

vite.config.ts
import path from 'path' export default defineConfig({ // ... server:{ open: true, port: 5173, host: "0.0.0.0" }, resolve: { alias: { // '~/': `${path.resolve(__dirname, 'src')}/`, // '@': `${path.resolve(__dirname, 'src')}/`, '@': join(__dirname, "src"), } } });
"><!-- 配置路径别名 ```json tsconfig.json // ... "allowJs": true, //允许js "baseUrl": "./", //基础路径,默认为当前根目录 "paths":{ "@/*": ["src/*"] //@ 别名 } ``` --> ## 示例代码 ```js 入口文件 main.ts import { createApp } from 'vue' import './style.css' import App from './App.vue' import Router from "./Router.js" import { Store } from '@/stores' const Vue = createApp(App) Vue.use(Router) Vue.use(Store) Vue.mount('#app')
路由集合 Router.ts
import {createRouter,createWebHashHistory} from 'vue-router' // 组件 import Home from './Home.vue' import Word from './Word.vue' import About from './About.vue' import Nofined from './Nofined.vue' const routes:any = [ { path: '/', component: Home }, { path: '/about', component: About, children:[ {path:'word',component:Word}, ] }, { path:'/404', component: Nofined}, { path:'/:catchAll(.*)',redirect:'/404'} ] const router:any = createRouter({ history: createWebHashHistory(), routes, }) export default router
状态管理link
import { createPinia,defineStore } from "pinia"; export const Store = createPinia() export const useStore = defineStore('main',{ state:()=>({ counter:0 }), getters:{ double:(state)=> state.counter + 1, }, actions:{ addNum(){ this.counter++ } } })
Home.vue
<script setup> import { useStore } from '@/stores/index.js' const store = useStore() console.log(store.counter) const clickAb =()=>{ store.counter++ // store.$patch({ // counter: store.counter + 1, // }) } </script> <template> <div @click="clickAb">About{{store.counter}} <Home/> </div> </template>

测试配置

Vitest 是一个由 Vite 提供支持的极速单元测试框架,@vue/test-utils 则是用来测试vue组件的可选工具

yarn add -D vitest || npm install -D vitest
yarn add -D @vue/test-utils || npm install -D @vue/test-utils

配置命令,给 TypeScript 配置。如下

vite.config.js
// ... export default defineConfig({ plugins: [vue()], test: { _// ..._ }, })
package.json
{ "scripts": { "test": "vitest", "coverage": "vitest run --coverage", "vitestui": "启动vitest的UI界面", "vitestui": "vitest --ui" } }

测试示例

App.vue
<template> <div> <a href="https://vitejs.dev" target="_blank"> <img src="/vite.svg" class="logo" alt="Vite logo" /> </a> </div> </template>
__tests__/App.test.ts
import { mount } from "@vue/test-utils"; import App from '../src/App.vue' import { describe,expect, test } from "vitest"; // 测试分组 describe("App.vue", () => { // 拥有img元素,拥有logo class test("has logo img",()=>{ const wrapper = mount(App); expect(wrapper.find('[class="logo"]').exists()).toBe(true) expect(wrapper.find('img').exists()).toBe(true) }) });

相关扩展

在项目中配置模块的相关目录

__tests__/ (Vitest测试文件)
 |——App.test.ts
 ......
src/
 |——api/ (所有请求:用户相关,功能相关等)
   |——user.js 
 |——assets/ (静态资源:图片,视频,字体等)
   |——images/ 
 |——components/ (所有组件:主页,登录,注册等)
   |——Home.vue
 |——config/ (所有配置项:网关,路由字体大小等)
   |——geteway.js
   |——router.js
 |——stores/  (pinia的数据中心)
   |——index.js
   |——home.js
 |——utils/  (工具集合:请求,常用函数等)
   |——require.js
 min.js

在手机端设置区分前进后退的代码

路由集合 Router.js
//区分前进后退,主要是为了在移动端添加不同的动画 Vue.prototype.push = function(url){ //路由前进 let old=this.$router.currentRoute.path if(old==url){ this.$router.go(0); }else{ Vue.prototype.transitionName = "slide-left"; //通过设置不同的class触发动画 this.$router.push(url); } }; Vue.prototype.pop = function(n=-1){ //路由后退 Vue.prototype.transitionName = "slide-right"; let path = this.$router.currentRoute.path; if (path == "/") { return;} this.$router.go(n); }

在本地项目中配置proxy解决跨域请求

这是 vite工具的配置,在项目的根目录创建 vite.config.js

vite.config.js
module.exports = { server: { //请求配置 proxy: { '/api': { target: 'http://10.31.27.56:8087/', changeOrigin: true, // 是否跨域 rewrite: path => path.replace('/^\/api/', '/api'), }, }, } }

在项目中配置接口请求和数据异步处理

yarn add axios
yarn add qs

/axios.js
import axios from "axios" import qs from "qs" // 设置超时 axios.defaults.timeout = 5000; // 表示跨域请求时是否需要使用凭证 axios.defaults.withCredentials = false; // 允许跨域 axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'; axios.defaults.headers.post["Access-Control-Allow-Origin-Type"] = "*"; // 请求拦截器 axios.interceptors.request.use(function (config) { //数据格式化 if (config.method === "post" ||config.method === "put" ||config.method === "delete") { config.data = qs.parse(config.data); } //添加身份验证 // if(config.headers.Authorization === undefined) { // config.headers.Authorization = 'Bearer ' + store.state.token // } return config; }, error => { console.log(error.data.error.message) // 也可以通过组件的message抛出 return Promise.reject(error.data.error.message); }) // 响应拦截器 axios.interceptors.response.use(function (config) { if (config.status === 200 || config.status === 204) { return Promise.resolve(config); } else { return Promise.reject(config); } },function (error) { if (error.response.status) { switch (error.response.status) { case 400: console.log("发出的请求有错误,服务器没有进行新建或修改数据的操作==>" + error.response.status) break; case 401: //未登录 重定向,跳转登录页面 console.log("token:登录失效==>" + error.response.status + ":" + store.state.Roles) // router.replace({ path: '/Login', }); break; case 403: //token过期,清除本地token和清空vuex中token对象,跳转登录页面 console.log("用户得到授权,但是访问是被禁止的==>" + error.response.status) break; case 404: //资源不存在 console.log("网络请求不存在==>" + error.response.status) break; case 406: console.log("请求的格式不可得==>" + error.response.status) break; case 410: console.log("请求的资源被永久删除,且不会再得到的==>" + error.response.status) break; case 422: console.log("当创建一个对象时,发生一个验证错误==>" + error.response.status) break; case 500: console.log("服务器发生错误,请检查服务器==>" + error.response.status) break; case 502: console.log("网关错误==>" + error.response.status) break; case 503: console.log("服务不可用,服务器暂时过载或维护==>" + error.response.status) break; case 504: console.log("网关超时==>" + error.response.status) break; default: console.log("其他错误错误==>" + error.response.status) } return Promise.reject(error.response); } else { // 处理断网的情况 // store.commit('changeNetwork', false); } }) // 封装的require请求 export const require = (option)=>{ return new Promise((fulfill,reject)=>{ Vue.axios(option).then((res)=>{ fulfill(res) }).catch((rej)=>{ if(rej.response){ reject(rej.response) }else if(rej.require){ reject(rej.require) }else{ reject(rej.message) } }) }) } export default require;
/config/geteway.js
// export const network='https://api.ixiaowai.cn' export const network='' //通过vue-cli设置服务器代理,在上线前需要改正 export const auto='api'
vue.config.js
module.exports={ devServer:{ proxy:{ "/api":{ target: 'https://api.ixiaowai.cn/', changeOrigin:true, pathRewrite:{ //重写请求路径,将api标记清除 '^/api':'' } }, } } }
/api/other.js
import requires from '@/utils/require' import {network,auto} from '@/config/geteway' export const saying=function(params){ //接口请求 return require({ url:`${network}/${auto}/ylapi.php`, method:'GET', params }) }

问题

响应式的边界问题

需要注意的是,相比较vue2,vue3中的原始数据不是响应式的

export default {
  data() {
    return {
      someObject: {}
    }
  },
  mounted() {
    const newObject = {}
    this.someObject = newObject

    console.log(newObject === this.someObject) // false
  }
}

Vue的代码风格:选项式API和组合式API

与选项式API不同的是,组合式API需要搭配 <script setup> 进行使用;

选项式
<script> export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } }, mounted() { console.log(`The initial count is ${this.count}.`) } } </script> <template> <button @click="increment">Count is: {{ count }}</button> </template>
组合式
<script setup> import { ref, onMounted } from 'vue' // 响应式状态 const count = ref(0) // 用来修改状态、触发更新的函数 function increment() { count.value++ } // 生命周期钩子 onMounted(() => { console.log(`The initial count is ${count.value}.`) }) </script> <template> <button @click="increment">Count is: {{ count }}</button> </template>

涉及知识

Vite
官网:https://cn.vitejs.dev/ Vite是一个前端构建工具,它包含一个开发服务器,一套构建指令。vite 以当前工作目录作为根目录启动开发服务器。你也可以通过 vite serve some/sub/dir 来指定一个替代的根目录。 注意的是 index.html 与项目根目录,vite以index.html作为入口文件。也支持多个 .html 作入口点的 多页面应用模式

Vitest
Vitest 是一个由 Vite 提供支持的极速单元测试框架

TypeScript
vite天然支持TypeScript,Vite 仅执行 .ts 文件的转译工作,并 不 执行任何类型检查。并假设类型检查已经被你的 IDE 或构建过程接管了。

Vue Router
Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。

Pinia
Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态,Pinia 最初是在 2019 年 11 月左右重新设计使用 Composition API 。从那时起,最初的原则仍然相同,但 Pinia 对 Vue 2 和 Vue 3 都有效,并且不需要您使用组合 API。

Vue