前一篇文章分析了函数式组件用法(https://blog.csdn.net/liubangbo/article/details/104034122) ,这篇文章从源码的角度看看函数式组件,看看它怎么是无状态的,以及怎么没有实例的。我们就用之前文章的例子来进行分析。
要渲染的组件如下:
<div id="app">
<list-view-comp :id="'listViewId'" :list-data="listData"></list-view-comp>
</div>
list-view-comp 就是函数式组件(在options里面定义了functional属性为true),id和list-data是传递给这个组件的props数据。经过模板编译之后,走到创建组件这个地方,我们就从创建组件开始分析:
function _createElement (
context,
tag,
data,
children,
normalizationType
) {
// some code
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
);
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options,
'components', tag))) {
// component
console.log("liubbc comp tag: " + tag)
console.log("liubbc comp data: " + JSON.stringify(data))
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
);
}
传递给createComponent函数的参数tag,data 打印如下:
liubbc comp tag: list-view-comp
liubbc comp data: {"attrs":{"id":"listViewId","list-data":[{"icon":"./icon.png","logo":"./logo.png","title":"title 1","subTitle":"sub title 1"}]}}
data参数是个object对象,attrs属性包含传递给这个组件的 id, list-data数据。接着我们看createComponent函数。
function createComponent (
Ctor,
data,
context,
children,
tag
) {
//some code
// extract props
var propsData = extractPropsFromVNodeData(data, Ctor, tag);
console.log("liubbc createComponent data is: " + JSON.stringify(data));
console.log("liubbc createComponent propsData is: " + JSON.stringify(propsData));
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
//some code
}
加的两行打印如下:
liubbc createComponent data is: {"attrs":{"id":"listViewId"}}
liubbc createComponent propsData is: {"listData":[{"icon":"./icon.png","logo":"./logo.png","title":"title 1","subTitle":"sub title 1"}]}
createComponent函数对data数据进行了处理,把父组件的动态数据提取出来传递给了子组件,完成了父组件向子组件传递数据的过程(props down)。data数据中的attrs对象中只剩下id属性了。
接下来会判断组件的选项里面是否定义了functional,如果为true,则createFunctionalComponent,并直接返回。主角粉墨登场了,我们就一探createFunctionalComponent究竟。
function createFunctionalComponent (
Ctor,
propsData,
data,
contextVm,
children
) {
var options = Ctor.options;
var props = {};
var propOptions = options.props;
if (isDef(propOptions)) { //如果在函数式组件的选项中定义了props选项
for (var key in propOptions) {
props[key] = validateProp(key, propOptions, propsData || emptyObject);
}
} else {
//没有在函数式组件的选项中定义props选项,则用data中的attrs和props数据
if (isDef(data.attrs)) { mergeProps(props, data.attrs); }
if (isDef(data.props)) { mergeProps(props, data.props); }
}
//new了一个渲染上下文实例,这个实例里面有data,props等属性,以及_c方法(createElement方法)
var renderContext = new FunctionalRenderContext(
data,
props,
children,
contextVm,
Ctor
);
//用call方法,把函数式组件中的render函数中的this设置为null, 这样render函数就没有实例了。并
//传递了_c方法,以及新new的渲染上下文实例
var vnode = options.render.call(null, renderContext._c, renderContext);
//最后生成了vnode
if (vnode instanceof VNode) {
return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
} else if (Array.isArray(vnode)) {
var vnodes = normalizeChildren(vnode) || [];
var res = new Array(vnodes.length);
for (var i = 0; i < vnodes.length; i++) {
res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext);
}
return res
}
}
我们对关键代码进行了注释。最后我们看看FunctionalRenderContext 构造函数:
function FunctionalRenderContext (
data,
props,
children,
parent,
Ctor
) {
var this$1 = this;
var options = Ctor.options;
// ensure the createElement function in functional components
// gets a unique context - this is necessary for correct named slot check
var contextVm;
if (hasOwn(parent, '_uid')) {
contextVm = Object.create(parent);
// $flow-disable-line
contextVm._original = parent;
} else {
// the context vm passed in is a functional context as well.
// in this case we want to make sure we are able to get a hold to the
// real context instance.
contextVm = parent;
// $flow-disable-line
parent = parent._original;
}
var isCompiled = isTrue(options._compiled);
var needNormalization = !isCompiled;
this.data = data; //对data进行赋值
this.props = props; //对props进行赋值
this.children = children;
this.parent = parent;
this.listeners = data.on || emptyObject;
this.injections = resolveInject(options.inject, parent);
this.slots = function () {
if (!this$1.$slots) {
normalizeScopedSlots(
data.scopedSlots,
this$1.$slots = resolveSlots(children, parent)
);
}
return this$1.$slots
};
Object.defineProperty(this, 'scopedSlots', ({
enumerable: true,
get: function get () {
return normalizeScopedSlots(data.scopedSlots, this.slots())
}
}));
// support for compiled functional template
if (isCompiled) {
// exposing $options for renderStatic()
this.$options = options;
// pre-resolve slots for renderSlot()
this.$slots = this.slots();
this.$scopedSlots = normalizeScopedSlots(data.scopedSlots, this.$slots);
}
if (options._scopeId) {
this._c = function (a, b, c, d) {
var vnode = createElement(contextVm, a, b, c, d, needNormalization);
if (vnode && !Array.isArray(vnode)) {
vnode.fnScopeId = options._scopeId;
vnode.fnContext = parent;
}
return vnode
};
} else {
//其实_c函数就是createElement函数,用来创建vnode的
this._c = function (a, b, c, d) { return createElement(contextVm, a, b, c, d, needNormalization); };
}
}
哪里有错误,或不妥之处,欢迎指正。谢谢!如果感觉写的还可以,对您有帮助也欢迎点赞啊。