目录
- 前置内容
- 解析模板
- 总结
前面我们分析了v-model的原理,接下来我们看看v-bind的实现又是怎样的呢?
前置内容
<template>
<div>
<test :propTest="a"></test>
<div @click="changeA">点我</div>
</div>
</template>
<script>
import test from './test.vue'
export default {
name: "TestWebpackTest",
components:{
test
},
mounted() {
console.log(this);
},
methods:{
changeA(){
this.a = Math.random()
}
},
data() {
return {
a:111,
};
}
};
</script>
...
<template>
<div>
{{propTest}}
</div>
</template>
<script>
export default {
name: 'test',
mounted() {
console.log(this);
},
props:{
propTest:Number
}
}
</script>
<style>
</style>
解析模板
// App.vue
var render = function render() {
var _vm = this,
_c = _vm._self._c
return _c(
"div",
[
_c("test", { attrs: { propTest: _vm.a } }),
_vm._v(" "),
_c("div", { on: { click: _vm.changeA } }, [_vm._v("点我")]),
],
1
)
}
可以看出v-on:propTest='a’会被解析成attrs: { propTest: _vm.a },看过前几篇文章的都知道会触发a变量的get方法收集依赖。目前主要是看当前组件是怎么把attrs属性传递给子组件的:
function createElement$1(context, tag, data, children, normalizationType, alwaysNormalize) {
if (isArray(data) || isPrimitive(data)) {
normalizationType = children;
children = data;
data = undefined;
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE;
}
return _createElement(context, tag, data, children, normalizationType);
}
_c(“test”, { attrs: { propTest: _vm.a } })方法主要执行createElement$1方法:
function _createElement(context, tag, data, children, normalizationType) {
...
else if ((!data || !data.pre) &&
isDef((Ctor = resolveAsset(context.$options, 'components', tag)))) {
// component
vnode = createComponent(Ctor, data, context, children, tag);
}
...
if (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方法,传入参数如图所示:
接下来执行createComponent函数,并创建test的vm函数然后创建test的vnode:
function createComponent(Ctor, data, context, children, tag) {
...
var baseCtor = context.$options._base;
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
...
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)) {
// @ts-expect-error
transformModel(Ctor.options, data);
}
// extract props
// @ts-expect-error
var propsData = extractPropsFromVNodeData(data, Ctor, tag);
// functional component
// @ts-expect-error
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
var listeners = data.on;
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn;
// @ts-expect-error
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
var slot = data.slot;
data = {};
if (slot) {
data.slot = slot;
}
}
// install component management hooks onto the placeholder node
installComponentHooks(data);
// return a placeholder vnode
// @ts-expect-error
var name = getComponentName(Ctor.options) || tag;
var vnode = new VNode(
// @ts-expect-error
"vue-component-".concat(Ctor.cid).concat(name ? "-".concat(name) : ''), data, undefined, undefined, undefined, context,
// @ts-expect-error
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }, asyncFactory);
return vnode;
}
创建的vnode如下图所示,其中componetnOptions是创建vm函数时的参数。这个在后面实例化test的vm函数时有用
此时所有vnode基本创建完毕。此时执行vm._update(vm._render(), hydrating)方法,该方法主要执行vm.$el = vm._patch_(prevVnode, vnode)方法,该方法执行createChildren去遍历vnode执行createElm方法:
function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
...
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return;
}
...
}
由于第一个node是test是一个组件,所有会执行createComponent方法:
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef((i = i.hook)) && isDef((i = i.init))) {
i(vnode, false /* hydrating */);
}
...
}
}
...
init: function (vnode, hydrating) {
...
else {
var child = (vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance));
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
}
该方法执行componentVNodeHooks的init方法的createComponentInstanceForVnode去创建test组件的vm实例:
function createComponentInstanceForVnode(parent) {
var options = {
_isComponent: true,
_parentVnode: vnode,
parent: parent
};
// check inline-template render functions
var inlineTemplate = vnode.data.inlineTemplate;
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render;
options.staticRenderFns = inlineTemplate.staticRenderFns;
}
return new vnode.componentOptions.Ctor(options);
}
实例化过程中使用了vnode过程中创建的vm函数,在实例化的过程中会执行initInternalComponent函数,该函数从父vnode的componentOptions中获取prop数据:
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options);
}
到这里为止从App.vue中的attrs属性就已经传到test组件上了。initInternalComponent方法执行完毕继续执行initState方法:
function initState(vm) {
var opts = vm.$options;
if (opts.props)
initProps$1(vm, opts.props);
// Composition API
initSetup(vm);
if (opts.methods)
initMethods(vm, opts.methods);
if (opts.data) {
initData(vm);
}
else {
var ob = observe((vm._data = {}));
ob && ob.vmCount++;
}
if (opts.computed)
initComputed$1(vm, opts.computed);
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
首先执行initProps$1方法:
function initProps$1(vm, propsOptions) {
var propsData = vm.$options.propsData || {};
var props = (vm._props = shallowReactive({}));
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
var keys = (vm.$options._propKeys = []);
var isRoot = !vm.$parent;
// root instance props should be converted
if (!isRoot) {
toggleObserving(false);
}
var _loop_1 = function (key) {
keys.push(key);
var value = validateProp(key, propsOptions, propsData, vm);
/* istanbul ignore else */
{
var hyphenatedKey = hyphenate(key);
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn$2("\"".concat(hyphenatedKey, "\" is a reserved attribute and cannot be used as component prop."), vm);
}
defineReactive(props, key, value, function () {
if (!isRoot && !isUpdatingChildComponent) {
warn$2("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: \"".concat(key, "\""), vm);
}
});
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, "_props", key);
}
};
for (var key in propsOptions) {
_loop_1(key);
}
toggleObserving(true);
}
获取到propsOptions并循环执行_loop_1(key)方法,该方法首先执行validateProp方法校验数据和我们在test组件中定义的props类型是否相同。然后执行defineReactive方法将该prop设置在vm._props中并设置get和set。
此时我们得出一个结论,test组件会先根据props校验propsData的类型并获取值,test组件定义的props会被设置响应式。至此App.vue中的v-on中给的值已经被传到test组件并设置了初始值。
不难看出,当App.vue中的数据发生变化时会重新执行变量中的watcher的update方法重新将值传入test组件。由于该组件已经创建了会存在prevVnode值,所以不会再次创建只会执行vm._patch_(prevVnode, vnode)去更新组件的值:
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode;
var restoreActiveInstance = setActiveInstance(vm);
vm._vnode = vnode;
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
}
else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode);
}
...
};
至此整个过程结束。
总结
- 会将v-on:propTest="a"解析成attrs: { propTest: _vm.a },并在渲染组件test的时候把该值传过去。
- 在实例化组件的时候会根据props里面创建的值和传进来的值做类型校验,然后并设置响应式同时设置初始值。
- 父组件值变化的时候会触发该变量的set方法父组件执行updateChildren方法对比新旧子组件并更新值。