Vue 常考点

目录

实验介绍

Vue 生命周期

v-for 中 key 的作用

Vue 组件通讯

计算属性和监听器

Vue3 新特性

实验总结


实验介绍

Vue 作为最受国内开发者喜爱的前端框架,同时也是面试中的重要考点。特别是 Vue3 版本的发布,一度让 Vue3 成为面试必考点,可见掌握好 Vue 框架是十分必要的。那面试中面试官都爱问哪些 Vue 相关的问题呢?下面我们来共同学习。

知识点

  • Vue 生命周期
  • v-for 中 key 的作用
  • Vue 组件通讯
  • Vue 中计算属性和监听器
  • Vue 新增内容

Vue 生命周期

Vue 的生命周期都有哪些?你通常在哪个生命周期下进行网络请求?这基本上是面试过程中最常遇见的问题。如果面试官没有明确说 Vue 的哪个版本,那就意味着需要分版本作答,如下所示,

Vue2 生命周期:

  • beforeCreate 数据为初始化前执行 created 数据和方法已初始化
  • beforeMount 模板已经在内存中编译完成但是尚未渲染到页面上
  • mounted 页面已渲染
  • beforeUpdate 数据已经被更新,但是页面没被更新
  • updated 页面和 data 都已更新
  • beforeDestroy Vue 实例销毁之前执行
  • destroyed 实例销毁执行

可以看到 Vue2 中的生命周期钩子函数还是比较多的。有的面试官会这样问:请你说说 Vue 在初始化时都触发哪些钩子函数?

这道题的关键点就是问的是在初始化时触发的哪些钩子,在 Vue 初始化的时候触发的钩子只有四个,如下所示:

  • beforeCreate 数据为初始化前执行
  • created 数据和方法已初始化
  • beforeMount 模板已经在内存中编译完成但是尚未渲染到页面上
  • mounted 页面已渲染

其他的钩子都不是在初始化的时候触发的。

Vue3 生命周期:

  • setup 对应 Vue2 中的 beforeCreate 和 created
  • onBeforeMount 对应 Vue2 中的 beforeMount
  • onMounted 对应 Vue2 中的 mounted
  • onBeforeUpdate 对应 Vue2 中的 beforeUpdate
  • onUpdated 对应 Vue2 中的 updated
  • onBeforeUnmount 对应 Vue2 中的 beforeDestroy
  • onUnmounted 对应 Vue2 中的 destroyed

这些生命周期钩子注册函数只能在 setup() 期间同步使用, 因为它们依赖于内部的全局状态来定位当前组件实例(正在调用 setup() 的组件实例)如下所示:

import { onMounted, onUpdated, onUnmounted } from "vue";

const MyComponent = {
  setup() {
    onMounted(() => {
      console.log("mounted!");
    });
    onUpdated(() => {
      console.log("updated!");
    });
    onUnmounted(() => {
      console.log("unmounted!");
    });
  },
};

v-for 中 key 的作用

在实际面试中,面试官会这样问:v-for 中 key 的作用是什么?如果不添加 key 程序会不会报错? 下面我们就带着问题具体学习。

为了搞清楚这个问题,我们需要先了解什么是虚拟 DOM 以及 diff 算法。

  • 虚拟 DOM

虚拟 DOM 简称 VNode 其实是一棵以 JavaScript 对象作为基础的树, 是对真实 DOM 的抽象。虚拟 DOM 经过一系列转换可以变成真实 DOM 并渲染到页面上。

我们可以用虚拟 DOM 来描述一个简单的 vue 组件,如下所示:

<template>
  <span class="demo" v-show="isShow"> This is a span. </span>
</template>

对应的 VNode

{
    tag: 'span',
    data: {
        /* 指令集合数组 */
        directives: [
            {
                /* v-show指令 */
                rawName: 'v-show',
                expression: 'isShow',
                name: 'show',
                value: true
            }
        ],
        /* 静态class */
        staticClass: 'demo'
    },
    text: undefined,
    children: [
        /* 子节点是一个文本VNode节点 */
        {
            tag: undefined,
            data: undefined,
            text: 'This is a span.',
            children: undefined
        }
    ]
}
  • diff 算法

假设现在我们已经有一棵 VNode 树,现在又有一棵 VNode 新树,现在我们需要把最新的这棵树更新上去要怎么操作呢?

最简单粗暴的方式就是直接拿最新的树把之前的树替换掉,然后页面重新渲染即可,这样确实可以解决问题,但是肯定不是最佳解决方案。我们肯定是想可不可以只更新那些变化的地方,那些没有变化的元素不更新,这样不就更高效吗?

要想实现这种高效的更新,就需要我们在两棵树中找出变化的地方从而进行更新。而 diff 算法就是专门来干这件事情的一种算法。

