Vue3.0新特性(一)

    科技2024-07-25  10

    目录

    一、setup

    二、ref 传入原始数据类型

    三、reactive 传入对象类型

    四、生命周期钩子函数


    一、setup

    setup 函数是 composition API(组合式 API) 的一个入口点

    ✦ 调用时机

    先创建组件实例,再初始化 props,紧接着调用 setup 函数。从生命周期钩子视角来看,setup 函数会在 beforeCreate 钩子之前被调用

    ✦ 模版中使用

    如果 setup 返回一个对象,则对象的属性将会被合并到组件模版的渲染上下文

    <template> <div>{{ count }} {{ object.foo }}</div> </template> <script lang="ts"> import { ref, reactive } from 'vue' export default { setup() { const count = ref(0) const object = reactive({ foo: 'bar' }) // 暴露给模板 return { count, object, } }, } </script>

    注意:setup 返回的 ref 在模版中会自动解开,不需要写 .value 属性

    ✦ 渲染函数 / JSX 中使用

    setup 也可以返回一个函数,函数中也能使用当前 setup 函数作用域中的响应式数据

    import { h, ref, reactive } from 'vue' export default { setup() { const count = ref(0) const object = reactive({ foo: 'bar' }) return () => h('div', [count.value, object.foo]) }, }

    ✦ setup 参数

    setup 函数接收 props 作为它的第一个参数

    export default { props: { name: String, }, setup(props) { console.log(props.name) }, }

    props 对象是响应式的,watchEffect 或 watch 会观察和响应式 props 更新

    export default { props: { name: String, }, setup(props) { watchEffect(() => { console.log(`name is: ` + props.name) }) }, }

    不要解构 props 对象,那样会使其失去响应式

    export default { props: { name: String, }, setup({ name }) { watchEffect(() => { console.log(`name is: ` + name) // Will not be reactive! }) }, }

    setup 函数第二个参数提供了一个上下文对象 context 

    const MyComponent = { setup(props, context) { context.attrs context.slots context.emit }, }

    attrs 和 slots 都是内部组件实例上对应项的代理,可以确保在更新后仍然是最新值。可以解构无需担心后面访问到过期的值

    const MyComponent = { setup(props, { attrs }) { // 一个可能之后回调用的签名 function onClick() { console.log(attrs.foo) // 一定是最新的引用,没有丢失响应性 } }, }

    ✦ this 用法

    this 在 setup 函数中不可用

    ✦ 类型定义

    interface Data { [key: string]: unknown } interface SetupContext { attrs: Data slots: Slots emit: (event: string, ...args: unknown[]) => void // void 表示一个函数不需要返回值 } function setup(props: Data, context: SetupContext): Data

     为了获得传递给 setup() 参数的类型推断,需要使用 defineComponent (定义组件)方法

     

    二、ref 传入原始数据类型

    ref 是一个函数,它接受一个参数,返回一个神奇的响应式对象 

    接受一个参数值并返回一个响应式且可改变的 ref 对象ref 对象拥有一个指向内部值的单一属性 .value

    const count = ref(0) console.log(count.value) // 0 count.value++ console.log(count.value) // 1

     ✦ 模版中访问

    当 ref 在 setup() 返回的对象中,并在模板中使用时,它会自动解套,无需在模板内额外书写 .value

    <template> <div>{{ count }}</div> </template> <script> export default { setup() { return { count: ref(0), } }, } </script>

     ✦ 作为响应式对象的属性访问

    当 ref 作为 reactive 对象的 property 被访问或修改时,也将自动解套 value 值,其行为类似普通属性

    const count = ref(0) const state = reactive({ count, }) console.log(state.count) // 0 state.count = 1 console.log(count.value) // 1

    注意当嵌套在 reactive Object 中时,ref 才会解套。从 Array 或者 Map 等原生集合类中访问 ref 时,不会自动解套

    const arr = reactive([ref(0)]) // 这里需要 .value console.log(arr[0].value) const map = reactive(new Map([['foo', ref(0)]])) // 这里需要 .value console.log(map.get('foo').value)

     ✦ 类型定义

    interface Ref<T> { value: T } function ref<T>(value: T): Ref<T>

    有时我们可能需要为 ref 做一个较为复杂的类型标注。我们可以通过在调用 ref 时传递泛型参数来覆盖默认推导: 

    const foo = ref<string | number>('foo') // foo 的类型: Ref<string | number> foo.value = 123 // 能够通过!

     ✦ 案例

    <template> <section> <p>{{count}}</p> <p>{{double}}</p> <button @click="increase">👍 +1</button> </section> </template> <script lang="ts"> import { computed, ref } from 'vue' export default { name: 'App', setup() { /** * ref 是一个函数,它接受一个参数,返回一个神奇的响应式对象 * 初始化的 0 作为参数包裹到这个对象中去,在未来可以检测到改变并作出对应的相应 */ const count = ref(0) const double = computed(() => { return count.value * 2 }) const increase = () => { count.value++ } return { count, increase, double } } } </script>

     

    三、reactive 传入对象类型

    接收一个普通对象然后返回该普通对象的响应式代理

    const obj = reactive({ count: 0 })

     ✦ ​​​​​​​类型定义

    function reactive<T extends object>(raw: T): T

      ✦ ​​​​​​​案例

    <template> <section> <p>{{count}}</p> <p>{{double}}</p> <button @click="increase">👍 +1</button> <ul> <li v-for="item in numbers" :key="item">{{item}}</li> </ul> <p>{{person.name}}</p> </section> </template> <script lang="ts"> import { computed, reactive, toRefs } from 'vue' // interface 接口中的语句用分号分隔 interface DataProps { count: number; // Expected a semicolon 需要一个分号 double: number; increase: () => void; numbers: number[]; person: { name?: string }; } export default { name: 'App', setup() { // 定义 data 数据类型 const data: DataProps = reactive({ count: 0, double: computed(() => { return data.count * 2 }), increase: () => { data.count++ }, numbers: [1, 2, 3], person: {}, }) // 修改数组或对象中的值 data.numbers[0] = 11 data.numbers.push(4) data.person.name = 'viking' // toRefs API 保证 reactive 对象属性保持响应性 const refData = toRefs(data) console.log('refData', refData) console.log('refData.count', refData.count) return { // 非响应式数据 (property) DataProps.count: number // count: data.count, // double: data.double, // increase: data.increase, // 响应式数据 (property) count: Ref<number> // count: refData.count, // double: refData.double, // increase: refData.increase, ...refData // 使用 spread 扩展运算符 } } } </script>

    ✦ 使用 ref 还是 reactive 可以使用以下准则:

    1) 使用 ref 传入原始类型,使用 reactive 传入对象类型 2) 所有场景都使用 reactive,但一定要记得使用 toRefs API 保证 reactive 对象属性保持响应性

    四、生命周期钩子函数

    可以直接导入 onXXX 一族的函数来注册生命周期钩子 生命周期钩子注册函数只能在 setup() 期间同步使用, 因为它们依赖于内部的全局状态来定位当前组件实例 与 2.x 版本生命周期相对应的组合式 API

    beforeCreate -> 使用 setup()created -> 使用 setup()beforeMount -> onBeforeMountmounted -> onMountedbeforeUpdate -> onBeforeUpdateupdated -> onUpdatedbeforeDestroy -> onBeforeUnmount(unmount 卸载)destroyed -> onUnmountederrorCaptured -> onErrorCaptured(captured 捕获)

    Vue3.0 新增调试钩子函数,两个钩子函数都接收一个 DebuggerEvent

    onRenderTracked(Tracked 跟踪)onRenderTriggered(Triggered 触发)

    <template> <section> <p>{{msg}}</p> <button @click="handleClickTab">切换</button> </section> </template> <script lang="ts"> import { onBeforeMount, onBeforeUnmount, onBeforeUpdate, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onUnmounted, onUpdated, reactive, toRefs } from 'vue' interface DataProps { msg: string; handleClickTab: () => void; } export default { name: 'App', setup() { const data: DataProps = reactive({ msg: 'Hello world', handleClickTab: () => { // data.msg = 'Bye world' // 三元操作符语句:条件 ? 条件成立时执行的语句 : 条件不成立时执行的语句 data.msg = data.msg === 'Hello world' ? 'Bye world' : 'Hello world' } }) const refData = toRefs(data) onBeforeMount(() => { console.log('onBeforeMount') }) onMounted(() => { console.log('onMounted') }) onBeforeUpdate(() => { console.log('onBeforeUpdate') }) onUpdated(() => { console.log('onUpdated') }) onBeforeUnmount(() => { console.log('onBeforeUnmount') }) onUnmounted(() => { console.log('onUnmounted') }) onErrorCaptured(() => { console.log('onErrorCaptured') }) onRenderTracked((event) => { console.log('onRenderTracked', event) }) onRenderTriggered((event) => { console.log('onRenderTriggered', event) }) return { ...refData } } } </script>

     

    参考文档:

    vue3.0 composition api 官方文档

    免费 DOG API

    https://www.jianshu.com/p/03862c7bf35adocument.title API

    课程文档 

    http://docs.vikingship.xyz/typescript.html​​​​​​​https://shimo.im/docs/YT9cdpDcKKCWV3CX/read

    API

    https://thecatapi.com/https://api.thecatapi.com/v1/images/search?limit=1https://dog.ceo/dog-api/

    Processed: 0.010, SQL: 8