跳过正文
  1. 文章/
  2. 前端/
  3. Vue/

11、Vue3

·3219 字·7 分钟· loading · loading · ·
前端 Vue
GradyYoung
作者
GradyYoung
Vue - 点击查看当前系列文章
§ 11、Vue3 「 当前文章 」

Vue3
#

相比vue2的提升
#

  • 性能提升
    • 打包大小减少41%
    • 初次渲染快55%,更新渲染快133%
    • 内存减少43%
  • 源码升级
    • 使用Proxy代替defineProperty实现响应式
    • 重写虚拟DOM的实现和Tree-Shaking
  • 支持TypeScript
    • 可以更好的支持TypeScript
  • 新特性
    • Composition API
    • 新的内置组件

Vite创建Vue3工程
#

Vue3 不推荐使用 vue-cli 来创建(使用vue/cli,要确保版本大于4.5.0),而是使用 Vite。

Vite是新一代的前端构建工具,在尤雨溪开发Vue3.0的时候诞生。类似于Webpack+ Webpack-dev-server。

其主要利用浏览器ESM特性导入组织代码,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。

生产中利用Rollup作为打包工具,号称下一代的前端构建工具。

npm create vite@latest

注意 Node 的版本,如果过低可能会报错。

安装依赖
#

默认创建的工程中,没有 vue-router 等依赖。

vue-router
#

npm install vue-router

然后在 src 目录下创建 router/router.js,并声明自己定义的views

import { createRouter, createWebHistory } from "vue-router";

const routes = [
  {
    path: "/",
    component: () => import("@/views/Index.vue"),
  },
  {
    path: "/about",
    component: () => import("@/views/About.vue"),
  },
];

export const router = createRouter({
  history: createWebHistory(),
  routes,
});

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");

sass
#

npm i sass sass-loader --save-dev

Vue3的变化
#

main.js
#

// 不再是引入Vue构造函数,而是引入createApp工厂函数
import { createApp } from 'vue'
import App from './App.vue'

//创建应用实例对象,类似于Vue实例对象,但是比Vue实例对象更轻
createApp(App).mount('#app')

App.vue
#

<!-- vue3中的组件模板可以没有根标签 -->
<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>
<style>
</style>

Composition API
#

setup
#

vue3中的一个新的组件配置项,值是一个函数,setup是所有Composition API表演的舞台

组件中用到的所有数据、方法、计算属性,全部要配置在setup中

  • setup函数的返回值
    • 返回对象:对象的属性、方法在模板中可以直接使用
    • 返回渲染函数:从vue中引入h,返回h渲染的结果
<template>
  <h1>name{{name}}</h1>
  <h1>age{{age}}</h1>
  <button @click="show">show</button>
</template>

<script>
export default {
  name: 'App',
  setup(){
    //数据
    let name = "lucy";
    let age = 18;
    //方法
    function show(){
      alert("姓名:" + name + ",年龄:" + age);
    }
    return {name,age,show}
  }
}
</script>

注意:尽量不要和vue2配置混用(vue2配置可以读取setup的属性和方法,但是setup不能访问vue2配置的);setup不能使用async修饰

setup函数的执行时机
#

在beforeCreate之前执行一次,this是undefined,也就是说,setup中不要写this

ref函数
#

对于在setup函数中返回的属性,虽然组件可以直接使用,但是不是响应式(页面随着数据进行改变)的,如果需要该属性是响应式的,也就是需要vue监测该属性,那么需要使用vue提供的ref函数

ref函数,接受的数据可以是基本类型也可以是对象类型

对于基本类型:底层依靠的是defineProperty()get()set()实现

对于对象类型:底层利用vue3的一个新函数reactive()实现

<template>
  <h1>name{{name}}</h1>
  <h1>age{{age}}</h1>
  <button @click="ageAdd10">ageAdd10</button>
</template>

