创建vue3项目
1、使用vue-cli创建
安装或者升级
npm install -g @vue/cli
保证vue cli 版本在4.5.0以上
vue --version
创建项目
vue create my-project
是否使用ES6类的语法创建vue组件 n
是否使用baby y
是否使用历史记录 y
选择编译器 less
配置文件放在单独文件还是package里面 In dedicated config files
是否保存当前选择,保存预设 n
vue2、vue3区别
vue2:入口文件min.js的区别
去掉根组件会报错
import Vue from "vue";
import App from "./App";
import store from "./store";
import router from "./router";
new Vue({//new实例化方式
el: "#app",
router,
store,
render: h => h(App)
});
vue3:入口文件min.ts的区别
import {createApp} from 'vue'//createApp全局api
import App from './App.vue'
import router from './router.vue'
import store from './store.vue'
createApp(App).use(store).use(router).mount('#app')
vue2需要根组件,会报错
vue3不需要根组件,不会报错;自动生成根组件的文档碎片
api(常用部分)
setup
以前定义的data和methods一样,但是在vue3中更推荐使用setup函数
- setup是一个函数,只要在初始化时执行一次(大部分代码都在setup函数中写)
- 返回一个对象,对象中的属性或方法,模板中可以使用
- setup返回的数据会和data和methods进行合并,setup优先级更高
- setup函数中没有this(以后开发都不使用this)
- setup不要写成async函数
因为setup函数必须返回json对象供模板使用,若果setup是一个async函数返回的将是一个promise对象
如果setup是一个async函数,那该组件就成一个异步组件,更需要配合Suspense组件才能使用
ref
setup函数中返回了一些数据,但如果我们直接修改这些数据,我们发现并不是响应式,
如何创建响应式的数据,用ref
import {definceComponent,ref} from 'vue'
export default definceComponent({
name:'Home',
setup(){
let a = 0;
let b = ref(1);
const handte = () =>{
a++;
b.value++;
console.log(a)//能打印出新值,但页面无变化,不死响应式
console.log(b)//页面响应式
}
};
return{
a,
b,
handte,
}
})
- 作用:定义响应式数据
- 语法:const name = ref(initValue);
创建一个包含响应式数据的引用(reference)对象
js中修改数据:name.value = otherValue
模板中显示数据,不需要.value。直接使用{{name}}
- 一般用未定义一个原始类型的响应式数据
let str = ref('abc')
let bool = ref(true)
let num = ref(0)
let obj = ref({a:12})
reactive和toRefs
reactive
- 作用:定义对象格式的响应式数据
如果用ref定义对象/数组,内部会自动将对象/数组转换为reactive的对象
- const proxy = reactive(obj);接收一个普通对象然后返回改普通对象的响应式代理器对象
- js中修改数据不需要操作.vue
- 一般用定义一个引用类型的响应式数据
import {definceComponent,ref,reactive,toRefs} from 'vue'
const state = reactive({
a:1,
str:"abc",
boo:true,
obj:{
a:123,
}
})
const update = () => {
state.a++;
state.str+= "--";
state.boo = !state.boo;
state.obj.a++;
}
return{
state,//<h1>{{state.a}}</h1>
...state,//<h1>{{a}}</h1>a的值没变化
//<h1>{{obj}}</h1>值有变化,每点击一次加一。对象引用
...toRefs(state),//<h1>{{a}}</h1>a的值有变化,每点击一次加一
update,
}
toRefs
- 将响应式对象中所有属性包装为ref对象,并返回包含这些ref对象的普通对象
- 应用:对reactive定义的对象进行toRefs包装,包装之后的对象中每个属性都是响应式的
比较vue2与vue3的响应式
vue2中的问题
- 对象直接添加新的属性或删除已有属性,界面不会自动更新
- 直接通过下表修改元素(arr[1] = xxx)或更新数据的下length,界面不会自动更新,不是响应式
vue3中不存在vue2的问题
vue2的响应式
- 核心:
- 对象:通过defineProperty对对象的已有属性值得读取和修改进行劫持(监视/拦截)
//假设vm是vue实例
const vm = {}
//data数据
const data = {
name:'John',
age:18,
}
//便利data,将data属性绑定到vm上,对属性的读取和修改进行拦截
Object.entres(data).forEach(([prop,value]) => {
let initValue = value;
Obiect.defineProperty(vm,prop,{
get(){
console.log("执行get")
return initValue
},
set(){
console.log("执行set")
initValue = newValue
},
}
})
//读取属性值
console.log(vm.anem)//执行get John
//修改属性
vm.name = 'bob';//执行set
console.log(vm.name)//执行get bob
//添加属性
vm.sex = '男';//不会执行set方法
console.lgo(vm.sex)//能打印出'男',但是不会执行get方法
数组:通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
数组的push、pop、splice等方法之所以能正常使用,其实是因为被vue重写了
const obj = {
push(){},//_push(){},
prop(){},//_prop(){},
shift(){},//_shift(){},
unshift(){},//_unshift(){},
sort(){},//_sort(){},
reverse(){},//_reverse(){},
}
//便利obg,使用defineProperty监听
Object.keys(obj).forEach(key => {
Object.defineProperty(obj,key,{
value:function(...args){
return Array.prototype[key].call(this,...args)
//return Array.prototype[key.substr(1)].call(this,...args)
}
})
})
const arr = new Array();
/*arr.push(1);//obj.push(1)
arr.__proto__ === Arrar.prototype;//通过原型链查到原型
arr.push(1)
*/
arr.__proto__ = obj;//将数组的隐式原型只想obj
//我们知道arr.__proto__等于它的构造函数的原型,也就是Array.prototype,所以arr可以执行push、prp等方法,但现在arr.__proto__又等于obj了,所以arr.push就相当于obj.push了,而obj.push我们用defineProperty进行了监听,执行obj.push()就会执行value函数
//测试
arr.push(1)//执行这一句就相当于执行obj.push(1)
console.log(arr)
vue3的响应式
- 核心:
- 通过Proxy(代理):拦截对象本身的操作,包括属性值得读写,属性的添加,属性的删除等...
- 通过Reflect(反射):动态对被代理对象的相应属性进行特定的操作
const user = {
name:'John',
age:18,
}
//代理对象
const proxyUser = new Proxy(user,{
get(target,prop){
console.log("劫持get()",prop);
return Reflect.get(target,prop);
},
set(target,prop,val){
console.log("劫持set()",prop,val);
return Reflect.set(target,prop,val);
},
delectProperty(target,prop){
console.log("劫持delete()",prop);
return Reflect.delectProperty(target,prop);
},
})
//读取属性值
console.log(proxyUser === user)//false
//设置属性值
proxyUser.name = "bob";//劫持set() name bob
proxyUser.age = 18
console.log(user)
//添加属性
proxyUser.sex = "男";//劫持set() sex 男
console.log(user)
//删除属性
delete proxyUser.sex;//劫持delete sex
console.log(user)
现在我们可以利用Proxy手动实现ref和reactive了
总结:正式由于vue3使用Proxy代理的方式拦截对象本身,所以在vue3中添加/删除属性都是响应式的,通过下表修改数组也是响应式的
setup的参数
- props-接收父组件传入的通过props声明过得属性
- context-是一个对象,解构出来包含:
- attrs-接收父组件传入的没有通过props声明过的属性,相当于this.$attrs
- slots-接收父组件传入的插槽内容的对象,相当于this.$slots
- emit-用来分发自定义事件的函数,相当于this.$emit
子组件 Chile.vue文件
<template>
<div>msg:{{msg}}</div>
<div>msg2:{{msg2}}</div>
<slot name "Jhon"></slot>
<button @click="btnClick"></button>
</template>
<script lang="ts">
import {defineComponent} from "vue"
export default defineComponent({
props:["msg"],
emits:["custom-event"],//自定义事件用emits声明
setup(props,{attrs,slots,emit}){
consol.log(props,attrs,slots,emit)//emit事件函数
const btnClick = () => {
emit("custom-event",111)//父组件绑定事件
}
return{
msg2:attr.msg2,
btnClick,
}
}
})
</script>
//父组件 index.vue
<template>
<Child msg = "hello" msg2 = "你好vue3" @custom-event="handle"></Child>
<template #Jhon>我插槽</template>
</template>
<script lang="ts">
import Child from "./Child"
export default defineComponent({
components:{Child},
props:["msg"],
emits:["custom-event"],//自定义事件用emits声明
setup(){
const handle = (val) => {
console.log(val)//111
}
return{
handle,
}
}
})
</script>
计算属性computed
回顾vue2中的计算属性
computed:{
//只有getter
fullName(){
return this.firstName + " " + this.lastName;
}
//只有getter和setter
fullName2:{
get(){
return this.firstName + " " + this.lastName;
},
get(){
const names = value.split(" ");
this.firstName = name[0];
this.lastName = name[1];
}
}
}
vue3的计算属性
- computed-用法跟vue2类似,不过需要引入computed
-
<template> <div>firstName:<input type="text" v-model="firstName" /></div> <div>lastName:<input type="text" v-model="lastName" /></div> <div>{{fullName}}</div> <button @click="updata">修改fullName</button> </template> <script lang="ts"> import {defineComponent,reactive,toRefs,computed} from 'vue' export default defineComponent({ setup(){ const user = reactive({ firstName:'zhang', lastName:'san', }) /*const fullName= computed(() => { return user.firstName + " " + user.lastName })*/ const fullName= computed({ get(){ return user.firstName + " " + user.lastName }, set(value:string){ const names = value.split(" "); user.firstName = name[0]; user.lastName= name[1]; }, }) const updata = () => { fullName.value = 'li si' } //只有getter计算属性 return{ ...toRef(user), fullName, updata, } } }) </script>
侦听属性watch
回顾vue2的监听
watch:{
obj(newVal,oldVal){
console.log(newVal,oldVal)
},
//立即监听、深度监听
obj:{
hander(newVal,oldVal){
console.log(newVal,oldVal)
},
immediate:true,//初始化立即执行一次
deep:true,//深度监听
},
'obj.a'(newVal,oldVal){
console.log(newVal,oldVal)
}
}
vue3的侦听属性:
- watch-指监听数据
- 监听指定的一个或多个响应式数据,一旦数据发生变化,就会自动执行监视回调
如果是监听reactive对象中的属性,必须通过函数来指定
监听多个数据,使用数组来指定
- 默认初始时不执行回调,但可以通过配置immediate为true,来指定初始时立即执行死一次
- 通过配置deep为true来指定深度监视
- watchEffect - 不指定监听数据
- 不用直接指定要监视的数据,回调函数中使用的哪些响应式数据就监视哪些响应式数据
- 默认初始化时就会执行第一次
使用是需要引入watch和watchEffect
//监听字符串
const str = ref('abc')
wacth(str,(newValue,oldValue) => {
console.log(newValue,oldValue)
})
//监听对象
const user = reactive({
firstName:'zhang',
lastName:'san',
})
wacth(() => user.firstName,(newValue,oldValue) => {
console.log(newValue,oldValue)
})
//监听多个数据用数组
wacth([str, () => user.firstName],(newValue,oldValue) => {
console.log(newValue,oldValue)
})
//立即监听
const fullName = ref("");
wacth([() => user.firstName,() => user.lastName],(newValue,oldValue) => {
console.log(newValue,oldValue)
fullName.value = newValue[0]+" "+ newValue[1]
},
{
immediate:true,
deep:true
}
)
//watchEffect
watchEffect(() => {
fullName.value = user.firstName+" "+ user.lastName
})
生命周期
vue2中的生命周期钩子函数依旧可以使用,不过建议使用vue3的狗子函数
vue2与vue3生命周期对比
vue2 | vue3 |
beforeCreate | setup |
created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
注意:beforeDestroy和destroyed已经被废弃,如果想继续使用vue2的写法,对应的api是onBeforeUnmount和onUnmounted