web前端面试高频考点——Vue组件间的通信及高级特性(多种组件间的通信、自定义v-model、nextTick、插槽)

简介: web前端面试高频考点——Vue组件间的通信及高级特性(多种组件间的通信、自定义v-model、nextTick、插槽)

一、Vue组件间通信

1、props 和 $emit

  • props 常用于 父给子 传递数据
  • this.$emit 常用于 子给父 传递数据
  • event.$emit 常用于 兄弟组件间 传递数据

示例:两个子组件(输入框组件&列表组件)动态添加和删除

父组件(index.vue)

  • 父组件在子组件标签上绑定 @add 和 @delete 自定义事件
<template>
  <div>
    <Input @add="addHandler" />
    <List :list="list" @delete="deleteHandler" />
  </div>
</template>
<script>
import Input from "./Input";
import List from "./List";
export default {
  components: {
    Input,
    List,
  },
  data() {
    return {
      list: [
        {
          id: "id-1",
          title: "标题1",
        },
        {
          id: "id-2",
          title: "标题2",
        },
      ],
    };
  },
  methods: {
    // 添加项目
    addHandler(title) {
      this.list.push({
        id: `id-${Date.now()}`,
        title,
      });
    },
    // 删除项目
    deleteHandler(id) {
      this.list = this.list.filter((item) => item.id !== id);
    },
  },
  // 创建
  created() {
    console.log("index created");
  },
  // 挂载
  mounted() {
    console.log("index mounted");
  },
  // 更新前
  beforeUpdate() {
    console.log("index before update");
  },
  // 更新
  updated() {
    console.log("index updated");
  },
};
</script>

子组件(input.vue)

  • 按钮绑定 addTitle 事件
  • 使用 this.$emit('add', this.title) 调用父组件的事件
  • 使用 event.$emit("onAddTitle", this.title) 调用自定义事件(兄弟组件定义的事件)
<template>
  <div>
    <input type="text" v-model="title" />
    <button @click="addTitle">add</button>
  </div>
</template>
<script>
import event from "./event";
export default {
  data() {
    return {
      title: "",
    };
  },
  methods: {
    addTitle() {
      // 调用父组件的事件
      if (this.title.trim() !== "") {
        this.$emit("add", this.title);
        // 调用自定义事件
        event.$emit("onAddTitle", this.title);
        this.title = "";
      } else {
        alert('输入内容不能为空')
      }
    },
  },
};
</script>

子组件(List.vue)

  • 使用 props,接收父组件传来的的 list,并做了类型限制和默认值
  • 按钮绑定 deleteItem 函数,里面使用 this.$emit("delete", id) 调用父组件的事件,根据 id,进行删除
  • event.$on("onAddTitle", this.addTitleHandler) 挂载(mounted)的时候绑定自定义事件
  • event.$off("onAddTitle", this.addTitleHandler) 销毁前(beforeDestroy)的时候销毁自定义事件
  • 注意:绑定和销毁自定义事件时,第二个参数是传入的一个普通函数,不要写箭头函数(this 指向会发生改变)
<template>
  <div>
    <ul>
      <li v-for="item in list" :key="item.id">
        {{ item.title }}
        <button @click="deleteItem(item.id)">删除</button>
      </li>
    </ul>
  </div>
</template>
<script>
import event from "./event";
export default {
  // props: ['list']
  props: {
    // prop 类型和默认值
    list: {
      type: Array,
      default() {
        return []
      }
    },
  },
  methods: {
    deleteItem(id) {
      this.$emit("delete", id);
    },
    addTitleHandler(title) {
      console.log("on add title", title);
    },
  },
  created() {
    console.log("list created");
  },
  mounted() {
    console.log("list mounted");
    // 绑定自定义事件
    event.$on("onAddTitle", this.addTitleHandler);
  },
  beforeUpdate() {
    console.log("list before update");
  },
  updated() {
    console.log("list updated");
  },
  beforeDestroy() {
    // 及时销毁,否则可能造成内存泄露
    event.$off("onAddTitle", this.addTitleHandler);
  },
};
</script>

