关于前端:滴滴前端高频vue面试题边面边更

36次阅读

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

Vue-router 路由模式有几种

vue-router3 种路由模式:hashhistoryabstract,对应的源码如下所示

switch (mode) {
    case 'history':
    this.history = new HTML5History(this, options.base)
    break
    case 'hash':
    this.history = new HashHistory(this, options.base, this.fallback)
      break
    case 'abstract':
    this.history = new AbstractHistory(this, options.base)
      break
  default:
    if (process.env.NODE_ENV !== 'production') {assert(false, `invalid mode: ${mode}`)
    }
}

其中,3 种路由模式的阐明如下:

  • hash: 应用 URL hash 值来作路由,反对所有浏览器
  • history : 依赖 HTML5 History API 和服务器配置
  • abstract : 反对所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会主动强制进入这个模式.

为什么 Vuex 的 mutation 中不能做异步操作?

  • Vuex 中所有的状态更新的惟一路径都是 mutation,异步操作通过 Action 来提交 mutation 实现,这样能够不便地跟踪每一个状态的变动,从而可能实现一些工具帮忙更好地理解咱们的利用。
  • 每个 mutation 执行实现后都会对应到一个新的状态变更,这样 devtools 就能够打个快照存下来,而后就能够实现 time-travel 了。如果 mutation 反对异步操作,就没有方法晓得状态是何时更新的,无奈很好的进行状态的追踪,给调试带来艰难。

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 寄存共享的变量

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 的性能的加强或补充

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()}

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>

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

为什么要应用异步组件

  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 
}

从 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:用来放我的项目的页面文件

keep-alive 应用场景和原理

  • keep-aliveVue 内置的一个组件,能够实现组件缓存 ,当组件切换时不会对以后组件进行卸载。 个别联合路由和动静组件一起应用,用于缓存组件
  • 提供 includeexclude 属性,容许组件有条件的进行缓存。两者都反对字符串或正则表达式,include 示意只有名称匹配的组件会被缓存,exclude 示意任何名称匹配的组件都不会被缓存,其中 exclude 的优先级比 include
  • 对应两个钩子函数 activateddeactivated,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated
  • keep-alive 的中还使用了 LRU(最近起码应用) 算法,抉择最近最久未应用的组件予以淘汰
  • <keep-alive></keep-alive> 包裹动静组件时,会缓存不流动的组件实例, 次要用于保留组件状态或防止从新渲染
  • 比方有一个列表和一个详情,那么用户就会常常执行关上详情 => 返回列表 => 关上详情…这样的话列表和详情都是一个频率很高的页面,那么就能够对列表组件应用 <keep-alive></keep-alive> 进行缓存,这样用户每次返回列表的时候,都能从缓存中疾速渲染,而不是从新渲染

对于 keep-alive 的根本用法

<keep-alive>
  <component :is="view"></component>
</keep-alive>

应用 includesexclude

<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>

<!-- 正则表达式 (应用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>

<!-- 数组 (应用 `v-bind`) -->
<keep-alive :include="['a','b']">
  <component :is="view"></component>
</keep-alive>

匹配首先查看组件本身的 name 选项,如果 name 选项不可用,则匹配它的部分注册名称 (父组件 components 选项的键值),匿名组件不能被匹配

设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activateddeactivated):

  • 首次进入组件时:beforeRouteEnter > beforeCreate > created> mounted > activated > … … > beforeRouteLeave > deactivated
  • 再次进入组件时:beforeRouteEnter >activated > … … > beforeRouteLeave > deactivated

应用场景

应用准则:当咱们在某些场景下不须要让页面从新加载时咱们能够应用keepalive

举个栗子:

当咱们从 首页 –> 列表页 –> 商详页 –> 再返回,这时候列表页应该是须要keep-alive

首页 –> 列表页 –> 商详页 –> 返回到列表页 (须要缓存)–> 返回到首页 (须要缓存)–> 再次进入列表页(不须要缓存),这时候能够按需来管制页面的keep-alive

在路由中设置 keepAlive 属性判断是否须要缓存

{
  path: 'list',
  name: 'itemList', // 列表页
  component (resolve) {require(['@/pages/item/list'], resolve)
 },
 meta: {
  keepAlive: true,
  title: '列表页'
 }
}

应用<keep-alive>

