关于vue.js:2023前端二面高频vue面试题集锦

8次阅读

共计 29261 个字符,预计需要花费 74 分钟才能阅读完成。

vuex 是什么?怎么应用?哪种性能场景应用它?

Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。vuex 就是一个仓库,仓库里放了很多对象。其中 state 就是数据源寄存地,对应于个别 vue 对象外面的 data 外面寄存的数据是响应式的,vue 组件从 store 读取数据,若是 store 中的数据产生扭转,依赖这相数据的组件也会产生更新它通过 mapState 把全局的 stategetters 映射到以后组件的 computed 计算属性

  • vuex 个别用于中大型 web 单页利用中对利用的状态进行治理,对于一些组件间关系较为简单的小型利用,应用 vuex 的必要性不是很大,因为齐全能够用组件 prop 属性或者事件来实现父子组件之间的通信,vuex 更多地用于解决跨组件通信以及作为数据中心集中式存储数据。
  • 应用 Vuex 解决非父子组件之间通信问题 vuex 是通过将 state 作为数据中心、各个组件共享 state 实现跨组件通信的,此时的数据齐全独立于组件,因而将组件间共享的数据置于 State 中能无效解决多层级组件嵌套的跨组件通信问题

vuexState 在单页利用的开发中自身具备一个“数据库”的作用,能够将组件中用到的数据存储在 State 中,并在 Action 中封装数据读写的逻辑。这时候存在一个问题,个别什么样的数据会放在 State 中呢?目前次要有两种数据会应用 vuex 进行治理:

  • 组件之间全局共享的数据
  • 通过后端异步申请的数据

包含以下几个模块

  • stateVuex 应用繁多状态树, 即每个利用将仅仅蕴含一个store 实例。外面寄存的数据是响应式的,vue 组件从 store 读取数据,若是 store 中的数据产生扭转,依赖这相数据的组件也会产生更新。它通过 mapState 把全局的 stategetters 映射到以后组件的 computed 计算属性
  • mutations:更改 Vuexstore中的状态的惟一办法是提交mutation
  • gettersgetter 能够对 state 进行计算操作,它就是 store 的计算属性尽管在组件内也能够做计算属性,然而 getters 能够在多给件之间复用如果一个状态只在一个组件内应用,是能够不必 getters
  • actionaction 相似于 muation, 不同在于:action 提交的是 mutation, 而不是间接变更状态action 能够蕴含任意异步操作
  • modules:面对简单的应用程序,当治理的状态比拟多时;咱们须要将 vuexstore对象宰割成模块(modules)

modules:我的项目特地简单的时候,能够让每一个模块领有本人的statemutationactiongetters,使得构造十分清晰,方便管理

答复范例

思路

  • 给定义
  • 必要性论述
  • 何时应用
  • 拓展:一些集体思考、实践经验等

答复范例

  1. Vuex 是一个专为 Vue.js 利用开发的 状态管理模式 + 库。它采纳集中式存储,治理利用的所有组件的状态,并以相应的规定保障状态以一种可预测的形式发生变化。
  2. 咱们期待以一种简略的“单向数据流”的形式治理利用,即 状态 -> 视图 -> 操作单向循环 的形式。但当咱们的利用遇到多个组件共享状态时,比方:多个视图依赖于同一状态或者来自不同视图的行为须要变更同一状态。此时单向数据流的简洁性很容易被毁坏。因而,咱们有必要把组件的共享状态抽取进去,以一个全局单例模式治理。通过定义和隔离状态治理中的各种概念并通过强制规定维持视图和状态间的独立性,咱们的代码将会变得更结构化且易保护。这是 vuex 存在的必要性,它和 react 生态中的 redux 之类是一个概念
  3. Vuex 解决状态治理的同时引入了不少概念:例如 statemutationaction 等,是否须要引入还须要依据利用的理论状况掂量一下:如果不打算开发大型单页利用,应用 Vuex 反而是繁琐冗余的,一个简略的 store 模式就足够了。然而,如果要构建一个中大型单页利用,Vuex 根本是标配。
  4. 我在应用 vuex 过程中感触到一些等

