Evan's blog Evan's blog
首页
关于
  • 分类
  • 标签
  • 归档
  • H5&CSS3
  • JS
  • TS
  • Node
  • Webpack
  • Vue2
  • Vue3
  • 微信小程序
  • Andorid
  • Flutter
推荐
GitHub (opens new window)

conanan

真相只有一个
首页
关于
  • 分类
  • 标签
  • 归档
  • H5&CSS3
  • JS
  • TS
  • Node
  • Webpack
  • Vue2
  • Vue3
  • 微信小程序
  • Andorid
  • Flutter
推荐
GitHub (opens new window)
  • 基础

  • 组件

  • 动画

  • Composition Api

    • 旧解决方案
    • 响应式Api
      • Options Api 的弊端
      • setup 函数 🔥
        • 介绍
        • 参数
        • 返回值
        • 示例
      • ref Api 🔥
        • ref 🔥
        • unref 🔥
        • isRef
        • shallowRef—浅层
        • triggerRef
        • customRef
        • setup中获取元素的el 🔥
      • reactive Api 🔥
        • reactive 🔥
        • toRefs、toRef—解构reactive 🔥
        • shallowReactive—浅层
      • readonly 🔥
        • readonly 🔥
        • shallowReadonly
      • reactive、readonly 其他 API
        • isProxy
        • isReactive
        • isReadonly
        • toRaw—谨慎使用
    • 计算属性&侦听器
    • 其他
  • 高级语法

  • Vue源码

  • VueCLI&Vite

  • VueRouter

  • Vuex

  • 项目

  • Vue3.x
  • Composition Api
xugaoyi
2022-02-03
目录

响应式Api

# 响应式Api

# Options Api 的弊端

在Vue2中,我们编写组件的方式是Options API:

  • Options API的一大特点就是在对应的属性中编写对应的功能模块;

  • 比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命 周期钩子

但是这种代码有一个很大的弊端:

  • 当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中,如data、method 等;
  • 当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散;
  • 尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人);

改进:将同一个逻辑关注点相关的代码收集在一起会更好。

# setup 函数 🔥

# 介绍

  • setup 函数就是 composition api 编写的地方!
  • 它也是组件中的另一个选项(option),只不过可以替代之前的 options
  • setup 是围绕 beforeCreate 和 created 生命周期钩子运行的。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写

# 参数

setup(props, context)

props就是父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可以直接通过props参数获取:

  • 对于定义props的类型,我们还是和之前的规则是一样的,在props选项中定义;或者definedProps中
  • 并且在template中依然是可以正常去使用props中的属性,比如message;
  • 如果我们在setup函数中想要使用props,那么不可以通过 this 去获取(后面我会讲到为什么);
  • 因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可;

context,我们也称之为是一个SetupContext,它里面包含三个属性

  • attrs:所有的非prop的attribute;
  • slots:父组件传递过来的插槽(这个在以渲染函数返回时会有作用,后面会讲到);
  • emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件)

官方关于this有这样一段描述

  • this并没有指向当前组件实例
  • setup调用发生在data、computed、methods等被解析之前
  • 所以他们无法在setup中获取!

# 返回值

setup既然是一个函数,那么它也可以有返回值,它的返回值用来做什么呢?

  • setup的返回值可以在模板template中被使用;

  • 也就是说我们可以通过setup的返回值来替代data选项;

  • 甚至是我们可以返回一个执行函数来代替在methods中定义的方法

# 示例

<template>
  <div>
    Home Page
    <h2>{{message}}</h2>

    <h2>{{title}}</h2>
    <h2>当前计数: {{counter}}</h2>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
  export default {
    props: {
      message: {
        type: String,
        required: true
      }
    },
    data() {
      return {
        counter: 100
      }
    },
    setup(props, {attrs, slots, emit}) {
      console.log(props.message);
      console.log(attrs.id, attrs.class);
      console.log(slots);
      console.log(emit);

      return {
        title: "Hello Home",
        counter: 100
      }
    },
    methods: {
      btnClick() {
        this.$emit("")
      }
    }
  }
