共计 19830 个字符,预计需要花费 50 分钟才能阅读完成。
怎么实现路由懒加载呢
这是一道应用题。当打包利用时,JavaScript 包会变得十分大,影响页面加载。如果咱们能把不同路由对应的组件宰割成不同的代码块,而后当路由被拜访时才加载对应组件,这样就会更加高效
// 将
// import UserDetails from './views/UserDetails'
// 替换为
const UserDetails = () => import('./views/UserDetails')
const router = createRouter({
// ...
routes: [{path: '/users/:id', component: UserDetails}],
})
答复范例
- 当打包构建利用时,JavaScript 包会变得十分大,影响页面加载。利用路由懒加载咱们能把不同路由对应的组件宰割成不同的代码块,而后当路由被拜访的时候才加载对应组件,这样会更加高效,是一种优化伎俩
- 一般来说,对所有的 路由都应用动静导入 是个好主见
- 给
component
选项配置一个返回Promise
组件的函数就能够定义懒加载路由。例如:{path: '/users/:id', component: () => import('./views/UserDetails') }
- 联合正文
() => import(/* webpackChunkName: "group-user" */ './UserDetails.vue')
能够做webpack
代码分块
Vue complier 实现
- 模板解析这种事,实质是将数据转化为一段
html
,最开始呈现在后端,通过各种解决吐给前端。随着各种mv*
的衰亡,模板解析交由前端解决。 - 总的来说,
Vue complier
是将template
转化成一个render
字符串。
能够简略了解成以下步骤:
parse
过程,将template
利用正则转化成AST
形象语法树。optimize
过程,标记动态节点,后diff
过程跳过动态节点,晋升性能。generate
过程,生成render
字符串
assets 和 static 的区别
相同点: assets
和 static
两个都是寄存动态资源文件。我的项目中所须要的资源文件图片,字体图标,款式文件等都能够放在这两个文件下,这是相同点
不相同点: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 与父组件的交互如下:
- 父组件将
searchText
变量传入 custom-input 组件,应用的 prop 名为value
; - 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 实现
- 建设与其余属性(如:
data
、Store
)的分割; - 属性扭转后,告诉计算属性从新计算
实现时,次要如下
- 初始化
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
把全局的state
和getters
映射到以后组件的computed
计算属性
vuex
个别用于中大型web
单页利用中对利用的状态进行治理,对于一些组件间关系较为简单的小型利用,应用vuex
的必要性不是很大,因为齐全能够用组件prop
属性或者事件来实现父子组件之间的通信,vuex
更多地用于解决跨组件通信以及作为数据中心集中式存储数据。- 应用
Vuex
解决非父子组件之间通信问题vuex
是通过将state
作为数据中心、各个组件共享state
实现跨组件通信的,此时的数据齐全独立于组件,因而将组件间共享的数据置于State
中能无效解决多层级组件嵌套的跨组件通信问题
vuex
的State
在单页利用的开发中自身具备一个“数据库”的作用,能够将组件中用到的数据存储在State
中,并在Action
中封装数据读写的逻辑。这时候存在一个问题,个别什么样的数据会放在State
中呢?目前次要有两种数据会应用vuex
进行治理:
- 组件之间全局共享的数据
- 通过后端异步申请的数据
包含以下几个模块
state
:Vuex
应用繁多状态树, 即每个利用将仅仅蕴含一个store
实例。外面寄存的数据是响应式的,vue
组件从store
读取数据,若是store
中的数据产生扭转,依赖这相数据的组件也会产生更新。它通过mapState
把全局的state
和getters
映射到以后组件的computed
计算属性mutations
:更改Vuex
的store
中的状态的惟一办法是提交mutation
getters
:getter
能够对state
进行计算操作,它就是store
的计算属性尽管在组件内也能够做计算属性,然而getters
能够在多给件之间复用如果一个状态只在一个组件内应用,是能够不必getters
action
:action
相似于muation
, 不同在于:action
提交的是mutation
, 而不是间接变更状态action
能够蕴含任意异步操作modules
:面对简单的应用程序,当治理的状态比拟多时;咱们须要将vuex
的store
对象宰割成模块(modules
)
modules
:我的项目特地简单的时候,能够让每一个模块领有本人的state
、mutation
、action
、getters
,使得构造十分清晰,方便管理
答复范例
思路
- 给定义
- 必要性论述
- 何时应用
- 拓展:一些集体思考、实践经验等
答复范例
Vuex
是一个专为Vue.js
利用开发的 状态管理模式 + 库。它采纳集中式存储,治理利用的所有组件的状态,并以相应的规定保障状态以一种可预测的形式发生变化。- 咱们期待以一种简略的“单向数据流”的形式治理利用,即 状态 -> 视图 -> 操作单向循环 的形式。但当咱们的利用遇到多个组件共享状态时,比方:多个视图依赖于同一状态或者来自不同视图的行为须要变更同一状态。此时单向数据流的简洁性很容易被毁坏。因而,咱们有必要把组件的共享状态抽取进去,以一个全局单例模式治理。通过定义和隔离状态治理中的各种概念并通过强制规定维持视图和状态间的独立性,咱们的代码将会变得更结构化且易保护。这是
vuex
存在的必要性,它和react
生态中的redux
之类是一个概念 Vuex
解决状态治理的同时引入了不少概念:例如state
、mutation
、action
等,是否须要引入还须要依据利用的理论状况掂量一下:如果不打算开发大型单页利用,应用Vuex
反而是繁琐冗余的,一个简略的store
模式就足够了。然而,如果要构建一个中大型单页利用,Vuex
根本是标配。- 我在应用
vuex
过程中感触到一些等
可能的诘问
vuex
有什么毛病吗?你在开发过程中有遇到什么问题吗?- 刷新浏览器,
vuex
中的state
会从新变为初始状态。解决方案 - 插件vuex-persistedstate
action
和mutation
的区别是什么?为什么要辨别它们?
action
中解决异步,mutation
不能够mutation
做原子操作action
能够整合多个mutation
的汇合mutation
是同步更新数据(外部会进行是否为异步形式更新数据的检测)$watch
严格模式下会报错action
异步操作,能够获取数据后调佣mutation
提交最终数据
- 流程程序:“相应视图—> 批改 State”拆分成两局部,视图触发
Action
,Action 再触发
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
对象,指定path
、name
、params
等信息 - 如果页面中简略示意跳转链接,应用
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
,不须要.value
;ref
能够接管对象或数组等非原始值,但外部仍然是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')
)
答复范例
- 在大型利用中,咱们须要宰割利用为更小的块,并且在须要组件时再加载它们。
- 咱们不仅能够在路由切换时懒加载组件,还能够在页面组件中持续应用异步组件,从而实现更细的宰割粒度。
- 应用异步组件最简略的形式是间接给
defineAsyncComponent
指定一个loader
函数,联合 ES 模块动静导入函数import
能够疾速实现。咱们甚至能够指定loadingComponent
和errorComponent
选项从而给用户一个很好的加载反馈。另外Vue3
中还能够联合Suspense
组件应用异步组件。 - 异步组件容易和路由懒加载混同,实际上不是一个货色。异步组件不能被用于定义懒加载路由上,解决它的是
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
所辨认 (且获取) 的个性绑定 (class
和style
除外 )。当一个组件没有申明任何prop
时,这里会蕴含所有父作用域的绑定 (class
和style
除外 ),并且能够通过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
进行传递 - 先人与后辈组件数据传递可抉择
attrs
与listeners
或者Provide
与Inject
- 简单关系的组件数据传递能够通过
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-for
和v-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
挪动至容器元素上 (比方ul
、ol
)或者外面包一层template
即可
- 为了过滤列表中的我的项目 (比方
- 文档中明确指出永远不要把
v-if
和v-for
同时用在同一个元素上,显然这是一个重要的注意事项 - 源码外面对于代码生成的局部,可能清晰的看到是先解决
v-if
还是v-for
,程序上vue2
和vue3
正好相同,因而产生了一些症状的不同,然而不管怎样都是不能把它们写在一起的
vue2.x 源码剖析
在 vue 模板编译的时候,会将指令系统转化成可执行的
render
函数
编写一个 p
标签,同时应用 v-if
与 v-for
<div id="app">
<p v-if="isShow" v-for="item in items">
{{item.title}}
</p>
</div>
创立 vue
实例,寄存 isShow
与items
数据
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) }
}
_l
是vue
的列表渲染函数,函数外部都会进行一次if
判断- 初步失去论断:
v-for
优先级是比v-i
f 高 - 再将
v-for
与v-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-for
与 v-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()
答复范例
- 我晓得几种办法:
- 能够通过
watch
选项或者watch
办法监听状态 - 能够应用
vuex
提供的 API:store.subscribe()
watch
选项形式,能够以字符串模式监听$store.state.xx
;subscribe
形式,能够调用store.subscribe(cb)
, 回调函数接管mutation
对象和state
对象,这样能够进一步判断mutation.type
是否是期待的那个,从而进一步做后续解决。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()!');
}
})