<div id="app" class='wrapper'>
    <keep-alive>
        <!-- 须要缓存的视图组件 --> 
        <router-view v-if="$route.meta.keepAlive"></router-view>
     </keep-alive>
      <!-- 不须要缓存的视图组件 -->
     <router-view v-if="!$route.meta.keepAlive"></router-view>
</div>

思考题:缓存后如何获取数据

解决方案能够有以下两种:

  • beforeRouteEnter:每次组件渲染的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){
    next(vm=>{console.log(vm)
        // 每次进入路由执行
        vm.getData()  // 获取数据})
},
  • actived:在 keep-alive 缓存的组件被激活的时候,都会执行 actived 钩子
// 留神:服务器端渲染期间 avtived 不被调用
activated(){this.getData() // 获取数据
},

扩大补充:LRU 算法是什么?

LRU 的核心思想是如果数据最近被拜访过,那么未来被拜访的几率也更高,所以咱们将命中缓存的组件 key 从新插入到 this.keys 的尾部,这样一来,this.keys 中越往头部的数据行将来被拜访几率越低,所以当缓存数量达到最大值时,咱们就删除未来被拜访几率最低的数据,即 this.keys 中第一个缓存的组件

相干代码

keep-alivevue 中内置的一个组件

源码地位:src/core/components/keep-alive.js

export default {
  name: "keep-alive",
  abstract: true, // 形象组件

  props: {
    include: patternTypes, // 要缓存的组件
    exclude: patternTypes, // 要排除的组件
    max: [String, Number], // 最大缓存数
  },

  created() {this.cache = Object.create(null); // 缓存对象  {a:vNode,b:vNode}
    this.keys = []; // 缓存组件的 key 汇合 [a,b]
  },

  destroyed() {for (const key in this.cache) {pruneCacheEntry(this.cache, key, this.keys);
    }
  },

  mounted() {
    // 动静监听 include  exclude
    this.$watch("include", (val) => {pruneCache(this, (name) => matches(val, name));
    });
    this.$watch("exclude", (val) => {pruneCache(this, (name) => !matches(val, name));
    });
  },

  render() {
    const slot = this.$slots.default; // 获取包裹的插槽默认值 获取默认插槽中的第一个组件节点
    const vnode: VNode = getFirstComponentChild(slot); // 获取第一个子组件
    // 获取该组件节点的 componentOptions
    const componentOptions: ?VNodeComponentOptions =
      vnode && vnode.componentOptions;
    if (componentOptions) {
      // 获取该组件节点的名称,优先获取组件的 name 字段,如果 name 不存在则获取组件的 tag
      const name: ?string = getComponentName(componentOptions);
      const {include, exclude} = this;
      // 不走缓存 如果 name 不在 inlcude 中或者存在于 exlude 中则示意不缓存,间接返回 vnode
      if (
        // not included  不蕴含
        (include && (!name || !matches(include, name))) ||
        // excluded  排除外面
        (exclude && name && matches(exclude, name))
      ) {
        // 返回虚构节点
        return vnode;
      }

      const {cache, keys} = this;
      // 获取组件的 key 值
      const key: ?string =
        vnode.key == null
          ? // same constructor may get registered as different local components
            // so cid alone is not enough (#3269)
            componentOptions.Ctor.cid +
            (componentOptions.tag ? `::${componentOptions.tag}` : "")
          : vnode.key;
      // 拿到 key 值后去 this.cache 对象中去寻找是否有该值,如果有则示意该组件有缓存,即命中缓存
      if (cache[key]) {
        // 通过 key 找到缓存 获取实例
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        remove(keys, key); // 通过 LRU 算法把数组外面的 key 删掉
        keys.push(key); // 把它放在数组开端
      } else {cache[key] = vnode; // 没找到就换存下来
        keys.push(key); // 把它放在数组开端
        // prune oldest entry  // 如果超过最大值就把数组第 0 项删掉
        if (this.max && keys.length > parseInt(this.max)) {pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }

      vnode.data.keepAlive = true; // 标记虚构节点曾经被缓存
    }
    // 返回虚构节点
    return vnode || (slot && slot[0]);
  },
};

能够看到该组件没有 template,而是用了render,在组件渲染的时候会主动执行render 函数

this.cache是一个对象,用来存储须要缓存的组件,它将以如下模式存储:

this.cache = {
  'key1':'组件 1',
  'key2':'组件 2',
  // ...
}

在组件销毁的时候执行 pruneCacheEntry 函数

function pruneCacheEntry (
  cache: VNodeCache,
  key: string,
  keys: Array<string>,
  current?: VNode
) {const cached = cache[key]
  /* 判断以后没有处于被渲染状态的组件,将其销毁 */
  if (cached && (!current || cached.tag !== current.tag)) {cached.componentInstance.$destroy()
  }
  cache[key] = null
  remove(keys, key)
}

mounted 钩子函数中观测 includeexclude 的变动,如下:

mounted () {
  this.$watch('include', val => {pruneCache(this, name => matches(val, name))
  })
  this.$watch('exclude', val => {pruneCache(this, name => !matches(val, name))
  })
}

如果 includeexclude 产生了变动,即示意定义须要缓存的组件的规定或者不须要缓存的组件的规定产生了变动,那么就执行pruneCache 函数,函数如下

function pruneCache (keepAliveInstance, filter) {const { cache, keys, _vnode} = keepAliveInstance
  for (const key in cache) {const cachedNode = cache[key]
    if (cachedNode) {const name = getComponentName(cachedNode.componentOptions)
      if (name && !filter(name)) {pruneCacheEntry(cache, key, keys, _vnode)
      }
    }
  }
}

在该函数内对 this.cache 对象进行遍历,取出每一项的 name 值,用其与新的缓存规定进行匹配,如果匹配不上,则示意在新的缓存规定下该组件曾经不须要被缓存,则调用 pruneCacheEntry 函数将其从 this.cache 对象剔除即可

对于 keep-alive 的最弱小缓存性能是在 render 函数中实现

首先获取组件的 key 值:

const key = vnode.key == null? 
componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key

拿到 key 值后去 this.cache 对象中去寻找是否有该值,如果有则示意该组件有缓存,即命中缓存,如下:

/* 如果命中缓存,则间接从缓存中拿 vnode 的组件实例 */
if (cache[key]) {vnode.componentInstance = cache[key].componentInstance
    /* 调整该组件 key 的程序,将其从原来的中央删掉并从新放在最初一个 */
    remove(keys, key)
    keys.push(key)
} 

间接从缓存中拿 vnode 的组件实例,此时从新调整该组件 key 的程序,将其从原来的中央删掉并从新放在 this.keys 中最初一个

this.cache对象中没有该 key 值的状况,如下:

/* 如果没有命中缓存,则将其设置进缓存 */
else {cache[key] = vnode
    keys.push(key)
    /* 如果配置了 max 并且缓存的长度超过了 this.max,则从缓存中删除第一个 */
    if (this.max && keys.length > parseInt(this.max)) {pruneCacheEntry(cache, keys[0], keys, this._vnode)
    }
}

表明该组件还没有被缓存过,则以该组件的 key 为键,组件 vnode 为值,将其存入 this.cache 中,并且把 key 存入 this.keys

此时再判断 this.keys 中缓存组件的数量是否超过了设置的最大缓存数量值this.max,如果超过了,则把第一个缓存组件删掉

vue-router 动静路由是什么

咱们常常须要把某种模式匹配到的所有路由,全都映射到同个组件。例如,咱们有一个 User 组件,对于所有 ID 各不相同的用户,都要应用这个组件来渲染。那么,咱们能够在 vue-router 的路由门路中应用“动静门路参数”(dynamic segment) 来达到这个成果

const User = {template: "<div>User</div>",};

const router = new VueRouter({
  routes: [
    // 动静门路参数 以冒号结尾
    {path: "/user/:id", component: User},
  ],
});

问题: vue-router 组件复用导致路由参数生效怎么办?

解决办法:

  1. 通过 watch 监听路由参数再发申请
watch: { // 通过 watch 来监听路由变动
 "$route": function(){this.getData(this.$route.params.xxx);
 }
}
  1. :key 来阻止“复用”
<router-view :key="$route.fullPath" />

答复范例

  1. 很多时候,咱们须要将给定匹配模式的路由映射到同一个组件,这种状况就须要定义动静路由
  2. 例如,咱们可能有一个 User 组件,它应该对所有用户进行渲染,但用户 ID 不同。在 Vue Router中,咱们能够在门路中应用一个动静字段来实现,例如:{path: '/users/:id', component: User},其中 :id 就是门路参数
  3. 门路参数 用冒号 : 示意。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params 的模式裸露进去。
  4. 参数还能够有多个,例如 /users/:username/posts/:postId;除了 $route.params 之外,$route 对象还公开了其余有用的信息,如 $route.query$route.hash

diff 算法

工夫复杂度: 个树的齐全 diff 算法是一个工夫复杂度为 O(n*3),vue 进行优化转化成 O(n)

了解:

  • 最小量更新, key 很重要。这个能够是这个节点的惟一标识,通知 diff 算法,在更改前后它们是同一个 DOM 节点

    • 扩大 v-for 为什么要有 key,没有 key 会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改 DOM),加 key 只会挪动缩小操作 DOM。
  • 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。
  • 只进行同层比拟,不会进行跨层比拟。

diff 算法的优化策略:四种命中查找,四个指针

  1. 旧前与新前(先比结尾,后插入和删除节点的这种状况)
  2. 旧后与新后(比结尾,前插入或删除的状况)
  3. 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)
  4. 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)