event.js 文件

  • 创建一个 vue 实例,可以使用 $emit,$off,$on 等方法
import Vue from 'vue'
export default new Vue()

分割线 ------------------------------------------------------------------------------

d3e8e5201f294fa79538bfe037955878.png分割线 ------------------------------------------------------------------------------

d2791cb7092149bab41d1cfadb803997.png

分割线 ------------------------------------------------------------------------------


9e4483754c4044f4a52e09d5a27c0f80.png

2、生命周期

生命周期详解【参考链接】

  • beforeCreate:vue 实例被创建出来,el 和 data 都还没有初始化,不能访问 data 和 method,一般在这个阶段不进行操作。一般在这个阶段不进行操作

created:vue 实例中的 data、method 已被初始化,属性也被绑定。但是此时还是虚拟dom,真实 dom 还没生成,$el 还不可用。一般在此对数据进行初始化

beforeMount:模板已经编译完成,但还没有被渲染至页面中(即为虚拟dom加载为真实dom)

mounted:模板已经被渲染成真实 DOM,用户已经可以看到渲染完成的页面。执行完 mounted 就表示,实例已经被完全创建好了

beforeUpdate:重新渲染之前触发,然后 vue 的虚拟 dom 机制会重新构建虚拟 dom 与上一次的虚拟 dom 树利用 diff 算法进行对比之后重新渲染。只有 view 上面的数据变化才会触发 beforeUpdate 和 updated,仅属于 data 中的数据改变是并不能触发。

updated:数据已经更改完成,dom 也重新 render 完成。

beforeDestroy:销毁前执行($destroy方法被调用的时候就会执行),一般在这里:清除计时器、清除自定义绑定的事件等等…

destroyed:销毁后 (Dom 元素存在,只是不再受 vue 控制),卸载watcher,事件监听,子组件。

参考 props 和 $emit 的示例的结果,总结父子组件生命周期如下:

01.父组件 before create
02.父组件 created
03.父组件 before mount
04.子组件 before create
05.子组件 created
06.子组件 before mount
07.子组件 mounted
08.父组件 mounted
09.父组件 before update
10.子组件 before update
11.子组件 updated
12.父组件 updated
13.父组件 before destroy
14.子组件 before destroy
15.子组件 destroyed
16.父组件 destroyed

二、Vue的高级特性

自定义 v-model,$nextTick,slot,动态、异步组件,keep-alive,mixin

1、自定义 v-model

示例:自定义实现 v-model

CustomVModel.vue 子组件

  • input 使用了 :value 而不是 v-model
  • change1 属性对应起来
  • text1 属性对应起来
  • prop 也就是调用该组件的父组件中使用 v-model 指令绑定的属性
  • event 对应的是修改 prop 指定属性的值的函数
<template>
  <input
    type="text"
    :text="text1"
    @input="$emit('change1', $event.target.value)"
  />
</template>
<script>
export default {
  name: "CustomVModel",
  model: {
    prop: "text1", // 对应 props text1
    event: "change1",
  },
  props: {
    type: String,
    default() {
      return "";
    },
  },
};
</script>

index.vue 父组件

  • 子组件标签上使用 v-model 双向数据绑定 name
<template>
  <div>
    <p>vue 高级特性</p>
    <hr />
    <p>{{ name }}</p>
    <CustomVModel v-model='name'/>
  </div>
</template>
<script>
import CustomVModel from "./CustomVModel.vue";
export default {
  name: "index",
  components: { CustomVModel },
  data() {
    return {
        name: '杂货铺'
    }
  }
};
</script>

173ca8d4b8354b6fa6d413a7b07eb9bc.png

2、nextTick

  • Vue 是异步渲染
  • data 改变之后,DOM 不会立刻渲染
  • $nextTick 会在 DOM 渲染之后被触发,以获取最新的 DOM 节点

示例:添加子节点,并获取子节点的总长度

NextTick.vue 组件

  • ref 用于打标识
  • refs 用于获取 DOM 元素
  • this.$nextTick(() => {...}) 为异步渲染,待 DOM 渲染完再回调
  • 如果不加 $nextTick 则输出的结果是 3
