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
    • 计算属性&侦听器
      • computed 🔥
      • watchEffect—立即&自动收集响应式数据的依赖 🔥
        • 介绍 🔥
        • 停止侦听 🔥
        • 清除副作用—onInvalidate 🔥
        • 执行时机—flush 🔥
        • watchPostEffect
        • watchSyncEffect
      • watch—惰性&手动指定侦听的数据源
        • 介绍 🔥
        • 侦听单个数据源 🔥
        • 侦听多个数据源 🔥
        • 源码—查看侦听类型 🔥
        • 深层的侦听—deep 🔥
        • 立即执行—immediate 🔥
    • 其他
  • 高级语法

  • Vue源码

  • VueCLI&Vite

  • VueRouter

  • Vuex

  • 项目

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

计算属性&侦听器

# 计算属性&侦听器

# computed 🔥

  • 在Composition API中,我们可以在 setup 函数中使用 computed 方法来编写一个计算属性
    • 接收一个getter函数,并为 getter 函数返回的值,返回一个不变的 ref 对象
    • 接收一个具有 get 和 set 的对象,返回一个可变的(可读写)ref 对象
<template>
  <div>
    <h2>{{ fullName }}</h2>
    <button @click="changeName">修改firstName</button>
  </div>
</template>

<script>
import { ref, computed } from 'vue'

export default {
  setup() {
    const firstName = ref('Kobe')
    const lastName = ref('Bryant')

    // 1.用法一: 传入一个getter函数
    // computed的返回值是一个ref对象
    // const fullName = computed(() => firstName.value + ' ' + lastName.value)
    // const fullName2 = computed(() => firstName.value + ' @ ' + lastName.value)

    // 2.用法二: 传入一个对象, 对象包含getter/setter
    const fullName = computed({
      get: () => firstName.value + ' ' + lastName.value,
      set(newValue) {
        const names = newValue.split(' ')
        firstName.value = names[0]
        lastName.value = names[1]
      },
    })

    const changeName = () => {
      // firstName.value = 'James'
      fullName.value = 'coder why'
    }

    return {
      fullName,
      changeName,
    }
  },
}
</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

# watchEffect—立即&自动收集响应式数据的依赖 🔥

# 介绍 🔥

侦听data或者props的数据变化

  • watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖
  • 只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行
<template>
  <div>
    <h2>{{name}}-{{age}}</h2>
    <button @click="changeName">修改name</button>
    <button @click="changeAge">修改age</button>
  </div>