Class 与 Style 如何动静绑定

Class 能够通过对象语法和数组语法进行动静绑定

对象语法:

<div v-bind:class="{active: isActive,'text-danger': hasError}"></div>

data: {
  isActive: true,
  hasError: false
}

数组语法:

<div v-bind:class="[isActive ? activeClass :'', errorClass]"></div>

data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}

Style 也能够通过对象语法和数组语法进行动静绑定

对象语法:

<div v-bind:style="{color: activeColor, fontSize: fontSize +'px'}"></div>

data: {
  activeColor: 'red',
  fontSize: 30
}

数组语法:

<div v-bind:style="[styleColor, styleSize]"></div>

data: {
  styleColor: {color: 'red'},
  styleSize:{fontSize:'23px'}
}

Vue template 到 render 的过程

vue 的模版编译过程次要如下:template -> ast -> render 函数

vue 在模版编译版本的码中会执行 compileToFunctions 将 template 转化为 render 函数:

// 将模板编译为 render 函数 const {render, staticRenderFns} = compileToFunctions(template,options// 省略}, this)

CompileToFunctions 中的次要逻辑如下∶ (1)调用 parse 办法将 template 转化为 ast(形象语法树)

constast = parse(template.trim(), options)
  • parse 的指标:把 tamplate 转换为 AST 树,它是一种用 JavaScript 对象的模式来形容整个模板。
  • 解析过程:利用正则表达式程序解析模板,当解析到开始标签、闭合标签、文本的时候都会别离执行对应的 回调函数,来达到结构 AST 树的目标。

