Vue3 常用知识

发布于:2022-12-24 ⋅ 阅读:(384) ⋅ 点赞:(0)

vue 3.x

跳转链接

(科普)简述–前端

MVC 模式

即 Model(模型),View(视图) 和 Controller(控制器)
技术:jQuery + Bootstrap 一把梭

MVVM 模式
即 Model(模型),View(视图) 和 ViewModel(视图模型)
标志:AngularJS 的诞生
特点:数据驱动页面(数据发生变化后,通知页面更新)。各大框架在这个步骤上,各显神通:

  • Angular 1 就是最老套的脏检查

脏检查:对数据变化的检查上,遵循每次用户交互时都检查一次数据是否变化,有变化就去更新 DOM

  • React 1 使用了虚拟 DOM

虚拟 DOM:就像是数据和实际 DOM 的一个缓存层。数据有变化的时候,我们生成一份新的虚拟 DOM 数据,然后再对之前的虚拟 DOM 进行计算,算出需要修改的 DOM(计算逻辑:Diff),再去页面进行操作


缺点:如果虚拟 DOM 树过于庞大,使得计算时间大于 16.6ms,那么就可能会造成性能的卡顿

  • Vue 1 使用了响应式

响应式:初始化的时候,Watcher 监听了数据的每个属性,这样数据发生变化的时候,我们就能精确地知道数据的哪个 key 变了,去针对性修改对应的 DOM 即可


缺点:Watcher 监听,本身就比较损耗性能,导致内存占用过多

  • Angular 2 引入了 TypeScript、RxJS 等新内容,但是不支持向前兼容

设计很优秀,但是抛弃了老用户,这也是 Angular 这个优秀的框架现在在国内没有大面积推广的原因

  • React 2 借鉴了操作系统时间分片的概念,引入了 Fiber 架构,巧妙地利用空闲实现计算,解决了卡顿的问题

把整个虚拟 DOM 树微观化,变成链表,然后我们利用浏览器的空闲时间计算 Diff。一旦浏览器有需求,我们可以把没计算完的任务放在一旁,把主进程控制权还给浏览器,等待浏览器下次空闲

  • Vue 2 引入虚拟 DOM

组件之间的变化,可以通过响应式来通知更新;组件内部的数据变化,则通过虚拟 DOM 去更新页面。这样就把响应式的监听器,控制在了组件级别,而虚拟 DOM 的量级,也控制在了组件的大小
示意图

版本原理

示意图

vue 2

特性

Options API

缺点:

  • 由于所有数据都挂载在 this 之上,因而 Options API 的写法对 TypeScript 的类型推导很不友好,并且这样也不好做 Tree-shaking 清理代码。
  • 新增功能基本都得修改 data、method 等配置,并且代码上 300 行之后,会经常上下反复横跳,开发很痛苦。
  • 代码不好复用,Vue 2 的组件很难抽离通用逻辑,只能使用 mixin,还会带来命名冲突的问题。
	<div id="app">
      <h1 @click="add">{{count}} * 2 = {{double}}</h1>
    </div>
    <script src="https://unpkg.com/vue@next"></script>
    <script>
      let App = {
        data() {
          return {
            count: 1,
          }
        },
        methods: {
          add() {
            this.count++
          },
        },
        computed: {
          double() {
            return this.count * 2
          },
        },
      }
      Vue.createApp(App).mount('#app')
    </script>

vue 3

源码链接

特性

Composition API,即组合 API

缺点

vue 3 响应式系统用了 Proxy,会存在兼容性问题,即不兼容 IE11

优点

  • 所有 API 都是 import 引入的,对 Tree-shaking 很友好,没用到的功能,打包的时候会被清理掉 ,减小包的大小。
  • 不再上下反复横跳,我们可以把一个功能模块的 methods、data 都放在一起书写,维护更轻松。
  • 代码方便复用,可以把一个功能所有的 methods、data 封装在一个独立的函数里,复用代码非常容易。
  • Composotion API 新增的 return 等语句,在实际项目中使用