</template>

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

  export default {
    setup() {
      // watchEffect: 自动收集响应式的依赖
      const name = ref("why");
      const age = ref(18);

      const changeName = () => name.value = "kobe"
      const changeAge = () => age.value++

      watchEffect(() => {
        console.log("name:", name.value, "age:", age.value);
      });

      return {
        name,
        age,
        changeName,
        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
32
33

# 停止侦听 🔥

如果在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可

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

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

  export default {
    setup() {
      // watchEffect: 自动收集响应式的依赖
      const name = ref("why");
      const age = ref(18);

      const stop = watchEffect(() => {
        console.log("name:", name.value, "age:", age.value);
      });

      const changeName = () => name.value = "kobe"
      const changeAge = () => {
        age.value++;
        if (age.value > 25) {
          stop();
        }
      }

      return {
        name,
        age,
        changeName,
        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
32
33
34
35
36
37
38

# 清除副作用—onInvalidate 🔥

什么是清除副作用呢?

  • 比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。
  • 那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用;

在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数onInvalidate,这个失效回调会被触发:

  • 副作用即将重新执行时(每次依赖的数据变化时)
  • 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)
<template>
  <div>
    <h2>{{name}}-{{age}}</h2>
    <button @click="changeName">修改name</button>
    <button @click="changeAge">修改age</button>
  </div>
</template>

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

  export default {
    setup() {
      // watchEffect: 自动收集响应式的依赖
      const name = ref("why");
      const age = ref(18);

      const stop = watchEffect((onInvalidate) => {
        const timer = setTimeout(() => {
          console.log("网络请求成功~");
        }, 2000)

        // 根据name和age两个变量发送网络请求
        onInvalidate(() => {
          // 在这个函数中清除额外的副作用
          // request.cancel()
          clearTimeout(timer);
          console.log("onInvalidate");
        })
        console.log("name:", name.value, "age:", age.value);
      });

      const changeName = () => name.value = "kobe"
      const changeAge = () => {
        age.value++;
        if (age.value > 25) {
          stop();
        }
      }

      return {
        name,
        age,
        changeName,
        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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 执行时机—flush 🔥

如果需要在组件更新(例如:当与模板引用 (opens new window)一起)后重新运行侦听器副作用,我们可以传递带有 flush 选项的附加 options 对象 (默认为 'pre'):

flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
1
  • pre:在组件更新后触发,这样你就可以访问更新的 DOM。这也将推迟副作用的初始运行,直到组件的首次渲染完成

  • post:默认

  • sync:强制效果始终同步触发。然而,这是低效的,应该很少需要。

3.2+,推荐使用 watchPostEffect、watchSyncEffect

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

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

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

    watchEffect(
      () => {
        console.log(title.value)
      },
      {
        // 当没有该配置,或者为默认值 pre,则上面log打印2次,第一次为null,第二次为el
        // 这是因为setup函数在执行时就会立即执行传入的副作用函数,这个时候DOM并没有挂载,所以打印为null
        // 而当DOM挂载时,会给title的ref对象赋值新的值,副作用函数会再次执行,打印出来对应的元素
        flush: 'post',
      }
    )

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

# watchPostEffect

3.2+。watchEffect 的别名,带有 flush: 'post' 选项。

# watchSyncEffect

3.2+。watchEffect 的别名,带有 flush: 'sync' 选项。

# watch—惰性&手动指定侦听的数据源

# 介绍 🔥

侦听data或者props的数据变化

watch的API完全等同于组件watch选项的Property:

  • watch需要侦听特定的数据源,并在回调函数中执行副作用;
  • 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调;

与watchEffect的比较,watch允许我们:

  • 懒执行副作用(第一次不会直接执行);
  • 更具体的说明当哪些状态发生变化时,触发侦听器的执行;
  • 访问侦听状态变化前后的值;

# 侦听单个数据源 🔥

watch侦听函数的数据源有两种类型:

  • 一个getter函数,但是该getter函数必须引用可响应式的对象(比如reactive或者ref);
  • 直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref,reactive类型需要进行某些转换);
<template>
  <div>
    <h2 ref="title">{{ info.name }}</h2>
    <button @click="changeData">修改数据</button>
  </div>
</template>

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

export default {
  setup() {
    const info = reactive({ name: 'why', age: 18 })

    // 1.侦听watch时,传入一个getter函数
    watch(
      () => info.name,
      (newValue, oldValue) => {
        console.log('newValue:', newValue, 'oldValue:', oldValue)
      }
    )

    // 2.传入一个可响应式对象: reactive对象
    // 2.1 情况一: reactive对象获取到的newValue和oldValue本身都是reactive对象。所以new、old值为reactive,且相同!
    // watch(info, (newValue, oldValue) => {
    //   console.log("newValue:", newValue, "oldValue:", oldValue);
    // })
    // 2.2 情况二:如果希望newValue和oldValue是一个普通的对象,需解构!!!
    watch(
      () => {
        return { ...info }
      },
      (newValue, oldValue) => {
        console.log('newValue:', newValue, 'oldValue:', oldValue)
      }
    )
      
    // 3.传入一个可响应式对象: ref对象
    // ref对象获取newValue和oldValue是value值的本身,所以new、old不同
    // const name = ref("why");
    // watch(name, (newValue, oldValue) => {
    //   console.log("newValue:", newValue, "oldValue:", oldValue);
    // })

    const changeData = () => {
      info.name = 'kobe'
    }

    return {
      changeData,
      info,
    }
  },
}
</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
51
52
53
54
55

# 侦听多个数据源 🔥

就是数组包含侦听单个数据源的方式!!!详见单个数据源!

<template>
  <div>
    <h2 ref="title">{{info.name}}</h2>
    <button @click="changeData">修改数据</button>
  </div>
</template>

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

  export default {
    setup() {
      // 1.定义可响应式的对象
      const info = reactive({name: "why", age: 18});
      const name = ref("why");

      // 2.侦听器watch
      watch([() => ({...info}), name], ([newInfo, newName], [oldInfo, oldName]) => {
        console.log(newInfo, newName, oldInfo, oldName);
      })

      const changeData = () => {
        info.name = "kobe";
      }

      return {
        changeData,
        info
      }
    }
  }
</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

# 源码—查看侦听类型 🔥

/packages/runtime-core/src/apiWatch.ts#doWatch

const instance = currentInstance
let getter: () => any
let forceTrigger = false
let isMultiSource = false

if (isRef(source)) {
    getter = () => source.value
    forceTrigger = !!source._shallow
} else if (isReactive(source)) {
    getter = () => source
    deep = true
} else if (isArray(source)) {
    isMultiSource = true
    forceTrigger = source.some(isReactive)
    getter = () =>
    source.map(s => {
        if (isRef(s)) {
            return s.value
        } else if (isReactive(s)) {
            return traverse(s)
        } else if (isFunction(s)) {
            return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
        } else {
            __DEV__ && warnInvalidSource(s)
        }
    })
} else if (isFunction(source)) {
    if (cb) {
        // getter with cb
        getter = () =>
        callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
    } else {
        // no cb -> simple effect
        getter = () => {
            if (instance && instance.isUnmounted) {
                return
            }
            if (cleanup) {
                cleanup()
            }
            return callWithAsyncErrorHandling(
                source,
                instance,
                ErrorCodes.WATCH_CALLBACK,
                [onInvalidate]
            )
        }
    }
} else {
    getter = NOOP
    __DEV__ && warnInvalidSource(source)
}
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

可以看出,总共支持监听4中类型:

  • ref:getter 取的是ref的value,是普通数据类型,所以new、old值不同!
  • reactive:getter取的是原reactive的Proxy对象,所以new、old值同为最新的reactive的Proxy对象!
  • function(即getter)
  • array:即监听多个数据源,处理方式还是按照map后单个处理!!!

# 深层的侦听—deep 🔥

  • 查看上面源码,注意 reactive 对象默认 deep 为 true!!!但是该方式 getter 直接取 reactive 对象,若需要返回普通对象,则需要侦听器传入解构对象。且若需要深度侦听,则需手动指定 deep 为 true
  • 其他类型根据需要设置 deep 的值

# 立即执行—immediate 🔥

默认情况下,watch第一次不会执行,必须侦听的值改变了!

编辑 (opens new window)
上次更新: 2022/03/23, 17:55:39
响应式Api
其他

← 响应式Api 其他→

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