1. setup函数(所有的composition api函数使用的地方)
const app = Vue.createApp({
template: `
<div @click="handleClick">name:{{ name }}, age:{{ age }}</div>
`,
setup(props, context){
return {
name: 'dell',
handleClick: () => {
... ...
}
}
}
})
setup函数是在created周期实例被完全初始化之前执行的,setup函数中不能用this,因为setup执行的时候,还没有this。有了实例之后,可以在其他地方通过this.$options.setup()来执行,setup中不能通过this来调用其setup外的实例方法,但是setup外部的实例方法(如methods中的方法)可以访问setup中的方法。
在
setup
中你应该避免使用this
,因为它不会找到组件实例。setup
的调用发生在data
property、computed
property 或methods
被解析之前,所以它们无法在setup
中被获取。
2. ref、reactive
通过proxy对数据进行封装,当数据变化时,触发模版等内容的更新。
ref用于处理基础类型的数据,将其变成响应式的。
const { ref, reactive } = Vue
const app = Vue.createApp({
template: `
<div @click="handleClick">name:{{ name }}</div>
`,
setup(props, context){
let name = ref('dell')
setTimeout(()=>{
name.value = 'lee'
},2000)
return { name }
}
})
使用ref变成响应式数据之后,可以通过变量名.value的方式来赋值,在模版中直接使用变量名来使用。使用.value来赋值的原因主要是变量通过ref之后变为proxy({value:'data'})的方式,所以赋值需要使用value
用reactive处理非基础类型的数据。
在处理对象类型时,由{name: 'dell'}变为proxy({name:'dell'})
const { ref, reactive } = Vue
const app = Vue.createApp({
template: `
<div @click="handleClick">name:{{ name.name }}</div>
`,
setup(props, context){
const name = reactive({name :' dell'})
setTimeout(()=>{
name.name = 'lee'
},2000)
return { name }
}
})
在处理数组方面数据时,也用reactive。
const { ref, reactive } = Vue
const app = Vue.createApp({
template: `
<div @click="handleClick">name:{{ name[0] }}</div>
`,
setup(props, context){
const name = reactive([123])
setTimeout(()=>{
name[0]= 456
},2000)
return { name }
}
})
在Vue3中,可以不用定义了data了,可以直接通过ref和reactive来定义响应式的数据。
3. readonly
const { ref, reactive } = Vue
const app = Vue.createApp({
template: `
<div @click="handleClick">name:{{ name.name }}</div>
`,
setup(props, context){
const name = reactive({name :' dell'})
const nameReadOnly = readonly(name);
setTimeout(()=>{
name.name = 'lee'
nameReadOnly.name = 'lee' // 调用readonlyAPI修饰后的变量是无法修改的
},2000)
return { name }
}
})
4. toRefs && toRef
const { ref, reactive } = Vue
const app = Vue.createApp({
template: `
<div @click="handleClick">name:{{ name.name }}</div>
`,
setup(props, context){
const nameObj = reactive({name :' dell', age: 12})
setTimeout(()=>{
nameObj.name = 'lee'
},2000)
const { name, age } = nameObj //采用这种方式解构之后得到的name变量是没有响应式作用的
return { name }
}
})
想解决上面的问题,需要使用toRefs。
const { name, age } = toRefs(nameObj)
如果下述:
const nameObj = reactive({ name:'dell', age:12 })
const gender = toRefs(nameObj)
上述的nameObj中没有gender,此时通过解构得出的gender是undefined。如果在程序中出现这种情况,很容易引起程序崩溃,导致空指针异常。解决的办法就是toRef(没有s)
const nameObj = reactive({ name:'dell', age:12 })
const gender = toRef(nameObj,'gender')
这样之后,如果对象中没有gender也会赋空值给gender,同时gender还是有响应式特性的,而不是undefined。
5. setup函数的context参数
setup函数会自动传两个值,一个是props参数,还有一个是context参数。
context参数中有三个参数,一个是attrs,一个是slots还有一个是emit。
const { attrs, slots, emit } = context
其中attrs用来接收Non-Props属性,就是父组件通过属性传给子组件,同时子组件还没有props接收的。
const { ref, reactive } = Vue
const app = Vue.createApp({
template: `
<child app='app' />
`
})
app.component('child', {
template: `
<div>child</div>
`,
setup(props, context){
const { attrs, slots, emit } = context
console.log(attrs.app) //此处的app在子组件没有通过props接收,存在attrs中
return { }
}
})
slots这个参数就是用来接收父组件传给子组件的标签中的内容。
const { ref, reactive } = Vue
const app = Vue.createApp({
template: `
<child>parents</child>
`
})
app.component('child', {
setup(props, context){
const { h } = Vue
const { attrs, slots, emit } = context
return () => h('div', {}, slots.default())
}
})
在不用composition api的时候, 获得父组件传进来的slot使用的是this.$slots.
最后一个参数emit是用来向外出发事件使用的,在setup函数中,作用和this.$emit差不多。
const { ref, reactive } = Vue
const app = Vue.createApp({
template: `
<child @change="handleChange">parents</child>
`
})
app.component('child', {
template: `<div @click="handleClick">123123</div>`,
setup(props, context){
const { h } = Vue
const { attrs, slots, emit } = context
function handleClick() { emit('change'); }
return { handleClick }
}
})
6. computed计算属性
const app = Vue.createApp({
template: `
<div @click="handleClick">{{count}}--{{countAddFive}}</div>
`,
setup(props, context){
const { ref, computed } = Vue
const count = ref(0)
const handleClick = () => { count.value += 1}
let countAddFive = computed(() => {
return count.value + 5
})
return { count, countAddFive, handleClick }
}
})
// computed还有一种复杂写法:
let countAddFive = computed({
get: () => {
return count.value + 5
}
// 写set方法后,对countAddFive进行修改时会执行set方法
set: (param) => {
count.value = param - 5
}
})
7. watch与watchEffect
const app = Vue.createApp({
template: `
<div @click="handleClick">{{count}}--{{countAddFive}}</div>
`,
setup(props, context){
const { ref, watch. toRefs } = Vue
const nameObj = reactive({name: 'dell'})
// 使用reactive响应式命令时,要用箭头函数来监听
watch(()=>nameObj.name, (currentValue, prevValue) => {
console.log(currentValue, prevValue)
})
// watchEffect 是立即执行,没有惰性,immdiate
// watchEffect函数不需要传递很多参数,只要传递一个回调函数即可
// watchEffect 自动感知代码依赖,即其函数中的代码涉及的变量发生改变的话,会立即执行回调函数。
// watchEffect 不能获取改变前的值,只能获得改变后的新值
watchEffect(() =>{
console.log(nameObj.name)
})
const { name, englishName } = toRefs(nameObj)
return { name, englishName }
}
})
// watch 函数还支持监听两个数
watch([()=>nameObj.name,()=>nameObj.englishName],([curName, curEngName],[prevName, prevEngName]) => {
console.log(curName, prevName, curEngName, prevEngName)
})
// 上述侦听两个数的,其实新值与旧值是一个解构的过程,如果采用下述写法:
watch([()=>nameObj.name,()=>nameObj.englishName],(currentValue,prevValue) => {
console.log(currentValue, prevValue)
})
// 这时,currentValue和prevValue的值是数组,currentValue是旧值组成的一个数组,prevValue是新值组成的一个数组
// 由于watch是惰性的,所以在一个方法里,对watch监听的多个数据进行改变,watch的回调方法只会执行一次
// 将watch或者watchEffect函数停止侦听的方法可以使用显示调用函数返回值的方式
const stop = watchEffect(() => {
/* ... */
})
// later
stop()
// watch函数还可以接收第三个参数,主要是
watch(
[()=>nameObj.name,()=>nameObj.englishName],
(currentValue,prevValue) => {
console.log(currentValue, prevValue)
},
{ imediate: true }
)
8. 生命周期函数的新写法
可以通过在生命周期钩子函数前面加上“on”来访问组件的生命周期钩子。
选项式 API | Hook inside setup | 触发时机 |
---|---|---|
beforeCreate | Not needed* | |
created | Not needed* | |
beforeMount | onBeforeMount | |
mounted | onMounted | |
beforeUpdate | onBeforeUpdate | |
updated | onUpdated | |
beforeUnmount | onBeforeUnmount | |
unmounted | onUnmounted | |
errorCaptured | onErrorCaptured | |
renderTracked | onRenderTracked | 触发响应式依赖的时候调用,每次渲染页面的时候都会收集一次响应式依赖,在beforeMount和mounted之间。 |
renderTriggered | onRenderTriggered | 每次重新渲染被触发的时候。第一次加载页面的时候不会触发,重新刷新页面的时候会触发,触发时机在beforeMount之前 |
activated | onActivated | |
deactivated | onDeactivated |
在setup函数中,可以通过此方法来访问钩子函数。
export default {
setup() {
// 此处就是mounted生命周期函数
onMounted(() => {
console.log('Component is mounted!')
})
}
}
beforeCreate和created两个声明周期函数是没有composition api的,因为setup函数就是create阶段的函数。
9. provide、inject
const app = Vue.createApp({
template: `
<child />
`,
setup(props, context){
const { provide } = Vue
provide{'name', 'abc'}
return { }
}
})
app.component('child', {
setup() {
const { inject } = Vue
const name = inject('name') // inject('name', 'aaa') 取不到name就赋值’aaa‘
return { name }
},
template: `<div>{{name}}</div>`
})
子组件如果想修改父组件传过来的数据,根据单向数据流的思想,谁提供数据,谁修改数据。
const app = Vue.createApp({
template: `
<child />
`,
setup(props, context){
const { provide, ref, readonly } = Vue
const name = ref('abc')
provide('name', readonly(name)) // 传给子组件数据时,可以通过readonly的方式向子组件传值,防止子组件不调用父组件的方法直接修改值
provide('changeName', (value) => {
name.value = value
})
return { }
}
})
app.component('child', {
setup() {
const { inject } = Vue
const name = inject('name') // inject('name', 'aaa') 取不到name就赋值’aaa‘
const changeName = inject('changeName')
const handleClick = () => {
changeName(name)
}
return { name, handleClick }
},
template: `<div @click="handleClick">{{name}}</div>`
})
10.DOM中的ref
const app = Vue.createApp({
setup() {
const { ref } = Vue
const hello = ref(null)
return { hello }
},
template: `<div ref="hello">hello</div>`
})
// hello.value 是节点的具体内容
在Vue2中,可以向DOM节点上增加ref属性,然后通过this.$refs来定位到相应的节点。在composition api中也可以实现定位DOM。
如上代码,定一个空ref,变量名与DOM节点中ref属性的值一致时,会进行自动绑定到DOM的节点中。
11.composition api 之VueX
import { toRefs } from 'vue';
import { useStore } from 'vuex';
export default {
setup(){
const store = useStore()
const { name } = toRefs(store.state)
const handleClick = () => {
store.commit('changeName')
}
return { name, handleClick }
}
}