面试官 : ” 说一下 Vue 中的 setup 中的 props 和 context “

一、props:和 Vue2 核心逻辑完全一致,仅访问方式微调

props 作为 Vue 「父传子」的核心通信方式

Vue3 中 单向数据流、类型校验、默认值 / 必传项 等核心规则和 Vue2 完全一样,唯一区别是「访问方式」:

1. 共性(Vue2/Vue3 通用)

  • 单向数据流:子组件不能直接修改 props,必须通过 emit 通知父组件修改;
  • 支持类型校验( String / Number / Array / Object 等)、默认值、自定义校验规则;
  • 父组件传的属性如果没被 props 声明,会落到 attrs 中(下文会提)。

2. 用法对比(Vue2 选项式 vs Vue3 组合式)

xml 复制代码
<!-- Vue2 选项式 API -->
<script>
export default {
  props: {
    name: { type: String, default: '默认名' },
    age: { type: Number, required: true }
  },
  mounted() {
    console.log(this.name); // 👉 通过 this 访问
  }
}
</script>

<!-- Vue3 组合式 API(setup) -->
<script>
export default {
  // 👉 props 定义规则和 Vue2 完全一样
  props: {
    name: { type: String, default: '默认名' },
    age: { type: Number, required: true }
  },
  // 👉 props 作为 setup 第一个参数传入,无需 this
  setup(props) {
    console.log(props.name); // 直接访问
    // 注意:props 是响应式的,解构会丢失响应式,需用 toRefs
    const { name } = Vue.toRefs(props);
    console.log(name.value); // ref 需 .value 访问
  }
}
</script>

二、context:Vue3 把 Vue2 的「this 上的通信属性」聚合到上下文

contextsetup 的第二个参数(非响应式,可直接解构),核心作用是替代 Vue2 中 this 上的「非 props 相关通信能力」,你提到的几个属性对应关系精准,补充用法细节:

context 属性 Vue2 对应写法 核心用法示例
context.emit this.$emit 子传父触发事件:context.emit('change', { id: 1 })(父组件用 @change 接收)
context.slots this.$slots 访问父组件传入的插槽:context.slots.header()(Vue3 插槽是函数,需加 () 调用)
context.attrs this.$attrs 接收父组件未被 props 声明的属性:父传 class="box" 且 props 未声明 → context.attrs.class
context.expose() 无(Vue2 无此能力) 主动暴露子组件内部属性给父组件:context.expose({ fn: () => console.log('暴露的方法') })

核心示例(context 解构使用,更简洁)

xml 复制代码
<script>
export default {
  setup(props, { emit, slots, attrs, expose }) {
    // 1. 子传父:触发自定义事件
    const handleClick = () => emit('submit', '子组件数据');

    // 2. 访问具名插槽
    console.log(slots.footer()); // 获取父组件传入的 footer 插槽内容

    // 3. 访问未声明的属性
    console.log(attrs['data-id']); // 父传 data-id="123" 且未被 props 声明

    // 4. 暴露内部方法给父组件(父通过 ref 仅能访问暴露的内容)
    const internalFn = () => '内部逻辑';
    expose({ internalFn }); // 父组件 ref.value.internalFn() 可调用

    return { handleClick };
  }
}
</script>

1. 子组件(Child.vue):核心逻辑详解

xml 复制代码
<template>
  <!-- 点击按钮触发子传父事件 -->
  <button @click="handleClick">点击触发submit事件</button>

  <!-- 渲染父组件传入的footer具名插槽 -->
  <div class="slot-container">
    <slot name="footer"></slot>
  </div>

  <!-- 把attrs中的data-id透传给内部div(演示attrs用法) -->
  <div :data-id="attrs['data-id']">透传父组件未声明的data-id属性</div>
</template>

<script>
// 导入vue的核心方法(按需导入,Vue3组合式API规范)
import { toRefs } from 'vue';

