Vue中v-bind原理深入探究
前面我們分析了v-model的原理,接下來(lái)我們看看v-bind的實(shí)現(xiàn)又是怎樣的呢?
前置內(nèi)容
<template>
<div>
<test :propTest="a"></test>
<div @click="changeA">點(diǎn)我</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("點(diǎn)我")]),
],
1
)
}
可以看出v-on:propTest='a’會(huì)被解析成attrs: { propTest: _vm.a },看過(guò)前幾篇文章的都知道會(huì)觸發(fā)a變量的get方法收集依賴。目前主要是看當(dāng)前組件是怎么把a(bǔ)ttrs屬性傳遞給子組件的:
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ù)如圖所示:

接下來(lái)執(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í)的參數(shù)。這個(gè)在后面實(shí)例化test的vm函數(shù)時(shí)有用

此時(shí)所有vnode基本創(chuàng)建完畢。此時(shí)執(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;
}
...
}由于第一個(gè)node是test是一個(gè)組件,所有會(huì)執(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實(shí)例:
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);
}
實(shí)例化過(guò)程中使用了vnode過(guò)程中創(chuàng)建的vm函數(shù),在實(shí)例化的過(guò)程中會(huì)執(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方法校驗(yàn)數(shù)據(jù)和我們?cè)趖est組件中定義的props類型是否相同。然后執(zhí)行defineReactive方法將該prop設(shè)置在vm._props中并設(shè)置get和set。
此時(shí)我們得出一個(gè)結(jié)論,test組件會(huì)先根據(jù)props校驗(yàn)propsData的類型并獲取值,test組件定義的props會(huì)被設(shè)置響應(yīng)式。至此App.vue中的v-on中給的值已經(jīng)被傳到test組件并設(shè)置了初始值。
不難看出,當(dāng)App.vue中的數(shù)據(jù)發(fā)生變化時(shí)會(huì)重新執(zhí)行變量中的watcher的update方法重新將值傳入test組件。由于該組件已經(jīng)創(chuàng)建了會(huì)存在prevVnode值,所以不會(huì)再次創(chuàng)建只會(huì)執(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);
}
...
};至此整個(gè)過(guò)程結(jié)束。
總結(jié)
- 會(huì)將v-on:propTest="a"解析成attrs: { propTest: _vm.a },并在渲染組件test的時(shí)候把該值傳過(guò)去。
- 在實(shí)例化組件的時(shí)候會(huì)根據(jù)props里面創(chuàng)建的值和傳進(jìn)來(lái)的值做類型校驗(yàn),然后并設(shè)置響應(yīng)式同時(shí)設(shè)置初始值。
- 父組件值變化的時(shí)候會(huì)觸發(fā)該變量的set方法父組件執(zhí)行updateChildren方法對(duì)比新舊子組件并更新值。
到此這篇關(guān)于Vue中v-bind原理深入探究的文章就介紹到這了,更多相關(guān)Vue v-bind內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3中g(shù)etCurrentInstance不推薦使用及在<script?setup>中獲取全局內(nèi)容的三種方式
這篇文章主要給大家介紹了關(guān)于vue3中g(shù)etCurrentInstance不推薦使用及在<script?setup>中獲取全局內(nèi)容的三種方式,文中通過(guò)介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-02-02
vue3之a(chǎn)xios跨域請(qǐng)求問(wèn)題及解決
這篇文章主要介紹了vue3之a(chǎn)xios跨域請(qǐng)求問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
vue3.0報(bào)錯(cuò)Cannot?find?module‘worker_threads‘的解決辦法
這篇文章介紹了vue3.0報(bào)錯(cuò)Cannot?find?module‘worker_threads‘的解決辦法。對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-11-11
vue video和vue-video-player實(shí)現(xiàn)視頻鋪滿教程
這篇文章主要介紹了vue video和vue-video-player實(shí)現(xiàn)視頻鋪滿教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
VUE3中引入.env下的環(huán)境變量,顯示process未定義問(wèn)題
這篇文章主要介紹了VUE3中引入.env下的環(huán)境變量,顯示process未定義問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04

