源码分析1:src\core\global-api\assets.js
//是component,filter,directive三个的综合方法
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
//['component','filter','directive']循环遍历这三个方法
ASSET_TYPES.forEach(type => {
//动态附加三个静态方法,然后分别去定义
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
//如果def是对象,那么definition就是组件的配置对象
if (type === 'component' && isPlainObject(definition)) {
//定义组件name
definition.name = definition.name || id
//extend创建组件构造函数,def变成了构造函数
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
//注册 this.options[components][comp]=Ctor
//在当前vue的选项中加上components选项,这样的话
//定义全局组件,将来会被继承在所有的子组件里
this.options[type + 's'][id] = definition
return definition
}
}
})
}
其实写的并不是一个组件而是组件的一个配置对象。
vue-loader会编译template为render函数,最终导出的依然是组件配置对象。
(style里面的代码会用style-loader,css里的会用css-loader处理成css文件)
所以我们现在写的.vue文件最终都会导出为js对象,这些对象都是组件的配置对象而不是组件的构造函数。
源码位置:lifecycle.js-mountComponent()
组件、watcher、渲染函数和更新函数之间的关系
mountComponent()定义了组件和watcher一一对应的关系。
//组件的实例在执行$mount的时候会调用的方法
export function mountComponent (
//创建一个组件的时候也创建了一个与之对应的watcher实例,
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
...
updateComponent = () => {
vm._update(vm._render(), hydrating)
//调用组件的更新函数和渲染函数
}
创建一个组件的时候也创建了一个与之对应的watcher实例,
如果这个组件中的数据发生了变化,其实只会调用该组件的渲染函数
如果在应用程序中,合理切割组件的力度,比如说将一个经常发生数据变化的一块内容,切割成一个组件,将来频繁执行的渲染函数和更新函数和打补丁的范围就变得更小了.所以把数据变化频繁的提取为组件,可以有效地提升性能。
构造函数,src\core\global-api\extend.js
实例化及挂载,src\core\vdom\patch.js-createElm()
组件的构造函数通过extend来生成,extend执行的时间点不完全相同,如果说是全局注册组件,是立刻执行,甚至在当前实例化之前就执行了。如果是一个局部组件,声明在一个单文件组件中,可能在运行时的某个时刻才会去执行extend方法,具体是什么时刻可以看一下patch的源码。
我们都知道dom是一个树,那虚拟dom也是一个树,那么这棵树的起始点一定有一个根节点,实际上根节点就是起始点,那么根节点一定不是一个组件,而是vue的根实例。那就不存在自定义组件这些东西。
当vue实例化的时候,它执行挂载之后,会有一个初始化过程,要执行打补丁patch,在此过程中会执行createElm方法,因为根节点需要创建相对应的一个真实的dom,
看看createElm方法中有几行代码是和组件化息息相关的。
//如果要创建的是组件,走下面的流程,如果传入的是vue实例,就不执行
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
//这里createComponent是把前面的那个执行的结果vnode转化为真实的dom
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
//获取管理钩子函数
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
//存在init钩子,则执行之创建实例并挂载
//这个初始化函数是在create-component.js中定义的
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
//如果组件实例存在
if (isDef(vnode.componentInstance)) {
//属性初始化
initComponent(vnode, insertedVnodeQueue)
//dom插入操作
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
create-component.js
//默认组件管理钩子
const componentVNodeHooks = {
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 {
//不是keep-alive而是要重新创建实例的时候
//创建组件实例
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
//子组件里面的子元素或者子组件也进行了初始化
//创建完成并挂载(挂载后才能从虚拟dom变成真实dom)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
实例化的过程是自上而下的,从根往叶子节点进行创建,但是由于子节点的创建会先执行,子节点创建后就立刻挂载了,所以挂载是从下而上的。
7.组件应该是高内聚、低耦合的;
高内聚:组件本来就是一个独立功能单元,功能应该是单一的,独立的,这样组件才更容易复用。
低耦合:不应该与其他组件有太多的耦合关系。
8.遵循单向数据流的原则。
通过属性把值传进来或者通过vuex的方式直接把值注入进来。如果要改变值,通过事件派发出去,然后让父级接管这个事件,从而修改数据。如果使用vuex,使用commit或者dispatch的方式,让vuex去改变这个值。而不是组件自己去改数据。让组件功能更单一。