对VUE组件如果要加class、绑定事件,要在外面包一个标签,不要直接加在组件标签上,会不生效。
响应式ref和reactive
一个简单例子:计数器
vue2
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
<h1>{{count}}</h1>
<button @click="handleClick">👍🏻 +1</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
export default defineComponent({
name: 'App',
data () {
return {
count: 0
}
},
methods: {
handleClick () {
this.count++
}
},
components: {
HelloWorld
}
})
</script>
<style>
...
</style>
将其改写为vue3,使用ref将count转化为响应式,并增加计算属性。
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
<h1>{{count}}</h1>
<h2>{{double}}</h2>
<button @click="handleClick">👍🏻 +1</button>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
export default defineComponent({
name: 'App',
components: { HelloWorld },
setup () {
const count = ref(0)
const double = computed(() => {
return count.value * 2
})
const handleClick = () => {
count.value++ /* 使用ref包裹后count是一个响应式对象,修改值时要修改里面的value,但是在模板中显示的时候可以直接用count */
}
return {
count,
handleClick
}
}
})
</script>
<style>
...
</style>
ref一般处理的是原始类型,而引用类型一般使用reactive
<script lang="ts">
import { defineComponent, computed, reactive } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
export default defineComponent({
name: 'App',
components: { HelloWorld },
setup () {
// const count = ref(0)
// const double = computed(() => {
// return count.value * 2
// })
// const handleClick = () => {
// count.value++
// }
const data = reactive({
count: 0,
/* 这时const data会报错,因为computed里的回调使用了data.count
会造成类型的循环推断,data被推断为any类型。vue3暂时无法解决该问题 */
double: computed(() => data.count * 2),
handleClick: () => { data.count++ }
})
return {
data
}
}
})
</script>
解决办法:定义一个interface
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
<h1>{{data.count}}</h1>
<h2>{{data.double}}</h2>
<button @click="data.handleClick">👍🏻 +1</button>
</template>
<script lang="ts">
import { defineComponent, computed, reactive } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
interface dataProps {
count: number;
double: number;
handleClick: () => void
}
export default defineComponent({
name: 'App',
components: { HelloWorld },
setup () {
// const count = ref(0)
// const double = computed(() => {
// return count.value * 2
// })
// const handleClick = () => {
// count.value++
// }
const data:dataProps = reactive({
count: 0,
double: computed(() => data.count * 2),
handleClick: () => { data.count++ }
})
return {
data
}
}
})
</script>
<style>
...
</style>
但是发现在使用时需要data.×××,可不可以利用ES6的解构语法呢?答案是不行,一旦进行解构,解构出来的变量都是js/ts的普通类型,而不是响应式对象了。要解决这个问题,需要使用toRefs。
const refData = toRefs(data);
return {
...refData
}
生命周期函数
// vue2 -> vue3
beforeCreate -> use setup()
created -> use setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
deactivated -> onDeactivated
errorCaptured -> onErrorCaptured
// 新增
onRenderTracked
onRenderTriggered
watch
假设在setup()函数里写了一个方法,每次点击按钮,就执行这个方法,修改document.title。但由于setup()只在一开始页面加载时执行,以后每次虽然都执行了这个方法,修改了document.title,但是标签页上看到的title不会发生变化(没有重新渲染)。解决这个问题,可以使用watch来监听数据的变化。
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
<h1>{{count}}</h1>
<h2>{{double}}</h2>
<button @click="handleClick">👍🏻 +1</button>
<button @click="updataGreeting">Update Greetings</button>
</template>
<script lang="ts">
import { defineComponent, computed, reactive, toRefs, ref, watch } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
interface dataProps {
count: number;
double: number;
handleClick: () => void
}
export default defineComponent({
name: 'App',
components: { HelloWorld },
setup () {
const data:dataProps = reactive({
count: 0,
double: computed(() => data.count * 2),
handleClick: () => { data.count++ }
})
const greetings = ref('')
const updataGreeting = () => {
greetings.value += ' Hello! '
}
watch(greetings, () => {
console.log(oldValue, newValue)
document.title = 'updated' + greetings.value
})
const refData = toRefs(data)
return {
...refData,
updataGreeting
}
}
})
</script>
<style>
...
</style>
侦听一个源:
// 侦听一个getter
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// 直接侦听一个ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
若要监听多个数据,则可传入一个数组。
另外,watch的回调函数还接收两个参数,分别是新的值和旧的值。
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
watch([greetings, data], (newValue, oldValue) => {
console.log(oldValue, newValue)
document.title = 'updated' + greetings.value + data.count
})
还有,watch监听的值必须是:A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.
假设只想监听data.count,需要将其改成一个函数:
watch([greetings, () => data.count], (newValue, oldValue) => {
console.log(oldValue, newValue)
document.title = 'updated' + greetings.value + data.count
})
TypeScript对vue3的加持
在HelloWorld.vue中:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
/* 如果没有defineComponent,那么导出的就是一个普通对象,
也没有setup、methods这些语法提示 */
export default defineComponent({
name: 'HelloWorld',
props: {
msg: String
},
/* setup()函数接收两个参数,第一个是前面定义的props,第二个参数是一个环境,
里面包含了vue2的一些属性:this.$attr、this.$emit和slot*/
setup (props, context) {
console.log(props.msg)
console.log(context.attrs) /* context包含attrs、slots、emit */
}
})
</script>
<style scoped>
...
</style>