Vue 中用过三种响应式解决方案,分别是 defineProperty API、Proxy 和 value setter。示意图

	<div id="app">
      <h1 @click="add">{{state.count}} * 2 = {{double}</h1>
    </div>
    <script src="https://unpkg.com/vue@next"></script>
    <script>
      const { reactive, computed } = Vue
      let App = {
        setup() {
          const state = reactive({
            count: 1,
          })
          function add() {
            state.count++
          }
          const double = computed(() => state.count * 2)
          return { state, add, double }
        },
      }
      Vue.createApp(App).mount('#app')
    </script>

新的组件

  • Fragment: Vue 3 组件不再要求有一个唯一的根节点,清除了很多无用的占位 div。
  • Teleport: 允许组件渲染在别的元素内,主要开发弹窗组件的时候特别有用。
  • Suspense: 异步组件,更方便开发有异步请求的组件。

新的工程化工具

Vite,它不在 Vue 3 的代码包内,和 Vue 也不是强绑定。Vite 的竞品是 Webpack,而且按照现在的趋势看,使用率超过 Webpack 是迟早的事。

  • Webpack 等工程化工具的原理,是根据你的 import 依赖逻辑,形成一个依赖图,然后调用对应的处理工具,把整个项目打包后,放在内存里再启动调试。由于要预打包,所以复杂项目的开发,启动调试环境需要 3 分钟都很常见。
    示意图
    Webpack 要把所有路由的依赖打包后,才能开始调试。

  • 现代浏览器已经默认支持了 ES6 的 import 语法,Vite 就是基于这个原理来实现的。即:在调试环境下,不需要全部预打包,只是把你首页依赖的文件,依次通过网络请求去获取,整个开发体验得到巨大提升,做到了复杂项目的秒级调试和热更新。
    示意图
    Vite 一开始就可以准备联调,然后根据首页的依赖模块,再去按需加载,这样启动调试所需要的资源会大大减少。

从零开始

  1. 下载安装 Node.js 的过程是傻瓜式的,直接去Node.js 官网下载安装即可(可以选择 LTS 版本,也就是稳定版)

在命令行窗口执行 node -v 指令,出现版本号就算安装完成啦示意图

  1. 对于 Vue 2,官方推荐用 Vue-cli 创建项目;而对于 Vue 3,建议使用 Vite 创建项目,因为 vite 能够提供更好更快的调试体验

命令:npm init vite示意图
项目骨架:
.
├── .vscode
│ └── extensions.json
├── public 【资源文件】
│ └── vite.svg
├── src 【源码】
│ ├── assets
│ │ └── vue.svg
│ ├── components
│ │ └── HelloWorld.vue
│ ├── App.vue 【 单文件组件】
│ └── main.js 【入口】
│ └── style.css
├── .gitignore
├── index.html 【入口文件】
├── package.json 【管理项目依赖和配置】
├── README.md
├── vite.config.js 【vite工程化配置文件】

  1. 在 first-project 文件夹内执行 npm install 命令,来进行依赖的安装;然后执行 npm run dev 命令来启动项目

示意图

  1. 在 Chrome 里打开 http://127.0.0.1:5173/ 有页面显示

  2. 安装 Vuexvue-router

Vuex 负责管理数据,安装命令:npm install vue-router@4
vue-router 负责管理路由,安装命令:npm install vuex@next --save

  1. 规范组织结构
  • src 目录的组织结构:

├── src
│ ├── api 【数据请求】
│ ├── assets 【静态资源】
│ ├── components 【组件】
│ ├── pages 【页面】
│ ├── router 【路由配置】
│ ├── store 【vuex数据】
│ └── utils 【工具函数】

  • 进入到 router 文件夹中,新建 index.js,写入下面的代码:

import {
  createRouter, // createRouter 函数,用来新建路由实例
  createWebHashHistory, // createWebHashHistory 函数,用来配置我们内部使用 hash 模式的路由
} from 'vue-router'
import Home from '../pages/home.vue'
import About from '../pages/about.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router
  • 在 pages 下面新建两个文件 about.vue 和 home.vue,分别输入如下内容:
<template>
    <h1>这是about页面</h1>
</template>
<template>
    <h1>这是home页面</h1>
</template>
  • 在 main.js 中,加载 router 的配置:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

import router from './router/index'

createApp(App).use(router).mount('#app')
  • 在 App.vue 中,修改:
<template>
  <div>
    <router-link to="/">首页</router-link> |
    <router-link to="/about">关于</router-link>
    <router-view></router-view>
  </div>
</template>
  1. 至此,项目的基本骨架已经完成。

css 样式

1. 局部样式scoped

<style scoped>,使 CSS 只会应用到当前组件的元素上,这样就很好地避免了一些样式冲突的问题。

<style scoped>
h1 {
  color: red;
}
</style>>

组件就会解析成下面代码的样子:

<h1 data-v-3de47834="">1</h1>

<style scoped>
h1[data-v-3de47834] {
    color: red;
}
</style>
2. scoped 内部的全局样式

如果在 scoped 内部,还想写全局的样式,那么可以用 :global 来标记

3. 在 CSS 中使用 JavaScript 中的变量
<template>
  <div>
    <h1 @click="add">{{ count }}</h1>
  </div>
</template>

<script setup>
import { ref } from "vue";
let count = ref(1)
let color = ref('red')
function add() {
  count.value++
  color.value = Math.random()>0.5? "blue":"red"
}
</script>

<style scoped>
h1 {
  color:v-bind(color);
}
</style>>

ref 基础

<template>
  <div>
    <h1 @click="add">{{count}}</h1>
  </div>
</template>

<script setup>
import { ref } from "vue";
let count = ref(1)
function add(){
    count.value++
}
</script>

<style>
h1 { color: red; }
</style>
  • 对于 ref 返回的响应式数据,我们需要修改 .value 才能生效
  • <script setup> 标签内定义的变量和函数,都可以在模板中直接使用

复用

<template>
    <h1>这是首页</h1>
    <TodoList />    
</template>

<script setup>
import TodoList from '../components/TodoList.vue'
</script>

计算属性

<template>
  <div>
    <input type="text" v-model="title" @keydown.enter="addTodo" />
    <button v-if="active < all" @click="clear">清理</button>
    <ul v-if="todos.length">
      <li v-for="todo in todos">
        <input type="checkbox" v-model="todo.done" />
        <span :class="{ done: todo.done }"> {{ todo.title }}</span>
      </li>
    </ul>
    <div v-else>暂无数据</div>
    <div>
      全选<input type="checkbox" v-model="allDone" />
      <span> {{ active }} / {{ all }} </span>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from "vue";
let title = ref("");
let todos = ref([{ title: "学习Vue", done: false }]);

function addTodo() {
  todos.value.push({ title: title.value, done: false });
  title.value = "";
}
function clear() {
  todos.value = todos.value.filter((v) => !v.done);
}
let active = computed(() => {
  return todos.value.filter((v) => !v.done).length;
});
let all = computed(() => todos.value.length);
let allDone = computed({
  get: function () {
    return active.value === 0;
  },
  set: function (value) {
    todos.value.forEach((todo) => {
      todo.done = value;
    });
  },
});
</script>

计算属性 computed 需要单独引入使用,而不是依赖 this 上下文

函数封装

把功能独立的模块封装成一个独立的函数,真正做到按需拆分


<template>
  <div>
    <input type="text" v-model="title" @keydown.enter="addTodo" />
    <button v-if="active < all" @click="clear">清理</button>
    <ul v-if="todos.length">
      <li v-for="todo in todos">
        <input type="checkbox" v-model="todo.done" />
        <span :class="{ done: todo.done }"> {{ todo.title }}</span>
      </li>
    </ul>
    <div v-else>暂无数据</div>
    <div>
      全选<input type="checkbox" v-model="allDone" />
      <span> {{ active }} / {{ all }} </span>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from "vue";


let count = ref(1);

function add() {
  count.value++;
}

let { title, todos, addTodo, clear, active, all, allDone } = useTodos();

// 和清单相关的所有数据和方法
function useTodos() {
  let title = ref("");
  let todos = ref([{ title: "学习Vue", done: false }]);
  function addTodo() {
    todos.value.push({
      title: title.value,
      done: false,
    });
    title.value = "";
  }
  function clear() {
    todos.value = todos.value.filter((v) => !v.done);
  }
  let active = computed(() => {
    return todos.value.filter((v) => !v.done).length;
  });
  let all = computed(() => todos.value.length);
  let allDone = computed({
    get: function () {
      return active.value === 0;
    },
    set: function (value) {
      todos.value.forEach((todo) => {
        todo.done = value;
      });
    },
  });
  return { title, todos, addTodo, clear, active, all, allDone };
}
</script>

抽离

把组件内部的任何一段代码,从组件文件里抽离出一个独立的文件进行维护

功能:显示鼠标的坐标位置

import {ref, onMounted,onUnmounted} from 'vue'

export function useMouse(){
    const x = ref(0)
    const y = ref(0)
    function update(e) {
      x.value = e.pageX
      y.value = e.pageY
    }
    onMounted(() => {
      window.addEventListener('mousemove', update)
    })
    onUnmounted(() => {
      window.removeEventListener('mousemove', update)
    })
    return { x, y }
}
  • 组件加载的时候,会触发 onMounted 生命周期,我们执行监听 mousemove 事件,从而去更新鼠标位置的 x 和 y 的值
  • 组件卸载的时候,会触发 onUnmounted 生命周期,解除 mousemove 事件
<template>
  <div>
    <h2>x:{{ x }},y:{{ y }}</h2>
  </div>
</template>

<script setup>
import { useMouse } from "../utils/mouse";

let { x, y } = useMouse();
</script>

抽离----本地化

问题:解决所有的操作状态在刷新后就都没了这个问题

思路:让操作状态和本地存储能够同步

操作:显式地声明同步的逻辑,而 watchEffect 这个函数让我们在数据变化之后可以执行指定的函数

import { ref, watchEffect, computed } from "vue";

let title = ref("");
let todos = ref(JSON.parse(localStorage.getItem('todos')||'[]'));
watchEffect(()=>{
    localStorage.setItem('todos',JSON.stringify(todos.value))
})
function addTodo() {
  todos.value.push({
    title: title.value,
    done: false,
  });
  title.value = "";
}

升级版:把 useStorage 这个函数可以抽离成一个文件,放在工具函数文件夹中

function useStorage(name, value=[]){
    let data = ref(JSON.parse(localStorage.getItem(name)|| value))
    watchEffect(()=>{
        localStorage.setItem(name,JSON.stringify(data.value))
    })
    return data
}

使用时,把 ref 变成 useStorage

let todos = useStorage('todos',[])

function addTodo() {
  ...code
}

抽离----替换 favicon 图标

把对图标的对应修改的操作封装成了 useFavicon 函数,并且通过 ref 和 watch 的包裹,我们还把小图标变成了响应式数据

import {ref,watch} from 'vue'
export default function useFavicon( newIcon ) {
    const favicon = ref(newIcon)

    const updateIcon = (icon) => {
      document.head
        .querySelectorAll(`link[rel*="icon"]`)
        .forEach(el => el.href = `${icon}`)
    }
    const reset = ()=>favicon.value = '/favicon.ico'

    watch( favicon,
      (i) => {
        updateIcon(i)
      }
    )
    return {favicon,reset}
  } 

通过响应式的方式去修改和使用小图标,通过对 faivcon.value 的修改就可以随时更换网站小图标。下面的代码,就实现了在点击按钮之后,修改了网页的图标为 girl.png 的操作

 <script setup>
 import useFavicon from './utils/favicon'
 let {favicon}  = useFavicon()
 function loading(){
   favicon.value = '/girl.png'
 }
</script>

<template>
  <button @click="loading">123</button>
</template>
VueUse 插件

Composition API 的工具集合,适用于 Vue 2.x 或者 Vue 3.x

安装: npm install @vueuse/core

使用 VueUse 的 useFullscreen 来返回全屏的状态和切换全屏的函数:

<template>
  <h1 @click="toggle">click</h1>
</template>
<script setup>
import { useFullscreen } from '@vueuse/core'
const { isFullscreen, enter, exit, toggle } = useFullscreen()
</script>

一些函数

  1. defineProps

作用:规范传递数据的格式

<template>
    <div>
        {{rate}}
    </div>
</template>

<script setup>
import { defineProps,computed } from 'vue';

let props = defineProps({
  value: Number,
  theme: { type: String, default: "orange" },
});

let rate = computed(()=>"★★★★★☆☆☆☆☆".slice(5 - props.value, 10 - props.value))

const themeObj = {
  black: "#00",
  white: "#fff",
  red: "#f5222d",
  orange: "#fa541c",
  yellow: "#fadb14",
  green: "#73d13d",
  blue: "#40a9ff",
};
const fontstyle = computed(() => {
  return `color:${themeObj[props.theme]};`;
});
</script>
  1. defineEmits

接收父组件传递过来的方法

父子传值

使用 defineEmit 向父元素传递数据

<template>
   <span @click="onRate(num)" @mouseover="mouseOver(num)" v-for='num in 5' :key="num"></span>
</template>
<script setup>
import { defineProps, defineEmits,computed, ref} from 'vue';

let emits = defineEmits('update-rate')
function onRate(num){
    emits('update-rate',num)
}
</script>

用 @update-rate 接收组件 emit 的数据,并且修改 score 的值

<template>
	<h1>你的评分是 {{score}}</h1>
	<Rate :value="score" @update-rate="update"></Rate>
</template>

<script setup>
import {ref} from 'vue'
import Rate from './components/Rate1.vue'
let score = ref(3.5)
function update(num){
    score.value = num
}
</script>

Vuex

Vuex 由于在 API 的设计上,对 TypeScript 的类型推导的支持比较复杂,用起来很是痛苦。
为了解决这个问题,Vuex 的作者最近发布了一个新的作品叫 Pinia,并将其称之为下一代的 Vuex。
Pinia 不需要 Vuex 自定义复杂的类型去支持 TypeScript,天生对类型推断就非常友好,并且对 Vue Devtool 的支持也非常好。

骨架分析
  • state 中定义数据
  • mutation 中修改数据
import { createStore } from 'vuex'
const store = createStore({ 
	state () { 
		return { 
			count: 666 
		}
	}, 
	mutations: { 
		add (state) { 
			state.count++ 
		} 
	},
	getters:{    
		double(state){          
			return state.count*2      
		}  
	},
	actions:{      
		asyncAdd({commit}){          
			setTimeout(()=>{            
				commit('add')          
			},1000)      
		}  
	}
})
export default store
function asyncAdd(){
    store.dispatch('asyncAdd')
}
  • getters 类似计算属性,对 state 中数据行进扩展
  • actions 中做任意的异步处理。可以通过解构获得 commit 函数来执行 mutations 去更新数据。

action 并不是直接修改数据,而是通过 mutations 去修改

h 函数

h 函数内部是调用 createVnode 来返回虚拟 DOM。即,创建虚拟 DOM 的函数。

更方便的写 h 函数的方式 ---- JSX

使用 JSX 的本质,还是在写 JavaScript
在 JavaScript 里面写 HTML 的语法,就叫做 JSX

从 JSX 到 createVNode 函数的转化过程中,我们需要安装一个 JSX 插件:

npm install @vitejs/plugin-vue-jsx -D

配置 vite.config.js:

import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx';

export default defineConfig({
  plugins: [vue(),vueJsx()]
})

模块化

1. 默认导出、导入
  • 导出语法:export default 默认导出的成员
let n = 10 // 模块私有成员

function show(){} // 模块私有方法

export default { n, show }
  • 导入语法:import 接收名称 from ‘模块标识符’
// 从 01_m1.js 模块中导入 export default 向外共享的成员:
import m1 from './01_m1.js' 

console.log(m1)
// 打印输出为 { n: 10, show: [Function: show] }

注意事项:

  1. 导入时的接收名称可以自定义
  2. 每个模块中只能存在一个 export default,多了会报错:
export default { n, show }

export default { n2 } // 会报错
2. 按需导出、导入

导出语法:export 按需导出的成员

export let n = 10

export function show(){}

导入语法:import { 导出成员的名称 } from ‘模块标识符’

import { n, show } from './01_m1.js'

console.log(n)
// 打印输入 10

console.log(show)
// 打印输出 [Function: show]

注意事项:

  1. 按需导入的成员名称必须和按需导出的名称一致
  2. 按需导入时可以使用 as 关键字进行重命名
  3. 按需导入可以和默认导入一起使用
3. 直接导入并执行模块中的代码

如果只想单纯地执行某个模块中的代码,并不需要得到模块中向外共享的成员。此时,可以直接导入并执行模块代码:

// 导入该模块时,单纯执行下 for 循环操作

for (let i = 0; i < 3; i++) {
	console.log(i)
}

import './01_m1.js'

TypeScript

官方演示链接

类型限制
let boyName:string = '小明'
let age:number = 18
let isMan:boolean = false
let boySales:undefined
let timer:null = null
let girl:[string,number] = ['小红',18]
枚举 enum
enum girlFriends {'小三','小红','小花'} // 枚举类型
let scores = [girlFriends['小三'], girlFriends['小红'], girlFriends['小花']]
接口 interface

定义对象的类型限制

interface person {
    name:string,
    price:number[], // 类型为数字组成的数组
    friend?:string|boolean, // string 或者 boolean,并且通过 ? 设置为可选属性
    readonly age:number // readonly 为只读
}
let vueCourse:person = {
	name:'小三',
    price:1800,
    friend:false
    age:18
} 
泛型 T

指有些函数的参数,不确定要定义成什么类型,而返回值类型需要根据参数来确定。

function identity0(arg: any): any {
    return arg
}
// 相当于type T = arg的类型
function identity<T>(arg: T): T {
    return arg
}
identity<string>('玩转vue 3全家桶') // 这个T就是string,所以返回值必须得是string
identity<number>(1)
函数类型

语法:function 函数名(参数:参数类型):返回值类型{}

function add(x: number, y: number): number {
    return x + y;
}
add(1, 2);

语法:(参数类型) => 返回值类型
特点:使用变量的方式去定义函数

let add1:(a:number,b:number)=>number = 
function(x: number, y: number): number {    
	return x + y;
}
可选择
  1. | 符号
let course:string|number = '哈哈'

type course1 = '一' | '二' | '三'
let course2:course1 = '三'
  1. keyof 语法
interface VueCourse5 {
    name:string,
    price:number
}
type CourseProps = keyof VueCourse5 // 只能是name和price选一个
let k:CourseProps = 'name'
let k1:CourseProps = 'p' // 改成price
三元表达式 extends
// T extends U ? X : Y 类型三元表达式

type ExtendsType<T> = T extends boolean ? "重学前端" : "玩转Vue 3"
type ExtendsType1 = ExtendsType<boolean> // type ExtendsType1='重学前端'
type ExtendsType2 = ExtendsType<string> // type ExtendsType2='玩转Vue 3'
遍历 in

通过 k in Courses 语法,相当于遍历了 Courses 所有的类型作为 CourseObj 的属性,值的类型是 number

type Courses = '玩转Vue 3'|'重学前端'
type CourseObj = {
    [k in Courses]:number // 遍历Courses类型作为key
}
// 上面的代码等于下面的定义
// type CourseObj = {
//     玩转Vue 3: number;
//     重学前端: number;
// }

vue 3 中使用 echarts

下载 echarts:

npm install echarts --save

使用 echarts:

<template>
  <div ref="chartDom"></div>
</template>

<script lang="ts" setup>
import { ref, onMounted } from "vue";
import * as echarts from "echarts"; //  按需引入 echarts

const chartDom = ref() // 使用 ref 创建虚拟 DOM 引用,使用时用 chartDom.value

onMounted(() => {
    init()
})

function init() {
  
  const myChart = echarts.init(chartDom.value); // 基于准备好的 DOM,初始化 echarts 实例
  
  const option = { // 指定图表的配置项和数据
    title: {
      text: 'ECharts 入门示例'
    },
    tooltip: {},
    legend: {
      data: ['销量']
    },
    xAxis: {
      data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
    },
    yAxis: {},
    series: [
      {
        name: '销量',
        type: 'bar',
        data: [5, 20, 36, 10, 10, 20]
      }
    ]
  };
  
  myChart.setOption(option); // 使用刚指定的配置项和数据显示图表
}
</script>

若要动态渲染,只需将动态数据替换掉 data 即可。
特别注意:数据的获取时机很重要,有的时候数据还没获取到 echarts 就已经渲染好了,导致数据不能在 echarts 上正常展示。建议将要渲染的数据存储到 Pinia 或本地存储,在渲染 echarts 之前就将数据给到 echarts,保证万无一失。

vue 3 中使用反向代理

我使用 vite 搭建的 vue3.X 项目,使用 ts 开发,项目骨架如下:
在这里插入图片描述

在 src/utils/request.ts 文件中封装请求:

// request.ts

import axios, { AxiosError } from "axios";

import type { AxiosInstance, AxiosResponse, AxiosRequestConfig } from "axios";

const service: AxiosInstance = axios.create({
  baseURL: "/api", // 接口添加 /api 前缀。如接口 /user/get 会映射成 /api/user/get
  timeout: 5000,
});

// 请求拦截器
service.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    return config;
  },
  (error: AxiosError) => {
    console.log(error);
    return Promise.reject();
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response: AxiosResponse) => {
    if (response.status === 200) {
      return response;
    } else {
      Promise.reject();
    }
  },
  (error: AxiosError) => {
    console.log(error);
    return Promise.reject();
  }
);

export default service;

在项目根目录的 vite.config.ts 文件中写 代理

// vite.config.ts

import { defineConfig } from "vite";
// ...
export default defineConfig({
	// ...
	server: {
    	proxy: {
      		"/api": {
      			// 如:请求 http://127.0.0.1:5173/api/user/get 会被映射到 http://127.0.0.1:3000/user/get 
        		target: "http://127.0.0.1:3000/",
        		changeOrigin: true,
        		rewrite: (path) => path.replace(/^\/api/, ""),
      		},
    	},
  	},
})
本文含有隐藏内容,请 开通VIP 后查看