# 1、Vue3 的特点
- vue3 中全部采用函数式写法,替换了原来类的写法。
- 移除了原有的生命周期函数,和
data
、computed
、watch
、method
等 vue2 中的对象,去掉了this
, 并且去除了过滤器filter
。 - vue3 源码全部采用 ts 编写,编码中实现了对 ts 更好的支持。
- vue3 完全兼容 vue2,在 vue3 中依然可以按照 vue2 的方式去写代码,而且两种写法可以同时存在。
- 组件中同时存在两种写法时,当 setup 返回值中定义的方法和 methods 中的方法同名时,会抛出错误。
定义的数据和 data 定义的数据字段相同时,会被 data 定义的字段覆盖 - vue3 采用
proxy
的方式实现数据代理,只会代理第一层数据 避免了 vue2 中对 data 的深层递归,提升了组件渲染性能。
// 在 vue3 中定义一个响应式数据 | |
const state = reactive({data: {obj: {}}}); | |
state.data.obj = xxx; |
上例中,返回的 state 是一个 proxy 对象,默认只会对 data 进行代理
那么 vue3 是怎么实现深层数据劫持呢,例如我们要修改 obj 那么是怎么监听到 obj 的修改呢?
当我们要对深层对象 obj 进行修改时,会调用 state.data
的 get
方法,在 get
方法中会对 state.data
进行代理,劫持 state.data
中的属性, get
方法返回的不是 state.data
本身,而是被 proxy
代理过的对象,从而巧妙的实现了深层数据劫持,在用到该属性的时候一定会调用父级的 get
方法,这时候才会去劫持属性的 get
和 set
方法。
# 2、setup 函数
# setup 函数是 vue3 中所有 api 的入口和出口
vue3 中用 setup
函数整合了所有的 api, setup
函数只执行一次,在生命周期函数前执行,所以在 setup
函数中拿不到当前实例 this
,不能用 this
来调用 vue2 写法中定义的方法。
vue3 中去掉了 data
,使用 setup
的返回值来给模板绑定 value
return 的对象如果是常量,不会变成响应式数据。
this.$emit
用 context.emit
方法来替代。
//props - 组件接受到的属性, context - 上下文 | |
setup(props, context){ | |
return { | |
// 要绑定的数据和方法 | |
} | |
} |
# 3、生命周期
生命周期函数,都变成了回调的形式,写在 setup 函数中 可以一次写多个相同的生命周期函数,按照注册顺序执行。
setup() { | |
onMounted(() => { | |
console.log('组件挂载1'); | |
}); | |
onMounted(() => { | |
console.log('组件挂载2'); | |
}); | |
onUnmounted(() => { | |
console.log('组件卸载'); | |
}); | |
onUpdated(() => { | |
console.log('组件更新'); | |
}); | |
onBeforeUpdate(() => { | |
console.log('组件将要更新'); | |
}); | |
onActivated(() => { | |
console.log('keepAlive 组件 激活'); | |
}); | |
onDeactivated(() => { | |
console.log('keepAlive 组件 非激活'); | |
}); | |
return {}; | |
}, |
# 4、ref - 简单的响应式数据
- ref 可以将某个普通值包装成响应式数据,仅限于简单值,内部是将值包装成对象,再通过
defineProperty
来处理的。
通过 ref 包装的值,取值和设置值的时候,需用通过 value 来进行设置 - 可以用 ref 来获取组件的引用,替代
this.$refs
的写法。
<template> | |
<div class="mine"> | |
<input v-model="inputVal" /> | |
<button @click="addTodo">添加</button> | |
<ul> | |
<li v-for="(item, i) in todoList" :key="i"> | |
<!--swig0--> | |
</li> | |
</ul> | |
</div> | |
<div></div> | |
</template> | |
setup() { | |
const inputVal = ref(''); | |
const todoList = ref<string[]>([]); | |
function addTodo() { | |
todoList.value.push(inputVal.value); | |
inputVal.value = ''; | |
} | |
return { | |
addTodo, | |
inputVal, | |
todoList, | |
}; | |
}, |
# 5、reactive - 数据绑定
使用 reactive 来对复杂数据进行响应式处理,它的返回值是一个 proxy 对象,在 setup
函数中返回时,可以用 toRefs
对 proxy 对象进行结构,方便在 template 中使用。
通过 reactive 来创建响应式数据 data ,用 toRefs
来进行解构,在模板中直接使用 inputVal
、 todoList
。
模板中绑定的方法也需要 在 setup 函数中定义,并返回,才能绑定到模板中,例如 addTodo 方法。
vue3 模板:一个 templage 可以有多个平级的标签(vue2 中只能在 template 写一个子标签)
<template> | |
<div class="mine"> | |
<input v-model="inputVal" /> | |
<button @click="addTodo">添加</button> | |
<ul> | |
<li v-for="(item, i) in todoList" :key="i"> | |
<!--swig1--> | |
</li> | |
</ul> | |
</div> | |
<div></div> | |
</template> | |
setup() { | |
const data = reactive({ | |
inputVal: '', | |
todoList: [], | |
}); | |
function addTodo() { | |
data.todoList.push(data.inputVal); | |
data.inputVal = ''; | |
} | |
return { | |
...toRefs(data), | |
addTodo, | |
}; | |
}, |
# 6、computed
计算属性,变成了函数写法,当依赖的值发生改变时会重新计算 computed 包装后的值,需要用 .value
去取值,template 中不需要使用 .value
。
async setup() { | |
const data = reactive({ | |
a: 10, | |
b: 20, | |
}); | |
let sum = computed(() => data.a + data.b); | |
return { sum }; | |
}, |
# 7、watch
变成了函数写法,与 vue2 中用法相同。
// 侦听一个 | |
const state = reactive({ count: 0 }) | |
watch( | |
() => state.count, | |
(count, prevCount) => { | |
/* ... */ | |
} | |
) | |
// 直接侦听一个 ref | |
const count = ref(0) | |
watch(count, (count, prevCount) => { | |
/* ... */ | |
}) |
# 8、watchEffect
响应式地跟踪函数中引用的响应式数据,当响应式数据改变时,会重新执行函数。
const count = ref(0) | |
// 当 count 的值被修改时,会执行回调 | |
watchEffect(() => console.log(count.value)) |
# 9、vue-router
组件中使用路由时用 useRoute 和 useRouter。
import {useRoute, useRouter} from 'vue-router' | |
const route = useRoute(); // 相当于 vue2 中的 this.$route | |
const router = useRouter(); // 相当于 vue2 中的 this.$router | |
//route 用于获取当前路由数据 | |
//router 可以的得到一些关于路由的方法 |
# 10、vuex
使用 useStore
来获取 store
对象 从 vuex 中取值时,要注意必须使用 computed
进行包装,这样 vuex 中状态修改后才能在页面中响应。
import {useStore} from 'vuex' | |
setup(){ | |
const store = useStore(); // 相当于 vue2 中的 this.$store | |
store.dispatch(); // 通过 store 对象来 dispatch 派发异步任务 | |
store.commit(); //commit 修改 store 数据 | |
let category = computed(() => store.state.home.currentCagegory | |
return { category } | |
} |
# 11、用 jsx 来定义 vue 组件
vue3 中支持使用 jsx 语法来定义 vue 组件。
export const AppMenus = defineComponent({ | |
setup() { | |
return () => { | |
return ( | |
<div class="app-menus"> | |
<h1>这是一个vue组件</h1> | |
</div> | |
); | |
}; | |
}, | |
}); |
# 12、插槽修改
<div class="container"> | |
<header> | |
<slot name="header"></slot> | |
</header> | |
<main> | |
<slot></slot> | |
</main> | |
<footer> | |
<slot name="footer"></slot> | |
</footer> | |
</div> |