可能的诘问

  1. vuex有什么毛病吗?你在开发过程中有遇到什么问题吗?
  2. 刷新浏览器,vuex中的 state 会从新变为初始状态。解决方案 - 插件 vuex-persistedstate
  3. actionmutation 的区别是什么?为什么要辨别它们?
  • action中解决异步,mutation不能够
  • mutation做原子操作
  • action能够整合多个 mutation 的汇合
  • mutation 是同步更新数据(外部会进行是否为异步形式更新数据的检测) $watch 严格模式下会报错
  • action 异步操作,能够获取数据后调佣 mutation 提交最终数据
  • 流程程序:“相应视图—> 批改 State”拆分成两局部,视图触发ActionAction 再触发Mutation`。
  • 基于流程程序,二者表演不同的角色:Mutation:专一于批改 State,实践上是批改State 的惟一路径。Action:业务代码、异步申请
  • 角色不同,二者有不同的限度:Mutation:必须同步执行。Action:能够异步,但不能间接操作State

Watch 中的 deep:true 是如何实现的

当用户指定了 watch 中的 deep 属性为 true 时,如果以后监控的值是数组类型。会对对象中的每一项进行求值,此时会将以后 watcher存入到对应属性的依赖中,这样数组中对象发生变化时也会告诉数据更新

源码相干

get () {pushTarget(this) // 先将以后依赖放到 Dep.target 上 
    let value 
    const vm = this.vm 
    try {value = this.getter.call(vm, vm) 
    } catch (e) {if (this.user) {handleError(e, vm, `getter for watcher "${this.expression}"`) 
        } else {throw e} 
    } finally {if (this.deep) { // 如果须要深度监控 
        traverse(value) // 会对对象中的每一项取值, 取值时会执行对应的 get 办法 
    }popTarget()}

Vue3 速度快的起因

Vue3.0 性能晋升体现在哪些方面

  • 代码层面性能优化次要体现在全新响应式 API,基于Proxy 实现,初始化工夫和内存占用均大幅改良;
  • 编译层面做了更多编译优化解决,比方 动态标记 pachFlagdiff算法减少了一个动态标记,只比照有标记的 dom 元素)、事件减少缓存 动态晋升 (对不参加更新的元素,会做动态晋升,只会被创立一次,之后会在每次渲染时候被不停的复用)等,能够无效跳过大量diff 过程;
  • 打包时更好的反对tree-shaking,因而整体体积更小,加载更快
  • ssr渲染以字符串形式渲染

一、编译阶段

试想一下,一个组件构造如下图

<template>
    <div id="content">
        <p class="text"> 动态文本 </p>
        <p class="text"> 动态文本 </p>
        <p class="text">{message}</p>
        <p class="text"> 动态文本 </p>
        ...
        <p class="text"> 动态文本 </p>
    </div>
</template>

能够看到,组件外部只有一个动静节点,残余一堆都是动态节点,所以这里很多 diff 和遍历其实都是不须要的,造成性能节约

因而,Vue3 在编译阶段,做了进一步优化。次要有如下:

  • diff算法优化
  • 动态晋升
  • 事件监听缓存
  • SSR优化

1. diff 算法优化

  • Vue 2x 中的虚构 dom 是进行全量的比照。
  • Vue 3x 中新增了动态标记(PatchFlag): 在与上次虚构结点进行比照的时候,值比照 带有 patch flag 的节点,并且能够通过 flag 的信息得悉以后节点要比照的具体内容化

Vue2.x 的 diff 算法

vue2.xdiff 算法叫做 全量比拟 ,顾名思义,就是当数据扭转的时候,会从头到尾的进行vDom 比照,即便有些内容是永恒固定不变的

Vue3.0 的 diff 算法

vue3.0diff 算法有个叫动态标记(PatchFlag)的小玩意,啥是动态标记呢?简略点说,就是如果你的内容会变,我会给你一个flag,下次数据更新的时候我间接来比照你,我就不比照那些没有标记的了

曾经标记动态节点的 p 标签在 diff 过程中则不会比拟,把性能进一步提高

export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [_createVNode("p", null, "'HelloWorld'"),
  _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
                        // 下面这个 1 就是动态标记
 ]))
}

对于动态类型枚举如下

TEXT = 1 // 动静文本节点
CLASS=1<<1,1 // 2// 动静 class
STYLE=1<<2,// 4 // 动静 style
PROPS=1<<3,// 8 // 动静属性,但不蕴含类名和款式
FULLPR0PS=1<<4,// 16 // 具备动静 key 属性,当 key 扭转时,须要进行残缺的 diff 比拟。HYDRATE_ EVENTS = 1 << 5,// 32 // 带有监听事件的节点
STABLE FRAGMENT = 1 << 6, // 64 // 一个不会扭转子节点程序的 fragment
KEYED_ FRAGMENT = 1 << 7, // 128 // 带有 key 属性的 fragment 或局部子字节有 key
UNKEYED FRAGMENT = 1<< 8, // 256 // 子节点没有 key 的 fragment
NEED PATCH = 1 << 9, // 512 // 一个节点只会进行非 props 比拟
DYNAMIC_SLOTS = 1 << 10 // 1024 // 动静 slot
HOISTED = -1 // 动态节点
// 批示在 diff 算法中退出优化模式
BALL = -2

2. hoistStatic 动态晋升

  • Vue 2x : 无论元素是否参加更新,每次都会从新创立。
  • Vue 3x : 对不参加更新的元素,会做动态晋升,只会被创立一次,之后会在每次渲染时候被不停的复用。这样就免去了反复的创立节点,大型利用会受害于这个改变,免去了反复的创立操作,优化了运行时候的内存占用
<p>HelloWorld</p>
<p>HelloWorld</p>

<p>{message}</p>

开启动态晋升前

export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [_createVNode("p", null, "'HelloWorld'"),
  _createVNode("p", null, "'HelloWorld'"),
  _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
 ]))
}

开启动态晋升后编译后果

const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "'HelloWorld'", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "'HelloWorld'", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [
  _hoisted_1,
  _hoisted_2,
  _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
 ]))
}

能够看到开启了动态晋升后,间接将那两个内容为 helloworldp标签申明在里面了,间接就拿来用了。同时 _hoisted_1_hoisted_2 被打上了 PatchFlag,动态标记值为 -1,非凡标记是负整数示意永远不会用于 Diff

3. cacheHandlers 事件监听缓存

  • 默认状况下 绑定事件会被视为动静绑定,所以每次都会去追踪它的变动
  • 然而因为是同一个函数,所以没有追踪变动,间接缓存起来复用即可
<div>
 <button @click = 'onClick'> 点我 </button>
</div>

开启事件侦听器缓存之前:

export const render = /*#__PURE__*/_withId(function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [_createVNode("button", { onClick: _ctx.onClick}, "点我", 8 /* PROPS */, ["onClick"])
                       // PROPS=1<<3,// 8 // 动静属性,但不蕴含类名和款式
 ]))
})

这里有一个 8,示意着这个节点有了动态标记,有动态标记就会进行diff 算法比照差别,所以会浪费时间

开启事件侦听器缓存之后:

export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [
  _createVNode("button", {onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
  }, "点我")
 ]))
}

上述发现开启了缓存后,没有了动态标记。也就是说下次 diff 算法的时候间接应用

4. SSR 优化

当动态内容大到一定量级时候,会用 createStaticVNode 办法在客户端去生成一个static node,这些动态node,会被间接innerHtml,就不须要创建对象,而后依据对象渲染

<div>
    <div>
        <span> 你好 </span>
    </div>
    ...  // 很多个动态属性
    <div>
        <span>{{message}}</span>
    </div>
</div>

编译后

import {mergeProps as _mergeProps} from "vue"
import {ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate} from "@vue/server-renderer"

export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {const _cssVars = { style: { color: _ctx.color}}
  _push(`<div${_ssrRenderAttrs(_mergeProps(_attrs, _cssVars))
  }><div><span> 你好 </span>...<div><span> 你好 </span><div><span>${_ssrInterpolate(_ctx.message)
  }</span></div></div>`)
}

二、源码体积

相比 Vue2Vue3 整体体积变小了,除了移出一些不罕用的API,再重要的是Tree shanking

任何一个函数,如 refreactivecomputed 等,仅仅在用到的时候才打包,没用到的模块都被摇掉,打包的整体体积变小

import {computed, defineComponent, ref} from 'vue';
export default defineComponent({setup(props, context) {const age = ref(18)

    let state = reactive({name: 'test'})

    const readOnlyAge = computed(() => age.value++) // 19

    return {
        age,
        state,
        readOnlyAge
    }
  }
});

三、响应式零碎

vue2中采纳 defineProperty来劫持整个对象,而后进行深度遍历所有属性,给每个属性增加 gettersetter,实现响应式

vue3采纳 proxy 重写了响应式零碎,因为 proxy 能够对整个对象进行监听,所以不须要深度遍历

  • 能够监听动静属性的增加
  • 能够监听到数组的索引和数组 length 属性
  • 能够监听删除属性

什么是递归组件?举个例子阐明下?

剖析

递归组件咱们用的比拟少,然而在 TreeMenu 这类组件中会被用到。

体验

组件通过组件名称援用它本人,这种状况就是递归组件

<template>
  <li>
    <div> {{model.name}}</div>
    <ul v-show="isOpen" v-if="isFolder">
      <!-- 留神这里:组件递归渲染了它本人 -->
      <TreeItem
        class="item"
        v-for="model in model.children"
        :model="model">
      </TreeItem>
    </ul>
  </li>
<script>
export default {
  name: 'TreeItem',
  // ...
}
</script>

答复范例

  1. 如果某个组件通过组件名称援用它本人,这种状况就是递归组件。
  2. 理论开发中相似 TreeMenu 这类组件,它们的节点往往蕴含子节点,子节点构造和父节点往往是雷同的。这类组件的数据往往也是树形构造,这种都是应用递归组件的典型场景。
  3. 应用递归组件时,因为咱们并未也不能在组件外部导入它本人,所以设置组件 name 属性,用来查找组件定义,如果应用 SFC,则能够通过SFC 文件名推断。组件外部通常也要有递归完结条件,比方 model.children 这样的判断。
  4. 查看生成渲染函数可知,递归组件查找时会传递一个布尔值给resolveComponent,这样理论获取的组件就是以后组件自身

原理

递归组件编译后果中,获取组件时会传递一个标识符 _resolveComponent("Comp", true)

const _component_Comp = _resolveComponent("Comp", true)

就是在传递maybeSelfReference

export function resolveComponent(
  name: string,
  maybeSelfReference?: boolean
): ConcreteComponent | string {return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name
}

resolveAsset中最终返回的是组件本身:

if (!res && maybeSelfReference) {
    // fallback to implicit self-reference
    return Component
}

怎么了解 Vue 的单向数据流

数据总是从父组件传到子组件,子组件没有权力批改父组件传过来的数据,只能申请父组件对原始数据进行批改。这样会 避免从子组件意外扭转父级组件的状态,从而导致你的利用的数据流向难以了解

留神:在子组件间接用 v-model 绑定父组件传过来的 prop 这样是不标准的写法 开发环境会报正告

如果切实要扭转父组件的 prop 值,能够在 data 外面定义一个变量 并用 prop 的值初始化它 之后用$emit 告诉父组件去批改

有两种常见的试图扭转一个 prop 的情景 :

  1. 这个 prop 用来传递一个初始值;这个子组件接下来心愿将其作为一个本地的 prop 数据来应用。在这种状况下,最好定义一个本地的 data 属性并将这个 prop用作其初始值
props: ['initialCounter'],
data: function () {
  return {counter: this.initialCounter}
}
  1. 这个 prop 以一种原始的值传入且须要进行转换。在这种状况下,最好应用这个 prop 的值来定义一个计算属性
props: ['size'],
computed: {normalizedSize: function () {return this.size.trim().toLowerCase()}
}

为什么不倡议用 index 作为 key?

应用 index 作为 key 和没写基本上没区别,因为不论数组的程序怎么颠倒,index 都是 0, 1, 2… 这样排列,导致 Vue 会复用谬误的旧子节点,做很多额定的工作。

参考 前端进阶面试题具体解答

怎么缓存以后的组件?缓存后怎么更新

缓存组件应用 keep-alive 组件,这是一个十分常见且有用的优化伎俩,vue3keep-alive 有比拟大的更新,能说的点比拟多

思路

  • 缓存用keep-alive,它的作用与用法
  • 应用细节,例如缓存指定 / 排除、联合 routertransition
  • 组件缓存后更新能够利用 activated 或者beforeRouteEnter
  • 原理论述

答复范例

  1. 开发中缓存组件应用 keep-alive 组件,keep-alivevue 内置组件,keep-alive包裹动静组件 component 时,会缓存不流动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,避免反复渲染DOM
<keep-alive>
  <component :is="view"></component>
</keep-alive>
  1. 联合属性 includeexclude能够明确指定缓存哪些组件或排除缓存指定组件。vue3中联合 vue-router 时变动较大,之前是 keep-alive 包裹 router-view,当初须要反过来用router-view 包裹keep-alive
<router-view v-slot="{Component}">
  <keep-alive>
    <component :is="Component"></component>
  </keep-alive>
</router-view>
  1. 缓存后如果要获取数据,解决方案能够有以下两种
  2. beforeRouteEnter:在有 vue-router 的 我的项目,每次进入路由的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){
  next(vm=>{console.log(vm)
    // 每次进入路由执行
    vm.getData()  // 获取数据})
},
  • actived:在 keep-alive 缓存的组件被激活的时候,都会执行 actived 钩子
activated(){this.getData() // 获取数据
},
  1. keep-alive是一个通用组件,它外部定义了一个 map,缓存创立过的组件实例,它返回的渲染函数外部会查找内嵌的component 组件对应组件的 vnode,如果该组件在map 中存在就间接返回它。因为 componentis属性是个响应式数据,因而只有它变动,keep-aliverender 函数就会从新执行

Vue 中组件和插件有什么区别

1. 组件是什么

组件就是把图形、非图形的各种逻辑均形象为一个对立的概念(组件)来实现开发的模式,在 Vue 中每一个.vue 文件都能够视为一个组件

组件的劣势

  • 升高整个零碎的耦合度,在放弃接口不变的状况下,咱们能够替换不同的组件疾速实现需要,例如输入框,能够替换为日历、工夫、范畴等组件作具体的实现
  • 调试不便,因为整个零碎是通过组件组合起来的,在呈现问题的时候,能够用排除法间接移除组件,或者依据报错的组件疾速定位问题,之所以可能疾速定位,是因为每个组件之间低耦合,职责繁多,所以逻辑会比剖析整个零碎要简略
  • 进步可维护性,因为每个组件的职责繁多,并且组件在零碎中是被复用的,所以对代码进行优化可取得零碎的整体降级

2. 插件是什么

插件通常用来为 Vue 增加全局性能。插件的性能范畴没有严格的限度——个别有上面几种:

  • 增加全局办法或者属性。如: vue-custom-element
  • 增加全局资源:指令 / 过滤器 / 过渡等。如 vue-touch
  • 通过全局混入来增加一些组件选项。如vue-router
  • 增加 Vue 实例办法,通过把它们增加到 Vue.prototype 上实现。
  • 一个库,提供本人的 API,同时提供下面提到的一个或多个性能。如vue-router

3. 两者的区别

两者的区别次要体现在以下几个方面:

  • 编写模式
  • 注册模式
  • 应用场景

3.1 编写模式

编写组件

编写一个组件,能够有很多形式,咱们最常见的就是 vue 单文件的这种格局,每一个 .vue 文件咱们都能够看成是一个组件

vue 文件规范格局

<template>
</template>
<script>
export default{...}
</script>
<style>
</style>

咱们还能够通过 template 属性来编写一个组件,如果组件内容多,咱们能够在内部定义 template 组件内容,如果组件内容并不多,咱们可间接写在 template 属性上

<template id="testComponent">     // 组件显示的内容
    <div>component!</div>   
</template>

Vue.component('componentA',{ 
    template: '#testComponent'  
    template: `<div>component</div>`  // 组件内容少能够通过这种模式
})

编写插件

vue插件的实现应该裸露一个 install 办法。这个办法的第一个参数是 Vue 结构器,第二个参数是一个可选的选项对象

MyPlugin.install = function (Vue, options) {
  // 1. 增加全局办法或 property
  Vue.myGlobalMethod = function () {// 逻辑...}

  // 2. 增加全局资源
  Vue.directive('my-directive', {bind (el, binding, vnode, oldVnode) {// 逻辑...}
    ...
  })

  // 3. 注入组件选项
  Vue.mixin({created: function () {// 逻辑...}
    ...
  })

  // 4. 增加实例办法
  Vue.prototype.$myMethod = function (methodOptions) {// 逻辑...}
}

3.2 注册模式

组件注册

vue 组件注册次要分为 全局注册 部分注册

全局注册通过 Vue.component 办法,第一个参数为组件的名称,第二个参数为传入的配置项

Vue.component('my-component-name', { /* ... */})

部分注册只需在用到的中央通过 components 属性注册一个组件

const component1 = {...} // 定义一个组件

export default {
    components:{component1   // 部分注册}
}

插件注册

插件的注册通过 Vue.use() 的形式进行注册(装置),第一个参数为插件的名字,第二个参数是可抉择的配置项

Vue.use(插件名字,{ /* ... */} )

留神的是:

注册插件的时候,须要在调用 new Vue() 启动利用之前实现

Vue.use会主动阻止屡次注册雷同插件,只会注册一次

4. 应用场景

  • 组件 (Component) 是用来形成你的 App 的业务模块,它的指标是 App.vue
  • 插件 (Plugin) 是用来加强你的技术栈的功能模块,它的指标是 Vue 自身

简略来说,插件就是指对 Vue 的性能的加强或补充

Composition API 与 Options API 有什么不同

剖析

Vue3最重要更新之一就是 Composition API,它具备一些列长处,其中不少是针对Options API 裸露的一些问题量身打造。是 Vue3 举荐的写法,因而把握好 Composition API 利用对把握好 Vue3 至关重要

What is Composition API?(opens new window)

  • Composition API呈现就是为了解决 Options API 导致雷同性能代码扩散的景象

体验

Composition API能更好的组织代码,上面用 composition api 能够提取为useCount(),用于组合、复用

compositon api 提供了以下几个函数:

  • setup
  • ref
  • reactive
  • watchEffect
  • watch
  • computed
  • toRefs
  • 生命周期的hooks

答复范例

  1. Composition API是一组 API,包含:Reactivity API 生命周期钩子 依赖注入 ,使用户能够通过导入函数形式编写vue 组件。而 Options API 则通过申明组件选项的对象模式编写组件
  2. Composition API最次要作用是可能简洁、高效复用逻辑。解决了过来 Options APImixins的各种毛病;另外 Composition API 具备更加麻利的代码组织能力,很多用户喜爱 Options API,认为所有货色都有固定地位的选项搁置代码,然而单个组件增长过大之后这反而成为限度,一个逻辑关注点扩散在组件各处,造成代码碎片,保护时须要重复横跳,Composition API 则能够将它们无效组织在一起。最初 Composition API 领有更好的类型推断,对 ts 反对更敌对,Options API在设计之初并未思考类型推断因素,尽管官网为此做了很多简单的类型体操,确保用户能够在应用 Options API 时取得类型推断,然而还是没方法用在 mixinsprovide/inject
  3. Vue3首推 Composition API,然而这会让咱们在代码组织上多花点心理,因而在抉择上,如果咱们我的项目属于中低复杂度的场景,Options API 仍是一个好抉择。对于那些大型,高扩大,强保护的我的项目上,Composition API会取得更大收益

可能的诘问

  1. Composition API是否和 Options API 一起应用?

能够在同一个组件中应用两个 script 标签,一个应用 vue3,一个应用 vue2 写法,一起应用没有问题

<!-- vue3 -->
<script setup>
  // vue3 写法
</script>

<!-- 降级 vue2 -->
<script>
  export default {data() {},
    methods: {}}
</script>

为什么要应用异步组件

  1. 节俭打包出的后果,异步组件离开打包,采纳 jsonp 的形式进行加载,无效解决文件过大的问题。
  2. 外围就是包组件定义变成一个函数,依赖import() 语法,能够实现文件的宰割加载。
components:{AddCustomerSchedule:(resolve)=>import("../components/AddCustomer") // require([]) 
}

原理

export function (Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string): VNode | Array<VNode> | void { 
    // async component 
    let asyncFactory 
    if (isUndef(Ctor.cid)) { 
        asyncFactory = Ctor 
        Ctor = resolveAsyncComponent(asyncFactory, baseCtor) // 默认调用此函数时返回 undefiend 
        // 第二次渲染时 Ctor 不为 undefined 
        if (Ctor === undefined) { 
            return createAsyncPlaceholder( // 渲染占位符 空虚构节点 
                asyncFactory, 
                data, 
                context, 
                children, 
                tag 
            ) 
        } 
    } 
}
function resolveAsyncComponent (factory: Function, baseCtor: Class<Component>): Class<Component> | void {if (isDef(factory.resolved)) { 
        // 3. 在次渲染时能够拿到获取的最新组件 
        return factory.resolved 
    }
    const resolve = once((res: Object | Class<Component>) => {factory.resolved = ensureCtor(res, baseCtor) 
        if (!sync) {forceRender(true) //2. 强制更新视图从新渲染 
        } else {owners.length = 0} 
    })
    const reject = once(reason => {if (isDef(factory.errorComp)) {factory.error = true forceRender(true) 
        } 
    })
    const res = factory(resolve, reject)// 1. 将 resolve 办法和 reject 办法传入,用户调用 resolve 办法后 
    sync = false 
    return factory.resolved 
}

子组件能够间接扭转父组件的数据么,阐明起因

这是一个实际知识点,组件化开发过程中有个 单项数据流准则,不在子组件中批改父组件是个常识问题

思路

  • 讲讲单项数据流准则,表明为何不能这么做
  • 举几个常见场景的例子说说解决方案
  • 联合实际讲讲如果须要批改父组件状态应该如何做

答复范例

  1. 所有的 prop 都使得其父子之间造成了一个单向上行绑定:父级 prop 的更新会向下流动到子组件中,然而反过来则不行。这样会避免从子组件意外变更父级组件的状态,从而导致你的利用的数据流向难以了解。另外,每次父级组件产生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件外部扭转 prop。如果你这样做了,Vue 会在浏览器控制台中收回正告
const props = defineProps(['foo'])
// ❌ 上面行为会被正告, props 是只读的!
props.foo = 'bar'
  1. 理论开发过程中有两个场景会想要批改一个属性:

这个 prop 用来传递一个初始值;这个子组件接下来心愿将其作为一个本地的 prop 数据来应用。 在这种状况下,最好定义一个本地的 data,并将这个 prop 用作其初始值:

const props = defineProps(['initialCounter'])
const counter = ref(props.initialCounter)

这个 prop 以一种原始的值传入且须要进行转换。 在这种状况下,最好应用这个 prop 的值来定义一个计算属性:

const props = defineProps(['size'])
// prop 变动,计算属性自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
  1. 实际中如果的确想要扭转父组件属性应该 emit 一个事件让父组件去做这个变更。留神尽管咱们不能间接批改一个传入的对象或者数组类型的prop,然而咱们还是可能间接改内嵌的对象或属性

Vue 组件之间通信形式有哪些

Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信 父子组件通信 隔代组件通信 兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信

组件传参的各种形式

组件通信罕用形式有以下几种

  • props / $emit 实用 父子组件通信

    • 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的
  • ref$parent / $children(vue3 废除) 实用 父子组件通信

    • ref:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例
    • $parent / $children:拜访拜访父组件的属性或办法 / 拜访子组件的属性或办法
  • EventBus($emit / $on) 实用于 父子、隔代、兄弟组件通信

    • 这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件
  • $attrs / $listeners(vue3 废除) 实用于 隔代组件通信

    • $attrs:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 (classstyle 除外 )。当一个组件没有申明任何 prop时,这里会蕴含所有父作用域的绑定 (classstyle 除外 ),并且能够通过 v-bind="$attrs" 传入外部组件。通常配合 inheritAttrs 选项一起应用
    • $listeners:蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过 v-on="$listeners" 传入外部组件
  • provide / inject 实用于 隔代组件通信

    • 先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。provide / inject API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系
  • $root 实用于 隔代组件通信 拜访根组件中的属性或办法,是根组件,不是父组件。$root 只对根组件有用
  • Vuex 实用于 父子、隔代、兄弟组件通信

    • Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state )
    • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
    • 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。

依据组件之间关系探讨组件通信最为清晰无效

  • 父子组件:props/$emit/$parent/ref
  • 兄弟组件:$parent/eventbus/vuex
  • 跨层级关系:eventbus/vuex/provide+inject/$attrs + $listeners/$root

上面演示组件之间通信三种状况: 父传子、子传父、兄弟组件之间的通信

1. 父子组件通信

应用 props,父组件能够应用props 向子组件传递数据。

父组件 vue 模板father.vue:

<template>
  <child :msg="message"></child>
</template>

<script>
import child from './child.vue';
export default {
  components: {child},
  data () {
    return {message: 'father message';}
  }
}
</script>

子组件 vue 模板child.vue:

<template>
    <div>{{msg}}</div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      required: true
    }
  }
}
</script>

回调函数(callBack)

父传子:将父组件里定义的 method 作为 props 传入子组件

// 父组件 Parent.vue:<Child :changeMsgFn="changeMessage">
methods: {changeMessage(){this.message = 'test'}
}
// 子组件 Child.vue:<button @click="changeMsgFn">
props:['changeMsgFn']

子组件向父组件通信

父组件向子组件传递事件办法,子组件通过 $emit 触发事件,回调给父组件

父组件 vue 模板father.vue:

<template>
    <child @msgFunc="func"></child>
</template>

<script>
import child from './child.vue';
export default {
    components: {child},
    methods: {func (msg) {console.log(msg);
        }
    }
}
</script>

子组件 vue 模板child.vue:

<template>
    <button @click="handleClick"> 点我 </button>
</template>

<script>
export default {
    props: {
        msg: {
            type: String,
            required: true
        }
    },
    methods () {handleClick () {
          //........
          this.$emit('msgFunc');
        }
    }
}
</script>

2. provide / inject 跨级拜访先人组件的数据

父组件通过应用 provide(){return{}} 提供须要传递的数据

export default {data() {
    return {
      title: '我是父组件',
      name: 'poetry'
    }
  },
  methods: {say() {alert(1)
    }
  },
  // provide 属性 可能为前面的后辈组件 / 嵌套的组件提供所须要的变量和办法
  provide() {
    return {
      message: '我是先人组件提供的数据',
      name: this.name, // 传递属性
      say: this.say
    }
  }
}

子组件通过应用 inject:[“参数 1”,”参数 2”,…] 接管父组件传递的参数

<template>
  <p> 曾孙组件 </p>
  <p>{{message}}</p>
</template>
<script>
export default {
  // inject 注入 / 接管先人组件传递的所须要的数据即可 
  // 接管到的数据 变量 跟 data 外面的变量一样 能够间接绑定到页面 {{}}
  inject: ["message","say"],
  mounted() {this.say();
  },
};
</script>

3. $parent + $children 获取父组件实例和子组件实例的汇合

  • this.$parent 能够间接拜访该组件的父实例或组件
  • 父组件也能够通过 this.$children 拜访它所有的子组件;须要留神 $children 并不保障程序,也不是响应式的
<!-- parent.vue -->
<template>
<div>
  <child1></child1>   
  <child2></child2> 
  <button @click="clickChild">$children 形式获取子组件值 </button>
</div>
</template>
<script>
import child1 from './child1'
import child2 from './child2'
export default {data(){
    return {total: 108}
  },
  components: {
    child1,
    child2  
  },
  methods: {funa(e){console.log("index",e)
    },
    clickChild(){console.log(this.$children[0].msg);
      console.log(this.$children[1].msg);
    }
  }
}
</script>
<!-- child1.vue -->
<template>
  <div>
    <button @click="parentClick"> 点击拜访父组件 </button>
  </div>
</template>
<script>
export default {data(){
    return {msg:"child1"}
  },
  methods: {
    // 拜访父组件数据
    parentClick(){this.$parent.funa("xx")
      console.log(this.$parent.total);
    }
  }
}
</script>
<!-- child2.vue -->
<template>
  <div>
    child2
  </div>
</template>
<script>
export default {data(){
    return {msg: 'child2'}
  }
}
</script>

4. $attrs + $listeners 多级组件通信

$attrs 蕴含了从父组件传过来的所有 props 属性

// 父组件 Parent.vue:<Child :name="name" :age="age"/>

// 子组件 Child.vue:<GrandChild v-bind="$attrs" />

// 孙子组件 GrandChild
<p> 姓名:{{$attrs.name}}</p>
<p> 年龄:{{$attrs.age}}</p>

$listeners蕴含了父组件监听的所有事件

// 父组件 Parent.vue:<Child :name="name" :age="age" @changeNameFn="changeName"/>

// 子组件 Child.vue:<button @click="$listeners.changeNameFn"></button>

5. ref 父子组件通信

// 父组件 Parent.vue:<Child ref="childComp"/>
<button @click="changeName"></button>
changeName(){console.log(this.$refs.childComp.age);
    this.$refs.childComp.changeAge()}

// 子组件 Child.vue:data(){
    return{age:20}
},
methods(){changeAge(){this.age=15}
}

6. 非父子, 兄弟组件之间通信

vue2中废除了 broadcast 播送和散发事件的办法。父子组件中能够用 props$emit()。如何实现非父子组件间的通信,能够通过实例一个 vue 实例 Bus 作为媒介,要互相通信的兄弟组件之中,都引入 Bus,而后通过别离调用 Bus 事件触发和监听来实现通信和参数传递。Bus.js 能够是这样:

// Bus.js

// 创立一个地方工夫总线类  
class Bus {constructor() {this.callbacks = {};   // 寄存事件的名字  
  }  
  $on(name, fn) {this.callbacks[name] = this.callbacks[name] || [];  
    this.callbacks[name].push(fn);  
  }  
  $emit(name, args) {if (this.callbacks[name]) {this.callbacks[name].forEach((cb) => cb(args));  
    }  
  }  
}  

// main.js  
Vue.prototype.$bus = new Bus() // 将 $bus 挂载到 vue 实例的原型上  
// 另一种形式  
Vue.prototype.$bus = new Vue() // Vue 曾经实现了 Bus 的性能  
<template>
    <button @click="toBus"> 子组件传给兄弟组件 </button>
</template>

<script>
export default{
    methods: {toBus () {this.$bus.$emit('foo', '来自兄弟组件')
    }
  }
}
</script>

另一个组件也在钩子函数中监听 on 事件

export default {data() {
    return {message: ''}
  },
  mounted() {this.$bus.$on('foo', (msg) => {this.message = msg})
  }
}

7. $root 拜访根组件中的属性或办法

  • 作用:拜访根组件中的属性或办法
  • 留神:是根组件,不是父组件。$root只对根组件有用
var vm = new Vue({
  el: "#app",
  data() {
    return {rootInfo:"我是根元素的属性"}
  },
  methods: {alerts() {alert(111)
    }
  },
  components: {
    com1: {data() {
        return {info: "组件 1"}
      },
      template: "<p>{{info}} <com2></com2></p>",
      components: {
        com2: {
          template: "<p> 我是组件 1 的子组件 </p>",
          created() {this.$root.alerts()// 根组件办法
            console.log(this.$root.rootInfo)// 我是根元素的属性
          }
        }
      }
    }
  }
});

8. vuex

  • 实用场景: 简单关系的组件数据传递
  • Vuex 作用相当于一个用来存储共享变量的容器
  • state用来寄存共享变量的中央
  • getter,能够减少一个 getter 派生状态,(相当于 store 中的计算属性),用来取得共享变量的值
  • mutations用来寄存批改 state 的办法。
  • actions也是用来寄存批改 state 的办法,不过 action 是在 mutations 的根底上进行。罕用来做一些异步操作

小结

  • 父子关系的组件数据传递抉择 props$emit进行传递,也可抉择ref
  • 兄弟关系的组件数据传递可抉择 $bus,其次能够抉择$parent 进行传递
  • 先人与后辈组件数据传递可抉择 attrslisteners或者 ProvideInject
  • 简单关系的组件数据传递能够通过 vuex 寄存共享的变量

怎么监听 vuex 数据的变动

剖析

  • vuex数据状态是响应式的,所以状态变视图跟着变,然而有时还是须要晓得数据状态变了从而做一些事件。
  • 既然状态都是响应式的,那天然能够 watch,另外vuex 也提供了订阅的 API:store.subscribe()

答复范例

  1. 我晓得几种办法:
  2. 能够通过 watch 选项或者 watch 办法监听状态
  3. 能够应用 vuex 提供的 API:store.subscribe()
  4. watch选项形式,能够以字符串模式监听 $store.state.xxsubscribe 形式,能够调用 store.subscribe(cb), 回调函数接管mutation 对象和 state 对象,这样能够进一步判断 mutation.type 是否是期待的那个,从而进一步做后续解决。
  5. watch形式简略好用,且能获取变动前后值,首选;subscribe办法会被所有 commit 行为触发,因而还须要判断 mutation.type,用起来略繁琐,个别用于vuex 插件中

实际

watch形式

const app = createApp({
    watch: {'$store.state.counter'() {console.log('counter change!');
      }
    }
})

subscribe形式:

store.subscribe((mutation, state) => {if (mutation.type === 'add') {console.log('counter change in subscribe()!');
    }
})

vue3.2 自定义全局指令、部分指令

// 在 src 目录下新建一个 directive 文件,在此文件夹下新建一个 index.js 文件夹,接着输出如下内容
const directives =  (app) => {
  // 这里是给元素获得名字,尽管是 focus,然而理论援用的时候必须以 v 结尾
  app.directive('focus',{
    // 这里的 el 就是获取的元素
    mounted(el) {el.focus() 
     }
  })
}

// 默认导出 directives
export default directives
// 在全局注册 directive
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import directives from './directives'

const app = createApp(App)
directives(app)

app.use(store).use(router).mount('#app')
<!-- 在你须要的页面进行自定义指令的应用 -->
<template>
  <div class="container">
    <div class="content">
      <input type="text"  v-focus>
      内容
    </div>
  </div>
</template>

<script setup>
import {reactive, ref} from 'vue'
// const vMove:Directive = () =>{//}
</script>

vue3.2 setup 语法糖模式下,自定义指令变得及其简略

<input type="text" v-model="value" v-focus>

<script setup>
// 间接写,然而必须是 v 结尾
const vFocus = {mounted(el) {// 获取 input,并调用其 focus()办法
    el.focus()}
}
</script>
<!-- demo 进去页面主动获取焦点,而后让盒子的色彩依据你 input 框输出的内容变色,并且作防抖解决 -->

<template>
  <div class="container">
    <div class="content" v-move="{background: value}">
      内容
      <input type="text" v-model="value" v-focus @keyup="see">
    </div>
  </div>
</template>

<script setup>
import {reactive, ref} from 'vue'
const value = ref('')

const vFocus = {mounted(el) {// 获取 input,并调用其 focus()办法
    el.focus()}
}

let timer = null

const vMove = (el, binding) => {if (timer !== null) {clearTimeout(timer)
  }
  timer = setTimeout(() => {
    el.style.background = binding.value.background
    console.log(el);
  }, 1000);
}

</script>

<style lang="scss" scoped>
.container {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;

  .content {
    border-top: 5px solid black;
    width: 200px;
    height: 200px;
    cursor: pointer;
    border-left: 1px solid #ccc;
    border-right: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
  }
}
</style>

Vue computed 实现

  • 建设与其余属性(如:dataStore)的分割;
  • 属性扭转后,告诉计算属性从新计算

实现时,次要如下

  • 初始化 data,应用 Object.defineProperty 把这些属性全副转为 getter/setter
  • 初始化 computed, 遍历 computed 里的每个属性,每个 computed 属性都是一个 watch 实例。每个属性提供的函数作为属性的 getter,应用 Object.defineProperty 转化。
  • Object.defineProperty getter 依赖收集。用于依赖发生变化时,触发属性从新计算。
  • 若呈现以后 computed 计算属性嵌套其余 computed 计算属性时,先进行其余的依赖收集

Vue 中 diff 算法原理

DOM操作是十分低廉的,因而咱们须要尽量地缩小 DOM 操作。这就须要找出本次 DOM 必须更新的节点来更新,其余的不更新,这个找出的过程,就须要利用 diff 算法

vuediff 算法是平级比拟,不思考跨级比拟的状况。外部采纳 深度递归的形式 + 双指针 (头尾都加指针) 的形式进行比拟。

简略来说,Diff 算法有以下过程

  • 同级比拟,再比拟子节点(依据 keytag标签名判断)
  • 先判断一方有子节点和一方没有子节点的状况 (如果新的children 没有子节点,将旧的子节点移除)
  • 比拟都有子节点的状况(外围diff)
  • 递归比拟子节点
  • 失常 Diff 两个树的工夫复杂度是 O(n^3),但理论状况下咱们很少会进行跨层级的挪动DOM,所以VueDiff进行了优化,从 O(n^3) -> O(n),只有当新旧children 都为多个子节点时才须要用外围的 Diff 算法进行同层级比拟。
  • Vue2的外围 Diff 算法采纳了 双端比拟 的算法,同时从新旧 children 的两端开始进行比拟,借助 key 值找到可复用的节点,再进行相干操作。相比 ReactDiff算法,同样状况下能够缩小挪动节点次数,缩小不必要的性能损耗,更加的优雅
  • 在创立 VNode 时就确定其类型,以及在 mount/patch 的过程中采纳位运算来判断一个 VNode 的类型,在这个根底之上再配合外围的 Diff 算法,使得性能上较 Vue2.x 有了晋升

vue3 中采纳最长递增子序列来实现 diff 优化

答复范例

思路

  • diff算法是干什么的
  • 它的必要性
  • 它何时执行
  • 具体执行形式
  • 拔高:说一下 vue3 中的优化

答复范例

  1. Vue中的 diff 算法称为 patching 算法,它由 Snabbdo m 批改而来,虚构DOM 要想转化为实在 DOM 就须要通过 patch 办法转换
  2. 最后 Vue1.x 视图中每个依赖均有更新函数对应,能够做到精准更新,因而并不需要虚构 DOMpatching算法反对,然而这样粒度过细导致 Vue1.x 无奈承载较大利用;Vue 2.x中为了升高 Watcher 粒度,每个组件只有一个 Watcher 与之对应,此时就须要引入 patching 算法能力准确找到发生变化的中央并高效更新
  3. vuediff 执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行 render 函数取得最新的虚构 DOM,而后执行patc h 函数,并传入新旧两次虚构 DOM,通过比对两者找到变动的中央,最初将其转化为对应的DOM 操作
  4. patch过程是一个递归过程,遵循深度优先、同层比拟的策略;以 vue3patch为例
  5. 首先判断两个节点是否为雷同同类节点,不同则删除从新创立
  6. 如果单方都是文本则更新文本内容
  7. 如果单方都是元素节点则递归更新子元素,同时更新元素属性
  8. 更新子节点时又分了几种状况

    • 新的子节点是文本,老的子节点是数组则清空,并设置文本;
    • 新的子节点是文本,老的子节点是文本则间接更新文本;
    • 新的子节点是数组,老的子节点是文本则清空文本,并创立新子节点数组中的子元素;
    • 新的子节点是数组,老的子节点也是数组,那么比拟两组子节点,更新细节 blabla
  9. vue3中引入的更新策略:动态节点标记等

vdom 中 diff 算法的繁难实现

以下代码只是帮忙大家了解 diff 算法的原理和流程

  1. vdom 转化为实在dom
const createElement = (vnode) => {
  let tag = vnode.tag;
  let attrs = vnode.attrs || {};
  let children = vnode.children || [];
  if(!tag) {return null;}
  // 创立元素
  let elem = document.createElement(tag);
  // 属性
  let attrName;
  for (attrName in attrs) {if(attrs.hasOwnProperty(attrName)) {elem.setAttribute(attrName, attrs[attrName]);
    }
  }
  // 子元素
  children.forEach(childVnode => {
    // 给 elem 增加子元素
    elem.appendChild(createElement(childVnode));
  })

  // 返回实在的 dom 元素
  return elem;
}
  1. 用繁难 diff 算法做更新操作
function updateChildren(vnode, newVnode) {let children = vnode.children || [];
  let newChildren = newVnode.children || [];

  children.forEach((childVnode, index) => {let newChildVNode = newChildren[index];
    if(childVnode.tag === newChildVNode.tag) {
      // 深层次比照, 递归过程
      updateChildren(childVnode, newChildVNode);
    } else {
      // 替换
      replaceNode(childVnode, newChildVNode);
    }
  })
}

</details>

动静给 vue 的 data 增加一个新的属性时会产生什么?怎么解决?

Vue 不容许在曾经创立的实例上动静增加新的响应式属性

若想实现数据与视图同步更新,可采取上面三种解决方案:

  • Vue.set()
  • Object.assign()
  • $forcecUpdated()

Vue.set()

Vue.set(target, propertyName/index, value)

参数

  • {Object | Array} target
  • {string | number} propertyName/index
  • {any} value

返回值:设置的值

通过 Vue.set 向响应式对象中增加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新

对于 Vue.set 源码(省略了很多与本节不相干的代码)

源码地位:src\core\observer\index.js

function set (target: Array<any> | Object, key: any, val: any): any {
  ...
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

这里无非再次调用 defineReactive 办法,实现新增属性的响应式

对于 defineReactive 办法,外部还是通过 Object.defineProperty 实现属性拦挡

大抵代码如下:

function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {get() {console.log(`get ${key}:${val}`);
            return val
        },
        set(newVal) {if (newVal !== val) {console.log(`set ${key}:${newVal}`);
                val = newVal
            }
        }
    })
}

Object.assign()

间接应用 Object.assign() 增加到对象的新属性不会触发更新

应创立一个新的对象,合并原对象和混入对象的属性

this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2 ...})

$forceUpdate

如果你发现你本人须要在 Vue中做一次强制更新,99.9% 的状况,是你在某个中央做错了事

$forceUpdate迫使Vue 实例从新渲染

PS:仅仅影响实例自身和插入插槽内容的子组件,而不是所有子组件。

小结

  • 如果为对象增加大量的新属性,能够间接采纳Vue.set()
  • 如果须要为新对象增加大量的新属性,则通过 Object.assign() 创立新对象
  • 如果你切实不晓得怎么操作时,可采取 $forceUpdate() 进行强制刷新 (不倡议)

PS:vue3是用过 proxy 实现数据响应式的,间接动静增加新属性仍能够实现数据响应式

v-if 和 v -show 区别

  • v-show暗藏则是为该元素增加 css--display:nonedom 元素仍旧还在。v-if显示暗藏是将 dom 元素整个增加或删除
  • 编译过程:v-if切换有一个部分编译 / 卸载的过程,切换过程中适合地销毁和重建外部的事件监听和子组件;v-show只是简略的基于 css 切换
  • 编译条件:v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染
  • v-showfalse 变为 true 的时候不会触发组件的生命周期
  • v-iffalse 变为 true 的时候,触发组件的 beforeCreatecreatebeforeMountmounted 钩子,由 true 变为 false 的时候触发组件的 beforeDestorydestoryed 办法
  • 性能耗费:v-if有更高的切换耗费;v-show有更高的初始渲染耗费

v-show 与 v -if 的应用场景

  • v-ifv-show 都能管制 dom 元素在页面的显示
  • v-if 相比 v-show 开销更大的(间接操作 dom 节 点减少与删除)
  • 如果须要十分频繁地切换,则应用 v-show 较好
  • 如果在运行时条件很少扭转,则应用 v-if 较好

v-show 与 v -if 原理剖析

  1. v-show原理

不论初始条件是什么,元素总是会被渲染

咱们看一下在 vue 中是如何实现的

代码很好了解,有 transition 就执行 transition,没有就间接设置display 属性

// https://github.com/vuejs/vue-next/blob/3cd30c5245da0733f9eb6f29d220f39c46518162/packages/runtime-dom/src/directives/vShow.ts
export const vShow: ObjectDirective<VShowElement> = {beforeMount(el, { value}, {transition}) {
    el._vod = el.style.display === 'none' ? '' : el.style.display
    if (transition && value) {transition.beforeEnter(el)
    } else {setDisplay(el, value)
    }
  },
  mounted(el, { value}, {transition}) {if (transition && value) {transition.enter(el)
    }
  },
  updated(el, { value, oldValue}, {transition}) {// ...},
  beforeUnmount(el, { value}) {setDisplay(el, value)
  }
}
  1. v-if原理

v-if在实现上比 v-show 要简单的多,因为还有else else-if 等条件须要解决,这里咱们也只摘抄源码中解决 v-if 的一小部分

返回一个 node 节点,render函数通过表达式的值来决定是否生成DOM

// https://github.com/vuejs/vue-next/blob/cdc9f336fd/packages/compiler-core/src/transforms/vIf.ts
export const transformIf = createStructuralDirectiveTransform(/^(if|else|else-if)$/,
  (node, dir, context) => {return processIf(node, dir, context, (ifNode, branch, isRoot) => {
      // ...
      return () => {if (isRoot) {
          ifNode.codegenNode = createCodegenNodeForBranch(
            branch,
            key,
            context
          ) as IfConditionalExpression
        } else {
          // attach this branch's codegen node to the v-if root.
          const parentCondition = getParentCondition(ifNode.codegenNode!)
          parentCondition.alternate = createCodegenNodeForBranch(
            branch,
            key + ifNode.branches.length - 1,
            context
          )
        }
      }
    })
  }
)

理解 history 有哪些办法吗?说下它们的区别

history 这个对象在 html5 的时候新退出两个 api history.pushState()history.repalceState() 这两个API 能够在不进行刷新的状况下,操作浏览器的历史纪录。惟一不同的是,前者是新增一个历史记录,后者是间接替换以后的历史记录。

从参数上来说:

window.history.pushState(state,title,url)
//state:须要保留的数据,这个数据在触发 popstate 事件时,能够在 event.state 里获取
//title:题目,根本没用,个别传 null
//url:设定新的历史纪录的 url。新的 url 与以后 url 的 origin 必须是一样的,否则会抛出谬误。url 能够时绝对路径,也能够是相对路径。// 如 以后 url 是 https://www.baidu.com/a/, 执行 history.pushState(null, null, './qq/'),则变成 https://www.baidu.com/a/qq/,// 执行 history.pushState(null, null, '/qq/'),则变成 https://www.baidu.com/qq/

window.history.replaceState(state,title,url)
// 与 pushState 基本相同,但她是批改以后历史纪录,而 pushState 是创立新的历史纪录

另外还有:

  • window.history.back() 后退
  • window.history.forward()后退
  • window.history.go(1) 后退或者后退几步

从触发事件的监听上来说:

  • pushState()replaceState() 不能被 popstate 事件所监听
  • 而前面三者能够,且用户点击浏览器后退后退键时也能够

从 0 到 1 本人构架一个 vue 我的项目,说说有哪些步骤、哪些重要插件、目录构造你会怎么组织

综合实际类题目,考查实战能力。没有什么相对的正确答案,把平时工作的重点有条理的形容一下即可

思路

  • 构建我的项目,创立我的项目根本构造
  • 引入必要的插件:
  • 代码标准:prettiereslint
  • 提交标准:husky,lint-staged`
  • 其余罕用:svg-loadervueusenprogress
  • 常见目录构造

