Vue中v-bind原理深入探究
前面我們分析了v-model的原理,接下來我們看看v-bind的實現(xiàn)又是怎樣的呢?
前置內(nèi)容
<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 },看過前幾篇文章的都知道會觸發(fā)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 } })方法主要執(zhí)行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();
}
}
主要執(zhí)行createComponent方法,傳入?yún)?shù)如圖所示:

接下來執(zhí)行createComponent函數(shù),并創(chuàng)建test的vm函數(shù)然后創(chuàng)建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;
}創(chuàng)建的vnode如下圖所示,其中componetnOptions是創(chuàng)建vm函數(shù)時的參數(shù)。這個在后面實例化test的vm函數(shù)時有用

此時所有vnode基本創(chuàng)建完畢。此時執(zhí)行vm._update(vm._render(), hydrating)方法,該方法主要執(zhí)行vm.$el = vm._patch_(prevVnode, vnode)方法,該方法執(zhí)行createChildren去遍歷vnode執(zhí)行createElm方法:
function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
...
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return;
}
...
}由于第一個node是test是一個組件,所有會執(zhí)行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);
}
}該方法執(zhí)行componentVNodeHooks的init方法的createComponentInstanceForVnode去創(chuàng)建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過程中創(chuàng)建的vm函數(shù),在實例化的過程中會執(zhí)行initInternalComponent函數(shù),該函數(shù)從父vnode的componentOptions中獲取prop數(shù)據(jù):
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屬性就已經(jīng)傳到test組件上了。initInternalComponent方法執(zhí)行完畢繼續(xù)執(zhí)行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);
}
}
首先執(zhí)行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并循環(huán)執(zhí)行_loop_1(key)方法,該方法首先執(zhí)行validateProp方法校驗數(shù)據(jù)和我們在test組件中定義的props類型是否相同。然后執(zhí)行defineReactive方法將該prop設置在vm._props中并設置get和set。
此時我們得出一個結論,test組件會先根據(jù)props校驗propsData的類型并獲取值,test組件定義的props會被設置響應式。至此App.vue中的v-on中給的值已經(jīng)被傳到test組件并設置了初始值。
不難看出,當App.vue中的數(shù)據(jù)發(fā)生變化時會重新執(zhí)行變量中的watcher的update方法重新將值傳入test組件。由于該組件已經(jīng)創(chuàng)建了會存在prevVnode值,所以不會再次創(chuàng)建只會執(zhí)行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的時候把該值傳過去。
- 在實例化組件的時候會根據(jù)props里面創(chuàng)建的值和傳進來的值做類型校驗,然后并設置響應式同時設置初始值。
- 父組件值變化的時候會觸發(fā)該變量的set方法父組件執(zhí)行updateChildren方法對比新舊子組件并更新值。
到此這篇關于Vue中v-bind原理深入探究的文章就介紹到這了,更多相關Vue v-bind內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue+tsc+noEmit導致打包報TS類型錯誤問題及解決方法
當我們新建vue3項目,package.json文件會自動給我添加一些配置選項,這寫選項基本沒有問題,但是在實際操作過程中,當項目越來越復雜就會出現(xiàn)問題,本文給大家分享vue+tsc+noEmit導致打包報TS類型錯誤問題及解決方法,感興趣的朋友一起看看吧2023-10-10
vue3之Suspense加載異步數(shù)據(jù)的使用
本文主要介紹了vue3之Suspense加載異步數(shù)據(jù)的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-02-02
vue?cli+axios踩坑記錄+攔截器使用方式,代理跨域proxy
這篇文章主要介紹了vue?cli+axios踩坑記錄+攔截器使用方式,代理跨域proxy,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04

