Vue3.0新特性(一)

目录

一、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 -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount(unmount 卸载)
  • destroyed -> onUnmounted
  • errorCaptured -> 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=1
https://dog.ceo/dog-api/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值