乐趣区

关于vue.js:百度前端常考vue面试题附答案

怎么实现路由懒加载呢

这是一道应用题。当打包利用时,JavaScript 包会变得十分大,影响页面加载。如果咱们能把不同路由对应的组件宰割成不同的代码块,而后当路由被拜访时才加载对应组件,这样就会更加高效

// 将
// import UserDetails from './views/UserDetails'
// 替换为
const UserDetails = () => import('./views/UserDetails')
​
const router = createRouter({
  // ...
  routes: [{path: '/users/:id', component: UserDetails}],
})

答复范例

  1. 当打包构建利用时,JavaScript 包会变得十分大,影响页面加载。利用路由懒加载咱们能把不同路由对应的组件宰割成不同的代码块,而后当路由被拜访的时候才加载对应组件,这样会更加高效,是一种优化伎俩
  2. 一般来说,对所有的 路由都应用动静导入 是个好主见
  3. component 选项配置一个返回 Promise 组件的函数就能够定义懒加载路由。例如:{path: '/users/:id', component: () => import('./views/UserDetails') }
  4. 联合正文 () => import(/* webpackChunkName: "group-user" */ './UserDetails.vue') 能够做 webpack 代码分块

Vue complier 实现

  • 模板解析这种事,实质是将数据转化为一段 html,最开始呈现在后端,通过各种解决吐给前端。随着各种 mv* 的衰亡,模板解析交由前端解决。
  • 总的来说,Vue complier 是将 template 转化成一个 render 字符串。

能够简略了解成以下步骤:

  • parse 过程,将 template 利用正则转化成AST 形象语法树。
  • optimize 过程,标记动态节点,后 diff 过程跳过动态节点,晋升性能。
  • generate 过程,生成 render 字符串

assets 和 static 的区别

相同点: assetsstatic 两个都是寄存动态资源文件。我的项目中所须要的资源文件图片,字体图标,款式文件等都能够放在这两个文件下,这是相同点

不相同点:assets 中寄存的动态资源文件在我的项目打包时,也就是运行 npm run build 时会将 assets 中搁置的动态资源文件进行打包上传,所谓打包简略点能够了解为压缩体积,代码格式化。而压缩后的动态资源文件最终也都会搁置在 static 文件中跟着 index.html 一起上传至服务器。static 中搁置的动态资源文件就不会要走打包压缩格式化等流程,而是间接进入打包好的目录,间接上传至服务器。因为防止了压缩间接进行上传,在打包时会进步肯定的效率,然而 static 中的资源文件因为没有进行压缩等操作,所以文件的体积也就绝对于 assets 中打包后的文件提交较大点。在服务器中就会占据更大的空间。

倡议: 将我的项目中 template须要的款式文件 js 文件等都能够搁置在 assets 中,走打包这一流程。缩小体积。而我的项目中引入的第三方的资源文件如iconfoont.css 等文件能够搁置在 static 中,因为这些引入的第三方文件曾经通过解决,不再须要解决,间接上传。

Vue 的性能优化有哪些

(1)编码阶段

  • 尽量减少 data 中的数据,data 中的数据都会减少 getter 和 setter,会收集对应的 watcher
  • v-if 和 v -for 不能连用
  • 如果须要应用 v -for 给每项元素绑定事件时应用事件代理
  • SPA 页面采纳 keep-alive 缓存组件
  • 在更多的状况下,应用 v -if 代替 v -show
  • key 保障惟一
  • 应用路由懒加载、异步组件
  • 防抖、节流
  • 第三方模块按需导入
  • 长列表滚动到可视区域动静加载
  • 图片懒加载

(2)SEO 优化

  • 预渲染
  • 服务端渲染 SSR

(3)打包优化

  • 压缩代码
  • Tree Shaking/Scope Hoisting
  • 应用 cdn 加载第三方模块
  • 多线程打包 happypack
  • splitChunks 抽离公共文件
  • sourceMap 优化

