vue3 教程(中)

简介: 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>
目录
相关文章
|
2天前
|
JavaScript
Vue中ref创建_基本类型的响应式数据,在Vue2的年代,数据配在data里,Vue3的区别是不把响应数据写在data里,那个数据是响应式的用ref包一下,let name = ref(“张三“)
Vue中ref创建_基本类型的响应式数据,在Vue2的年代,数据配在data里,Vue3的区别是不把响应数据写在data里,那个数据是响应式的用ref包一下,let name = ref(“张三“)
|
1天前
|
JavaScript API
vue3【实用教程】组件(含父子组件传值 defineProps,自定义事件 defineEmits,defineProps,插槽 slot,动态组件 :is 等)
vue3【实用教程】组件(含父子组件传值 defineProps,自定义事件 defineEmits,defineProps,插槽 slot,动态组件 :is 等)
8 1
|
1天前
vue3 自定义 v-model(无参数和带参数)【setup版】(两种方案)
vue3 自定义 v-model(无参数和带参数)【setup版】(两种方案)
5 0
|
1天前
vue3【实战】可撤销重做的 input 输入框
vue3【实战】可撤销重做的 input 输入框
5 0
|
1天前
|
JavaScript
Vue2和Vue3的安装,vue2的安装和初始化配置
Vue2和Vue3的安装,vue2的安装和初始化配置
|
1天前
|
存储 JavaScript 前端开发
vue3【实用教程】声明响应式状态(含ref,reactive,toRef(),toRefs() 等)
vue3【实用教程】声明响应式状态(含ref,reactive,toRef(),toRefs() 等)
12 0
|
1天前
|
JavaScript API
vue3【实用教程】侦听器 watch,自动侦听 watchEffect(),$watch,手动停止侦听器
vue3【实用教程】侦听器 watch,自动侦听 watchEffect(),$watch,手动停止侦听器
9 0
|
1天前
vue3【实用教程】v-model(含给 v-model 添加参数,绑定多个 v-model ,v-model 的内置修饰符,自定义 v-model 的修饰符等)
vue3【实用教程】v-model(含给 v-model 添加参数,绑定多个 v-model ,v-model 的内置修饰符,自定义 v-model 的修饰符等)
10 0
|
2天前
|
JavaScript
Vue3的使用,ref定义基本类型的响应式数据,如何创建对象类型的响应式数据,let car = {brand: ‘奔驰‘,price: 100},{{car.brand}},reactive的使用
Vue3的使用,ref定义基本类型的响应式数据,如何创建对象类型的响应式数据,let car = {brand: ‘奔驰‘,price: 100},{{car.brand}},reactive的使用
|
1天前
|
JavaScript
Vue卸载eslint的写法,单独安装eslint,单独卸载eslint
Vue卸载eslint的写法,单独安装eslint,单独卸载eslint