IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 【vue设计与实现】组件的实现原理 3 - props与组件的被动更新 -> 正文阅读

[JavaScript知识库]【vue设计与实现】组件的实现原理 3 - props与组件的被动更新

在虚拟DOM层面,组件的props与普通HTML标签的属性差别不大。假设我们有如下模板:

<MyComponenent title="A Big Title" :other="val" />

这段模板对应的虚拟DOM是:

const vnode = {
	type: MyComponent,
	props:{
		title: 'A big Title',
		other: this.val
	}
}

可以看到,模板与虚拟DOM几乎是“同构”的。另外,在编写组件时,需要显式地指定组件会接收那些props数据,如下面代码所示:

const MyComponent = {
	name: 'MyComponent',
	// 组件接收名为title的props,并且该props的类型为String
	props:{
		tite: String
	},
	render(){
		return{
			type: 'div',
			children: `count is: ${this.title}` // 访问props数据
		}
	}

}

所以,对于一个组件,有两部分关于props的内容需要关心的是:

  1. 为组件传递的props数据,即组件的vnode.props对象
  2. 组件选项对象中定义的props选项,即MyComponent.props对象

结合两个选项来解析出组件在渲染时需要用到的props数据,具体实现如下:

function mountComponent(vnode, container, anchor){
	const componentOptions = vnode.type
	// 从组建选项对象中取出props定义,即propsOption
	const {render, data, props: propsOption /* 其他省略 */ } = componentOptions
	
	beforeCreate && beforeCreate()
	const state = reactive(data())
	// 调用resolveProps函数解析出最终的props数据与attrs数据
	const [props, attrs] = resolveProps(propsOption, vnode.props)
	const instance = {
		state,
		// 将解析出的props数据包装为shallowReactive并定义到组件实例上
		props: shallowReacive(props),
		isMounted: false,
		subTree: null
	}
	vnode.comoponent = instance

	// 省略部分代码
	
}

// resolveProps 函数用于解析组件props和attrs数据
function resolveProps(options, propsData){
	const props = {}
	const attrs = {}
	// 遍历为组件传递的props数据
	for (const key in propsData){
		if(key in options){
			// 如果为组件传递的props数据在组件自身的props选项中有定义,则将其视为合法的props
			props[key] = propsData[key]
		}else{
			// 否则将其作为attrs
			attrs[key] = propsData[key]
		}
	}
	
	return [props,attrs]

}

我们将组件选项中定义的MyComponent.props对象和为组件传递的vnode.props对象想结合,最终解析出组件在渲染时需要使用的props和attrs数据。
这里需要注意两点:

  1. 在Vue.js3中,没有定义在MyComponent.props选项中的props数据将存储到attrs对象中
  2. 上述实现中没有包含默认值、类型校验等内容的处理。当然这些实现起来也不难

处理完props数据后,再来看关于props数据变化的问题。props本质上是父组件的数据,当props发生变化时,会触发父组件重新渲染,假设父组件的模板如下:

<template>
	<MyComponent :title="title" />
</template>

其中,响应式数据title的初始值为字符串"A big title",因此首次渲染时,父组件的虚拟DOM为:

const vnode = {
	type: MyComponent,
	props: {
		title: 'A big title'
	}
}

当响应式数据title发生变化时,父组件的渲染函数会重新执行,产生的新虚拟DOM如下:

const vnode = {
	type: MyComponent,
	props: {
		title: 'A small title'
	}
}

接着,父组件会进行自更新。在更新过程中,渲染器发现父组件的subTree包含组件类型的虚拟节点,所以会调用patchComponent函数完成子组件的更新,如下patch函数的代码所示:

function patch(n1,n2,container,anchor){
	if(n1 & n1.type !== n2.type){
		unmount(n1)
		n1 = null
	}

	const { type } = n2

	if(typeof type === 'string'){
		// 省略部分代码
	}else if(type === Text){
		// 省略部分代码
	}else if(type === Fragment){
		// 省略部分代码
	}else if(typeof type === 'object'){
		// vnode.type 的值是选项对象,作为组件来处理
		if(!n1){
			mountComponent(n2,container, anchor)
		}else{
			// 更新组件
			patchComponent(n1,n2,anchor)
		}
	}
}