(4)用户体验

  • 骨架屏
  • PWA
  • 还能够应用缓存 (客户端缓存、服务端缓存) 优化、服务端开启 gzip 压缩等。

v-show 与 v-if 有什么区别?

v-if 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是 惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简略得多——不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS 的“display”属性进行切换。

所以,v-if 实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show 则实用于须要十分频繁切换条件的场景。

v-model 能够被用在自定义组件上吗?如果能够,如何应用?

能够。v-model 实际上是一个语法糖,如:

<input v-model="searchText">

实际上相当于:

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

用在自定义组件上也是同理:

<custom-input v-model="searchText">

相当于:

<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>

显然,custom-input 与父组件的交互如下:

  1. 父组件将 searchText 变量传入 custom-input 组件,应用的 prop 名为value
  2. custom-input 组件向父组件传出名为 input 的事件,父组件将接管到的值赋值给searchText

所以,custom-input 组件的实现应该相似于这样:

Vue.component('custom-input', {props: ['value'],
  template: `    <input      v-bind:value="value"      v-on:input="$emit('input', $event.target.value)"    >  `
})

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

v-model 是如何实现的,语法糖理论是什么?

(1)作用在表单元素上 动静绑定了 input 的 value 指向了 messgae 变量,并且在触发 input 事件的时候去动静把 message 设置为目标值:

<input v-model="sth" />
//  等同于
<input     v-bind:value="message"     v-on:input="message=$event.target.value"
>
//$event 指代以后触发的事件对象;//$event.target 指代以后触发的事件对象的 dom;//$event.target.value 就是以后 dom 的 value 值;// 在 @input 办法中,value => sth;// 在:value 中,sth => value;

(2)作用在组件上 在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件

实质是一个父子组件通信的语法糖,通过 prop 和 $.emit 实现。 因而父组件 v-model 语法糖实质上能够批改为:

<child :value="message"  @input="function(e){message = e}"></child>

在组件的实现中,能够通过 v-model 属性来配置子组件接管的 prop 名称,以及派发的事件名称。
例子:

// 父组件
<aa-input v-model="aa"></aa-input>
// 等价于
<aa-input v-bind:value="aa" v-on:input="aa=$event.target.value"></aa-input>

// 子组件:<input v-bind:value="aa" v-on:input="onmessage"></aa-input>

props:{value:aa,}
methods:{onmessage(e){$emit('input',e.target.value)
    }
}

默认状况下,一个组件上的 v -model 会把 value 用作 prop 且把 input 用作 event。然而一些输出类型比方单选框和复选框按钮可能想应用 value prop 来达到不同的目标。应用 model 选项能够回避这些状况产生的抵触。js 监听 input 输入框输出数据扭转,用 oninput,数据扭转当前就会立即登程这个事件。通过 input 事件把数据 $emit 进来,在父组件承受。父组件设置 v -model 的值为 input $emit过去的值。

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。这样使得咱们能够不便地跟踪每一个状态的变动。

Vue 中的 key 到底有什么用?

key 是给每一个 vnode 的惟一 id, 依附 key, 咱们的 diff 操作能够更精确、更疾速 (对于简略列表页渲染来说 diff 节点也更快, 但会产生一些暗藏的副作用, 比方可能不会产生过渡成果, 或者在某些节点有绑定数据(表单)状态,会呈现状态错位。)

diff 算法的过程中, 先会进行新旧节点的首尾穿插比照, 当无奈匹配的时候会用新节点的 key 与旧节点进行比对, 从而找到相应旧节点.

更精确 : 因为带 key 就不是就地复用了, 在 sameNode 函数 a.key === b.key 比照中能够防止就地复用的状况。所以会更加精确, 如果不加 key, 会导致之前节点的状态被保留下来, 会产生一系列的 bug。

更疾速 : key 的唯一性能够被 Map 数据结构充分利用, 相比于遍历查找的工夫复杂度 O(n),Map 的工夫复杂度仅仅为 O(1)

Vue computed 实现

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

实现时,次要如下

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

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

Vue-router 除了 router-link 怎么实现跳转