</script>
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

# ref Api 🔥

# ref 🔥

[reference] 的意思

  • ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源;
  • 它内部的值是在ref的 value 属性中被维护的。值可以是所有数据类型!如基本数据类型、数组、对象等
const message = ref('hello')
1

注意:

  • 在模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用
  • 但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用ref.value的方式

ref 解包注意:

  • 模板中的解包是浅层的解包,如果我们的代码是下面的方式

  • 如果我们将ref放到一个reactive的属性当中,那么在模板中使用时,它会自动解包

    <template>
      <div>
        Home Page
        <h2>{{message}}</h2>
        <!-- 当我们在template模板中使用ref对象, 它会自动进行解包 -->
        <h2>当前计数: {{counter}}</h2>
        <!-- ref的解包只能是一个浅层解包(info是一个普通的JavaScript对象),此时就必须使用.value!!! -->
        <h2>当前计数: {{info.counter.value}}</h2>
        <!-- 当如果最外层包裹的是一个reactive可响应式对象, 那么内容的ref可以解包 -->
        <h2>当前计数: {{reactiveInfo.counter}}</h2>
        <button @click="increment">+1</button>
      </div>
    </template>
    
    <script>
      import { ref, reactive } from 'vue';
    
      export default {
        props: {
          message: {
            type: String,
            required: true
          }
        },
        setup() {
          let counter = ref(100);
    
          const info = {
            counter
          }
    
          const reactiveInfo = reactive({
            counter
          })
    
          // 局部函数
          const increment = () => {
            counter.value++;
            console.log(counter.value);
          }
    
          return {
            counter,
            info,
            reactiveInfo,
            increment
          }
        }
      }
    </script>
    
    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

# unref 🔥

  • 获取一个ref引用中的value,那么也可以通过unref方法。如果参数是一个 ref,则返回内部值,否则返回参数本身

  • 这是 val = isRef(val) ? val.value : val 的语法糖函数

# isRef

  • 判断值是否是一个ref对象

# shallowRef—浅层

  • 创建一个浅层的ref对象

# triggerRef

  • 手动触发和 shallowRef 相关联的副作用

    <template>
      <div>
        <h2>{{info}}</h2>
        <button @click="changeInfo">修改Info</button>
      </div>
    </template>
    
    <script>
      import { ref, shallowRef, triggerRef } from 'vue';
    
      export default {
        setup() {
          const info = shallowRef({name: "why"})
    
          const changeInfo = () => {
            info.value.name = "james";
            triggerRef(info);
          }
    
          return {
            info,
            changeInfo
          }
        }
      }
    </script>
    
    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

# customRef

创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制:

  • 它需要一个工厂函数,该函数接受 track 和 trigger 函数作为参数;
  • 并且应该返回一个带有 get 和 set 的对象;

示例:对双向绑定的属性进行**debounce(节流)**的操作

App.vue

<template>
  <div>
    <input v-model="message"/>
    <h2>{{message}}</h2>
  </div>
</template>

<script>
  import debounceRef from './hook/useDebounceRef';

  export default {
    setup() {
      const message = debounceRef("Hello World");

      return {
        message
      }
    }
  }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

useDebounceRef.js

import { customRef } from 'vue';

// 自定义ref
export default function(value, delay = 300) {
  let timer = null;
  return customRef((track, trigger) => {
    return {
      get() {
        track();
        return value;
      },
      set(newValue) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          value = newValue;
          trigger();
        }, delay);
      }
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# setup中获取元素的el 🔥

在options api中获取元素的el,只需要在模板中声明ref='tableRef',调用时执行this.$refs.tableRef即可

但是在setup中不能这样使用了!

<template>
  <div>
    <h2 ref="title">哈哈哈</h2>

    <h2 v-for="item of 3" :key="item" :ref="setH2Refs">{{ item }}</h2>
  </div>
</template>

<script>
import { ref, watchEffect, onBeforeUpdate } from 'vue'

export default {
  setup() {
    // 单个 ref
    const title = ref(null)

    watchEffect(
      () => {
        console.log(title.value)
        console.log(h2Refs)
      },
      {
        flush: 'post',
      }
    )

    // v-for 中 ref
    let h2Refs = []
    // DOM 被更新之前被调用
    onBeforeUpdate(() => {
      h2Refs = []
    })
    const setH2Refs = (el) => {
      if (el) h2Refs.push(el)
    }

    return {
      title,
      setH2Refs,
    }
  },
}
</script>
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

# reactive Api 🔥

# reactive 🔥

如果想为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive的函数

  • 这是因为当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集;
  • 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);
  • 事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的;