接下来我们再来说说 v-for 中的 key。首先我们先来看下不使用 key 的一段代码:

​
<template>
  <ul>
    <li v-for="v in arr">{{v}}</li>
  </ul>
</template>

<script>
  export default {
    data() {
      arr: ["A", "B", "C"];
    },
    create() {
      setTimeout(() => {
        this.arr.splice(1, 0, "D"); // [A,D,B,C]
      }, 2000);
    },
  };
</script>

​

上面的代码很简单就是把一个数组 [A,B,C] 变成 [A,D,B,C] 同时页面也更新,diff 过程如下所示:

一共做了两次更新一次插入操作。下面我们再来看下有 key 的情况,如下所示:

<template>
  <ul>
    <li v-for="v in arr" :key="v">{{v}}</li>
  </ul>
</template>

<script>
  export default {
    data() {
      arr: ["A", "B", "C"];
    },
    create() {
      setTimeout(() => {
        this.arr.splice(1, 0, "D"); // [A,D,B,C]
      }, 2000);
    },
  };
</script>

在有 key 的情况下,只是执行了一次插入操作。

所以我们可以知道,v-for 中 key 的作用就是让每个被循环元素有一个唯一的身份标识,这样 Vue 就可以更加精准的追踪到每个元素,从而更加高效的更新页面。当然如果没有 key 程序也不会报错,只不过此时的程序变得非常的“笨”。

Vue 组件通讯

这应该是 Vue 中必考的一道题,其重要性不言而喻。下面我们就来具体学习下 Vue 的通讯方式都有哪些。

Vue2 中组件通讯方式

  • 父组件向子组件传递数

通过 props 传递即可

<!-- 父组件 -->
<template>
  <!-- 传递数据 -->
  <child-page :childData="childData"></child-page>
</template>

<script>
  import childPage from "childPage.vue";
  export default {
    data() {
      return {
        childData: [1, 2, 3],
      };
    },
    components: {
      childPage,
    },
  };
</script>
<!-- 子组件 -->

<script>
  export default {
    props: {
      // 接收数据
      childData: {
        type: Array,
        default: () => [],
      },
    },
  };
</script>

  • 父组件调用子组件方法
  1. 设置子组件的 ref 属性
  2. 通过 this.$refs 进行触发
<template>
  <div>
    <!-- 设置ref -->
    <child-page ref="child"></child-page>
    <button @click="childFn">调用子组件事件</button>
  </div>
</template>

<script>
  import childPage from "./childPage.vue";
  export default {
    components: {
      childPage,
    },
    methods: {
      childFn() {
        //调用子组件
        this.$refs.child.sendMsg();
      },
    },
  };
</script>
<!-- 子组件 -->
<template>
  <div></div>
</template>

<script>
  export default {
    methods: {
      sendMsg() {
        console.log("调用子组件方法成功");
      },
    },
  };
</script>
  • 子组件向父组件传递数据

子组件向父组件传递参数只能通过触发父组件方法的形式去传递,如下所示:

<!--子组件  -->
<template>
  <div>
    <button @click="parentFn">向父组件传递数据</button>
  </div>
</template>

<script>
  export default {
    methods: {
      parentFn() {
        this.$emit("parent", "is son");
      },
    },
  };
</script>
<!--父组件 -->
<template>
  <div>
    <child-page @parent="parentchange"></child-page>
  </div>
</template>

<script>
  import childPage from "./childPage.vue";
  export default {
    components: {
      childPage,
    },
    methods: {
      parentchange(val) {
        console.log("这是子组件传递过来的值:", val);
      },
    },
  };
</script>
  • 子组件调用父组件的方法

和子组件向父组件传递数据方式一样。

  • 兄弟及跨组件传参
  1. provide / inject

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。 -vue 官方文档

这组 API 主要解决的是组件的跨级别通讯问题,同时也可以用于兄弟组件的通讯,数据流如下所示:

使用方式如下所示:

// 在父组件中定义
export default {
  name: "App",
  provide: {
    userInfo: {
      name: "蓝桥",
    },
  },
  components: {
    HelloWorld,
  },
};
<!-- 在任意一个子组件(跨级组件)中使用 -->

<template>
  <div>{{ userInfo.name }}</div>
</template>

<script>
  export default {
    inject: ["userInfo"],
  };
</script>

  1. 使用 Provider/inject 只能是父组件派发数据,子组件接受数据。如果要想父子组件都可以派发数据那就需要使用 EventBus(事件总线)。原理就是在 Vue 的 prototype 上在 new 出来一个 vue,通过$emit$son来进行数据通讯,如下所示:
// main.js
import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

//  设置EventBus
Vue.prototype.EventBus = new Vue();

