2022-09-16Vue00

vue中组件之间的通信方式?

题目分析:vue是组件化开发框架,所以对于vue应用来说组件间的数据通信非常重要。此题主要考查大家vue基本功,对于vue基础api运用熟练度。另外一些边界知识如provide/inject/attrs/attrs/listeners则体现了面试者的知识面。

思路分析:总分

  1. 总述知道的所有方式
  2. 按组件关系阐述使用场景

回答范例:

  1. 组件通信方式大体有以下8种:
  • props
  • emit/emit/on
  • children/children/parent
  • attrs/attrs/listeners
  • ref
  • $root
  • eventbus
  • vuex
  1. 根据组件之间关系讨论组件通信最为清晰有效
  • 父子组件

    • props
    • $emit/$on
    • $parent / $children
    • ref
    • $attrs / $listeners
  • 兄弟组件

    • $parent

    • eventbus

    • vuex

  • 跨层级关系

    • provide/inject
    • $root
    • eventbus
    • vuex
2022-09-16Vue00

简单说一说你对vuex理解?

分析

此题考查实践能力,能说出用法只能60分。更重要的是对vuex设计理念和实现原理的解读。

回答策略:3w1h

  1. 首先给vuex下一个定义
  2. vuex解决了哪些问题,解读理念
  3. 什么时候我们需要vuex
  4. 你的具体用法
  5. 简述原理,提升层级

首先是官网定义:

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

回答范例:

  1. vuex是vue专用的状态管理库。它以全局方式集中管理应用的状态,并且可以保证状态变更的可预测性。
  2. vuex主要解决的问题是多组件之间状态共享的问题,利用各种组件通信方式,我们虽然能够做到状态共享,但是往往需要在多个组件之间保持状态的一致性,这种模式很容易出现问题,也会使程序逻辑变得复杂。vuex通过把组件的共享状态抽取出来,以全局单例模式管理,这样任何组件都能用一致的方式获取和修改状态,响应式的数据也能够保证简洁的单向数据流动,我们的代码将变得更结构化且易维护。
  3. vuex并非必须的,它帮我们管理共享状态,但却带来更多的概念和框架。如果我们不打算开发大型单页应用或者我们的应用并没有大量全局的状态需要维护,完全没有使用vuex的必要。一个简单的store 模式就足够了。反之,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:Flux 架构就像眼镜:您自会知道什么时候需要它。
  4. 我在使用vuex过程中有如下理解:首先是对核心概念的理解和运用,将全局状态放入state对象中,它本身一棵状态树,组件中使用store实例的state访问这些状态;然后有配套的mutation方法修改这些状态,并且只能用mutation修改状态,在组件中调用commit方法提交mutation;如果应用中有异步操作或者复杂逻辑组合,我们需要编写action,执行结束如果有状态修改仍然需要提交mutation,组件中调用这些action使用dispatch方法派发。最后是模块化,通过modules选项组织拆分出去的各个子模块,在访问状态时注意添加子模块的名称,如果子模块有设置namespace,那么在提交mutation和派发action时还需要额外的命名空间前缀。
  5. vuex在实现单项数据流时需要做到数据的响应式,通过源码的学习发现是借用了vue的数据响应化特性实现的,它会利用Vue将state作为data对其进行响应化处理,从而使得这些状态发生变化时,能够导致组件重新渲染。
2022-09-16Vue00

vue-router中如何保护路由?

此题是考查项目实践能力,项目中基本都有路由守卫的需求,保护指定路由考查的就是这个知识点。

答题思路:

  1. 阐述vue-router中路由保护策略
  2. 描述具体实现方式
  3. 简单说一下它们是怎么生效的

回答范例:

  1. vue-router中保护路由安全通常使用导航守卫来做,通过设置路由导航钩子函数的方式添加守卫函数,在里面判断用户的登录状态和权限,从而达到保护指定路由的目的。
  2. 具体实现有几个层级:全局前置守卫beforeEach、路由独享守卫beforeEnter或组件内守卫beforeRouteEnter。以全局守卫为例来说,可以使用router.beforeEach((to,from,next)=>{})方式设置守卫,每次路由导航时,都会执行该守卫,从而检查当前用户是否可以继续导航,通过给next函数传递多种参数达到不同的目的,比如如果禁止用户继续导航可以传递next(false),正常放行可以不传递参数,传递path字符串可以重定向到一个新的地址等等。
  3. 这些钩子函数之所以能够生效,也和vue-router工作方式有关,像beforeEach只是注册一个hook,当路由发生变化,router准备导航之前会批量执行这些hooks,并且把目标路由to,当前路由from,以及后续处理函数next传递给我们设置的hook。

可能的追问:

  1. 能不能说说全局守卫、路由独享守卫和组件内守卫区别?

    • 作用范围

    • 组件实例的获取

      beforeRouteEnter(to,from,next) {
      	next(vm => {
      		
      	})
      }
    • 名称/数量/顺序

      1. 导航被触发。
      2. 在失活的组件里调用离开守卫。
      3. 调用全局的 beforeEach 守卫。
      4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
      5. 在路由配置里调用 beforeEnter
      6. 解析异步路由组件。
      7. 在被激活的组件里调用 beforeRouteEnter
      8. 调用全局的 beforeResolve 守卫 (2.5+)。
      9. 导航被确认。
      10. 调用全局的 afterEach 钩子。
      11. 触发 DOM 更新。
      12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
  2. 你项目中的路由守卫是怎么做的?

  3. 前后端路由一样吗?

  4. 前端路由是用什么方式实现的?

  5. 你前面提到的next方法是怎么实现的?