<script>
//引入ref函数
import {ref} from 'vue';
export default {
  name: 'App',
  setup(){
    let name = "lucy";
    //需要响应式的属性使用ref函数,生成引用实现对象
    let age = ref(18);
    //方法
    function ageAdd10(){
      //修改值的时候需要修改引用实现对象的value属性
      age.value += 10;
    }
    return {name,age,ageAdd10}
  }
}
</script>

reactive函数
#

和ref作用、用法都一样,定义响应式数据,通常用来定义对象类型数据,虽然ref也可以定义对象数据类型,但是底层调用的还是reactive

computed函数
#

用于在setup中定义计算属性

<template>
  <input type="text" v-model="name">
  <h1>{{sayHello}}</h1>
</template>

<script>
import {ref,computed} from 'vue';
export default {
  name: 'App',
  setup(){
    let name = ref("lucy");
    let sayHello = computed(() => {
      return "Hello " + name.value;
    });
    return {name,sayHello}
  }
}
</script>

watch函数
#

用于在setup中定义监视属性

注意:可以同时监视多个变量,只需要多个变量放到一个数组,传到第一个参数即可

<template>
  <input type="text" v-model="name">
</template>

<script>
import {ref,watch} from 'vue';
export default {
  name: 'App',
  setup(){
    let name = ref("lucy");
    watch(name,(after,before)=>{
      console.log(before,after);
    })
    watch(name,(after,before)=>{
      console.log(before,after);
    },{immediate: true}) //如果使用immediate:true,那么回调会在组件创建时调用一次
    return {name}
  }
}
</script>

watchEffect函数
#

这个函数的参数是一个回调函数(无参),可以智能的监视这个回调函数里面使用的数据,如果数据发生变化,就会调用该回调,并且在组件创建时调用一次

<template>
  <input type="text" v-model="name">
</template>

<script>
import {ref,watchEffect} from 'vue';
export default {
  name: 'App',
  setup(){
    let name = ref("lucy");
    watchEffect(()=>{
      console.log(before,after);
    })
    return {name}
  }
}
</script>

toRef函数
#

创建一个ref对象,其value值指向另一个响应式对象中的某个属性,这个value也是响应式的

setup(){
    let student = reactive({
        name: 'lucy',
        age: 18,
        clazz: {
            level: 9
        }
    })
    let name = toRef(student,'name');
    let level = toRef(student.clazz,'level');
    return {name}
}

toRefs函数
#

可以将一个响应式对象中的所有属性(第一层属性)都变成响应式对象,也就是不会将下面level变成响应式

setup(){
    let student = reactive({
        name: 'lucy',
        age: 18,
        clazz: {
            level: 9
        }
    })
    let stu = toRefs(student);
    return {name}
}

toRaw函数
#

可以将一个响应式对象转换为一个普通对象并返回

vue3没有this
#

1、由于vue3中setup函数的执行时机要比beforeCreate早,所以在setup中无法拿到this

2、由于vue3不是使用Vue构造进行构建,而是使用App,所以如果要挂载一些属性到Vue原型对象上,方法和vue2不同

例如挂载axios

// 引入axios
import axios from 'axios';
const app = createApp(App);
//挂载全局属性
app.config.globalProperties.$axios = axios;
app.use(router).mount("#app");

在组件中使用

setup(){
    const {proxy} = getCurrentInstance();
    proxy.$axios.get();
}

vue3声明周期
#

  • 和vue2相比,有两个钩子名称发生了变化
    • vue2中的beforeDestroy,在vue3中是beforeUnmount
    • vue2中的destroyed,在vue3中是unmouted
      实例的生命周期

Hook
#

Hook的本质就是一个函数,是对setup函数里面的组合式API进行封装,类似于Vue2中的mixin

  • 在vue3的组合式API中,对于声明周期钩子,也提供了对应的函数
选项式 API Hook inside setup
beforeCreate setup
created setup
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
activated onActivated
deactivated onDeactivated
import {onMounted} from 'vue'
<script>
    export default {
      name: 'Demo',
      setup() {
          // 可以在setup中直接写
          onMounted(() => {
              
          })
      }
    }
