• 02 【setup reactive ref】


    3.拉开序幕的setup

    3.1 为什么使用 setup ?

    • 大型组件中选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。 如果能够将同一个逻辑关注点相关代码收集在一起会更好。而这正是组合式 API 使我们能够做到的。

    • 通过创建 Vue 组件,我们可以将界面中重复的部分连同其功能一起提取为可重用的代码段。然而,光靠这一点可能并不够,尤其是当你的应用变得非常大的时候,共享和重用代码变得尤为重要。

    • 为了开始使用 组合式 API,我们首先需要一个可以实际使用它的地方。在 Vue 组件中,我们将此位置称为 setup。

    简单举例:
    角色权限页面功能:新增/删除角色、获取角色列表、获取角色对应的权限信息、编辑角色权限信息。

    • 通常以前会将这些功能写在一个sfc组件(.vue),当需要修改其中1处功能逻辑时,我们就需要跳转对应的各个options api选项进行操作。当有些业务组件复杂时,代码量上去了,浏览起来会更吃力。
    • 而有了setup、composition api后,我们可以将这例子中的4个功能逻辑拆分到对应的.js文件文件,使用composition api编写业务代码,然后引入到sfc组件的setup里使用。此时我们修改其中1处功能逻辑,只需要去对应的.js文件进行修改即可,而不必被其他的功能逻辑代码干扰。

    错误用法:

    有些人写setup,以为是将vue2的写法,挪到setup内声明、return出去,导致setup内代码特别长,并且比options api写法更难以阅读!(当然代码量少的情况下,不影响阅读 也可不拆分)

    3.2 基本使用

    1. 理解:Vue3.0中一个新的配置项,值为一个函数。
    2. setup 函数是一个新的组件选项。它是组件内部使用组合式 API 的入口点。
    3. 组件中所用到的:数据、方法等等,均要配置在setup中。
    4. 调用时间:在创建组件实例时,在初始 prop 解析之后立即调用 setup。在生命周期方面,它是在 beforeCreate 钩子之前调用的。
    5. setup函数的两种返回值:
      1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
      2. 若返回一个渲染函数:则可以自定义渲染内容。(了解)
    6. 注意点:
      1. 尽量不要与Vue2.x配置混用
        • Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。
        • 但在setup中不能访问到Vue2.x配置(data、methos、computed…)。
        • 如果有重名, setup优先。
      2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

    setup() 钩子是在组件中使用组合式 API 的入口,通常只在以下情况下使用:

    1. 需要在非单文件组件中使用组合式 API 时。
    2. 需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。

    其他情况下,都应优先使用 script setup 语法。

    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    注意事项:

    • setup 内的 this 是不指向组件实例的! this > undefined

    • props 对象是响应式的——即在传入新的 props 时会对其进行更新,通过使用 watchEffect 或 watch 进行观测和响应;

    • 但是,请不要解构 props 对象,因为它会失去响应式;

    3.3
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
        let person = reactive({
          name: 'dselegent',
          age: 21,
        });
        console.log(person);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image-20220704224406322

    通过reactive创建的其实就是ref创建的value属性内容

    注意:比如reactive中的数据是个对象,我们正常情况只能把整个对象导出去,如果只是想用某个属性,直接导出去的话不是响应式的,后面可以用toRef或者toRefs解决这种需求。

    5.ref函数

    5.1 基本使用

    reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref

    • 作用: 定义一个响应式的数据

    • 语法: const xxx = ref(initValue)

      • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
      • JS中操作数据: xxx.value
      • 模板中作为顶层属性被访问: 它们会被自动“解包”,所以不需要使用 .value,直接:
        {{xxx}}
    • 备注:

      • 接收的数据可以是:基本类型、也可以是对象类型。
      • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。
      • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。
    import { ref } from 'vue'
    
    const count = ref(0)
    
    • 1
    • 2
    • 3

    ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象:

    const count = ref(0)
    
    console.log(count) // { value: 0 }
    console.log(count.value) // 0
    
    count.value++
    console.log(count.value) // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    TypeScript 用户请参阅:为 ref 标注类型

    和响应式对象的属性类似,ref 的 .value 属性也是响应式的。

    如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。

    若要避免这种深层次的转换,请使用 shallowRef() 来替代。

    初体验

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    ref输出

        let name = ref('ds');
        console.log(name);
    
    • 1
    • 2

    image-20220704224049809

        let person = ref({
          name: 'dselegent',
          age: 21,
        });
        console.log(person);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image-20220704224110322

    5.2 ref的响应式

    一个包含对象类型值的 ref 可以响应式地替换整个对象:

    const objectRef = ref({ count: 0 })
    
    // 这是响应式的替换
    objectRef.value = { count: 1 }
    
    • 1
    • 2
    • 3
    • 4

    ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性:

    const obj = {
      foo: ref(1),
      bar: ref(2)
    }
    
    // 该函数接收一个 ref
    // 需要通过 .value 取值
    // 但它会保持响应性
    callSomeFunction(obj.foo)
    
    // 仍然是响应式的
    const { foo, bar } = obj
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    简言之,ref() 让我们能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。这个功能很重要,因为它经常用于将逻辑提取到 组合函数 中。

    5.3ref 在模板中的解包#

    当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value。下面是之前的计数器例子,用 ref() 代替:

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在演练场中尝试一下

    请注意,仅当 ref 是模板渲染上下文的顶层属性时才适用自动“解包”。 例如, foo 是顶层属性,但 object.foo 不是。

    所以我们给出以下 object:

    const object = { foo: ref(1) }
    
    • 1

    下面的表达式将不会像预期的那样工作:

    {{ object.foo + 1 }}
    
    • 1

    渲染的结果会是一个 [object Object],因为 object.foo 是一个 ref 对象。我们可以通过将 foo 改成顶层属性来解决这个问题:

    const { foo } = object
    {{ foo + 1 }}
    
    • 1
    • 2

    现在渲染结果将是 2

    需要注意的是,如果一个 ref 是文本插值(即一个 {{ }} 符号)计算的最终值,它也将被解包。因此下面的渲染结果将为 1

    {{ object.foo }}
    
    • 1

    这只是文本插值的一个方便功能,相当于 {{ object.foo.value }}

    5.4 ref的解包

    5.4.1 ref 在响应式对象中的解包

    当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样:

    const count = ref(0)
    const state = reactive({
      count
    })
    
    console.log(state.count) // 0
    
    state.count = 1
    console.log(count.value) // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如果将一个新的 ref 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的 ref:

    const otherCount = ref(2)
    
    state.count = otherCount
    console.log(state.count) // 2
    // 原始 ref 现在已经和 state.count 失去联系
    console.log(count.value) // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    只有当嵌套在一个深层响应式对象内时,才会发生 ref 解包。当其作为浅层响应式对象的属性被访问时不会解包。

    5.4.2 数组和集合类型的 ref 解包#

    跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包。

    const books = reactive([ref('Vue 3 Guide')])
    // 这里需要 .value
    console.log(books[0].value)
    
    const map = reactive(new Map([['count', ref(0)]]))
    // 这里需要 .value
    console.log(map.get('count').value)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5.5 响应性语法糖 (实验性功能)

    响应性语法糖目前是一个实验性功能,默认是禁用的,需要显式选择使用

    相对于普通的 JavaScript 变量,我们不得不用相对繁琐的 .value 来获取 ref 的值。这是一个受限于 JavaScript 语言限制的缺点。然而,通过编译时转换,我们可以让编译器帮我们省去使用 .value 的麻烦。Vue 提供了一种编译时转换,使得我们可以像这样书写之前的“计数器”示例:

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    你可以在 响应性语法糖 章节中了解更多细节。请注意它仍处于实验性阶段,在最终提案落地前仍可能发生改动。

  • 相关阅读:
    如何用VS2015新建一个空白项目
    springcloudalibaba-3
    浏览器插件有什么作用,怎么安装浏览器扩展插件
    25、MySQL 导出数据
    单元测试实战(二)Service 的测试
    优化程序性能
    ExtJS-绘图(Drawing)
    knative入门指南
    Unity 高级程序员应该具备怎样的能力?要怎样成长为 Unity 高级程序员?
    pandas Series 使用整数索引的一些问题
  • 原文地址:https://blog.csdn.net/DSelegent/article/details/126237663