ref

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

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

如果传入ref的是一个对象,将调用reactive方法进行深层响应转换

  • 模板中访问

当ref作为渲染上下文的属性返回(即在setup()返回的对象中)并在模板中使用时,他会自动 解套,无需再模板内额外书写.value:

  1. <template>
  2. <div>{{ count }}</div>
  3. </template>
  4. <script>
  5. export default {
  6. setup() {
  7. return {
  8. count: ref(0),
  9. }
  10. },
  11. }
  12. </script>
  • 作为响应式对象的属性访问
    当ref作为reactive对象的property被访问或修改是,也将自动解套value值,其行为类似普通属性: ```javascript const count = ref(0) const state = reactive({ count, })

console.log(state.count) // 0

state.count = 1 console.log(count.value) // 1

  1. - 注意如果讲一个新的ref分配给现有的ref,将替换旧的ref:
  2. ```javascript
  3. const otherCount = ref(2)
  4. state.count = otherCount
  5. console.log(state.count) // 2
  6. console.log(count.value) // 1
  • 注意当嵌套在reactive object中时,ref才会解套.从Array或者Map等原生集合类中访问ref时,不会自动解套: ```javascript 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)

  1. <a name="7476383b"></a>
  2. #### `computed`
  3. 传入一个getter函数,返回一个默认不可手动修改的ref对象.
  4. ```javascript
  5. const count = ref(1)
  6. const plusOne = computed(() => count.value + 1)
  7. console.log(plusOne.value) // 2
  8. plusOne.value++ // 错误!

或者传入一个拥有getset函数的对象,创建一个可手动修改的计算状态.

  1. const count = ref(1)
  2. const plusOne = computed({
  3. get: () => count.value + 1,
  4. set: (val) => {
  5. count.value = val - 1
  6. },
  7. })
  8. plusOne.value = 1
  9. console.log(count.value) // 0

Reactive

接收一个普通对象然后返回该普通对象的响应式代理.等同于2.x的Vue.observable()
const obj=reative({cout:0})
响应式转换是”深层的”:会影响对象内部所有嵌套的属性.基于ES2.15的Proxy实现,返回的代理对象不等于原始对象.

Computed

传入一个getter函数,返回一个默认不可手动修改的ref对象.

  1. const count = ref(1)
  2. const plusOne = computed(() => count.value + 1)
  3. console.log(plusOne.value) // 2
  4. plusOne.value++ // 错误!

或者传入一个拥有getset函数的对象,创建一个可手动修改的计算状态.

  1. const count = ref(1)
  2. const plusOne = computed({
  3. get: () => count.value + 1,
  4. set: (val) => {
  5. count.value = val - 1
  6. },
  7. })
  8. plusOne.value = 1
  9. console.log(count.value) // 0

Readonly

传入一个对象(响应式或普通)或ref,返回一个原始对象的只读代理.一个只读代理是”深层的”,对象内部任何嵌套的属性也都是制度的.

  1. const original = reactive({ count: 0 })
  2. const copy = readonly(original)
  3. watchEffect(() => {
  4. // 依赖追踪
  5. console.log(copy.count)
  6. })
  7. // original 上的修改会触发 copy 上的侦听
  8. original.count++
  9. // 无法修改 copy 并会被警告
  10. copy.count++ // warning!

Watcheffect

立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更是重新运行该函数.

  1. const count = ref(0)
  2. watchEffect(() => console.log(count.value))
  3. // -> 打印出 0
  4. setTimeout(() => {
  5. count.value++
  6. // -> 打印出 1
  7. }, 100)

停止侦听

watchEffect在组件的setup()函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止.

  1. const stop = watchEffect(() => {
  2. /* ... */
  3. })
  4. // 之后
  5. stop()

清除副作用