</script>

自定义hook
#

src/hooks/中新建一个js文件,将多组件复用的组合式API抽离,并将组件需要的属性返回即可

import {ref} from 'vue';
export default () => {
    //此处写组合式API的逻辑
    let name = ref('lucy');
    
    return name;
}

在需要该组合API的组件中引入并调用,一般函数名以use开头

import useName from '@/hooks/name.js';
<script>
    export default {
      name: 'Demo',
      setup() {
          // 在setup中调用,并接收参数
          let name = useName();
          return {name}
      }
    }
</script>

新增组件
#

Fragment
#

在vue2中,所有组件必须有跟标签

在vue3中,组件可以没有跟标签,所有元素在Fragment虚拟元素中

Teleport
#

可以将指定的html结构移动到指定位置

<template>
    <!-- 传送到body标签中 -->
    <teleport to="body">
        <input/>
    </teleport>
</template>

全局API变化
#

vue2 vue3
Vue.config.productionTip 移除
Vue.config.ignoredElements app.config.isCustomElement()
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use()
Vue.filter 移除
Vue.prototype app.config.globalProperties

其他变化
#

1、移除v-on.native

2、由于vue3中setup没有this,所以获取ref的方法有所改变

<template>
  <input type="text" ref="inputRef">
</template>
<script>
import { onMounted, ref } from 'vue'
/*
    ref获取元素: 利用ref函数获取组件中的标签元素
    功能需求: 让输入框自动获取焦点
*/
export default {
  setup() {
    const inputRef = ref(null)
    onMounted(() => {
      inputRef.value.focus()
    })
    return {
      inputRef
    }
  },
}
</script>

Vite 配置
#

vite使用的是vite.config.js,下面是 vue.config.js 中常用的配置项在vite.config.js中的写法。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

// 1. 路径别名配置(对应原Webpack的resolve.alias)
const resolve = (dir) => path.resolve(__dirname, dir)

// 2. 代理配置函数(更清晰的Vite风格)
const createProxy = (prefix, target) => ({
  [prefix]: {
    target,
    changeOrigin: true,
        configure: (proxy, _options) => {
          proxy.on('proxyReq', (proxyReq) => {
            if (proxyReq.getHeader('origin')) {
              proxyReq.setHeader('origin', target)
            }
          })
        },
    rewrite: (path) => path.replace(new RegExp(`^${prefix}`), '')
  }
})

export default defineConfig({
  plugins: [vue()],
  base: "./",
  
  // 3. 路径别名配置
  resolve: {
    alias: {
      '@': resolve('src'),
      '@assets': resolve('src/assets'),
      '@components': resolve('src/components'),
      '@views': resolve('src/views'),
      '@api': resolve('src/api'),
      '@util': resolve('src/util'),
      '@store': resolve('src/store')
    }
  },

  // 4. 开发服务器配置
  server: {
    open: false,          // 保持原配置
    host: '0.0.0.0',      // 保持原配置
    port: 8081,           // 保持原配置
    proxy: {
      ...createProxy('/apis', 'http://localhost:8080/'),
      ...createProxy('/assets', 'http://localhost:19000/')
    }
  },

  // 5. 生产环境配置
  build: {
    sourcemap: false,     // 对应原 productionSourceMap: false
  },

  // 6. 其他优化配置(可选)
  esbuild: {
    // 如果需要关闭ESLint(原lintOnSave: false)
    // 需要安装 vite-plugin-eslint 插件并配置
  }
})

解决路径别名爆红
#

tsconfig.json文件中,添加

{
  "compilerOptions": {
    "paths": {
      "@/*": ["src/*"],
      "@assets/*": ["src/assets/*"],
      "@components/*": ["src/components/*"],
      "@views/*": ["src/views/*"],
      "@api/*": ["src/api/*"],
      "@util/*": ["src/util/*"],
      "@store/*": ["src/store/*"],
    },
}
Vue - 点击查看当前系列文章
§ 11、Vue3 「 当前文章 」