AST 元素节点总共三种类型:type 为 1 示意一般元素、2 为表达式、3 为纯文本

(2)对动态节点做优化

optimize(ast,options)

这个过程次要剖析出哪些是动态节点,给其打一个标记,为后续更新渲染能够间接跳过动态节点做优化

深度遍历 AST,查看每个子树的节点元素是否为动态节点或者动态节点根。如果为动态节点,他们生成的 DOM 永远不会扭转,这对运行时模板更新起到了极大的优化作用。

(3)生成代码

const code = generate(ast, options)

generate 将 ast 形象语法树编译成 render 字符串并将动态局部放到 staticRenderFns 中,最初通过 new Function(` render`) 生成 render 函数。

什么是 mixin?

  • Mixin 使咱们可能为 Vue 组件编写可插拔和可重用的性能。
  • 如果心愿在多个组件之间重用一组组件选项,例如生命周期 hook、办法等,则能够将其编写为 mixin,并在组件中简略的援用它。
  • 而后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。

Vue2.x 响应式数据原理

整体思路是数据劫持 + 观察者模式

对象外部通过 defineReactive 办法,应用 Object.defineProperty 来劫持各个属性的 settergetter(只会劫持曾经存在的属性),数组则是通过 重写数组 7 个办法 来实现。当页面应用对应属性时,每个属性都领有本人的 dep 属性,寄存他所依赖的 watcher(依赖收集),当属性变动后会告诉本人对应的 watcher 去更新(派发更新)

Object.defineProperty 根本应用

