vue3 教程(中)

侦听器

用于侦听指定变量,当其响应式状态变化时触发回调函数。

watch()

watch() 需明确指定侦听的数据源,并且仅当数据源变化时,才会执行回调,在创建侦听器时,不会执行回调,可以获取到数据源变化前后的值。

  • 第一个参数为“数据源”,可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组
  • 第二个参数为回调函数

侦听–响应式变量 / 计算属性

const x = ref(0)
watch(x, (newX) => {
    
    
  console.log(`x is ${
      
      newX}`)
})

侦听–响应式对象

会隐式地创建一个深层侦听器,对象的属性和嵌套属性发生变化时,都会触发回调

const obj = reactive({
    
     count: 0 })
watch(obj, (newValue, oldValue) => {
    
    
  // 此处 `newValue` 和 `oldValue` 是相等的,因为它们是同一个对象!
})

若用getter 函数返回响应式对象 ,则只有在返回不同的对象时,才会触发回调:

watch(
  () => state.someObject,
  () => {
    
    
    // 仅当 state.someObject 被替换时触发
  }
)

通过添加 deep 选项,可以将其强制转成深层侦听器(即当对象的属性和嵌套属性发生变化时触发回调)

watch(
  () => state.someObject,
  (newValue, oldValue) => {
    
    
    // 此处 `newValue` 和 `oldValue` 是相等的,除非 state.someObject 被整个替换了
  },
  {
    
     deep: true }
)

侦听–对象的属性

方案1:用一个返回该属性的 getter 函数

// 侦听obj对象的count属性
watch(
  () => obj.count,
  (count) => {
    
    
    console.log(`count is: ${
      
      count}`)
  }
)

方案2:使用 toRefs

import {
    
     ref, toRefs, watch } from "vue";
let obj = ref({
    
     count: 30 });
let {
    
     count } = toRefs(obj.value);
watch(count, (newValue, oldValue) => {
    
    });

不能直接侦听响应式对象的属性值,因为属性值非响应式

const obj = reactive({
    
     count: 0 })

// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
    
    
  console.log(`count is: ${
      
      count}`)
})

侦听-- getter 函数

const x = ref(0)
const y = ref(0)

watch(
  () => x.value + y.value,
  (sum) => {
    
    
    console.log(`sum of x + y is: ${
      
      sum}`)
  }
)

侦听-- 多个数据源

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
    
    
  console.log(`x is ${
      
      newX} and y is ${
      
      newY}`)
})

watchEffect()

watchEffect()在创建侦听器时,会立即执行一遍回调,并从中自动分析出依赖的数据源(其响应性依赖关系不那么明确),当数据源发生改变时,再次触发回调。无法获取到数据源变化前的值。

watchEffect(async () => {
    
    
  const response = await fetch(url.value)
  data.value = await response.json()
})

上例中,在页面创建时会先请求 url.value 接口获得初始数据,并自动追踪 url.value
url.value 变化时,会再次执行回调,访问新的接口获取数据。

改变回调的触发时机

默认情况下,侦听器回调会在 Vue 组件更新之前被调用(在侦听器回调中访问的 DOM 是被 Vue 更新之前的状态)

添加 flush: 'post' 选项可以让侦听器回调在 Vue 组件更新之后再调用,这样就能在侦听器回调中访问被 Vue 更新之后的 DOM 啦!

watch(source, callback, {
    
    
  flush: 'post'
})

watchEffect(callback, {
    
    
  flush: 'post'
})

后置刷新的 watchEffect() 可以直接用 watchPostEffect()

import {
    
     watchPostEffect } from 'vue'

watchPostEffect(() => {
    
    
  /* 在 Vue 更新后执行 */
})

停止侦听器

同步语句创建的侦听器,会在组件卸载时自动停止。
异步回调创建的侦听器,必须手动停止它,以防内存泄漏。

setTimeout(() => {
    
    
  watchEffect(() => {
    
    })
}, 100)

手动停止侦听器的方法是调用 watch 或 watchEffect 返回的函数

const unwatch = watchEffect(() => {
    
    })
unwatch()

异步创建侦听器的情况很少,如果需要等待一些异步数据,可以使用条件式的侦听逻辑:

// 需要异步请求得到的数据
const data = ref(null)

watchEffect(() => {
    
    
  if (data.value) {
    
    
    // 数据加载后执行某些操作...
  }
})