<template>
  <div>
    <ul ref="ul1">
      <li v-for="(item, index) in list" :key="index">
        {{ item }}
      </li>
    </ul>
    <button @click="addItem">添加一项</button>
  </div>
</template>
<script>
export default {
  name: "NextTick",
  data() {
    return {
      list: ["a", "b", "c"],
    };
  },
  methods: {
    addItem() {
      this.list.push(`${Date.now()}`);
      this.list.push(`${Date.now()}`);
      this.list.push(`${Date.now()}`);
      // 异步渲染,$nextTick 待 DOM 渲染完再回调
      // 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
      this.$nextTick(() => {
        // 获取 DOM 元素
        const ulElem = this.$refs.ul1;
        console.log(ulElem.childNodes.length); // 6
      });
    },
  },
};
</script>

93fe30ec4b7b4a29a799b86559b2d9dd.png

3、slot 插槽

(1)默认插槽

  • 让父组件可以向子组件指定位置插入 html 结构
  • <solt> 标签体内为默认内容,即父组件没设置内容时,这里显示

示例:默认插槽的基本使用

index.vue 父组件

  • 父组件的子组件标签内的 {{ website.title }} 是子组件插槽中要呈现的内容
<template>
  <div>
    <p>vue 高级特性</p>
    <hr />
    <SlotDemo :url="website.url">
      {{ website.title }}
    </SlotDemo>
  </div>
</template>
<script>
import SlotDemo from "./SlotDemo.vue";
export default {
  components: { SlotDemo },
  data() {
    return {
      website: {
        url: "http://baidu.com/",
        title: "Baidu",
        subTitle: "百度",
      },
    };
  },
};
</script>

SlotDemo.vue 子组件

  • 当父组件的子组件标签内有内容时,则呈现相应内容
  • 当父组件的子组件标签内没有内容时,则呈现插槽的默认内容
<template>
    <a :href="url">
        <slot>
            默认内容,即父组件没设置内容时,这里显示
        </slot>
    </a>
</template>
<script>
export default {
    props: ['url'],
};
</script>

dd0d623af81b4323b4dd67283ce905a2.png

分割线 ------------------------------------------------------------------------------


ef4b1a3224a44438a413fe3297307ccd.png

(2)作用域插槽

  • 场景:插槽的内容可能想要同时使用父组件域内和子组件域内的数据

示例:显示子组件的 title,使用父组件的链接

index.vue 父组件

  • 在父组件的子组件标签内定义 <template>
  • <template> 标签内使用 v-slot 绑定自定义属性 slotProps
  • 之后通过 slotProps.soltData.title 获取子组件的 title 值
<template>
  <div>
    <p>vue 高级特性</p>
    <hr />
    <ScopedSlot:url="website.url">
      <template v-slot="slotProps">
        {{ slotProps.slotData.title }}
      </template>
    </ScopedSlot>
  </div>
</template>
<script>
import ScopedSlot from "./ScopedSlot.vue";
export default {
  components: { ScopedSlot},
  data() {
    return {
      website: {
        url: "http://baidu.com/",
        title: "Baidu",
        subTitle: "百度",
      },
    };
  },
};
</script>

ScopedSlot.vue 子组件

  • <slot> 标签内自定义动态属性 slotData,绑定 data 中的 website
  • 在标签内通过插值语法显示子组件的 title
<template>
  <a :href="url">
    <slot :slotData="website">
      {{ website.subTitle }}
    </slot>
  </a>
</template>
<script>
export default {
  props: ["url"],
  data() {
    return {
      website: {
        url: "http://bilibili.com/",
        title: "Bilibili",
        subTitle: "哔哩哔哩",
      },
    };
  },
};
</script>

1bbc07aae35142d6ba3bf372994a24c7.png

(3)具名插槽

  • 在子组件的 <slot> 中使用 name='xxx' 给插槽命名
  • 在父组件的 <template> 中使用 v-slot:xxx 或者 #xxx 对应子组件的插槽

示例:具名插槽的使用

index.vue 父组件

  • 在父组件的 <template> 中使用 v-slot:xxx 或者 #xxx 对应子组件的插槽