function observer(value) { // proxy reflect
    if (typeof value === 'object' && typeof value !== null)
    for (let key in value) {defineReactive(value, key, value[key]);
    }
}

function defineReactive(obj, key, value) {observer(value);
    Object.defineProperty(obj, key, {get() { // 收集对应的 key 在哪个办法(组件)中被应用
            return value;
        },
        set(newValue) {if (newValue !== value) {observer(newValue);
                value = newValue; // 让 key 对应的办法(组件从新渲染)从新执行
            }
        }
    })
}
let obj1 = {school: { name: 'poetry', age: 20} };
observer(obj1);
console.log(obj1)

源码剖析

class Observer {
  // 观测值
  constructor(value) {this.walk(value);
  }
  walk(data) {
    // 对象上的所有属性顺次进行观测
    let keys = Object.keys(data);
    for (let i = 0; i < keys.length; i++) {let key = keys[i];
      let value = data[key];
      defineReactive(data, key, value);
    }
  }
}
// Object.defineProperty 数据劫持外围 兼容性在 ie9 以及以上
function defineReactive(data, key, value) {observe(value); // 递归要害
  // -- 如果 value 还是一个对象会持续走一遍 odefineReactive 层层遍历始终到 value 不是对象才进行
  //   思考?如果 Vue 数据嵌套层级过深 >> 性能会受影响
  Object.defineProperty(data, key, {get() {console.log("获取值");

      // 须要做依赖收集过程 这里代码没写进去
      return value;
    },
    set(newValue) {if (newValue === value) return;
      console.log("设置值");
      // 须要做派发更新过程 这里代码没写进去
      value = newValue;
    },
  });
}
export function observe(value) {
  // 如果传过来的是对象或者数组 进行属性劫持
  if (Object.prototype.toString.call(value) === "[object Object]" ||
    Array.isArray(value)
  ) {return new Observer(value);
  }
}

说一说你对 vue 响应式了解答复范例

  • 所谓数据响应式就是 可能使数据变动能够被检测并对这种变动做出响应的机制
  • MVVM框架中要解决的一个外围问题是连贯数据层和视图层,通过 数据驱动 利用,数据变动,视图更新,要做到这点的就须要对数据做响应式解决,这样一旦数据发生变化就能够立刻做出更新解决
  • vue 为例阐明,通过数据响应式加上虚构 DOMpatch算法,开发人员只须要操作数据,关怀业务,齐全不必接触繁琐的 DOM 操作,从而大大晋升开发效率,升高开发难度
  • vue2中的数据响应式会依据数据类型来做不同解决,如果是 对象则采纳 Object.defineProperty() 的形式定义数据拦挡,当数据被拜访或发生变化时,咱们感知并作出响应;如果是数组则通过笼罩数组对象原型的 7 个变更办法 ,使这些办法能够额定的做更新告诉,从而作出响应。这种机制很好的解决了数据响应化的问题,但在理论应用中也存在一些毛病:比方初始化时的递归遍历会造成性能损失;新增或删除属性时须要用户应用Vue.set/delete 这样非凡的 api 能力失效;对于 es6 中新产生的 MapSet 这些数据结构不反对等问题
  • 为了解决这些问题,vue3从新编写了这一部分的实现:利用 ES6Proxy代理要响应化的数据,它有很多益处,编程体验是统一的,不须要应用非凡 api,初始化性能和内存耗费都失去了大幅改善;另外因为响应化的实现代码抽取为独立的reactivity 包,使得咱们能够更灵便的应用它,第三方的扩大开发起来更加灵便了

vue 和 react 的区别

=> 相同点:

1. 数据驱动页面,提供响应式的试图组件
2. 都有 virtual DOM, 组件化的开发,通过 props 参数进行父子之间组件传递数据,都实现了 webComponents 标准
3. 数据流动单向,都反对服务器的渲染 SSR
4. 都有反对 native 的办法,react 有 React native,vue 有 wexx

=> 不同点:

 1. 数据绑定:Vue 实现了双向的数据绑定,react 数据流动是单向的
 2. 数据渲染:大规模的数据渲染,react 更快
 3. 应用场景:React 配合 Redux 架构适宜大规模多人合作简单我的项目,Vue 适宜小快的我的项目
 4. 开发格调:react 举荐做法 jsx + inline style 把 html 和 css 都写在 js 了
      vue 是采纳 webpack + vue-loader 单文件组件格局,html, js, css 同一个文件