申明式导航

<router-link to="/about">Go to About</router-link>

编程式导航

// literal string path
router.push('/users/1')
​
// object with path
router.push({path: '/users/1'})
​
// named route with params to let the router build the url
router.push({name: 'user', params: { username: 'test'} })

答复范例

  • vue-router导航有两种形式:申明式导航和编程形式导航
  • 申明式导航形式应用 router-link 组件,增加 to 属性导航;编程形式导航更加灵便,可传递调用 router.push(),并传递path 字符串或者 RouteLocationRaw 对象,指定 pathnameparams 等信息
  • 如果页面中简略示意跳转链接,应用 router-link 最快捷,会渲染一个 a 标签;如果页面是个简单的内容,比方商品信息,能够增加点击事件,应用编程式导航
  • 实际上外部两者调用的导航函数是一样的

ref 和 reactive 异同

这是 Vue3 数据响应式中十分重要的两个概念,跟咱们写代码关系也很大

const count = ref(0)
console.log(count.value) // 0
​
count.value++
console.log(count.value) // 1

const obj = reactive({count: 0})
obj.count++
  • ref接管外部值(inner value)返回响应式 Ref 对象,reactive返回响应式代理对象
  • 从定义上看 ref 通常用于解决单值的响应式,reactive用于解决对象类型的数据响应式
  • 两者均是用于结构响应式数据,然而 ref 次要解决原始值的响应式问题
  • ref返回的响应式数据在 JS 中应用须要加上 .value 能力拜访其值,在视图中应用会主动脱 ref,不须要.valueref 能够接管对象或数组等非原始值,但外部仍然是 reactive 实现响应式;reactive外部如果接管 Re f 对象会主动脱ref;应用开展运算符(...) 开展 reactive 返回的响应式对象会使其失去响应性,能够联合 toRefs() 将值转换为 Ref 对象之后再开展。
  • reactive外部应用 Proxy 代理传入对象并拦挡该对象各种操作,从而实现响应式。ref外部封装一个 RefImpl 类,并设置get value/set value,拦挡用户对值的拜访,从而实现响应式

异步组件是什么?应用场景有哪些?

剖析

因为异步路由的存在,咱们应用异步组件的次数比拟少,因而还是有必要两者的不同。

体验

大型利用中,咱们须要宰割利用为更小的块,并且在须要组件时再加载它们

import {defineAsyncComponent} from 'vue'
// defineAsyncComponent 定义异步组件,返回一个包装组件。包装组件依据加载器的状态决定渲染什么内容
const AsyncComp = defineAsyncComponent(() => {
  // 加载函数返回 Promise
  return new Promise((resolve, reject) => {
    // ... 能够从服务器加载组件
    resolve(/* loaded component */)
  })
})
// 借助打包工具实现 ES 模块动静导入
const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

答复范例

  1. 在大型利用中,咱们须要宰割利用为更小的块,并且在须要组件时再加载它们。
  2. 咱们不仅能够在路由切换时懒加载组件,还能够在页面组件中持续应用异步组件,从而实现更细的宰割粒度。
  3. 应用异步组件最简略的形式是间接给 defineAsyncComponent 指定一个 loader 函数,联合 ES 模块动静导入函数 import 能够疾速实现。咱们甚至能够指定 loadingComponenterrorComponent选项从而给用户一个很好的加载反馈。另外 Vue3 中还能够联合 Suspense 组件应用异步组件。
  4. 异步组件容易和路由懒加载混同,实际上不是一个货色。异步组件不能被用于定义懒加载路由上,解决它的是 vue 框架,解决路由组件加载的是vue-router。然而能够在懒加载的路由组件中应用异步组件

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

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

组件中写 name 属性的益处

能够标识组件的具体名称不便调试和查找对应属性

// 源码地位 src/core/global-api/extend.js

// enable recursive self-lookup
if (name) {Sub.options.components[name] = Sub // 记录本人 在组件中递归本人  -> jsx
}

keep-alive 应用场景和原理