模板引用 ref

用于直接访问底层 DOM 元素,即 vue2 中的 $refs

  <input ref="input" />
import {
    
     ref, onMounted } from 'vue'

// 声明一个ref变量来存放该元素的引用,变量名必须和模板里的 ref 属性值相同
const input = ref(null)

onMounted(() => {
    
    
  // 页面加载后,输入框自动获得焦点
  input.value.focus()
})

只可以在组件挂载后才能访问模板引用
若侦听模板引用 ref 的变化,需考虑到其值为 null 的情况:

watchEffect(() => {
    
    
  if (input.value) {
    
    
    input.value.focus()
  } else {
    
    
    // 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制)
  }
})

ref 绑定函数

<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">
  • 绑定函数需使用 :ref
  • 每次Dom更新时函数都会被调用
  • Dom被卸载时,函数也会被调用一次,此时 el 参数的值是 null

子组件上的 ref

若子组件使用的是选项式 API 或没有使用 <script setup> ,则对子组件的模板引用即子组件的 this,可以直接访问子组件的属性和方法。(但仍推荐用标准的 props 和 emit 接口来实现父子组件交互)

使用了 <script setup> 的子组件是默认私有的:父组件无法访问私有子组件中的任何东西,除非子组件通过 defineExpose 宏显式暴露。

<script setup>
import {
      
       ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
      
      
  a,
  b
})
</script>

父组件通过模板引用获取到的实例类型为 { a: number, b: number } (ref 都会自动解包,和一般的实例一样)。

父子组件

父组件中使用子组件 import

vue3 中导入后就能直接使用,无需像 vue2 中进行注册

<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>
<template>
  <ButtonCounter />
</template>

子组件接收父组件传入的数据 props

子组件用 defineProps() 接收父组件传入的数据

<script setup>
defineProps(['title'])
</script>

<template>
  <h4>{
   
   { title }}</h4>
</template>

defineProps() 的参数和 props 选项的值相同

defineProps({
    
    
  title: String,
  likes: Number
})

搭配 TypeScript 使用类型标注来声明 props

<script setup lang="ts">
defineProps<{
      
      
  title?: string
  likes?: number
}>()
</script>

选项式风格中,props 对象会作为 setup() 函数的第一个参数被传入:

export default {
    
    
  props: ['title'],
  setup(props) {
    
    
    console.log(props.title)
  }
}

子组件触发自定义事件 emits

组件触发的事件不会冒泡,父组件只能监听直接子组件触发的事件。

父组件–在引入的子组件上绑定事件

<BlogPost @enlarge-text="postFontSize += 0.1"/>

子组件–用 defineEmits() 声明事件

<button @click="$emit('enlarge-text')">Enlarge text</button>
<script setup>
defineEmits(['enlarge-text']) // 多个事件则为 defineEmits(['inFocus', 'submit'])
</script>

选项式风格中,通过 emits 选项定义组件会抛出的事件,并用 setup() 的第二个参数(上下文对象)访问 emit 函数:

export default {
    
    
  emits: ['enlarge-text'],
  setup(props, ctx) {
    
    
    ctx.emit('enlarge-text')
  }
}

事件传参

子组件

<button @click="$emit('increaseBy', 1)"></button>

父组件

<MyButton @increase-by="(n) => count += n" />

<MyButton @increase-by="increaseCount" />
function increaseCount(n) {
    
    
  count.value += n
}

子组件继承样式

vue2 中限定只能有一个根节点,父组件中给子组件添加的样式,都会渲染在子组件的根节点上,如:

<!-- 子组件 -->
<p class="child">你好</p>
<!-- 父组件使用子组件时,添加了新的样式 father -->
<MyComponent class="father" />

最终渲染的效果为:

<p class="child father">你好</p>

vue3 中支持多个根节点,所以需要通过 $attrs 指定具体哪些节点继承父组件添加的样式。

<!-- 子组件:在需要继承样式的元素上,添加  :class="$attrs.class" -->
<p :class="$attrs.class">你好</p>
<span>我是朝阳</span>
<!-- 父组件使用子组件时,添加了新的样式 father -->
<MyComponent class="father" />

最终渲染的效果为:

<p class="father">你好</p>
<span>我是朝阳</span>

猜你喜欢

转载自blog.csdn.net/weixin_41192489/article/details/128247284