有时副作用函数会执行一些一步的副作用,这些响应需要在其失效时清除(即完成之前状态已改变了).可以侦听副作用传入的函数可以接受一个onInvalidate函数作入参,用来注册清理失效时的回调.当一下情况发生时,这个失效回调会被触发:

  • 副作用即将重新执行时
  • 侦听器被停止(如果在setup()或生命周期钩子函数中使用了watchEffect,则在卸载组件时)
  1. watchEffect((onInvalidate) => {
  2. const token = performAsyncOperation(id.value)
  3. onInvalidate(() => {
  4. //每一次被触发的时候都会先执行onInvalidate内部逻辑,然后执行onInvalidate外部的逻辑
  5. // id 改变时 或 停止侦听时
  6. // 取消之前的异步操作
  7. token.cancel()//这是一个形参函数 这是你的逻辑叫做cancle只是为了很好地去比喻
  8. })
  9. })
  10. //示例
  11. <template>
  12. <div>
  13. <input type="text"
  14. v-model="keyword">
  15. </div>
  16. </template>
  17. <script>
  18. import { ref, watchEffect } from '@vue/composition-api'
  19. export default {
  20. setup() {
  21. const keyword = ref('')
  22. const asyncPrint = val => {
  23. return setTimeout(() => {
  24. console.log('user input: ', val)
  25. }, 1000)
  26. }
  27. watchEffect(
  28. onInvalidate => {
  29. const timer = asyncPrint(keyword.value)
  30. onInvalidate(() => clearTimeout(timer))
  31. console.log('keyword change: ', keyword.value)
  32. },
  33. {
  34. flush: 'post' // 默认'post',同步'sync','pre'组件更新之前
  35. }
  36. )
  37. return {
  38. keyword
  39. }
  40. }
  41. }
  42. // 实现对用户输入“防抖”效果
  43. </script>

参考资料

我们之所以是通过传入一个函数去注册失效回调,而不是从回调返回他(如ReactuseEffect中的方式),是因为返回值对于异步错误处理很重要

在执行数据请求时,副作用函数往往是一个异步函数:

  1. const data = ref(null)
  2. watchEffect(async () => {
  3. data.value = await fetchData(props.id)
  4. })

我们知道异步函数都会隐式地返回一个Promise,但是清理函数必须要在Promise被resolve之前被注册.另外,Vue依赖这个返回的Promise来自动处理Promise链上的潜在错误.

Setup

setup函数是一个新的组件选项.作为在组件内使用CompositionAPi的入口点.

  • 调用时机

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

  • 模板中使用

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

  1. <template>
  2. <div>{{ count }} {{ object.foo }}</div>
  3. </template>
  4. <script>
  5. import { ref, reactive } from 'vue'
  6. export default {
  7. setup() {
  8. const count = ref(0)
  9. const object = reactive({ foo: 'bar' })
  10. // 暴露给模板
  11. return {
  12. count,
  13. object,
  14. }
  15. },
  16. }
  17. </script>

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

  • 渲染函数/jsx中使用

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

  1. import { h, ref, reactive } from 'vue'
  2. export default {
  3. setup() {
  4. const count = ref(0)
  5. const object = reactive({ foo: 'bar' })
  6. return () => h('div', [count.value, object.foo])
  7. },
  8. }
  • 参数
  1. export default {
  2. props: {
  3. name: String,
  4. },
  5. setup(props) {
  6. console.log(props.name)
  7. },
  8. }

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

  1. export default {
  2. props: {
  3. name: String,
  4. },
  5. setup(props) {
  6. watchEffect(() => {
  7. console.log(`name is: ` + props.name)
  8. })
  9. },
  10. }

然而不要结构props对象,那样会使其失去响应性:

  1. export default {
  2. props: {
  3. name: String,
  4. },
  5. setup({ name }) {
  6. watchEffect(() => {
  7. console.log(`name is: ` + name) // Will not be reactive!
  8. })
  9. },
  10. }

在开发过程中,props对象对用户空间代码是不可变的(用户代码尝试修改props时会触发警告)
第二个参数提供了一个上下文对象,从原来2.x中this选择性地暴露了一些property

  1. const MyComponent = {
  2. setup(props, context) {
  3. context.attrs
  4. context.slots
  5. context.emit
  6. },
  7. }

attrsslots都是内部组件实例上对应项的代理,可以确保在更新后仍然是最新值.都可以结构没无需担心后面访问到过期的值:

这里的attrs 相当于2.x的$attrs;相关资料说明 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件——在创建高级别的组件时非常有用。

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

处于一些原因将props作为第一个参数,而不是包含在上下文中:

  • 组件使用props的场景更多,有时候甚至只使用props

    • props独立出来作为第一个参数,可以让TypeScript 对props单独做类型推导,不会和上下文中的其他属性相混淆.这也使得setup.render和其他适用了TSX的函数式组件的签名保持一致.
  • this的用法
    thissetup()中不可用.由于setup()在解析2.x选项前被调用,setup()中的this将与2.x选项中的this完全不同.同时在setup()和2.x选项中使用this时将造成混乱.在setup()中避免这种情况的另一个原因是:这对初学者来学,混淆这两种情况的this是非常常见的错误:

    1. setup() {
    2. function onClick() {
    3. this // 这里 `this` 与你期望的不一样!
    4. }
    5. }

打印的this结果

vue 3.0 核心概念解析 - 图1

这里是vue3的this 提示: 为了获得传递为setup()参数的类型推断,需要使用defineComponent (自我理解:这是使用typescript的操作)