keep-alive 是 Vue 内置的一个组件,能够实现组件缓存,当组件切换时不会对以后组件进行卸载。

  • 罕用的两个属性 include/exclude,容许组件有条件的进行缓存。
  • 两个生命周期 activated/deactivated,用来得悉以后组件是否处于沉闷状态。
  • keep-alive 的中还使用了 LRU(最近起码应用) 算法,抉择最近最久未应用的组件予以淘汰。

v-if 和 v -for 哪个优先级更高

  • 实际中不应该把 v-forv-if放一起
  • vue2 中,v-for的优先级是高于 v-if,把它们放在一起,输入的渲染函数中能够看出会先执行循环再判断条件,哪怕咱们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比拟节约;另外须要留神的是在vue3 中则齐全相同,v-if的优先级高于 v-for,所以v-if 执行时,它调用的变量还不存在,就会导致异样
  • 通常有两种状况下导致咱们这样做:

    • 为了过滤列表中的我的项目 (比方 v-for="user in users" v-if="user.isActive")。此时定义一个计算属性 (比方 activeUsers),让其返回过滤后的列表即可(比方users.filter(u=>u.isActive)
    • 为了防止渲染本应该被暗藏的列表 (比方 v-for="user in users" v-if="shouldShowUsers")。此时把 v-if 挪动至容器元素上 (比方 ulol)或者外面包一层 template 即可
  • 文档中明确指出永远不要把 v-ifv-for 同时用在同一个元素上,显然这是一个重要的注意事项
  • 源码外面对于代码生成的局部,可能清晰的看到是先解决 v-if 还是 v-for,程序上vue2vue3正好相同,因而产生了一些症状的不同,然而不管怎样都是不能把它们写在一起的

vue2.x 源码剖析

在 vue 模板编译的时候,会将指令系统转化成可执行的 render 函数

编写一个 p 标签,同时应用 v-ifv-for

<div id="app">
  <p v-if="isShow" v-for="item in items">
    {{item.title}}
  </p>
</div>

创立 vue 实例,寄存 isShowitems数据

const app = new Vue({
  el: "#app",
  data() {
    return {
      items: [{ title: "foo"},
        {title: "baz"}]
    }
  },
  computed: {isShow() {return this.items && this.items.length > 0}
  }
})

模板指令的代码都会生成在 render 函数中,通过 app.$options.render 就能失去渲染函数

ƒ anonymous() {with (this) { return 
    _c('div', { attrs: { "id": "app"} }, 
    _l((items), function (item) 
    {return (isShow) ? _c('p', [_v("\n" + _s(item.title) + "\n")]) : _e()}), 0) }
}
  • _lvue 的列表渲染函数,函数外部都会进行一次 if 判断
  • 初步失去论断:v-for优先级是比v-i f 高
  • 再将 v-forv-if置于不同标签
<div id="app">
  <template v-if="isShow">
    <p v-for="item in items">{{item.title}}</p>
  </template>
</div>

再输入下 render 函数

ƒ anonymous() {with(this){return 
    _c('div',{attrs:{"id":"app"}},
    [(isShow)?[_v("\n"),
    _l((items),function(item){return _c('p',[_v(_s(item.title))])})]:_e()],2)}
}

这时候咱们能够看到,v-forv-if 作用在不同标签时候,是先进行判断,再进行列表的渲染

咱们再在查看下 vue 源码

源码地位:\vue-dev\src\compiler\codegen\index.js

export function genElement (el: ASTElement, state: CodegenState): string {if (el.parent) {el.pre = el.pre || el.parent.pre}
  if (el.staticRoot && !el.staticProcessed) {return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {return genOnce(el, state)
  } else if (el.for && !el.forProcessed) {return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {return genIf(el, state)
  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {return genSlot(el, state)
  } else {
    // component or element
    ...
}

在进行 if 判断的时候,v-for是比 v-if 先进行判断

最终论断:v-for优先级比 v-if

怎么监听 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()!');
    }
})
退出移动版