2022-09-16Vue00

你了解哪些Vue性能优化方法?

分析

这是一道综合实践题目,写过一定数量的代码之后小伙伴们自然会开始关注一些优化方法,答得越多肯定实践经验也越丰富,是很好的题目。

答题思路:

根据题目描述,这里主要探讨Vue代码层面的优化

回答范例

  • 我这里主要从Vue代码编写层面说一些优化手段,例如:代码分割、服务端渲染、组件缓存、长列表优化等

  • 最常见的路由懒加载:有效拆分App尺寸,访问时才异步加载

    const router = createRouter({
      routes: [
        // 借助webpack的import()实现异步组件
        { path: '/foo', component: () => import('./Foo.vue') }
      ]
    })
  • keep-alive缓存页面:避免重复创建组件实例,且能保留缓存组件状态

    <router-view v-slot="{ Component }">
    	<keep-alive>
      	<component :is="Component"></component>
      </keep-alive>
    </router-view>
  • 使用v-show复用DOM:避免重复创建组件

    <template>
      <div class="cell">
        <!-- 这种情况用v-show复用DOM,比v-if效果好 -->
        <div v-show="value" class="on">
          <Heavy :n="10000"/>
        </div>
        <section v-show="!value" class="off">
          <Heavy :n="10000"/>
        </section>
      </div>
    </template>
  • v-for 遍历避免同时使用 v-if:实际上在Vue3中已经是个错误写法

    <template>
        <ul>
          <li
            v-for="user in activeUsers"
            <!-- 避免同时使用,vue3中会报错 -->
            <!-- v-if="user.isActive" -->
            :key="user.id">
            {{ user.name }}
          </li>
        </ul>
    </template>
    <script>
      export default {
        computed: {
          activeUsers: function () {
            return this.users.filter(user => user.isActive)
          }
        }
      }
    </script>
  • v-once和v-memo:不再变化的数据使用v-once

    <!-- single element -->
    <span v-once>This will never change: {{msg}}</span>
    <!-- the element have children -->
    <div v-once>
      <h1>comment</h1>
      <p>{{msg}}</p>
    </div>
    <!-- component -->
    <my-component v-once :comment="msg"></my-component>
    <!-- `v-for` directive -->
    <ul>
      <li v-for="i in list" v-once>{{i}}</li>
    </ul>

    按条件跳过更新时使用v-momo:下面这个列表只会更新选中状态变化项

    <div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
      <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
      <p>...more child nodes</p>
    </div>

    https://vuejs.org/api/built-in-directives.html#v-memo

  • 长列表性能优化:如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容

    <recycle-scroller
      class="items"
      :items="items"
      :item-size="24"
    >
      <template v-slot="{ item }">
        <FetchItemView
          :item="item"
          @vote="voteItem(item)"
        />
      </template>
    </recycle-scroller>

    一些开源库:

  • 事件的销毁:Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。

    export default {
      created() {
        this.timer = setInterval(this.refresh, 2000)
      },
      beforeUnmount() {
        clearInterval(this.timer)
      }
    }
  • 图片懒加载

    对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。

    <img v-lazy="/static/img/1.png">

    参考项目:vue-lazyload

  • 第三方插件按需引入

    element-plus这样的第三方组件库可以按需引入避免体积太大。

    import { createApp } from 'vue';
    import { Button, Select } from 'element-plus';
    
    const app = createApp()
    app.use(Button)
    app.use(Select)
  • 子组件分割策略:较重的状态组件适合拆分

    <template>
      <div>
        <ChildComp/>
      </div>
    </template>
    
    <script>
    export default {
      components: {
        ChildComp: {
          methods: {
            heavy () { /* 耗时任务 */ }
          },
          render (h) {
            return h('div', this.heavy())
          }
        }
      }
    }
    </script>

    但同时也不宜过度拆分组件,尤其是为了所谓组件抽象将一些不需要渲染的组件特意抽出来,组件实例消耗远大于纯dom节点。参考:https://vuejs.org/guide/best-practices/performance.html#avoid-unnecessary-component-abstractions

  • 服务端渲染/静态网站生成:SSR/SSG

    如果SPA应用有首屏渲染慢的问题,可以考虑SSR、SSG方案优化。参考SSR Guide

2022-09-16Vue00

你知道nextTick吗,它是干什么的,实现原理是什么?

这道题考查大家对vue异步更新队列的理解,有一定深度,如果能够很好回答此题,对面试效果有极大帮助。

答题思路:

  1. nextTick是啥?下一个定义
  2. 为什么需要它呢?用异步更新队列实现原理解释
  3. 我再什么地方用它呢?抓抓头,想想你在平时开发中使用它的地方
  4. 下面介绍一下如何使用nextTick
  5. 最后能说出源码实现就会显得你格外优秀

先看看官方定义

Vue.nextTick( [callback, context] )

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
// DOM 更新了
})

回答范例:

  1. nextTick是Vue提供的一个全局API,由于vue的异步更新策略导致我们对数据的修改不会立刻体现在dom变化上,此时如果想要立即获取更新后的dom状态,就需要使用这个方法
  2. Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用。
  3. 所以当我们想在修改数据后立即看到dom执行结果就需要用到nextTick方法。
  4. 比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可。
  5. 我也有简单了解nextTick实现,它会在callbacks里面加入我们传入的函数,然后用timerFunc异步方式调用它们,首选的异步方式会是Promise。这让我明白了为什么可以在nextTick中看到dom操作结果。