組件基本上是現代 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 基本上從內部的一些設計到整個的生態建設,都是遵循着自身的設計理念,這是一種很重要的踐行和堅持,值得咱們深思。
滴滴前端技術團隊的團隊號已經上線,咱們也同步了必定的招聘信息,咱們也會持續增長更多職位,有興趣的同窗能夠一塊兒聊聊。