组件基本上是现代 Web 开发的标配,在 Vue 中组件也是其最核心的基石之一。javascript
Vue 在组件的这方面设计也是很是用心,开发者使用的成本能够说已经很低了,咱们就一块儿来分析下,并学习其中的技巧和优秀设计思想。css
咱们首先仍是须要理解下组件化开发。Vue 官网上有一个图,简单形象的描述了最核心的思想:html
也就是开发的时候,咱们将页面拆分为一个个的组件,他们之间互相组合,就像堆积木同样,最终组成了一个树的形式。那这个也就是组件化开发的核心思想了。前端
那这个时候,咱们就能够理解下前端的组件:一个功能较为独立的模块。vue
这里边有几个核心点:java
Vue 中的组件,有一个很好的入门 cn.vuejs.org/v2/guide/co… ,以及推荐相搭配的单文件组件 cn.vuejs.org/v2/guide/si… (我的仍是很是喜欢这种组织方式)node
那咱们其实就以一个使用组件的示例,带着顺便分析下 Vue 组件的内幕:react
import Vue from 'vue'
import App from './App.vue'
const vm = new Vue({
render (h) {
return h(App)
}
})
vm.$mount('#app')
复制代码
App.vue 就是一个上述的单文件组件,大概内容以下:git
<template>
<div id="app">
<div @click="show = !show">Toggle</div>
<p v-if="show">{{ msg }}</p>
</div>
</template>
<script> export default { data () { return { msg: 'Hello World!', show: false } } } </script>
<style lang="stylus"> #app font-family Avenir, Helvetica, Arial, sans-serif -webkit-font-smoothing antialiased -moz-osx-font-smoothing grayscale text-align center color #2c3e50 margin-top 60px </style>
复制代码
这里也能够进一步感觉到,在 Vue 中一个组件的样子:模板 + 脚本逻辑 + 样式。在逻辑部分,使用的就是和咱们在生命周期分析中所涉及到的初始化部分:对一些配置项(data、methods、computed、watch、provide、inject 等)的处理差很少。github
固然 Vue 中还有其余的不少的配置项,详细的能够参考官方文档,这里不细说了。
根据咱们的示例,结合咱们在生命周期文章中的分析,Vue 应用 mount 以后,就会调用 render() 函数获得 vdom 数据,而咱们也知道这个 h
就是实例的 $createElement
,同时参数 App 是咱们定义的一个组件。
回到源码 createElement 相关的具体实现就在 github.com/vuejs/vue/b… 这里简要看下:
import { createComponent } from './create-component'
export function createElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
// 直接 _createElement
return _createElement(context, tag, data, children, normalizationType)
}
export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array<VNode> {
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
if (process.env.NODE_ENV !== 'production' &&
isDef(data) && isDef(data.key) && !isPrimitive(data.key)
) {
if (!__WEEX__ || !('@binding' in data.key)) {
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
}
// support single function children as default scoped slot
if (Array.isArray(children) &&
typeof children[0] === 'function'
) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
// 字符串,咱们这里大概了解下
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// 内置元素,在 Web 中就是普通 HTML 元素
// platform built-in elements
if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') {
warn(
`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
context
)
}
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
// 组件场景
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// 这确定是组件场景,也就是咱们上述的 Case 会进入这里
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
复制代码
接下来的重点看起来就是这个 createComponent 了,来自 github.com/vuejs/vue/b…
export function createComponent ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
// 也就是 Vue
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
// 咱们的场景,由于是一个普通对象,因此这里会调用 Vue.extend 变为一个构造器
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
// if at this stage it's not a constructor or an async component factory,
// reject.
if (typeof Ctor !== 'function') {
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${String(Ctor)}`, context)
}
return
}
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
data = data || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
// 以前有涉及一点点的 抽象组件
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// install component management hooks onto the placeholder node
// 安装组件 hooks 很重要!!
installComponentHooks(data)
// return a placeholder vnode
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
// Weex specific: invoke recycle-list optimized @render function for
// extracting cell-slot template.
// https://github.com/Hanks10100/weex-native-directive/tree/master/component
/* istanbul ignore if */
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode)
}
return vnode
}
复制代码
仔细看看这个重要的安装 hooks
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
// ...
},
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
// ...
},
insert (vnode: MountedComponentVNode) {
// ...
},
destroy (vnode: MountedComponentVNode) {
// ...
}
}
const hooksToMerge = Object.keys(componentVNodeHooks)
// 安装组件 hooks
function installComponentHooks (data: VNodeData) {
const hooks = data.hook || (data.hook = {})
// 遍历 & 安装,hook 主要有 init prepatch insert destroy
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i]
const existing = hooks[key]
const toMerge = componentVNodeHooks[key]
if (existing !== toMerge && !(existing && existing._merged)) {
// 这个 mergeHook
hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
}
}
}
function mergeHook (f1: any, f2: any): Function {
// 返回了一个新的函数 新的函数 按照顺序 依次调用 f1 f2
const merged = (a, b) => {
// flow complains about extra args which is why we use any
f1(a, b)
f2(a, b)
}
merged._merged = true
return merged
}
复制代码
能够看出,基本上就是把 componentVNodeHooks 上定义的 hook 点(init prepatch insert destroy)的功能赋值到 vnode 的 data.hook 上。
以及这里还有一个技巧,mergeHook 利用闭包特性,使得能够达到合并函数执行的目的。
按照在生命周期的介绍,调用完 render() 后就会执行 patch 相关逻辑,进而会执执行到 createElm 中 github.com/vuejs/vue/b…
function createElm ( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) {
// ...
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
// ...
}
复制代码
这里就会优先执行 createComponent github.com/vuejs/vue/b…
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
// 重点:调用 vnode.data.hook 中的 init 钩子
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
// init 钩子中会建立组件实例 且 mounted 了,下面详细分析
// 此时 componentInstance 就会已经建立
if (isDef(vnode.componentInstance)) {
// 初始化组件
initComponent(vnode, insertedVnodeQueue)
// 插入元素
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
function initComponent (vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
vnode.data.pendingInsert = null
}
vnode.elm = vnode.componentInstance.$el
if (isPatchable(vnode)) {
// 触发 create hooks
invokeCreateHooks(vnode, insertedVnodeQueue)
setScope(vnode)
} else {
// empty component root.
// skip all element-related modules except for ref (#3455)
registerRef(vnode)
// make sure to invoke the insert hook
insertedVnodeQueue.push(vnode)
}
}
function invokeCreateHooks (vnode, insertedVnodeQueue) {
// 重点:cbs 中的 create 钩子
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode)
}
// vnode 上自带的
i = vnode.data.hook // Reuse variable
if (isDef(i)) {
// create 钩子
if (isDef(i.create)) i.create(emptyNode, vnode)
// 重点:插入钩子,没有当即调用 而是放在队列中了
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (nodeOps.parentNode(ref) === parent) {
nodeOps.insertBefore(parent, elm, ref)
}
} else {
nodeOps.appendChild(parent, elm)
}
}
}
复制代码
上边的分析有几个重点须要咱们关注下:
回到安装 hooks 中,咱们知道 componentVNodeHooks 中定义了 init 钩子须要作的事情 github.com/vuejs/vue/b…
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// 忽略
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
// 为 vnode 建立组件实例
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
// mount 这个组件实例
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
}
复制代码
而这个 createComponentInstanceForVnode 的逻辑是这样的
export function createComponentInstanceForVnode ( // we know it's MountedComponentVNode but flow doesn't vnode: any, // activeInstance in lifecycle state parent: any ): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render
options.staticRenderFns = inlineTemplate.staticRenderFns
}
// 实例化构造器
return new vnode.componentOptions.Ctor(options)
}
复制代码
而在前边的分析咱们已经知道了这个构造器就是一个继承 Vue 的子类,因此初始化的过程就是基本上是 Vue 初始化的过程;同时在 init 钩子里,有了组件实例,就会当即调用 $mount 挂载组件,这些逻辑都已经在生命周期相关的分析中已经分析过了,这里就不细说了,感兴趣的能够看 Vue - The Good Parts: 生命周期。
那 cbs 中的钩子来自哪里呢?这个须要回到 patch 中 github.com/vuejs/vue/b…
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
export function createPatchFunction (backend) {
let i, j
const cbs = {}
const { modules, nodeOps } = backend
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][hooks[i]])) {
cbs[hooks[i]].push(modules[j][hooks[i]])
}
}
}
// ...
}
复制代码
在 createPatchFunction
的最顶部,执行的时候,就会给 cbs 作赋值操做,依据的就是传入的 modules 中的配置。这里咱们就不须要看全部的 modules 都作了什么事情了,咱们能够挑选两个大概来看下,可能会作一些什么样的事情:一个是来自于 core 中的指令 github.com/vuejs/vue/b… ,另外一个是来自于平台 Web 的 style github.com/vuejs/vue/b…
// directives.js
export default {
// 钩子们,这里用到了 create update 以及 destroy
create: updateDirectives,
update: updateDirectives,
destroy: function unbindDirectives (vnode: VNodeWithData) {
updateDirectives(vnode, emptyNode)
}
}
function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (oldVnode.data.directives || vnode.data.directives) {
_update(oldVnode, vnode)
}
}
function _update (oldVnode, vnode) {
// 根据新旧 vnode 信息更新 指令信息
const isCreate = oldVnode === emptyNode
const isDestroy = vnode === emptyNode
const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)
const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)
const dirsWithInsert = []
const dirsWithPostpatch = []
let key, oldDir, dir
for (key in newDirs) {
oldDir = oldDirs[key]
dir = newDirs[key]
if (!oldDir) {
// 指令 bind 钩子
// new directive, bind
callHook(dir, 'bind', vnode, oldVnode)
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir)
}
} else {
// existing directive, update
dir.oldValue = oldDir.value
dir.oldArg = oldDir.arg
// 指令 update 钩子
callHook(dir, 'update', vnode, oldVnode)
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir)
}
}
}
if (dirsWithInsert.length) {
const callInsert = () => {
for (let i = 0; i < dirsWithInsert.length; i++) {
// 指令 inserted 钩子
callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
}
}
if (isCreate) {
mergeVNodeHook(vnode, 'insert', callInsert)
} else {
callInsert()
}
}
if (dirsWithPostpatch.length) {
mergeVNodeHook(vnode, 'postpatch', () => {
for (let i = 0; i < dirsWithPostpatch.length; i++) {
// 指令 componentUpdated 钩子
callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
}
})
}
if (!isCreate) {
for (key in oldDirs) {
if (!newDirs[key]) {
// no longer present, unbind
// 指令 unbind 钩子
callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
}
}
}
}
复制代码
能够看到基本上就是根据各类条件调用指令的各个周期的钩子函数,核心也是生命周期的思想。
// style.js
export default {
create: updateStyle,
update: updateStyle
}
function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {
const data = vnode.data
const oldData = oldVnode.data
if (isUndef(data.staticStyle) && isUndef(data.style) &&
isUndef(oldData.staticStyle) && isUndef(oldData.style)
) {
return
}
let cur, name
const el: any = vnode.elm
const oldStaticStyle: any = oldData.staticStyle
const oldStyleBinding: any = oldData.normalizedStyle || oldData.style || {}
// if static style exists, stylebinding already merged into it when doing normalizeStyleData
const oldStyle = oldStaticStyle || oldStyleBinding
const style = normalizeStyleBinding(vnode.data.style) || {}
// store normalized style under a different key for next diff
// make sure to clone it if it's reactive, since the user likely wants
// to mutate it.
vnode.data.normalizedStyle = isDef(style.__ob__)
? extend({}, style)
: style
const newStyle = getStyle(vnode, true)
for (name in oldStyle) {
if (isUndef(newStyle[name])) {
setProp(el, name, '')
}
}
for (name in newStyle) {
cur = newStyle[name]
if (cur !== oldStyle[name]) {
// ie9 setting to null has no effect, must use empty string
setProp(el, name, cur == null ? '' : cur)
}
}
}
复制代码
大概的逻辑就是新的和旧的style对比,去重置元素的style样式。
经过这种方式很好的实现了,在运行时动态扩展能力的特性。
那是由于须要保证 insert 的钩子必定是元素已经实际插入到 DOM 中以后再去执行 insert 的钩子。这种状况主要出如今子组件做为根节点,且是首次渲染的状况下,这个时候实际的 DOM 元素自己是一个,因此须要等到父组件的 initComponent 的时候插入到父组件 patch 的队列中,最后在执行。
这个逻辑在 patch 的最后阶段 github.com/vuejs/vue/b… 会调用 invokeInsertHook 这个有关系:
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the
// element is really inserted
// 咱们上边所解释的状况
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue
} else {
// 其余时候直接调用 vnode 的 data.hook.insert 钩子
for (let i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i])
}
}
}
复制代码
那么这个时候就再次回到了咱们的安装组件hook相关逻辑中,这个时候的 insert 钩子作了什么事情呢?github.com/vuejs/vue/b…
insert (vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
if (!componentInstance._isMounted) {
// 此时尚未 mounted
componentInstance._isMounted = true
// 调用组件实例的 mounted 钩子
callHook(componentInstance, 'mounted')
}
// keep alive 的状况
if (vnode.data.keepAlive) {
if (context._isMounted) {
// vue-router#1212
// During updates, a kept-alive component's child components may
// change, so directly walking the tree here may call activated hooks
// on incorrect children. Instead we push them into a queue which will
// be processed after the whole patch process ended.
queueActivatedComponent(componentInstance)
} else {
activateChildComponent(componentInstance, true /* direct */)
}
}
}
复制代码
这里咱们须要关注的重点就是第一个判断,此时子组件(咱们场景中 App 组件对应的实例)尚未调用挂载钩子,因此直接调用了 mounted 钩子,完成了调用挂载生命周期钩子。
接着,回到最初 Vue 实例的 patch 完成以后的逻辑,最终调用了 Vue 实例的 mounted 生命周期钩子。
到了这里基本上整个初始化且挂载的整个过程基本上就完成了,因此这里回顾下整个的过程:
那对应的若是涉及到组件销毁的过程,基本上是从更新组件开始,到 patch,发现被移除了,接着触发对应 vnode 的 destroy 钩子 github.com/vuejs/vue/b…
destroy (vnode: MountedComponentVNode) {
const { componentInstance } = vnode
if (!componentInstance._isDestroyed) {
if (!vnode.data.keepAlive) {
componentInstance.$destroy()
} else {
deactivateChildComponent(componentInstance, true /* direct */)
}
}
}
复制代码
剩下的就和在Vue - The Good Parts: 生命周期文章中所涉及的销毁的逻辑保持一致了。
如同咱们在开篇的时候分享的关于组件化和组件的内容,以及从前端自己的整个历史来看,组件化开发时一直是一种最佳实践。
最核心的缘由是组件化开发能够带给咱们最大的好处:分治,那分治能够带来的好处:拆分和隔离复杂度。
固然,还有其余的不少好处:
有了这些,从而达到了提高开发效率和可维护性的终极目标。
经过以上分析,咱们也更加清楚了 Vue 中是如何实现组件化的,组件都继承 Vue,因此基本上他们都具有相同的配置、生命周期、API。
那除了咱们对组件有了更深的理解以外,整个也是最重要的点,咱们还能够从 Vue 的实现中学到哪些东西呢?
在 Vue 里组件是按照类来设计的,虽然对于用户而言,更多的时候你写的就是一个普通的对象,传入一对的配置项,但在 Vue 内部处理的时候,仍是经过 extend 的方式转换为了一个构造器,进而方便进行实例化,这点就是一个经典的继承思惟。
如今咱们已知的 Vue 组件的配置项包含了,生命周期钩子们(create 相关、mount 相关、update 相关、destroy 相关),还有状态数据相关的 props、data、methods、computed、watch,也有 DOM 相关的 el、template、render。这些选项也是平常最最经常使用的部分了,因此咱们须要好好理解且知晓他们背后的实现和做用。
额外的, Vue 中组件还包含了资源相关 cn.vuejs.org/v2/api/#%E9… 、组合相关 cn.vuejs.org/v2/api/#%E9… 、还有其余 cn.vuejs.org/v2/api/#%E9… 这些的配置项,也都是经常使用的,感兴趣的能够本身研究下内部的实现以及找到他们实现的精粹。
除了配置项,还有组件实例,大多在咱们相关的分析中也有涉及,如 $props
、$data
、$el
、$attrs
、$watch
、$mount()
、$destroy()
以及事件相关 $on()
、$off()
、$emit()
、$once()
等,也能够看出从命名上都是以 $
开头的,很规范,能够参考官网了解更多。
还有很是好用的动态组件和异步组件,设计的十分友好 cn.vuejs.org/v2/guide/co…
modules 的组织,即 createPatchFunction 中传入的 modules。上边咱们也分析了两个 modules 的示例,能够看出,借助于咱们在 VDOM 层面设计好的 patch 钩子,咱们将不少的功能作了模块拆分,每一个模块自行去根据钩子的时机去作对应的事情。到这里你也能够发现这其实大概是一种插件化思惟的运用,插件化思惟自己又是一种微内核架构的体现。这个点也是符合 Vue 的整个设计理念的:渐进式的框架。
因此 Vue 基本上从内部的一些设计到整个的生态建设,都是遵循着自身的设计理念,这是一种很重要的践行和坚持,值得咱们深思。
滴滴前端技术团队的团队号已经上线,咱们也同步了必定的招聘信息,咱们也会持续增长更多职位,有兴趣的同窗能够一块儿聊聊。