new Vue({
  render: (h) => h(App),
}).$mount("#app");
<!-- 兄弟组件1 -->
<template>
  <div>
    <button @click="siblingsFn">传递参数到兄弟组件</button>
  </div>
</template>

<script>
  export default {
    methods: {
      siblingsFn() {
        this.EventBus.$emit("test", "ChildItem");
      },
    },
  };
</script>
<!-- 兄弟组件2 -->
<template> </template>

<script>
  export default {
    created() {
      // 数据监听
      this.EventBus.$on("test", (val) => {
        console.log(val);
      });
    },
  };
</script>

3. Vuex 

如果项目中有大量的数据需要跨组件进行通讯,那么我们最好使用专门的数据管理的库 Vuex 进行数据的管理,具体的使用方式我们可以在vuex 官网中进行学习。

Vue3 中组件通讯方式

Vue3 的通讯方式和 Vue2 类似,因为 component-api 没有了 this,所以在写法上有所区别。

  • 父组件向子组件传递数据
<!-- 父组件 -->
<template>
  <div>
    <child-page :childData="childData"> </child-page>
  </div>
</template>

<script>
  import { ref, defineComponent } from "vue";
  import childPage from "./childPage";

  export default defineComponent({
    components: {
      childPage,
    },
    setup() {
      const childData = ref([1, 2, 3]);
      return {
        childData,
      };
    },
  });
</script>
<!-- 子组件 -->
<template>
  <div v-for="v in childData" :key="v">{{ v }}</div>
</template>
<script>
  import { defineComponent } from "vue";
  export default defineComponent({
    props: {
      childData: {
        type: Array,
        default: () => [],
      },
    },
    setup(props) {
      console.log(props);
    },
  });
</script>

  • 父组件调用子组件方法

还是通过 ref 来完成,具体代码如下所示:

<template>
  <div>
    <child-page ref="chilData"> </child-page>
    <button @click="childFn">调用子组件里面的方法</button>
  </div>
</template>

<script>
  import { ref, defineComponent } from "vue";
  import childPage from "./childPage";

  export default defineComponent({
    components: {
      childPage,
    },
    setup() {
      // 在使用ref定义变量的时候,把变量名称和上面的ret=“xxx”对应起来就可以获取到子组件的实例
      const chilData = ref(null);
      const childFn = () => {
        chilData.value.myFn();
      };
      return {
        chilData,
        childFn,
      };
    },
  });
</script>
<!-- 子组件 -->
<template><div></div> </template>
<script>
  import { defineComponent } from "vue";
  export default defineComponent({
    setup() {
      const myFn = () => {
        console.log("子组件方法");
      };
      return {
        myFn,
      };
    },
  });
</script>

  • 子组件向父组件传递数据

同样也是通过 emit派发事件的方式进行传递

<!-- 子组件 -->
<template>
  <div>
    <button @click="parentFn">调用父组件方法</button>
  </div>
</template>

<script>
  import { defineComponent } from "vue";

  export default defineComponent({
    setup(props, { emit }) {
      const parentFn = () => {
        // 触发父组件中的方法
        emit("par", "from parent ");
      };
      return {
        parentFn,
      };
    },
  });
</script>

  • 子组件调用父组件的方法

同子组件向父组件传递数据方法一样。

  • 兄弟及跨组件传参
  1. provide/inject

用于跨组件传递,使用上和 Vue2 一致。

  1. 事件总线(mitt)

作用和 Vue2 的 EventBus 一样。只不过现在需要借助于 mitt 插件来完成这件事,使用上如下所示:

安装 mitt

npm install mitt -S | yarn add miss -S

使用 mitt 需要配合 provide/inject 一块使用

<!-- app.vue -->
<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <HelloWorld msg="Welcome to Your Vue.js App" />
</template>
<script>
  import HelloWorld from "./components/HelloWorld.vue";
  import Mitt from "mitt";
  const mitt = Mitt();
  export default {
    name: "App",
    provide: {
      mitt,
    },
    components: {
      HelloWorld,
    },
    setup() {
      mitt.emit("text", "蓝桥");
    },
  };
</script>

在任意一个子组件中

<template>
  <div>childPage</div>
</template>

<script>
  import { inject } from "vue";
  export default {
    setup() {
      const mitt = inject("mitt");
      mitt.on("text", (val) => {
        console.log(val);
      });
    },
  };
</script>
  1. vuex-next

vuex-next 是 Vue3 版本的 vuex 具体的使用方式可以看官方文档

  1. Pinia

Pinia 也是 Vue3 的状态管理框架,和 vuex-next 作用类似,但在使用上比 vuex-next 更加简洁,且 Pinia 对 TypeScript 项目更加友好,我们可以通过 Pinia 官网进行学习。

计算属性和监听器

也就是我们常用的 computed  watch,主要有以下区别:

computed

  1. 支持缓存,只有依赖数据发生改变,才会重新进行计算
  2. 不支持异步,当 computed 内有异步操作时无效,无法监听数据的变化
  3. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用 computed

watch

  1. 不支持缓存,数据变,直接会触发相应的操作
  2. watch 支持异步
  3. 监听数据必须是 data 中声明过或者父组件传递过来的 props 中的数据,当数据变化时,触发其他操作

在 Vue2 和 Vue3 中的使用方式有所不同,如下所示:

Vue2 中使用 computed watch

<template>
  <div>
    <input type="text" v-model="name" />
    <div>{{ myName }}</div>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        name: "",
      };
    },
    computed: {
      myName() {
        return `我的名字是${this.name}`;
      },
    },
    watch: {
      name(val) {
        if (val.length > 4) alert("您输入的名字过长");
      },
    },
  };
</script>

Vue3 中使用 computed 和 watch

<template>
  <div>
    <input type="text" v-model="name" />
    <div>{{ myName }}</div>
  </div>
</template>

<script>
  import { ref, watch } from "vue";
  export default {
    setup() {
      const name = ref("");
      const myName = computed(() => `我的名字是${name.value}`);
      watch(
        () => name,
        () => {
          if (val.length > 3) alert("您输入的名字过长");
        }
      );
      return {
        name,
        myName,
      };
    },
  };
</script>

Vue3 新特性

Vue3 势必会成为 Vue 开发的主流版本,在实际开发过程中也有很多项目都在向 Vue3 进行过渡,所以尽早的去了解及使用 Vue3 在面试中就越有优势。接下来我们就来具体学习下 Vue3 中都有哪些值得学习的新特性。

  • component-api

component-api 主要是为了解决组件逻辑之间的复用问题,也是 Vue3 最重要的新特性。 在 Vue2 中处理逻辑之间复用关系最常用的就是混入(Mixin),但是使用 Mixin 有以下弊端:

  1. 命名空间冲突;
  2. 如果一个组件使用多个 mixin 的模板时,很难看出某个数据是从哪一个 mixin 中注入的。

而使用 component-api 语法去组织代码就会让逻辑的复用变得简单高效,比如现在封装一段登陆逻辑,使用 component-api 我们可以这样组织。

// useLogin.js
import { ref } from "vue";
import axios from "axios";
const isLogin = ref(false);
const token = ref("");
async function UseisLoading() {
  const res = axios.get("/api/login");
  const { code, token } = res.data;
  if (code === 200) {
    isLogin.value = true;
    token.value = tokens;
  }

  return {
    isLogin,
    token,
  };
}
export default UseisLoading;

在业务中这样使用

import useLogin from "./useLogin.vue";

const { isLogin, token } = useLogin();

if (isLogin) {
  console.log(`登陆成功,登陆token为${token}`);
}

这样我们就封装了一个简单的业务逻辑。

  • 生命周期的变化

在 Vue2 中生命周期如下所示:

Vue3 中对这些生命周期钩子函数做了如下修改,细节如下:

值得注意的是 beforeCreate 和 created 这两个钩子函数在 Vue3 中被合并成了 setup

  • Teleport

Teleport 是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置。比如我们现在嵌套了很多层组件,这个时候需要最里面的组件弹出一个全局的弹框,这个时候可能就会出现弹窗被遮挡的问题,而 Teleport 可以让我们的弹框挂载到任意的 DOM 元素上,这样就从根本上解决了这个问题。使用方式如下所示:

<teleport to="#targetId">你的弹窗组件</teleport>
  • 根结点

在 2.x 中,不支持多根组件,当用户意外创建多根组件时会发出警告,因此,为了修复此错误,许多组件被包装在一个 <div> 中,如下所示:

<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

在 3.x 中,组件可以有多个根节点,如下所示:

<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>
  • <script setup> 及 <style> v-bind

在 Vue3.2 版本中 setup 可以直接写在 script 标签上面,而且在 style 中可以用 v-bind 方法去使用 script 中的变量, 如下所示:

<script setup>
import { ref } from "vue";
const color = ref("red");
</script>
<template>
  <button @click="color = color === 'red' ? 'green' : 'red'">
    Color is: {{ color }}
  </button>
</template>

<style scoped>
button {
  color: v-bind(color);
}
</style>

这样以来很大程度上减少了我们组件编写的代码量。

以上是 Vue3 新增部分,更多细节上的改变就不再这里赘述了,可以看官方文档进行更加细致的学习

实验总结

本节实验我们学习了 vue 的常考点,当然 vue 作为一个框架而言里面的知识点也非常多,并不是说在本节实验中没有提到的就不考,在平时的工作和学习中我们要注意知识的积累,逐渐去完善自己关于 vue 方面的知识体系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王水最甜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值