export default {
  // 第一步:声明props(仅声明name,未声明data-id,所以data-id会落到attrs中)
  props: {
    name: {
      type: String,
      default: '默认名称'
    }
  },

  // setup第二个参数解构出:emit(子传父)、slots(插槽)、attrs(透传属性)、expose(暴露内容)
  setup(props, { emit, slots, attrs, expose }) {
    // 👉 1. 子传父:触发自定义事件(核心用法)
    const handleClick = () => {
      // 第一个参数:事件名(父组件用@submit接收);第二个参数:传递给父组件的数据
      emit('submit', {
        msg: '子组件传递的数据',
        name: props.name // 结合props使用,把props数据也传给父组件
      });
    };

    // 👉 2. 访问具名插槽(控制台打印插槽内容,验证是否传入)
    console.log('===== 访问footer插槽 =====');
    // Vue3中slots的每个插槽都是函数,调用后返回VNode数组(插槽的DOM结构)
    if (slots.footer) { // 先判断父组件是否传入了footer插槽,避免报错
      const footerSlotContent = slots.footer();
      console.log('footer插槽的VNode内容:', footerSlotContent);
    } else {
      console.log('父组件未传入footer插槽');
    }

    // 👉 3. 访问父组件未被props声明的属性(attrs)
    console.log('===== 访问attrs =====');
    console.log('父组件传入的data-id:', attrs['data-id']); // 父传的data-id未被props声明,所以在attrs中
    console.log('父组件传入的class(若有):', attrs.class); // class/style会自动透传,也会在attrs中
    // 注意:attrs是非响应式的,若需要响应式,可结合toRefs(但一般attrs无需响应式)

    // 👉 4. 暴露子组件内部方法/属性给父组件(父组件通过ref访问)
    // 定义子组件内部方法(未return也能通过expose暴露)
    const internalFn = () => {
      return `内部方法执行成功!props.name的值是:${props.name}`;
    };
    // 定义内部属性(仅暴露给父组件,模板中无法直接使用,除非return)
    const internalData = '子组件内部私有数据';

    // 主动暴露指定内容(只有这里声明的,父组件才能通过ref访问)
    expose({
      internalFn, // 暴露内部方法
      internalData, // 暴露内部属性
      // 也可以暴露props(方便父组件直接获取props值)
      getPropsName: () => props.name
    });

    // 👉 5. 补充:props的响应式使用(可选)
    // 解构props并保留响应式(若需要单独使用props中的属性)
    const { name } = toRefs(props);
    console.log('===== props使用 =====');
    console.log('props.name的值(响应式):', name.value);

    // 把需要在模板中使用的方法return出去(handleClick在模板中绑定点击事件,必须return)
    return {
      handleClick,
      attrs // 把attrsreturn出去,方便模板中使用(如上面模板中的:data-id="attrs['data-id']")
    };
  }
};
</script>

2. 父组件(Parent.vue):调用子组件并配合使用

xml 复制代码
<template>
  <div class="parent-container">
    <h3>父组件</h3>
    <!-- 第二步:使用子组件,完成以下操作:
         1. 传props:name="测试名称"
         2. 传未声明的属性:data-id="10086"(会落到子组件attrs中)
         3. 绑定子组件的自定义事件:@submit="handleChildSubmit"
         4. 传入具名插槽:<template #footer>...</template>
         5. 给子组件加ref:childRef(用于访问子组件暴露的内容)
    -->
    <Child
      ref="childRef"
      name="测试名称"
      data-id="10086"
      class="child-component"
      @submit="handleChildSubmit"
    >
      <!-- 传入footer具名插槽(子组件会访问这个插槽) -->
      <template #footer>
        <p>这是父组件传给子组件的footer插槽内容</p>
      </template>
    </Child>

    <!-- 显示子组件传递的数据 -->
    <div class="child-data" v-if="childSubmitData">
      <h4>子组件传递的数据:</h4>
      <p>msg:{{ childSubmitData.msg }}</p>
      <p>name:{{ childSubmitData.name }}</p>
    </div>

    <!-- 点击按钮访问子组件暴露的方法/属性 -->
    <button @click="accessChildExpose">访问子组件暴露的内容</button>
  </div>
