#vue3与vue2区别以及vue3优点
- 默认进行懒观察(lazy observation)在 2.x 版本里,不管数据多大,都会在一开始就为其创建观察者。当数据很大时,这可能会在页面载入时造成明显的性能压力。3.x 版本,只会对「被用于渲染初始可见部分的数据」创建观察者,而且 3.x 的观察者更高效。
- 更精准的变更通知。2.x 版本中,使用 Vue.set 来给对象新增一个属性时,这个对象的所有 watcher 都会重新运行;3.x 版本中,只有依赖那个属性的 watcher 才会重新运行。
- 3.0 新加入了 TypeScript 以及 PWA 的支持
- 部分命令发生了变化:
a.下载安装 npm install -g vue@cli
b.删除了vue list
c.创建项目 vue create
d.启动项目 npm run serve - 默认项目目录结构也发生了变化:
移除了配置文件目录,config 和 build 文件夹
移除了 static 文件夹,新增 public 文件夹,并且 index.html 移动到 public 中
在 src 文件夹中新增了 views 文件夹,用于分类 视图组件 和 公共组件
移除data 取而代之使用 setup
在新版当中setup等效于之前2.0版本当中得到beforeCreate,和created,它是在组件初始化的时候执行,甚至是比created更早执行。值得注意的是,在3.0当中如果你要想使用setup里的数据,你需要将用到值return出来,返回出来的值在模板当中都是可以使用的。
假设如果你不return出来,而直接去使用的话浏览器是会提醒你:
如果要想在页面中使用生命周期函数的,根据以往2.0的操作是直接在页面中写入生命周期,而现在是需要去引用的,这就是为什么3.0能够将代码压缩到更低的原因。
如何知道数据发生了变化,Vue3 之前使用了 ES5 的一个 API Object.defineProperty。Vue3 中使用了 ES6 的 Proxy,都是对需要侦测的数据进行 变化侦测 ,添加 getter 和 setter ,这样就可以知道数据何时被读取和修改。
如何知道数据变化后哪里需要修改,Vue 对于每个数据都收集了与之相关的 依赖 ,这里的依赖其实就是一个对象,保存有该数据的旧值及数据变化后需要执行的函数。每个响应式的数据变化时会遍历通知其对应的每个依赖,依赖收到通知后会判断一下新旧值有没有发生变化,如果变化则执行回调函数响应数据变化(比如修改 dom)。
#vue3生命周期
vue3中使用的生命周期
使用defineComponent 构建应用及绑定事件
使用setup(props,content)父子通信
使用 reactive 绑定数据
使用 ref ( torefs ) 绑定数据
使用 getCurrentInstance 获取当前实例化对象上下文信
watch、watchEffect 数据监听
生命周期+axios数据请求
简单的 TodoList 点餐功能
computed 对应vue2中computed,使用方法不同
生命周期 |
---|
setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method |
onBeforeMount() : 组件挂载到节点上之前执行的函数。 |
onMounted() : 组件挂载完成后执行的函数。 |
onBeforeUpdate(): 组件更新之前执行的函数。 |
onUpdated(): 组件更新完成之后执行的函数。 |
onBeforeUnmount(): 组件卸载之前执行的函数。 |
onUnmounted(): 组件卸载完成后执行的函数 |
若组件被 keep-alive 包含,则多出下面两个钩子函数。 |
onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行 |
onDeactivated(): 比如从 A组件,切换到 B 组件,A 组件消失时执行。 |
VUE2 | VUE3 |
---|---|
beforeCreate | setup() |
created | setup() |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from 'vue'
export default {
setup() {
let test = ref('测试');
// 挂载开始之前被调用
onBeforeMount(() => {
console.log('onBeforeMount组件挂载到节点上之前执行的函数', test.value)
});
// 实例被挂载后调用,不会保证所有的子组件也被挂载完
onMounted(() => {
console.log('onMounted组件挂载完成后执行的函数', test.value)
});
// DOM更新前
onBeforeUpdate(() => {
console.log('onBeforeUpdate组件更新之前执行的函数', test.value)
});
// DOM更新后,不会保证所有的子组件也都一起被重绘
onUpdated(() => {
console.log('onUpdated组件更新完成之后执行的函数', test.value)
});
// 卸载组件实例之前,此时实例仍然是完全正常的
onBeforeUnmount(() => {
console.log('onBeforeUnmount组件卸载之前执行的函数', test.value)
});
// 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
onUnmounted(() => {
console.log('onUnmounted组件卸载完成后执行的函数', test.value)
});
// 当捕获一个来自子孙组件的错误时被调用
onErrorCaptured(() => {
console.log('onErrorCaptured当捕获一个来自子孙组件的错误时被调用', test.value)
});
// onRenderTracked和onRenderTriggered是用来调试的,这两个事件都带有一个DebuggerEvent,它使我们能够知道是什么导致了Vue实例中的重新渲染。。
onRenderTracked(() => {
console.log('onRenderTracked跟踪虚拟 DOM 重新渲染时调用', test.value)
});
onRenderTriggered(() => {
console.log('onRenderTriggered当虚拟 DOM 重新渲染', test.value)
});
return {test};
}
}
#demo使用各依赖版本
{
"name": "vue3-typescript",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^0.21.1",
"core-js": "^3.6.5",
"element-plus": "^1.2.0-beta.6",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
},
"devDependencies": {
"@types/node": "^17.0.0",
"@types/webpack-env": "^1.16.3",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"image-webpack-loader": "^7.0.1",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"typescript": "~4.1.5",
"vuex-module-decorators": "^1.0.1"
}
}
#vue3+typescript+element-plus
PS:搭建项目就不赘述了,网上好多很好的文章。
大佬文章: vue3项目搭建传送门
typescript的特性就是类型检测以及interface接口,既然决定使用typescript那么就要用,尽量不用any;
可以定义一个全局的interface接口,按需引入就可以。
//在src目录下建一个types目录创建一个store.ts
export interface TestObject {
name: string,
value: number
}
//在需要使用interface 接口.vue文件或者.ts文件引入
//以.vue文件举例
import { defineComponent, reactive} from 'vue';
import { TestObject } from '@/types/store';//引入类型
interface dataType {//定义setup reactive中的数据类型
testData: Array<TestObject>,//设置数组<对象>类型
}
export default defineComponent({
setup(){
const data:dataType = reactive({
testData: [
{ name: '测试', value: 0 }
]
})
}
})
以上是在vue3中typescript基本使用
element-plus是一个支持vue3.x的UI框架
element-plus官网
//全局引入与按需引入element-plus
//main.ts
import { createApp } from 'vue'
import App from './App.vue'
...
import ElementPlus from 'element-plus';
//import { ElButton } from 'element-plus'; //按需导入
import 'element-plus/dist/index.css'
//element-plus默认是英文,需要自己配置中文
import locale from 'element-plus/es/locale/lang/zh-cn'
const app = createApp(App);
//注册组件
//app.component('ElButton',ElButton)
...
app.use(ElementPlus, { size: 'small', zIndex: 3000, locale });
#vue3+typescript router
vue2与vue3中router还是有点区别的,尤其是加上typescript;
在main.ts中引入与注册我就不过多赘述了,主要说一下router的基础配置吧。
//在router>index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routes: Array<RouteRecordRaw> = [//RouteRecordRaw 当前记录的嵌套路由。
{
path: '/',
name: 'Homeg',
component: () => import(/* webpackChunkName: "Home" */ '../views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
},
{path:'/test',name:'test',component: () => import(/* webpackChunkName: "test" */ '../views/test.vue')}
];
// Vue-router新版本中,需要使用createRouter来创建路由
// createWebHashHistory hash模式 createWebHistory History模式
const router = createRouter({
history: createWebHistory(),
routes
});
router.afterEach((value) => {
//路由监听
console.log(value);
});
export default router
<!-- 使用路由 -->
<template>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
</div>
<router-view/>
</template>
#vue3+typescript 组件传值
基础用法:
<!-- 父组件 -->
<template>
<el-row>
<el-col>
<el-card>
<el-row>
<h2><b>父组件</b></h2>
<el-col> <b>父组件msg1值:</b>{{ msg1 }} </el-col>
<el-col> <b>父组件msg2值:</b>{{ msg2 }} </el-col>
</el-row>
</el-card>
</el-col>
<el-col>
<el-divider></el-divider>
</el-col>
<el-col>
<el-card>
<!-- :msg1="msg1" @setMsg1="getMsg1" 使用基础的使用方式与vue2.x基本一致 -->
<!-- 但是在vue3.x中移除了.sync语法糖 使用v-model替代 -->
<Childrens :msg1="msg1" v-model:msg2="msg2" @setMsg1="getMsg1"></Childrens>
</el-card>
</el-col>
</el-row>
</template>
<script lang='ts'>
import { defineComponent, reactive, toRefs } from "vue";
import Childrens from "./components/children.vue";
interface DataPType {
msg1: string;
msg2: string;
}
export default defineComponent({
name: "",
components: { Childrens },
setup() {
const data: DataPType = reactive({
msg1: "父组件msg1",
msg2: "父组件msg2",
});
const getMsg1 = (val:string) =>{
data.msg1 = val
};
return {
...toRefs(data),
getMsg1
};
},
});
</script>
关于vue2.x与vue3.x中语法变化,比如 .sync等语法糖的移除请看vue3语法官网
<!-- 子组件 -->
<template>
<el-row>
<h2><b>子组件</b></h2>
<el-col> <b>子组件使用msg1值:</b>{{ msg1 }} </el-col>
<el-col :span="24"> <b>子组件使用msg2值:</b>{{ msg2 }} </el-col>
<el-col>
<el-divider></el-divider>
</el-col>
<el-col>
<el-button type="primary" @click="getPropData">获取父组件传过来的值</el-button>
<el-button type="primary" @click="setMsg1">修改父组件msg1值</el-button>
<el-button type="primary" @click="setMsg2">修改父组件msg2值</el-button>
</el-col>
</el-row>
</template>
<script lang='ts'>
import { defineComponent, reactive, toRefs } from "vue";
export default defineComponent({
name: "Childrens",
props: {
msg1: String,
msg2: { type: String, default: "" },
},
//setup(props)第一个参数props 以用于获取父组件传过来的值
//setup(props,rex)第二个参数rex上下文对象(emit,slots,attrs)可以用 {emit} 或者直接 rex.emit
//{emit} 使用 emit("XXX",需要修改的值)
//rex 使用 rex.emit("XXX",需要修改的值)
setup(prop, { emit }) {
const getPropData = () => {
console.log(prop);
alert(prop.msg1);
alert(prop.msg2);
};
const setMsg1 = () => {
//基础用法 与 vue2.x 改动不大
emit("setMsg1", "这是子组件修改的值(msg1)");
};
const setMsg2 = () => {
emit("update:msg2", "这是子组件修改的值(msg2)");
//同等于 vue2.x中this.$emit("update:msg2", "这是子组件修改的值(msg2)")
//父组件在接收时不是在使用 msg2.sync="msg2" 而使用v-model:msg2="msg2"
};
return {
getPropData,
setMsg1,
setMsg2,
};
},
});
</script>
PS:关于toRefs的使用与介绍(大佬文章: 传送门);说一下比较实用的吧。
<template>
<el-row>
<!-- <el-col>注1: {{ data.msg }} </el-col> -->
<el-col>注2: {{ msg }} </el-col>
</el-row>
</template>
<script lang='ts'>
import { defineComponent, reactive, toRefs } from "vue";
interface DataPType {
msg: string;
}
export default defineComponent({
setup() {
const data: DataPType = reactive({
msg: "1",
});
return {
//data,//直接把 data 抛出去在 template中使用需要 data.msg (注1)
...toRefs(data),//使用了 toRefs(data) 的话,在template直接可以访问 msg (注2)
};
},
});
</script>
#vue3+typescript+watch
上代码
<template>
<div>
<h1>watch、watchEffect 数据监听</h1>
<p>msg: {{msg}}</p>
<p>msg2: {{msg2}}</p>
<p>
<button @click="changeMsg">changeMsg</button>
</p>
<p>
<button @click="changeMsg2">changeMsg2</button>
</p>
</div>
</template>
<script>
import {defineComponent, reactive, watchEffect, watch, toRefs} from 'vue';
export default defineComponent({
setup() {
const state: any = reactive({// 使用响应式函数reactive构建proxy响应式对象state
msg: 'value1',
msg2: 'value2',
changeMsg2: () => {
state.msg2 = '改变value2'
}
});
const changeMsg = () => {// 在外边定义methods
state.msg = '改变value1';
};
// watch监听只能是 getter/effect 函数、ref、reactive对象或数组
// 简单监听
watch(state, () => {
// 只要state中的值有变化,就会打印
console.log('观察整个state中的属性变化'+ '%c' + '《' + state.msg + '》','color:red');
});
// 监听指定的信息
watch(
() => state.msg, (newVal, oldVal) => {
console.log('01-msg的新值是: ' + '%c' + '《' + newVal + '》','color:red');
console.log('01-msg的旧值是: ' + '%c' + '《' + oldVal + '》','color:red');
}
);
// 监听多个属性(数组形式)
watch([() => state.msg, () => state.msg2], (newVal, oldVal) => {
console.log('02-msg的新值是: ' + '%c' + '《' + JSON.stringify(newVal) + '》','color:yellow');
console.log('02-msg的旧值是: ' + '%c' + '《' + JSON.stringify(oldVal) + '》','color:yellow');
});
// 不需要指定监听的属性
watchEffect(_ => {
// 程序运行时,初始化就会执行一次,完成监听准备工作
// 有点像computed计算属性,用到的数据发生改变才会执行
console.log('03-watchEffect监听 state 中数据变化'+'%c' + '《' + state.msg + '》','color:lime')
});
return {
...toRefs(state),
changeMsg,
}
}
})
</script>
#vue3+typescript 使用axios模块化
说一下我根据以前vue2.x常用的axios的配置改为vue3.x+typescript的配置
//先说一下啊目录结构
src
--http
---index.ts
---request.ts
---api
----queryAip.ts
//http>index.ts
/** 请求封装 **/
import axios from 'axios';
import {Payload} from '@/types/store'
//export interface Payload {//可以规定一下后端返回的数据类型
// code: string,
// msg?: string,
// data?: any
//}
//封装post(get、delete...与下面没有太大差异)
export const POST = (url: string, params: object) => {
return new Promise<Payload>((resolve, reject) => {
axios.post(url, params).then(res => {
resolve(res.data)
}).catch(err => {
reject(err)
})
})
};
//http>request.ts
/** 请求拦截 **/
import axios, {AxiosInstance, AxiosRequestConfig, AxiosResponse} from 'axios';
//axios 拦截
export class Request {
// 初始化拦截器
public static initInterceptors() {
axios.defaults.headers.post['Content-Type'] = 'application/json';
/**
* 请求拦截器
* 每次请求前,如果存在token则在请求头中携带token
*/
axios.interceptors.request.use(
(config: AxiosRequestConfig) => {
config.headers.token = '';
return config;
},
(error: any) => {
console.log(error);
},
);
// 响应拦截器
axios.interceptors.response.use(
// 请求成功
(response: AxiosResponse) => {
//...
},
// 请求失败
(error: any) => {
const {response} = error;
if (response) {
//...
} else {
// 处理断网的情况
// eg:请求超时或断网时,更新state的network状态
// network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
// 关于断网组件中的刷新重新获取数据,会在断网组件中说明
console.log('网络连接异常,请稍后再试!');
}
});
}
/**
* http握手错误
* @param res 响应回调,根据不同响应进行不同操作
*/
private static errorHandle(res: any) {
// 状态码判断
switch (res.status) {
case 401:
break;
case 403:
break;
case 404:
console.log('请求的资源不存在');
break;
default:
console.log('连接错误');
}
}
}
//http>api>queryApi.ts
/** 请求接口封装 **/
import { POST } from '@/http/index';
import {postType} from '@/types/store';
//export interface postType{
// limit: number,
// start: number
//}
//封装接口
const GetList = (obj: postType) => POST('/list',obj);
export {GetList}
//全局引入与注册
//main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { Request } from '@/http/request'
const app = createApp(App);
...
app.use(Request.initInterceptors);//初始化拦截器
//使用请求数据
<script lang='ts'>
import { defineComponent, reactive, onMounted } from 'vue';
import { GetList } from '@/http/api/textApi'
import { InstanceType } from '@/types/store';
//export interface InstanceType {//定义返回数据类型
// resultList: {}[],
// start: number,
// limit: number,
//}
interface dataType {
tableData: InstanceType[]
}
export default defineComponent({
setup() {
let data: dataType = reactive({
tableData: []
});
const getAxiosData = (start: number = 1) => {
GetList ({ start, limit: 10 }).then(res => {
if (res.code === '000000') {
data.tableData = res.data.resultList;
}
});
};
onMounted(() => {
getAxiosData()
});
return {
getAxiosData,//return出去可以在页面绑定
}
},
});
</script>
#vue3+typescript vuex
在vue2.x中使用vuex的结构基本同步移动vue3.x中
//看一下目录结构
src
--store
---index.ts
---actions.ts
---mutations.ts
上代码
//store > index.ts
import {createStore} from 'vuex'//引入vuex 在vuex4版本中需要使用 createStore来构建
import actions from './actions';//引入actions
import mutations from './mutations';//引入mutations
interface StoreType {//定义 state 中数据类型
vuexValue:string,
}
export default createStore({
state:<StoreType> {
vuexValue:'初始值'
},
mutations,
actions,
})
//store > actions.ts
import {Commit} from 'vuex'//引入 Commit 类型
const Actions = {
setVuexValue(context: { commit: Commit }, value: string) {
context.commit('SET_VUEX_VALUE', value)
}
};
export default Actions
//store > mutations.ts
const Mutations = {
SET_VUEX_VALUE(state: any, data: string){
state.vuexValue = data
},
};
export default Mutations
以上是vuex基本使用,其他的也不多少说了;下面介绍一下在.vue里面使用;
<template>
<el-row>
<el-col>VUEX-vuexValue值: {{ vuexValue }}</el-col>
<el-col>
<el-button type="primary" @click="setVuexValue('VUEX数据修改')">修改VUEX数据</el-button>
</el-col>
</el-row>
</template>
<script lang='ts'>
import { defineComponent, reactive, toRefs, computed } from "vue";
// import { useStore } from "vuex"; // 1 => 使用vuex方式
import store from '@/store'// 2 => 使用vuex方式
interface DataPType {}
export default defineComponent({
name: "vuex4",
setup() {
const data: DataPType = reactive({});
// const store = useStore(); //1 => 使用vuex方式 //vuex实例化
const setVuexValue = (value: string) => store.dispatch("setVuexValue", value); // 1,2 => 使用vuex方式
const vuexValue = computed(() => store.state.vuexValue); // 1,2 => 使用vuex方式
return {
...toRefs(data),
setVuexValue,
vuexValue,
};
},
});
</script>
#vue3+typescript vuex进阶
说一下vuex配合vuex-module-decorators的用法吧;
vuex-module-decorators的安装看一下官方的文档吧;传送门
//看一下目录结构
src
--store
---index.ts
---module
----test.ts
上代码!
//index.ts
import { createStore } from 'vuex'
import { config } from 'vuex-module-decorators' //引入 vuex-module 中的 config
config.rawError = true;//如果不设置 module层会拦截报错 会给出module层报错信息 设置为true 报错会正常
export default createStore({})
//test.ts
import { Module, VuexModule, Mutation, Action, getModule } from 'vuex-module-decorators'
import store from '../index'
// dynamic: true: 动态创建动态模块,即在 index.ts export default createStore({})里面不用注册的.空着就行,
// store,当前模块注册到store上.也可以写在getModule上,即getModule(PassengerStore,store)
// namespaced: true,
// name: 'passenger' 命名空间
@Module({ dynamic: true, store, name: 'TEST' })
export default class test extends VuexModule {
// 定义变量需要 使用 public 不然外面访问不到
/**
* public 默认共有属性
* private 私有属性
* protected 私有属性,派生类中仍可访问
* readonly 只读属性
* static 静态属性
* abstract 抽象类或方法
* **/
public vuexValue: string = '';//可直接使用 computed(() => TestModule.vuexValue)
get GetVuexValue(): string { //也可使用 computed(() => TestModule.GetVuexValue)
return this.vuexValue
}
// @Mutation // 注意:每一个 Mutation、Action... 都需要在前面声明 @XXX
// XXX(data: string) {
// this.XXX = data;
// };
// 在 class 中 this 指向的是当前 class
@Mutation
SET_VUEX_VALUE(data: string) {
this.vuexValue = data;
};
@Action
public setVuexValue(value: string) {
this.SET_VUEX_VALUE(value)
};
}
export const TestModule = getModule(test)
在 .vue 中使用
<template>
<el-row>
<el-col>vuex数据: {{ testValue }}</el-col>
<el-col>
<el-button type="primary" @click="setVuexValue('改变vuex值')"
>改变vuex值
</el-button>
</el-col>
</el-row>
</template>
<script lang='ts'>
import { defineComponent, reactive, toRefs, computed } from "vue";
import { TestModule } from "@/store/module/test"; //引入 vuex module
interface DataPType {
testValue: string;
}
export default defineComponent({
name: "",
setup() {
const data: DataPType = reactive({
testValue: computed(() => TestModule.vuexValue), //也可以直接调用
setVuexValue: (value: string) => TestModule.setVuexValue(value), //也可以直接调用,不过不建议这样
});
// 把 TestModule.setVuexValue 赋值到 setVuexValue 中, 也可以直接调用 TestModule.setVuexValue()
// const setVuexValue = (value: string) => TestModule.setVuexValue(value);//第一种使用setVuexValue
// const vuexValue = computed(() => TestModule.vuexValue);//第一种使用vuexValue
return {
...toRefs(data),
// setVuexValue,//第一种使用setVuexValue
// vuexValue,//第一种使用vuexValue
};
},
});
</script>