我们由父组件自更新所引起的子组件更新叫做子组件的被动更新
当子组件发生被动更新时,需要做的是:

  1. 检测子组件是否真的需要更新,因为子组件的props可能不变
  2. 如果需要更新,则更新子组件的props,slots等内容

patchComponent函数的具体实现如下:

function patchComponent(n1,n2,anchor){
	// 获取组件实例,即n1.component,同时让新的组件虚拟节点 n2.component也指向组件实例
	// 将组件实例添加到新的组件vnode对象上,即n2.component = n1.component
	const instance = (n2.component = n1.component) 
	// 获取当前的props数据
	const {props} = instance
	// 调用hasPropsChanged检测为子组件传递的props是否发生变化,如果没有变化,则不需要更新
	if(hasPropsChanged(n1.props, n2.props)){
		// 调用 resolveProps函数重新获取props数据
		const {nextProps} = resolveProps(n2.type.props, n2.props)
		// 更新props
		for(const k in nextProps){
			props[k] = nextProps[k]
		}
		// 删除不存在的props
		for(const k in props){
			if(!(k in nextProps)) delete props[k]
		}
	}
}

function hasPropsChanged(prevProps,nextProps){
	const nextKeys = Object.keys(nextProps)
	// 如果旧props的数量变了,则说明有变化
	if(nextKeys.length !== Object.keys(prevProps).length){
		return true
	}
	for(let i=0;i<nextKeys.length;i++){
		const key = nextKeys[i]
		// 有不相等的props, 则说明有变化
		if(nextProps[key] !== prevProps[key]) return true
	}
	return false

}


要注意的是

  1. 需要将组件实例添加到新的组件vnode对象上,即n2.component = n1.component,否则下次更新时将无法取得组件实例
  2. instance.props对象本身是浅响应的(即shallowReactive)。因此,在更新组件的props时,只需要设置instance.props对象下的属性值即可触发组件重新渲染。

上面的实现中,没有处理attrs与slots的更新,attrs的更新本质上与更新props的原理相似。本质上,原理都是根据组件的props选项定义以及为组件传递的props数据来处理。

由于props数据与组件自身的状态数据都需要暴露到渲染函数中,并使得渲染函数能够通过this访问它们,因此需要封装一个渲染上下文对象,如下面代码所示:

function mountComponent(vnode, container, anchor){
	// 省略部分代码
	const instance = {
		state, 
		props: shallowReactive(props),
		isMounted: false,
		subTree: null
	}

	vnode.component = instance
	
	// 创建渲染上下文对象,本质上是组件实例的代理
	const renderContext = new Proxy(instance, {
		get(t,k,r){
			// 取得组件自身状态与props数据
			const {state, props } = t
			// 先尝试读取自身状态数据
			if(state && k in state){
				return state[k]
			}else if(k in props){ // 如果组件自身没有改数据,则尝试从props中读取
				return props[k]
			}else{
				console.error('不存在')
			}
		},
		set(t,k,v,r){
			const {state, props} = t
			if(state && k in state){
				state[k] = v
			}else if(k in props){ 
				props[k] = v
			}else{
				console.error('不存在')
			}
		}
	})
	
	// 生命周期函数调用时要绑定渲染上下文对象
	created && created.call(renderContext)
	//省略部分代码
}

上面这段代码,为组件实例创建了一个代理对象,该对象即渲染上下文对象。其作用是拦截数据状态的读取和设置操作,每当在渲染函数或生命周期钩子中通过this读取数据时,都会优先从组件的自身状态中读取,如果组件本身并没有对应的数据,则再从props数据中读取。最后将渲染上下文作为渲染函数以及生命周期钩子的this值即可

补充知识
对象的解构赋值
如果变量名与属性名不一致,必须写成下面这样。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-11-05 00:19:49  更:2022-11-05 00:20:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 17:17:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码