答复范例

  1. 0 创立一个我的项目我大抵会做以下事件:我的项目构建、引入必要插件、代码标准、提交标准、罕用库和组件
  2. 目前 vue3 我的项目我会用 vite 或者 create-vue 创立我的项目
  3. 接下来引入必要插件:路由插件 vue-router、状态治理vuex/piniaui 库我比拟喜爱 element-plu s 和antd-vuehttp 工具我会选axios
  4. 其余比拟罕用的库有vueusenprogress,图标能够应用vite-svg-loader
  5. 上面是代码标准:联合 prettiereslint即可
  6. 最初是提交标准,能够应用huskylint-stagedcommitlint
  7. 目录构造我有如下习惯:.vscode:用来放我的项目中的 vscode 配置
  8. plugins:用来放 vite 插件的 plugin 配置
  9. public:用来放一些诸如 页头 icon 之类的公共文件,会被打包到dist 根目录下
  10. src:用来放我的项目代码文件
  11. api:用来放 http 的一些接口配置
  12. assets:用来放一些 CSS 之类的动态资源
  13. components:用来放我的项目通用组件
  14. layout:用来放我的项目的布局
  15. router:用来放我的项目的路由配置
  16. store:用来放状态治理 Pinia 的配置
  17. utils:用来放我的项目中的工具办法类
  18. views:用来放我的项目的页面文件
正文完
 0