组件
组件本身是来自Vue的继承, 在Vue初始化的时候会对component类型进行函数注册。
QQ20200412-0.png
继承函数 extend代码如下
Vue.extend = function (extendOptions) {
extendOptions = extendOptions || {};
var Super = this;
var SuperId = Super.cid;
var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
var name = extendOptions.name || Super.options.name;
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name);
}
var Sub = function VueComponent (options) {
this._init(options);
};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.cid = cid++;
Sub.options = mergeOptions(
Super.options,
extendOptions
);
Sub['super'] = Super;
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
if (Sub.options.props) {
initProps$1(Sub);
}
if (Sub.options.computed) {
initComputed$1(Sub);
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type];
});
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub;
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({}, Sub.options);
// cache constructor
cachedCtors[SuperId] = Sub;
return Sub
};
}
render生成vdom时 component的元素会挂载Vue的子类, patch的过程中执行子类的init方法,从而递归的一层层组件的渲染。
props
遵循数据单向的原则, 如果在子组件中试图改变props, vue会有报错提示,然而并不阻止数据的变更。
其判断逻辑代码如下
defineReactive$$1(props, key, value, function () {
if (!isRoot && !isUpdatingChildComponent) {
warn(
"Avoid mutating a prop directly since the value will be " +
"overwritten whenever the parent component re-renders. " +
"Instead, use a data or computed property based on the prop's " +
"value. Prop being mutated: \"" + key + "\"",
vm
);
}
});
测试示例如下
compTest.vue
<template>
<div>
my name is {{name}}
<p>
the msg is {{msg}}
<span @click="changeMsg"> change the msg</span>
<span @click="changeName"> try change prop</span>
</p>
</div>
</template>
<script>
export default {
props: {
name: {
type: String,
default: null
}
},
data() {
return {
msg: 'ddd'
}
},
methods: {
changeMsg() {
this.msg = 'two' + Date.now();
},
changeName() {
this.name = 'haha' + Date.now()
}
}
}
</script>
slot
在Vue渲染过程中,牢记其核心规则父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
匿名插槽示例
app.vue
<compTest :name="name">
<div>the top </div>
</compTest>
compTest.vue
<template>
<div>
<slot></slot>
my name is {{name}}
<p>
the msg is {{msg}}
<span @click="changeMsg"> change the msg</span>
<span @click="changeName"> try change prop</span>
</p>
</div>
</template>
编译后的代码
父元素的render
return e("div", { attrs: { id: "app" } }, [e("compTest", { attrs: { name: this.name } },
[e("div", [this._v("the top ")])]), this._v(" "), e("div", { on: { click: this.change } }, [this._v("click me")])], 1)
子元素的render
return t("div", [n._t("default"), n._v("\n my name is " + n._s(n.name) + "\n "),
t("p", [n._v("\n the msg is " + n._s(n.msg) + "\n "), t("span", { on: { click: n.changeMsg } },
[n._v(" change the msg")]), n._v(" "), t("span", { on: { click: n.changeName } }, [n._v(" try change prop")])])], 2)
通过生成的render函数我们可知,slot内部的元素仍然是在父作用域下进行vnode的生成。在子元素中可通过.slots获取的插槽信息, 从而实现在子元素中的渲染
function resolveSlots (
children,
context
) {
if (!children || !children.length) {
return {}
}
var slots = {};
for (var i = 0, l = children.length; i < l; i++) {
var child = children[i];
var data = child.data;
// remove slot attribute if the node is resolved as a Vue slot node
if (data && data.attrs && data.attrs.slot) {
delete data.attrs.slot;
}
// named slots should only be respected if the vnode was rendered in the
// same context.
if ((child.context === context || child.fnContext === context) &&
data && data.slot != null
) {
var name = data.slot;
var slot = (slots[name] || (slots[name] = []));
if (child.tag === 'template') {
slot.push.apply(slot, child.children || []);
} else {
slot.push(child);
}
} else {
(slots.default || (slots.default = [])).push(child);
}
}
// ignore slots that contains only whitespace
for (var name$1 in slots) {
if (slots[name$1].every(isWhitespace)) {
delete slots[name$1];
}
}
return slots
}
其函数的调用轨迹 this._init ——>initRender——>resolveSlots
作用域插槽
示例代码
app.vue
<template>
<div id="app">
<slotsTest>
<template v-slot:default="slotProps">
{{slotProps.user.firstName}}
</template>
</slotsTest>
</div>
</template>
slotTest.vue
<template>
<div>
<slot :user=user>
</slot>
{{user.lastName}}
</div>
</template>
<script>
export default {
data() {
return {
user:{
firstName: 'haha',
lastName: 'ali'
}
}
}
}
</script>
build后的代码
父元素的render
return t("div", { attrs: { id: "app" } }, [t("slotsTest", { scopedSlots: n._u([{ key: "default", fn: function (e) { return [n._v("\n " + n._s(e.user.firstName) + "\n ")] } }]) })], 1)
子元素的render
return (this._self._c || n)("div", [this._t("default", null, { user: this.user }), this._v("\n " + this._s(this.user.lastName) + "\n")], 2)
有上面代码可知,slot的编译过程仍是在父元素中完成。只不过生成的是个function 方法, 在子元素render的过程中会调用该方法,其对应的作用域data会作为参数传入。
函数式组件
函数式组件是一个接受一些 props , 无状态无实例,故而没有生命周期。
简单示例
app.vue
<funCompJS :name="name"></funCompJS>
funCompJS.jsx
const funCompJS = {
functional: true,
props: {
name: {
type: String,
default: null
}
},
render (h, context) {
return (
<div>
the content is {context.props.name}
</div>
);
}
}
export default funCompJS
在函数式组件中可通过context获取上下文
函数式组件createFunctionalComponent方法
function createFunctionalComponent (
Ctor,
propsData,
data,
contextVm,
children
) {
var options = Ctor.options;
var props = {};
var propOptions = options.props;
if (isDef(propOptions)) {
for (var key in propOptions) {
props[key] = validateProp(key, propOptions, propsData || emptyObject);
}
} else {
if (isDef(data.attrs)) { mergeProps(props, data.attrs); }
if (isDef(data.props)) { mergeProps(props, data.props); }
}
// 函数式render上下文
var renderContext = new FunctionalRenderContext(
data,
props,
children,
contextVm,
Ctor
);
var vnode = options.render.call(null, renderContext._c, renderContext);
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
}
}
调用轨迹
createComponent——>createFunctionalComponent——>var vnode = options.render.call(null, renderContext._c, renderContext);
异步组件
在代码优化中有些可以通过采用异步方案来实现优化的目的。
其中异步组件就是我们所需要考虑的
简单是示例
可以在components中进行异步引入
方法一
'async-comp': () => import('./asyncComp.vue')
方法二
'async-comp': function (resolve) {
require(['./asyncComp.vue'], resolve)
}
在createComponent方法中会对异步组件进行处理,异步组件首先会创建一个comment node的节点进行占位,等待异步组件加载完成后进行渲染替换
// async component
var 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
)
}
}
加载异步组件方法resolveAsyncComponent
function resolveAsyncComponent (
factory,
baseCtor
) {
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
if (isDef(factory.resolved)) {
return factory.resolved
}
var owner = currentRenderingInstance;
if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
// already pending
factory.owners.push(owner);
}
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
return factory.loadingComp
}
if (owner && !isDef(factory.owners)) {
var owners = factory.owners = [owner];
var sync = true;
var timerLoading = null;
var timerTimeout = null
;(owner).$on('hook:destroyed', function () { return remove(owners, owner); });
var forceRender = function (renderCompleted) {
for (var i = 0, l = owners.length; i < l; i++) {
(owners[i]).$forceUpdate();
}
if (renderCompleted) {
owners.length = 0;
if (timerLoading !== null) {
clearTimeout(timerLoading);
timerLoading = null;
}
if (timerTimeout !== null) {
clearTimeout(timerTimeout);
timerTimeout = null;
}
}
};
var resolve = once(function (res) {
// cache resolved
factory.resolved = ensureCtor(res, baseCtor);
// invoke callbacks only if this is not a synchronous resolve
// (async resolves are shimmed as synchronous during SSR)
if (!sync) {
forceRender(true);
} else {
owners.length = 0;
}
});
var reject = once(function (reason) {
process.env.NODE_ENV !== 'production' && warn(
"Failed to resolve async component: " + (String(factory)) +
(reason ? ("\nReason: " + reason) : '')
);
if (isDef(factory.errorComp)) {
factory.error = true;
forceRender(true);
}
});
var res = factory(resolve, reject);
if (isObject(res)) {
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject);
}
} else if (isPromise(res.component)) {
res.component.then(resolve, reject);
if (isDef(res.error)) {
factory.errorComp = ensureCtor(res.error, baseCtor);
}
if (isDef(res.loading)) {
factory.loadingComp = ensureCtor(res.loading, baseCtor);
if (res.delay === 0) {
factory.loading = true;
} else {
timerLoading = setTimeout(function () {
timerLoading = null;
if (isUndef(factory.resolved) && isUndef(factory.error)) {
factory.loading = true;
forceRender(false);
}
}, res.delay || 200);
}
}
if (isDef(res.timeout)) {
timerTimeout = setTimeout(function () {
timerTimeout = null;
if (isUndef(factory.resolved)) {
reject(
process.env.NODE_ENV !== 'production'
? ("timeout (" + (res.timeout) + "ms)")
: null
);
}
}, res.timeout);
}
}
}
sync = false;
// return in case resolved synchronously
return factory.loading
? factory.loadingComp
: factory.resolved
}
}
有代码可知,可为异步组件设置loading的展示组件,如果有loading时。首先render的为loading样式,而非commont占位节点。
可使用下面代码进行测试
'async-comp': () => ({
component: import('./asyncComp.vue'),
loading: LoadingComp,
delay: 200
})
网友评论