const state = reactive({
    name: 'conanan',
    counter: 100,
})
1
2
3
4

注意:reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型。如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告。可以使用上面介绍的 Ref api

# toRefs、toRef—解构reactive 🔥

如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后

  • 无论是修改结构后的变量
  • 还是修改reactive返回的state对象,

数据都不再是响应式的。那么有没有办法让我们解构出来的属性是响应式的呢?

  • Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref

  • 那么我们再次进行结构出来的 name 和 age 本身都是 ref的

    // 返回2个ref对象!
    const { name, age } = toRefs(state);
    
    1
    2
  • 这种做法相当于已经在state.name和ref.value之间建立了链接,任何一个修改都会引起另外一个变化

<template>
  <div>
    <h2>{{name}}-{{age}}</h2>
    <button @click="changeAge">修改age</button>
  </div>
</template>

<script>
  import { reactive, toRefs, toRef } from 'vue';

  export default {
    setup() {
      const info = reactive({name: "why", age: 18});
      // 1.toRefs: 将reactive对象中的所有属性都转成ref, 建立链接
      // let { name, age } = toRefs(info);
      // 2.toRef: 对其中一个属性进行转换ref, 建立链接
      let { name } = info;
      let age = toRef(info, "age");

      const changeAge = () => {
        age.value++;
      }

      return {
        name,
        age,
        changeAge
      }
    }
  }
</script>
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

# shallowReactive—浅层

  • 创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换(深层还是原生对象)。

# readonly 🔥

# readonly 🔥

我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改

  • Vue3为我们提供了readonly的方法;

  • readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改);

在开发中常见的readonly方法会传入三个类型的参数:

  • 普通对象
  • reactive返回的对象。传递给组件时
  • ref的对象。传递给组件时
<template>
  <div>
    <button @click="updateState">修改状态</button>
  </div>
</template>

<script>
  import { reactive, ref, readonly } from 'vue';

  export default {
    setup() {
      // 1.普通对象
      const info1 = {name: "why"};
      const readonlyInfo1 = readonly(info1);

      // 2.响应式的对象reactive
      const info2 = reactive({
        name: "why"
      })
      const readonlyInfo2 = readonly(info2);

      // 3.响应式的对象ref
      const info3 = ref("why");
      const readonlyInfo3 = readonly(info3);

      const updateState = () => {
        // readonlyInfo3.value = "coderwhy"
        info3.value = "coderwhy";
      }

      return {
        updateState,
      }
    }
  }
</script>
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

# shallowReadonly

  • 创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)

# reactive、readonly 其他 API

# isProxy

  • 检查对象是否是由 reactive 或 readonly 创建的 Proxy。

# isReactive

  • 检查对象是否是由 reactive创建的响应式代理:

  • 如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;

    const info1 = readonly({name: 'conanan'}) // false
    const info2 = readonly(reactive({name: 'conanan'})) // true
    
    1
    2

# isReadonly

  • 检查对象是否是由 readonly 创建的只读代理

# toRaw—谨慎使用

  • 返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)
编辑 (opens new window)
上次更新: 2022/03/23, 17:55:39
旧解决方案
计算属性&侦听器

← 旧解决方案 计算属性&侦听器→

最近更新
01
重点
04-12
02
搭建项目
04-04
03
TS补充
03-30
更多文章>
Theme by Vdoing | Copyright © 2019-2022 conanan | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式