Vue 实现的原理
把一个普通的 JavaScript 对象传入 Vue 实例作为
data
选项,Vue 将遍历此对象所有的 property,并使用 👉Object.defineProperty
把这些 property 全部转为 👉getter/setter。Object.defineProperty
是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
挂载数据对象
到data选项
,vue遍历
此对象所有的属性
,通过Object.defineProperty
转为为getter/setter
。getter
做的事情是依赖收集
,setter
做的事情是派发更新
。即对数据进行响应式劫持
。(将数据data变成可观察(observable))
Vue 3
放弃 Object.defineProperty ,使用更快的原生 Proxy
;👉Vue 组合式 API
new Vue() 发生了什么?
new Vue()
-->调用 this._init()
-->合并配置
,初始化生命周期
,初始化事件中心
,初始化渲染
,初始化 data、props、computed、watcher
等等。在初始化的最后,检测到如果有 el 属性,则调用 vm.$mount 方法挂载 vm,挂载的目标就是把模板渲染成最终的 DOM。
Vue 的挂载过程。
$mount
方法实际上会去调用mountComponent
方法,mountComponent
核心就是先实例化一个渲染Watcher
,在它的回调函数中会调用updateComponent
方法,在此方法中调用vm._render
方法先生成虚拟 Node,最终调用vm._update
更新 DOM。
Watcher
在这里起到两个作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数函数最后判断为根节点的时候设置
vm._isMounted
为true
, 表示这个实例已经挂载了,同时执行mounted
钩子函数总结:
this_init()
主要做了两件事:
初始化(包括生命周期、事件、render函数、state等)。 $mount组件。
组件渲染过程
初次渲染过程
:
解析模板为 render
函数触发响应式,监听data属性 getter
,setter
(模板中使用到的变量会触发getter
)执行 render
函数(触发getter
),生成vnode,patch(elem,vnode)
渲染到页面上
更新过程
:
修改data的数据,触发 setter
(此前data数据在getter
中已被监听)重新执行 render
函数,生成newVnode(新的虚拟dom)
使用 patch(vnode,newVnode)
更新到页面上
如何追踪变化?
解析模板为 render
函数,通过render函数
生成vdom
执行 render
函数,触发data中的getter
getter
方法收集依赖(通俗点就是,在模板里面触发了哪个变量的getter
,就把哪个变量观察起来)在依赖中 setter
修改data中的数据的时候,Notify
看下修改的那个数据是不是之前被观察起来的如果是之前观察起来的,就重新渲染( re-render
),重新生成render
函数,生成newVdom
形成一个闭环
vue中如何检测数组的变化 ?
通过分析
Vue源码
中的src/observer/array.js
文件,我们可以知道,Vue对push
、pop
、shift
、unshift
、splice
、sort
、reverse
等方法进行了重写。
Vue
将data中的数组进行了原型链重写,指向了自己定义的数组原型方法。通过Object.defineProperty
重写相关属性。这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。# src/core/observer/array.js
import { def } from '../util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) { // 从新定义属性
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
# src/core/utils/lang.js
/**
* Define a property. 定义属性
*/
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?
Object.defineProperty
本身有一定的监控到数组下标变化的能力,但是在 Vue 中,从性能/体验的性价比考虑,尤大大就弃用了这个特性(👉Vue 为什么不能检测数组变动 )。为了解决这个问题,经过 vue 内部处理后可以使用以下几种方法来监听数组.push();pop();shift();unshift();splice();sort();reverse();
由于只针对了以上 7 种方法进行了 hack 处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。
Object.defineProperty
只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue 2.x 里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。
Proxy
可以劫持整个对象,并返回一个新的对象。Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。👉Proxy MDN传送Object.defineProperty VS Proxy
1. Object.defineProperty只能劫持对象的属性,而Proxy是直接代理对象。
由于
Object.defineProperty
只能对属性进行劫持,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历。而Proxy
直接代理对象,不需要遍历操作。2. Object.defineProperty对新增属性需要手动进行Observe。
由于
Object.defineProperty
劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用Object.defineProperty
进行劫持。也正是因为这个原因,使用vue给
data
中的数组或对象新增属性时,需要使用vm.$set
才能保证新增的属性也是响应式的。3. Proxy支持13种拦截操作,这是defineProperty所不具有的
Vue的生命周期(四季轮回)
Vue来说它的生命周期
就是Vue实例从创建到销毁的过程
。具体生命周期图可查看👉官方传送
beforeCreate
前<创建>
后created
beforeMount
前<挂载>
后mounted
beforeUpdate
前<更新>
后updated
beforeDestroy
前<销毁>
后destroyed
# src/core/shared/constants.js
# 定义生命周期钩子函数
export const LIFECYCLE_HOOKS = [
'beforeCreate', // 是new Vue()之后触发的第一个钩子,在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问。
'created', // 在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果非要想,可以通过vm.$nextTick来访问Dom。
'beforeMount', // 发生在挂载之前,在这之前template模板已导入渲染函数编译。而当前阶段虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。
'mounted', // 在挂载完成后发生,在当前阶段,真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom节点,使用$refs属性对Dom进行操作。
'beforeUpdate', // 发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。
'updated', // 发生在更新完成之后,当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。
'beforeDestroy', // 发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。
'destroyed', // 发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
'activated', // 钩子函数是专门为 `keep-alive` 组件定制的钩子,结合 `keep-alive` 组件,可以实现从其他页面跳转到这个页面时会请求数据,当从下级页面返回这个页面时就不会重新请求数据
'deactivated', // 钩子函数是专门为 `keep-alive` 组件定制的钩子
'errorCaptured',
'serverPrefetch'
]
Vue事件机制
Vue.js为我们提供了四个事件API,分别是👉once,👉emit。
原生事件绑定是通过
addEventListener
绑定给真实元素的,组件事件绑定是通过Vue自定义的
$on
实现的。初始化事件,在vm上创建一个
_events
对象。export function initEvents (vm: Component) {
vm._events = Object.create(null) // /*在vm上创建一个_events对象,用来存放事件。*/
vm._hasHookEvent = false // /*这个bool标志位来表明是否存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/
// init parent attached events /*初始化父组件attach的事件*/
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}在
vm实例原型上定义
,$on
、$off
、$off
、$emit
方法。# src/core/instance/events.js
# $on方法用来在vm实例上监听一个自定义事件,该事件可用$emit触发。
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
# $once监听一个只能触发一次的事件,在触发以后会自动移除该事件。
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on () {
/*在第一次执行的时候将该事件销毁*/
vm.$off(event, on)
/*执行注册的方法*/
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
# $off用来移除自定义事件
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all
/*如果不传参数则注销所有事件*/
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
/*如果event是数组则递归注销事件*/
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$off(event[i], fn)
}
return vm
}
// specific event
const cbs = vm._events[event]
/*Github:https://github.com/answershuto*/
/*本身不存在该事件则直接返回*/
if (!cbs) {
return vm
}
/*如果只传了event参数则注销该event方法下的所有方法*/
if (arguments.length === 1) {
vm._events[event] = null
return vm
}
// specific handler
/*遍历寻找对应方法并删除*/
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
# $emit用来触发指定的自定义事件。
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if (process.env.NODE_ENV !== 'production') {
const lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(event)}" instead of "${event}".`
)
}
}
let cbs = vm._events[event]
if (cbs) {
/*将类数组的对象转换成数组*/
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
/*遍历执行*/
for (let i = 0, l = cbs.length; i < l; i++) {
cbs[i].apply(vm, args)
}
}
return vm
}
Vue模版编译原理知道吗?
Vue的编译过程就是将
template
转化为render
函数的过程
template
通过compile
执行parse
会被编译成AST
,那么AST是什么?
在计算机科学中,抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。具体可以查看👉抽象语法树 AST
经过generate得到render函数
,render的返回值是VNode
,VNode是Vue的虚拟DOM节点.具体如下:
compile
首先会将模板template
进行parse
得到一个AST
,再通过optimize
做一些优化,最后通过generate
得到render
以及staticRenderFns。
parse
:会用正则等方式解析template模板中的指令、class、style等数据,形成ASToptimize
:作用是标记static静态节点,这是Vue在编译过程中的一处优化,后面当update更新界面时,会有一个patch的过程,diff算法会直接跳过静态节点,从而减少了比较的过程,优化了patch的性能。generate
:将AST转化成render funtion字符串的过程,得到结果是render的字符串以及staticRenderFns字符串
计算属性Computed和侦听器Watch
对于任何复杂逻辑,你都应当使用
计算属性
。虽然计算属性在大多数情况下更合适,但有时也
需要一个自定义的侦听器
。这就是为什么 Vue 通过watch
选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作
时,这个方式是最有用的
Computed
本质是一个具备缓存的watcher
,依赖的属性发生变化就会更新视图。适用于计算比较消耗性能的计算场景。当表达式过于复杂时,在模板中放入过多逻辑会让模板难以维护,可以将复杂的逻辑放入计算属性中处理。
Watch
没有缓存性,更多的是观察的作用,可以监听某些数据执行回调。当我们需要深度监听对象中的属性时,可以打开deep:true
选项,这样便会对对象中的每一项进行监听。这样会带来性能问题,优化的话可以使用字符串形式
监听,如果没有写到组件中,不要忘记使用unWatch手动注销
哦。有啥不一样呢?
处理数据场景不同: 注意箭头方向
网络转载,如有侵权请联系删除 当依赖的值变化时
,在watch中,是可以做一些复杂的操作的
,而computed中的依赖,仅仅是一个值依赖于另一个值,是值上的依赖
。总结:
如果一个数据需要经过复杂计算就用 computed
如果一个数据需要被监听并且对数据做一些操作就用 watch
vue组件中的data为什么是一个函数?
组件是可复用的
vue
实例,本质上,这些实例用的都是同一个构造函数
。如果data是对象的话,对象属于引用类型
,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。
v-if和v-show的区别
当条件不成立时,
v-if
不会渲染DOM元素
v-show
操作的是样式(display),切换当前DOM的显示和隐藏。不管条件成立不成立,都会渲染DOM
vue中v-model的原理
v-model
本质上就是语法糖,其实就是既绑定了数据
,又添加了一个input事件监听
。实现靠的还是
v-bind:绑定响应式数据 触发oninput 事件并传递数据 # 父组件
<template>
<div id="app">
<Children v-model="msg"></Children>
<p>{{msg}}</p>
</div>
</template>
<script>
import Children from "./Children";
export default {
components: {
Children,
},
data() {
return {
msg: "hello"
}
},
}
</script>
# 子组件
<template>
<div>
<button @click="change"></button>
</div>
</template>
<script>
export default {
methods: {
change() {
this.$emit('input',`world`)
}
},
}
</script>
# 点击后会从 hello -> world
vue组件通信有哪些方式?
vue组件
关系和DOM树
类似。我们针对不同的使用场景,怎么选择有效的通信方式呢?组件通信方式:
props、$emit
[适用父子组件场景]
父组件向子组件传值(子组件通过props接收) 子组件向父组件传值(通过$emit事件形式)
中央事件总线EventBus
[适用隔代、相邻场景]
原理是: new Vue
创建一个EventBus,挂载到Vue原型上。通过$emit
、$on
、$off
调用相关事件。
vuex
大杀器 [一网打尽] 👉官网传送
Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。
$attrs
/$listeners
[让父组件之间传递消息给子子组件,即多级组件嵌套需要传递数据]简单来说: listeners 是两个对象, listeners里存放的是父组件中绑定的非原生事件。
意思就是父组件传向子组件传的,子组件非prop接受的数据都会放在attrs获取就可以了。如过从父->孙传,就在子组件中添加v-bind='attrs 和 $listeners 属性像两个收纳箱,一个负责收纳属性,一个负责收纳事件,都是以对象的形式来保存数据;
provide/inject
(主要在开发高阶插件/组件库时使用。不推荐用于普通应用程序代码中。)
$parent
/$children
&ref
[无法在跨级或兄弟间通信。]
$parent / $children
:访问父 / 子实例
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
.sync 修饰符
也是一个语法糖而已
<text-document :title.sync="title"></text-document>
# 当子组件需要更新 title 的值时,它需要显式地触发一个更新事件:
this.$emit('update:title', newValue)
# 常用于控制模态框的显示隐藏总结:
常见使用场景可以分为三类:
父子通信: 父向子传递数据是通过 props,子向父是通过 events( parent / attrs/attrs/$listeners
vue 中$nextTick作用是什么?
首先Vue中有一个重要的概念:
异步更新队列
Vue异步更新DOM的原理:Vue在观察到数据变化时,并不是直接更新DOM,而是开启一个队列,并且缓存 同一轮事件循环中的所有数据改变
。在缓冲时会除去重复的操作
,等到下一轮事件循环时,才开始更新。异步更新队列实现的选择 : Vue会根据当前浏览器环境优先使用原生的 Promise.then
和MutationObserver
(现已经换为MessageChannel
,是宏任务),如果都不支持,就会采用setTimeout
代替。最后:
$nextTick
就是用来告知DOM什么时候更新完
,当DOM更新完毕后
,nextTick方法里面的回调就会执行
。为什么要异步更新视图?
现在有这样的一种情况,mounted的时候test的值会被++循环执行1000次。 每次++时,都会根据响应式触发setter->Dep->Watcher->update->patch。 如果这时候没有异步更新视图,那么每次++都会直接操作DOM更新视图,这是非常消耗性能的。 所以Vue.js实现了一个queue队列,在下一个tick的时候会统一执行queue中Watcher的run。同时,拥有相同id的Watcher不会被重复加入到该queue中去,所以不会执行1000次Watcher的run。最终更新视图只会直接将test对应的DOM的0变成1000。
保证更新视图操作DOM的动作是在当前栈执行完以后下一个tick的时候调用,大大优化了性能。
我们了解到数据的变化到 DOM 的重新渲染是一个异步过程,发生在下一个 tick。这就是我们平时在开发的过程中,比如从服务端接口去获取数据的时候,数据做了修改,如果我们的某些方法去依赖了数据修改后的 DOM 变化,我们就必须在
nextTick
后执行export function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
}
# src/core/util/next-tick.js
/**
* Defer a task to execute it asynchronously.
*/
/*
延迟一个任务使其异步执行,在下一个tick时执行,一个立即执行函数,返回一个function
这个函数的作用是在task或者microtask中推入一个timerFunc,在当前调用栈执行完以后以此执行直到执行到timerFunc
目的是延迟到当前调用栈执行完以后执行
*/
export const nextTick = (function () {
/*存放异步执行的回调*/
const callbacks = []
/*一个标记位,如果已经有timerFunc被推送到任务队列中去则不需要重复推送*/
let pending = false
/*一个函数指针,指向函数将被推送到任务队列中,等到主线程任务执行完时,任务队列中的timerFunc被调用*/
let timerFunc
/*下一个tick时的回调*/
function nextTickHandler () {
/*一个标记位,标记等待状态(即函数已经被推入任务队列或者主线程,已经在等待当前栈执行完毕去执行),这样就不需要在push多个回调到callbacks时将timerFunc多次推入任务队列或者主线程*/
pending = false
/*执行所有callback*/
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
/*
这里解释一下,一共有Promise、MutationObserver以及setTimeout三种尝试得到timerFunc的方法
优先使用Promise,在Promise不存在的情况下使用MutationObserver,这两个方法都会在microtask中执行,会比setTimeout更早执行,所以优先使用。
如果上述两种方法都不支持的环境则会使用setTimeout,在task尾部推入这个函数,等待调用执行。
参考:https://www.zhihu.com/question/55364497
*/
if (typeof Promise !== 'undefined' && isNative(Promise)) {
/*使用Promise*/
var p = Promise.resolve()
var logError = err => { console.error(err) }
timerFunc = () => {
p.then(nextTickHandler).catch(logError)
// in problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
} else if (typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// use MutationObserver where native Promise is not available,
// e.g. PhantomJS IE11, iOS7, Android 4.4
/*新建一个textNode的DOM对象,用MutationObserver绑定该DOM并指定回调函数,在DOM变化的时候则会触发回调,该回调会进入主线程(比任务队列优先执行),即textNode.data = String(counter)时便会触发回调*/
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
} else {
// fallback to setTimeout
/* istanbul ignore next */
/*使用setTimeout将回调推入任务队列尾部*/
timerFunc = () => {
setTimeout(nextTickHandler, 0)
}
}
/*
推送到队列中下一个tick时执行
cb 回调函数
ctx 上下文
*/
return function queueNextTick (cb?: Function, ctx?: Object) {
let _resolve
/*cb存到callbacks中*/
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
_resolve = resolve
})
}
}
})()
vue双向绑定和 vuex 是否冲突?
当在
严格模式中
使用Vuex
时,会有。原因是:在严格模式中,由于这个修改不是在 mutation 函数中执行的, 这里会抛出一个错误。解决方案:👉详情看官方文档
vue 的父组件和子组件生命周期钩子执行顺序是怎么样的?
根据生命周期来定阶段过程,主要有以下过程:
加载渲染过程: 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted 子组件更新过程:父beforeUpdate->子beforeUpdate->子updated->父updated 父组件更新过程: 父beforeUpdate->父updated 销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
vue 注册组件有哪些方式?
组件只能有一个根标签
全局注册组件
:是直接给全局对象Vue注册了一个组件。在本页内已挂载Vue 实例的DOM目标元素 都可以使用(区别于局部组件只能是挂载哪一个DOM,哪个才能使用)。# 注册
Vue.component("hello-component",{
props:["message"],
template :"<div ><h1>组件定义之全局组件</h1><h4>{{message}}</h4></div>"
});
# 使用
<hello-component message=" global component"></hello-component>
局部注册组件
:通过某个 Vue 实例/组件的实例选项 components 注册仅在其作用域中可用的组件。 js中用反斜线“\”’实现字符串换行# 注册
var limitComponent = {
props:["message"],
template :"<div><h1>{{message}}</h1><input \
type='text' v-model='message'></input></div>"
}
new Vue ({
el : "#app",
components :{
"child-component": limitComponent
}
});
# 使用
<child-component message = "动态局部组件"></child-component>
extend
var com1 = Vue.extend({
template:'<h3>这是第一种方式</h3>'
});
Vue.component("myCom1",com1);
在vue-cli中如何批量导入组件?
批量的前提是使用了
webpack
打包工具,利用require.context
后,遍历。import Vue from "vue"
import upperFirst from "lodash/upperFirst"
import camelCase from "lodash/camelCase"
const requireComponent = require.context(
'./', //组件所在目录的相对路径
false, //是否查询其子目录
/Base[A-Z]\w+\.(vue|js)$/ //匹配基础组件文件名的正则表达式
)
requireComponent.keys().forEach(fileName=>{
// 获取文件名
var names = fileName.split("/").pop().replace(/\.\w+$/,"");//BaseBtn
// 获取组件配置
const componentConfig = requireComponent(fileName);
// 若该组件是通过"export default"导出的,优先使用".default",
// 否则退回到使用模块的根
Vue.component(names,componentConfig.default || componentConfig);
vue中的内置组件keep-alive有啥子用?
keep-alive
主要用于保留组件状态
或避免重新渲染
。👉keep-alive 官网传送能够缓存组件,防止重复渲染页面,提高用户体验,同时可以很大程度上减少接口请求,减小服务器压力。
vue中extend、mixins和extends有什么区别?
extend:创建组件的构造函数,Vue.extend是为了创建可复用的组件 mixins:mixins是对组件的扩充,包括methods、components、directive等。 extends:与mixins类似;合并规则和mixins一致。extends允许声明扩展另一个组件(可以是一个简单的选项对象或构造函数),而无需使用 Vue.extend。这主要是为了便于扩展单文件组件 继承钩子函数的时候,是不进行覆盖的,extends的钩子函数先触发,然后再是mixins的钩子函数触发,最后就是组件自身的钩子函数触发。extends优先级比mixins优先级高,先运行优先级高的钩子函数。
下一篇学习vue-router
。
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: zlevai 原文链接:https://juejin.im/post/6856606052889788429