<template>
  <div>
    <p>vue 高级特性</p>
    <hr />
    <NamedSlot>
      <template v-slot:header>
        <h4>将插入到 header slot 中</h4>
      </template>
      <p>将插入到 main slot 中,即未命名的 slot</p>
      <template #footer>
        <p>将插入到 footer slot 中</p>
      </template>
    </NamedSlot>
  </div>
</template>
<script>
import NamedSlot from "./NamedSlot.vue";
export default {
  components: { NamedSlot }
};
</script>

NamedSlot.vue 子组件

  • 在子组件的 <slot> 中使用 name='xxx' 给插槽命名
<template>
  <div>
    <header>
      <slot name="header">默认头部</slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer">默认尾部</slot>
    </footer>
  </div>
</template>
<script>
export default {};
</script>

35d34695a9e3429da8d4373f1ac8e6a0.png

不积跬步无以至千里 不积小流无以成江海

点个关注不迷路,持续更新中…


目录
打赏
0
0
0
0
10
分享
相关文章
|
1月前
|
【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
40 1
【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
探索现代Web应用的微前端架构
【10月更文挑战第40天】在数字时代的浪潮中,Web应用的发展日益复杂多变。微前端架构作为一种新兴的设计理念,正逐步改变着传统的单一前端开发模式。本文将深入探讨微前端的核心概念、实现原理及其在实际项目中的应用,同时通过一个简单的代码示例,揭示如何将一个庞大的前端工程拆分成小而美的模块,进而提升项目的可维护性、可扩展性和开发效率。
HTML与CSS在Web组件化中的核心作用及前端技术趋势
本文探讨了HTML与CSS在Web组件化中的核心作用及前端技术趋势。从结构定义、语义化到样式封装与布局控制,两者不仅提升了代码复用率和可维护性,还通过响应式设计、动态样式等技术增强了用户体验。面对兼容性、代码复杂度等挑战,文章提出了相应的解决策略,强调了持续创新的重要性,旨在构建高效、灵活的Web应用。
73 6
探索微前端架构:构建现代Web应用的新策略
本文探讨了微前端架构的概念、优势及实施策略,旨在解决传统单体应用难以快速迭代和团队协作的问题。微前端允许不同团队独立开发、部署应用的各部分,提升灵活性与可维护性。文中还讨论了技术栈灵活性、独立部署、团队自治等优势,并提出了定义清晰接口、使用Web组件、状态管理和样式隔离等实施策略。
Python 高级编程与实战:深入理解 Web 开发与 API 设计
在前几篇文章中,我们探讨了 Python 的基础语法、面向对象编程、函数式编程、元编程、性能优化、调试技巧以及数据科学和机器学习。本文将深入探讨 Python 在 Web 开发和 API 设计中的应用,并通过实战项目帮助你掌握这些技术。
|
5月前
|
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
303 3
如何在项目中高效地进行 Web 组件化开发
高效地进行 Web 组件化开发需要从多个方面入手,通过明确目标、合理规划、规范开发、加强测试等一系列措施,实现组件的高效管理和利用,从而提高项目的整体开发效率和质量,为用户提供更好的体验。
126 63
Django框架适合开发哪种类型的Web应用程序?
Django 框架凭借其强大的功能、稳定性和可扩展性,几乎可以适应各种类型的 Web 应用程序开发需求。无论是简单的网站还是复杂的企业级系统,Django 都能提供可靠的支持,帮助开发者快速构建高质量的应用。同时,其活跃的社区和丰富的资源也为开发者在项目实施过程中提供了有力的保障。
183 62
2025年,Web3开发学习路线全指南
本文提供了一条针对Dapp应用开发的学习路线,涵盖了Web3领域的重要技术栈,如区块链基础、以太坊技术、Solidity编程、智能合约开发及安全、web3.js和ethers.js库的使用、Truffle框架等。文章首先分析了国内区块链企业的技术需求,随后详细介绍了每个技术点的学习资源和方法,旨在帮助初学者系统地掌握Dapp开发所需的知识和技能。
2025年,Web3开发学习路线全指南
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
319 45

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等