</template>

<script>
// 导入子组件
import Child from './Child.vue';
// 导入vue的ref(用于创建子组件的引用)和onMounted(生命周期)
import { ref, onMounted } from 'vue';

export default {
  // 注册子组件
  components: {
    Child
  },

  setup() {
    // 👉 1. 创建子组件的ref引用(用于访问子组件暴露的内容)
    const childRef = ref(null);

    // 👉 2. 接收子组件的自定义事件数据
    const childSubmitData = ref(null);
    const handleChildSubmit = (data) => {
      console.log('父组件接收到子组件的submit事件数据:', data);
      childSubmitData.value = data; // 把数据存到响应式变量中,模板中显示
    };

    // 👉 3. 访问子组件通过expose暴露的内容
    const accessChildExpose = () => {
      // 确保子组件已挂载(避免初始时childRef.value为null)
      if (childRef.value) {
        // 调用子组件暴露的internalFn方法
        const fnResult = childRef.value.internalFn();
        console.log('调用子组件暴露的internalFn结果:', fnResult);

        // 获取子组件暴露的internalData属性
        const internalData = childRef.value.internalData;
        console.log('获取子组件暴露的internalData:', internalData);

        // 调用子组件暴露的getPropsName方法(获取子组件的props.name)
        const propsName = childRef.value.getPropsName();
        console.log('子组件的props.name:', propsName);

        // 注意:子组件未暴露的内容,父组件无法访问(比如子组件的handleClick)
        console.log('访问子组件未暴露的handleClick:', childRef.value.handleClick); // undefined
      }
    };

    // 👉 4. 生命周期:组件挂载后,也可以主动访问子组件暴露的内容
    onMounted(() => {
      console.log('===== 组件挂载后访问子组件暴露内容 =====');
      if (childRef.value) {
        console.log('挂载后获取internalData:', childRef.value.internalData);
      }
    });

    // return需要在模板中使用的变量/方法
    return {
      childRef,
      childSubmitData,
      handleChildSubmit,
      accessChildExpose
    };
  }
};
</script>

三、关键总结

  1. props规则完全继承 Vue2 ,仅在 Vue3 setup 中需通过第一个参数访问,注意响应式解构(用 toRefs);
  2. context替代 Vue2 中 this 上的通信属性 ,把 $emit/$slots/$attrs 聚合到 上下文( context ) ,新增 expose() 增强组件封装性(Vue2 父组件通过 ref 能访问子组件所有内容,Vue3 需主动暴露才可见);
  3. 简化记忆:setup(props, context) → 第一个参数管「父传子的 props」,第二个参数管「子传父、插槽、透传属性、暴露内容」。

这种设计既保留了 Vue2 的使用习惯,又让组合式 API 脱离了 this 的束缚,逻辑更聚合,是 Vue3 兼顾「易用性」和「灵活性」的核心设计。

相关推荐
KLW752 小时前
vue中 v-cloak指令
前端·javascript·vue.js
0思必得02 小时前
[Web自动化] Requests模块请求参数
运维·前端·python·自动化·html
幽络源小助理2 小时前
SpringBoot+Vue智能学习平台系统源码 | 教育类JavaWeb项目免费下载 – 幽络源
vue.js·spring boot·学习
进击的雷神3 小时前
软件面试相关问题整理
面试·职场和发展
xiaoxue..3 小时前
React Hooks :useRef、useState 与受控/非受控组件全解析
前端·react.js·前端框架
会算数的⑨3 小时前
Java场景化面经分享(一)—— JVM有关
java·开发语言·jvm·后端·面试
释怀不想释怀3 小时前
vue(登录,退出,浏览器本地存储机制)
前端·javascript·vue.js·ajax·html
wh_xia_jun3 小时前
vue 3极简教程草稿(未完成)
前端·javascript·vue.js
3824278273 小时前
Edge开发者工具:保留日志与禁用缓存详解
java·前端·javascript·python·selenium