Vue的props实现原理
这是一道常见的面试题——vue中的props的实现原理 我们都知道,在使用组件的时候可以传入一些属性:
<MyComponent a=1></MyComponent>
=>ast语法树 => vnode
简而言之我们写的这代码片段会通过ast语法树 解析成JS语法 最后会把这个语法生成所谓的虚拟节点vnode
那么虚拟节点产生的时候会把这个传入的属性会放在哪?
以下是源码片段源码文件
// 源码文件:create-component.js
// 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 }, // propsData
asyncFactory
)
源码中定义了vnode,分别有组件的名字、数据之类的。
后面会把当初传入的所有的属性放到propsData中,这里面就放的所有属性的定义,所以会将属性放到虚拟节点上,等会组件初始化的时候会进行一系列的操作
//源码文件:init.js
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData // 这里!!!
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
组件初始化的时候opts.propsData = vnodeComponentOptions.propsData 意为会拿到propsData 而且把这个propsData定义到了我们当前的VM的$options的opts.propsData 把我们默认虚拟节点的属性对象附到了我们当前示例上,接下来
//源码:state.js
//propsOptions 用户定义的属性
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {} //定义
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) { //如果是根元素 属性需要定义成响应式的
toggleObserving(false)
}//否则属性是从父组件传递过来的,我们不需要将属性定义响应式
for (const key in propsOptions) {
keys.push(key)//校验用户定义的属性和传入属性
const value = validateProp(key, propsOptions, propsData, vm)
...
} else {
defineReactive(props, key, value)
}
...
当我们初始化时可以通过vm.$options.propsData 拿到用户传入的数据(比如开篇的 a=1) ,那么接收的属性都在propsOptions里, 进一步声明vm._props = {}声明一个空对象,之后会判断是否为根元素如果是根元素 属性需要定义成响应式的,否则属性是从父组件传递过来的,我们不需要将属性定义响应式。
再之后会去循环用户定义的propos选项(for (const key in propsOptions))相当于
propos:{a:{type:String}}
根据用户定义的个对象,和我们用户传入的这样一个值进行一个校验,并且拿到最终的结果。如果是根元素靠defineReactive(props, key, value)把当前的Key和Value定义到我们的_props实现响应式
(那不是根元素的话就不会将它定义成响应式的,因为它本身这个属性就是从父组件传递给子组件的,所以它一定也是响应式的了)
最后的最后把属性都定义到了vm._props中,最后将vm._props定义到示例中,这样就可以拿到传入的属性