Vue-router 路由有哪些模式?

个别有两种模式:

(1)**hash 模式 **:前面的 hash 值的变动,浏览器既不会向服务器发出请求,浏览器也不会刷新,每次 hash 值的变动会触发 hashchange 事件。(2)**history 模式 **:利用了 HTML5 中新增的 pushState() 和 replaceState() 办法。这两个办法利用于浏览器的历史记录栈,在以后已有的 back、forward、go 的根底之上,它们提供了对历史记录进行批改的性能。只是当它们执行批改时,尽管扭转了以后的 URL,但浏览器不会立刻向后端发送申请。

Vue.extend 作用和原理

官网解释:Vue.extend 应用根底 Vue 结构器,创立一个“子类”。参数是一个蕴含组件选项的对象。

其实就是一个子类结构器 是 Vue 组件的外围 api 实现思路就是应用原型继承的办法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并

  • extend是结构一个组件的语法器。而后这个组件你能够作用到 Vue.component 这个全局注册办法里还能够在任意 vue 模板里应用组件。也能够作用到 vue 实例或者某个组件中的 components 属性中并在外部应用 apple 组件。
  • Vue.component你能够创立,也能够取组件。

相干代码如下

export default function initExtend(Vue) {
  let cid = 0; // 组件的惟一标识
  // 创立子类继承 Vue 父类 便于属性扩大
  Vue.extend = function (extendOptions) {
    // 创立子类的构造函数 并且调用初始化办法
    const Sub = function VueComponent(options) {this._init(options); // 调用 Vue 初始化办法
    };
    Sub.cid = cid++;
    Sub.prototype = Object.create(this.prototype); // 子类原型指向父类
    Sub.prototype.constructor = Sub; //constructor 指向本人
    Sub.options = mergeOptions(this.options, extendOptions); // 合并本人的 options 和父类的 options
    return Sub;
  };
}

v-once 的应用场景有哪些

剖析

v-onceVue 中内置指令,很有用的API,在优化方面常常会用到

体验

仅渲染元素和组件一次,并且跳过将来更新

<!-- 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-oncevue 的内置指令,作用是仅渲染指定组件或元素一次,并跳过将来对其更新
  • 如果咱们有一些元素或者组件在初始化渲染之后不再须要变动,这种状况下适宜应用 v-once,这样哪怕这些数据变动,vue 也会跳过更新,是一种代码优化伎俩
  • 咱们只须要作用的组件或元素上加上 v-once 即可
  • vue3.2之后,又减少了 v-memo 指令,能够有条件缓存局部模板并管制它们的更新,能够说控制力更强了
  • 编译器发现元素下面有 v-once 时,会将首次计算结果存入缓存对象,组件再次渲染时就会从缓存获取,从而防止再次计算

原理

上面例子应用了v-once

<script setup>
import {ref} from 'vue'
​
const msg = ref('Hello World!')
</script>
​
<template>
  <h1 v-once>{{msg}}</h1>
  <input v-model="msg">
</template>

咱们发现 v-once 呈现后,编译器会缓存作用元素或组件,从而防止当前更新时从新计算这一部分:

// ...
return (_ctx, _cache) => {return (_openBlock(), _createElementBlock(_Fragment, null, [
    // 从缓存获取 vnode
    _cache[0] || (_setBlockTracking(-1),
      _cache[0] = _createElementVNode("h1", null, [_createTextVNode(_toDisplayString(msg.value), 1 /* TEXT */)
      ]),
      _setBlockTracking(1),
      _cache[0]
    ),
// ...

Vue 组件间通信有哪几种形式?

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

(1)props / $emit 实用 父子组件通信 这种办法是 Vue 组件的根底,置信大部分同学耳闻能详,所以此处就不举例开展介绍。

(2)ref 与 $parent / $children 实用 父子组件通信

  • ref:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例
  • $parent / $children:拜访父 / 子实例

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

(4)$attrs/$listeners 实用于 隔代组件通信

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

(5)provide / inject 实用于 隔代组件通信 先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。provide / inject API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系。(6)Vuex 实用于 父子、隔代、兄弟组件通信 Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state)。

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
  • 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。

正文完
 0