vue3 学习3 - ref函数

1,028 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 25 天,点击查看活动详情

vue3 学习3 - ref函数

前言

  • 之前学习了 setup 的基础使用;
  • 今天记录一下学习 ref 的笔记;

开始

1. 遗留的问题

上一篇文章熟悉了 setup 的使用,我们知道了可以通过 setup 暴露数据给模板和组件实例。

但是还依旧存在一个问题:数据不是响应式的

例如:

<template>
  <div>
    <div>你还有多少money :{{ num }}</div>
    <button @click="add">点击我开始计数</button>
  </div>
</template>

<script>
export default {
  setup() {
    let num = 0
    function add() {
      num++
      console.log(num)
    }
    return {
      num,
      add,
    }
  },
}
</script>

image.png

不论我怎么点击按钮,可以看到控制台有输出数据,我们修改了num,但是视图一直没有更新。

总结:

  • 仅 setup 可以暴露数据,但是数据不是响应式的

2. ref()

想要 num 是响应式的,需要借助一个函数 ref()。 这里的 ref() 是一个函数,由 vue3 代码暴露出来。

官网对 ref() 的解释:”ref() 接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。“

2.1 基础使用

<template>
  <div>
    <!-- 4. 在模板中使用 -->
    <div>你还有多少money :( num ){{ num }}</div>
    <div>你还有多少money :( num.value ){{ num.value }}</div>

    <button @click="add">点击我开始计数</button>
  </div>
</template>

<script>
// 1. 从 vue 引入 ref 函数
import { ref } from 'vue'

export default {
  setup() {
    // 2. 参数传入的是 num.value 的默认值。
    let num = ref(0)

    // 3. ref返回的是一个对象,只有一个指向其内部的属性, `.value`
    console.log(num) // RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 0, _value: 0}

    function add() {
      num.value++
      console.log(num.value)
    }
    return {
      num,
      add,
    }
  },
}
</script>

输出总结:

  • 想要使用 ref,需要从 vue 中引入;
  • ref() 返回值是一个对象;
  • ref() 可以传入参数,当做返回对象 .value 的默认值;
  • 返回值在 setup 中可以通过 .value 的形式去使用;
  • 返回值在模板 template 中,无需 .value,模板即可解析;

2.2 ref()返回的对象

上面的示例代码,打印了 ref(0) 的返回值。官方介绍 ref() 是这样说的 :此对象只有一个指向其内部值的属性 .value

谷歌浏览器实际打印的:

image.png

{
    "__v_isShallow": false,
    "dep": {
        "w": 0,
        "n": 0
    },
    "__v_isRef": true,
    "_rawValue": 0,
    "_value": 0,
    "value": 0
}

总结:

  1. 首先确实有 value 属性;

  2. 其他的开头带 _ 的属性,可以理解它们为内部属性;

  3. dep 可以理解它也为内部属性;

  4. RefImpl 是什么意思?

    • 全称reference+implement 译为引用实现;

    • 打印示例代码的 num ,为什么展示 RefImpl?因为 num 是 class RefImpl 的实例

      console.log(num.__proto__.constructor.name)
      // 'RefImpl'
      
    • 后续我一定会读一读 ref() 的源码;

2.3 ref()传参的类型

2.3.1 验证 ref() 传入不同数据类型的参数会如何
<script>
import { ref } from 'vue'
export default {
  setup() {
    /* 基础类型 */
    let num = ref(0)
    console.log('num', num.value) // num 0

    let str = ref('tomato')
    console.log('str', str.value) // str tomato

    let bean = ref(false)
    console.log('bean', bean.value) // bean false

    let un = ref(undefined)
    console.log('un', un.value) // un undefined

    let nu = ref(null)
    console.log('nu', nu.value) // nu null

    let sym = ref(Symbol('hello'))
    console.log('sym', sym.value) // sym Symbol(hello)

    let bigNum = ref(10n)
    console.log('bigNum', bigNum.value) // bigNum 10n

    /* 引用类型 */
    let obj = ref({ name: '番茄', age: '18' })
    console.log('obj', obj.value) // obj Proxy {name: '番茄', age: '18'}
  },
}
</script>

由示例代码可得:

  1. 基础类型的数据,使用 ref(),会直接赋值给引用对象RefImpl 的 .value
  2. 引用类型的数据,使用 ref(),会给引用对象RefImpl 的 .value 赋值一个 Proxy 的实例对象。

官方的说明:

“ref() 当值为对象类型时,会用 reactive() 自动转换它的 .value。”

2.3.2 那如果是对象该如何获取对象中的属性。

代码

<template>
  <div>
    <input type="text" v-model="str" />
    <div>obj.hobby:{{ obj.hobby }}</div>
    <div>obj.hobby.game:{{ obj.hobby }}</div>

    <button @click="addHobby">添加爱好</button>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    let str = ref('')
    console.log('str', str.value)

    let obj = ref({
      name: '番茄',
      age: '18',
      hobby: {
        game: ['荒野大镖客2'],
      },
    })

    console.log('obj', obj.value.name)

    function addHobby() {
      obj.value.hobby.game.push(str)
    }

    return {
      str,
      obj,
      addHobby,
    }
  },
}
</script>

最初展示

image.png

添加爱好后

image.png

总结

  1. 给 ref() 传入基础类型的数据,会直接赋值给 RefImpl实例对象的 .value;

  2. 给 ref() 传入引用类型的数据,通过 reactive() 转换再赋值给 RefImpl实例对象的 .value;

    reactive() 后续会写,这里就理解为一个函数即可

  3. 使用起来的方式也比较类似,相当于是直接替换 RefImpl实例对象的 .value;所以在 setup 中可以 refImpl.value.xxx 操作数据;

2.4为什么在模板中使用 ref() 的返回值不需要 .value

刚刚的案例,在 setup 中操作数据是需要 .value 的,但是为啥模板中却不需要。

# 官方中文文档的解释是:
当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value。

# 我又去查看了英文的官方文档对应的原话是:
When refs are accessed as top-level properties in the template, they are automatically "unwrapped" so there is no need to use .value. 


# 解包 英文官方是用的这个单词  unwrapped(无包装的)

我自己的理解:可能是模板引擎对这里做了处理,省略 .value 方便我们使用数据,后续看到具体的源码再做验证。

初次之外,还有一个细节需要注意,顶层属性:

举个例子:

<template>
  <div>
    <!-- 10011 -->
    <div>str:{{ str + 1 }}</div>

    <!-- 10086 -->
    <div>obj.name:{{ obj.name }}</div>

    <!-- [object Object]1 -->
    <div>obj.name+1:{{ obj.name + 1 }}</div>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    let str = ref(10010)

    let str2 = ref(10086)

    return {
      str,
      obj: {
        name: str2,
      },
    }
  },
}
</script>

总结

综上所述:

  1. 需要数据响应式,可以借助 ref();
  2. ref() 返回值是一个 RefImpl 的实例对象;
  3. ref() 如果传入基础类型的数据,会直接赋值给 RefImpl 实例对象的 .value;
  4. ref() 如果传入引用类型的数据,会利用 reactive() 处理之后再赋值给 RefImpl 实例对象的 .value;
  5. 在模板中读取顶层属性不需要 .value

疑惑

  • ref() 实现原理,class RefImpl 中做了什么?
  • reactive()是什么?

end

  • 初步学习总结的笔记,表达不当请见谅。