前端面试二-----基础知识类

一、说说 Vue 3 的 setup 函数执行时机?如果要在 setup 里访问 this,应该怎么办?

(一) setup 函数的执行时机

setup 函数是 在组件实例被完全创建之前、在 beforeCreate created 生命周期钩子之间 同步执行的。

更具体的时间线可以这样理解:

  1. 初始化组件实例:Vue 开始创建组件实例(初始化组件实例的属性和事件等)。
  2. 执行 setup 函数:在 datacomputedmethods 等选项被解析或执行之前setup 函数就被立即调用了。
  3. 执行生命周期钩子
    • setup 执行完毕后,紧接着会执行 beforeCreate 钩子。
    • 然后,组件的 datacomputed 等选项被处理。
    • 最后,执行 created 钩子。

关键点:

  • 同步执行setup 是同步的,不是异步的。
  • “this” 尚未创建:因为在 setup 被调用时,组件的其他选项(如 datamethods)都还未被处理,所以组件实例 “this” 在 setup 内部是 undefined
  • beforeCreate 之前:你可以在 beforeCreate 钩子中访问到 setup 函数返回的对象属性,这证明了 setup 执行得更早。

(二) 在 setup 里访问 this 的正确方法

简短回答:你不应该,也不需要,也无法在 setup 中访问 this

Vue 3 的 Composition API 的设计理念就是通过函数参数来获取组件上下文,而不是依赖神秘的 thissetup 函数提供了两个参数来让你访问原本需要通过 this 访问的东西:

setup 函数的参数:

props:第一个参数,是响应式的,包含组件接收的所有 prop。你不能使用 ES6 解构它,这会破坏其响应性。如果需要解构,应使用 toRefstoRef

import { toRefs } from 'vue'

  export default {
    props: ['title'],
    setup(props) {
      // 将 `props` 转为一个其中每个 property 都是 ref 的对象
      const { title } = toRefs(props)
      console.log(title.value) // 访问值需要用 .value

      // 或者直接访问
      console.log(props.title)
    }
  }

context:第二个参数,是一个普通的 JavaScript 对象,它暴露了三个在组件中非常常用的 property。它不是响应式的,因此你可以安全地使用 ES6 解构。

export default {
  setup(props, context) {
    // 或者直接解构
    // setup(props, { attrs, slots, emit, expose })

    // Attribute(非响应式的对象,等价于 $attrs)
    console.log(context.attrs)

    // 插槽(非响应式的对象,等价于 $slots)
    console.log(context.slots)

    // 触发事件(函数,等价于 $emit)
    console.log(context.emit)

    // 暴露公共 property(函数,用于被父组件通过模板 ref 访问时暴露特定值)
    console.log(context.expose)
  }
}
如何替代 this 的功能?

通过 propscontext,你可以完全取代在 Options API 中通过 this 访问的所有功能:

Options API (通过 this

)

Composition API (在 setup

中)

this.$props

props

(第一个参数)

this.$attrs

context.attrs

this.$slots

context.slots

this.$emit

context.emit

this.$parent

使用 getCurrentInstance()

(不推荐)

this.$root

使用 getCurrentInstance()

(不推荐)

this.refs

模板 ref:使用 ref()

函数声明

关于 getCurrentInstance()
这是一个高级 API,主要用于库的开发者。它返回当前的组件实例,类似于 this强烈不推荐在应用代码中使用它,因为它破坏了 Composition API 的封装性和可测试性,并且你的代码在服务端渲染 (SSR) 时可能会出错。所有标准用例都应该通过 propscontext 来解决。

(三) 总结

  1. 执行时机setup 在组件实例创建之初、beforeCreate 之前同步执行。此时 this 毫无意义。
  2. 访问 this不要setup 里尝试访问 this,它是 undefined
  3. 正确方法:使用 setup 函数的两个参数 propscontext 来获取组件上下文、属性、插槽以及触发事件。这是 Vue 3 Composition API 设计的标准模式。

二、Vue 3 组件通信的方式有哪些?分别适用于哪些场景?

Vue 3 的组件通信方式非常丰富,可以根据组件关系和使用场景灵活选择。下面我将它们分类并详细说明其适用场景。

(一) 父子组件通信 (Props & Events)

这是最基础、最常用的通信方式,遵循 单向数据流 原则。

  1. 父传子:Props
    • 方式:父组件通过属性(Attributes)将数据传递给子组件,子组件使用 defineProps 来声明和接收。
    • 适用场景
      • 父组件需要向子组件传递初始数据或配置。
      • 子组件需要根据父组件的数据渲染内容(如列表项、表单数据)。
      • 任何需要明确数据来源和流向的场景,遵循单向数据流。
  1. 子传父:自定义事件 (Emits)
    • 方式:子组件通过 defineEmits 定义事件,并通过 emit('event-name', payload) 触发。父组件使用 v-on@ 来监听子组件触发的事件。
    • 适用场景
      • 子组件发生了某个动作(如按钮点击、表单提交),需要通知父组件。
      • 子组件需要让父组件来修改传递下来的 Prop(通过让父组件监听事件并更新自己的数据)。
      • 子组件想要向上传递数据(如输入框的值、选择的结果)。

代码示例:

<!-- 父组件 Parent.vue -->
<template>
  <Child :title="parentTitle" @update-title="parentTitle = $event" />
</template>

<script setup>
  import { ref } from 'vue'
  import Child from './Child.vue'

  const parentTitle = ref('Hello from Parent')
</script>
<!-- 子组件 Child.vue -->
<template>
  <h1>{
  
  { title }}</h1>
  <button @click="updateTitle('New Title!')">Change Title</button>
</template>

<script setup>
  // 接收 Props
  defineProps(['title'])
  // 声明事件
  const emit = defineEmits(['update-title'])

  const updateTitle = (newTitle) => {
    emit('update-title', newTitle) // 触发事件并传递数据
  }
</script>

(二) 跨层级组件通信 (Provide / Inject)

用于祖先组件向后代组件(无论层级多深)传递数据,避免“Prop 逐级透传”的麻烦。

  1. 方式:祖先组件使用 provide(key, value) 提供数据,任何后代组件都可以使用 inject(key) 来注入获取数据。
  2. 适用场景
    • 全局主题/样式配置(如主题色、语言包)。
    • 当前用户信息,在多个深层嵌套组件中都需要使用。
    • 第三方插件,Vue Router、Pinia 等库的内部实现就利用了 Provide/Inject。
    • 复杂的多层表单或组件结构,其中底层组件需要访问顶层组件的数据或方法。

代码示例:

<!-- 祖先组件 Ancestor.vue -->
<script setup>
  import { provide, ref } from 'vue'

  const userLocation = ref('North Pole')
  // 提供静态值或响应式 ref
  provide('location', userLocation)
</script>
<!-- 深层后代组件 Descendant.vue -->
<script setup>
  import { inject } from 'vue'

  // 注入祖先提供的数据,第二个参数是默认值(可选)
  const userLocation = inject('location', 'Earth')
</script>

(三) 兄弟组件通信 (通过共同的父组件 / 状态管理)

两个兄弟组件之间不能直接通信,需要一个共同的“桥梁”。

  1. 通过共同的父组件(Props + Events)
    • 方式:将数据提升到共同的父组件中管理。组件A通过事件通知父组件修改数据,父组件再通过Prop将更新后的数据传递给组件B。
    • 适用场景
      • 简单的应用,兄弟组件通信不频繁。
      • 组件结构不深,提升状态到父组件不会导致逻辑过于复杂。
  1. 使用状态管理库 (Pinia)
    • 方式:将需要共享的状态抽取到 Pinia Store 中。任何组件(无论关系如何)都可以导入并使用或修改这个 Store 中的状态。
    • 适用场景
      • 多个不相关的组件需要共享同一状态。
      • 中大型复杂应用,需要集中式、可预测的状态管理。
      • 需要持久化、序列化或调试(如时间旅行)的状态。
      • 兄弟组件通信频繁,通过父组件传递显得冗杂和难以维护。

(四) 任意组件通信 (状态管理 Pinia)

这是最强大和灵活的方式,彻底解耦了组件和它们所依赖的状态。

  • 方式:定义一个 Pinia Store,在其中管理状态和业务逻辑。任何组件都可以导入并使用这个 Store。
  • 适用场景
    • 用户登录状态、个人信息、权限等全局数据。
    • 购物车,需要在不同页面和组件中同步商品信息。
    • 复杂的、跨多组件和多页面的数据流。
    • 替代 Vuex 的最佳选择。

代码示例:

// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: {
    increment() {
      this.count++
    }
  }
})
<!-- 任意组件 AnyComponent.vue -->
<script setup>
  import { useCounterStore } from '@/stores/counter'

  const counter = useCounterStore()
</script>

<template>
  <button @click="counter.increment()">{
  
  { counter.count }}</button>
</template>

(五) 模板引用 (Template Refs) 和 组件实例方法 (defineExpose)

用于父组件直接访问子组件实例的属性或方法。

  • 方式
    1. 子组件使用 <script setup> 时,默认是关闭的。需要使用 defineExpose 显式暴露属性或方法。
    2. 父组件使用 ref 声明一个同名的模板 ref,并通过 ref.value 来访问。
  • 适用场景
    • 父组件需要直接调用子组件的方法(如 focus(), validate())。
    • 需要直接操作子组件的 DOM 元素(如管理焦点、触发动画)。
    • 应谨慎使用,因为它破坏了组件的封装性。优先考虑使用 Props 和 Events 进行通信。

代码示例:

<!-- 子组件 Child.vue -->
<script setup>
  import { ref } from 'vue'

  const input = ref(null)
  const publicMethod = () => { console.log('Called!') }

  // 只有暴露出去的,父组件才能访问到
  defineExpose({
    publicMethod,
    input
  })
</script>
<!-- 父组件 Parent.vue -->
<template>
  <Child ref="childRef" />
</template>

<script setup>
  import { ref, onMounted } from 'vue'
  import Child from './Child.vue'

  const childRef = ref(null)

  onMounted(() => {
    // 调用子组件暴露的方法
    childRef.value.publicMethod()
    // 访问子组件内部的 DOM 元素
    childRef.value.input.focus()
  })
</script>

(六) 事件总线 (Event Bus) - 已不推荐

  • 方式:创建一个新的 Vue 应用实例作为中央事件总线,使用 $on, $emit, $off<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值