Vue3 源码解读系列(十)——props/emit

props

props 的作用:允许组件的使用者在外部传递,实现各种各样的功能。

初始化 props

初始化 Props 主要做了 3 件事:

  1. 设置 props 的值
  2. 验证 props 合法
  3. 把 props 变为响应式并且添加到组件实例 instance 上
/*** 初始化组件*/
function setupComponent(instance, isSSR = false) {const { props, children, shapeFlag } = instance.vnode// 判断是否是一个有状态的组件const isStateful = shapeFlag & 4// 初始化 propsinitProps(instance, props, isStateful, isSSR)// 初始化插槽initSlots(instance, children)// 设置有状态的组件实例const setupResult = isStateful ? setupStatefulComponent(instance, isSSR) : undefinedreturn setupResult
}/*** 初始化 Props* initProps 主要做了 3 件事:* 1、设置 props 的值* 2、验证 props 合法* 3、把 props 变为响应式并且添加到组件实例 instance 上*/
function initProps(instance, rawProps, isStateful, isSSR = false) {const props = {}const attrs = {}def(attrs, InternalObjectKey, 1)// 1、设置 props 的值setFullProps(instance, rawProps, props, attrs)// 2、验证 props 合法(非生产环境下执行)if ((process.env.NODE_ENV !== 'production')) {validateProps(props, instance.type)}// 3、把 props 变为响应式并且添加到组件实例 instance 上// 有状态组件,响应式处理(有状态组件是通过对象方式定义的组件)if (isStateful) {instance.props = isSSR ? props : shallowReactive(props)}// 函数式组件处理else {if (!instance.type.props) {instance.props = attrs} else {instance.props = props}}// 普通属性赋值instance.attrs = attrs
}

设置 props 的值

/*** 设置 props 的值 - 对 props 求值,然后把求得的值赋值给 props 对象和 attrs 对象中* @param {Object} instance - 组件实例* @param {Object} rawProps - 原始的 props 值,即创建 vnode 过程中传递的 props* @param {Object} props - 解析后的 props 数据* @param {Object} attrs - 解析后的普通属性数据* setFullProps 主要做了 3 件事:* 1、标准化 props 的配置* 2、遍历 props 数据求值* 3、对需要做转换的 props 求值*/
function setFullProps(instance, rawProps, props, attrs) {// 1、标准化 props 的配置const [options, needCastKeys] = normalizePropsOptions(instance.type)// 2、遍历 props 数据求值if (rawProps) {for (const key in rawProps) {const value = rawProps[key]// 一些保留的 prop 比如 ref、 key 是不会传递的if (isReservedProp(key)) continue// 连字符形式的 props 也转成驼峰形式let camelKeyif (options && hasOwn(options, (camelKey = camelize(key)))) {props[camelKey] = value}// 非事件派发相关的,且不在 props 中定义的普通属性用 attrs 保留else if (!isEmitListener(instance.type, key)) {attrs[key] = value}}}// 3、对需要做转换的 props 求值if (needCastKeys) {const rawCurrentProps = toRaw(props) // 需要做转换的 propsfor (let i = 0; i < needCastKeys.length; i++) {const key = needCastKeys[i]props[key] = resolvePropValue(options, rawCurrentProps, key, rawCurrentProps[key])}}
}
标准化 props 的配置
/*** 标准化 props 的配置* @description 所有形式的 props 最终都会被标准化为对象形式*/
function normalizePropsOptions(comp) {// comp.__props 用于缓存标准化的结果,有缓存,则直接返回if (comp.__props) {return comp.__props}const raw = comp.propsconst normalized = {} // 标准化后的 props 定义const needCastKeys = [] // 需要转换的 props key// 处理 mixins和 extends 两个特殊的 prop,因为它们的作用是扩展组件的定义,所以需要对它们定义中的 props 递归执行 normalizePropsOptionslet hasExtends = falseif (!shared.isFunction(comp)) {const extendProps = (raw) => {const [props, keys] = normalizePropsOptions(raw)shared.extend(normalized, props)if (keys) {needCastKeys.push(...keys)}}if (comp.extends) {hasExtends = trueextendProps(comp.extends)}if (comp.mixins) {hasExtends = truecomp.mixins.forEach(extendProps)}}if (!raw && !hasExtends) {return (comp._props = shared.EMPTY_ARR)}// 处理数组形式的 props 定义,如果 props 以数组的形式定义,那么每一项一定要是一个字符串if (shared.isArray(raw)) {for (let i = 0; i < rawlength; i++) {// 非字符串项,报警告if (!shared.isString(raw[i])) {warn(/* ... */)}// 把字符串变为驼峰形式作为 key,并为每一个 key 创建一个空对象作为值const normalizedKey = shared.camelize(raw[i])if (validatePropName(normalizedKey)) {normalized[normalizedKey] = shared.EMPTY_OBJ}}}// 处理对象形式的 props 定义else if (raw) {if (!shared.isObject(raw)) {warn(/* ... */)}for (const key in raw) {const normalizedKey = shared.camelize(key)if (validatePropName(normalizedKey)) {const opt = raw[key]// 标准化 prop 的定义格式const prop = (normalized[normalizedKey] = shared.isArray(opt) || shared.isFunction(opt) ? { type: opt } : opt)if (prop) {const booleanlndex = getTypelndex(Boolean, prop.type)const stringIndex = getTypelndex(String, prop.type)prop[0/* shouldCast */] = booleanindex > -1prop[1 /* shouldCastTrue */] = stringIndex < 0 || booleanlndex < stringIndex//布尔类型和有默认值的 prop 都需要转换if (booleanIndex > -1 || shared.hasOwn(prop, 'default')) {needCastKeys.push(normalizedKey)}}}}}const normalizedEntry = [normalized, needCastKeys]// 缓存标准化结果comp._props = normalizedEntry// 返回标准化结果return normalizedEntry
}
遍历 props 数据求值
// 无额外的函数
对需要做转换的 props 求值
/*** 处理 Prop 的值 - 对 Props 求值,然后把求得的值赋给 Props 对象的 attrs 对象中*/
function resolvePropValue(options, props, key, value) {const opt = options[key]// 针对两种情况做转换if (opt != null) {const hasDefault = hasOwn(opt, 'default')// 默认值转换,当在 prop 中定义了默认值,且父组件没有传递 prop 时才取默认值if (hasDefault && value === undefined) {const defaultValue = opt.defaultvalue = opt.type !== Function && isFunction(defaultValue) ? defaultValue() : defaultValue}// 布尔类型转换if (opt[0 /* shouldCast */]) {// 如果在 prop 中定义了 Boolean 类型,且父组件没有传递 prop,且没有定义默认值时,直接转换为 falseif (!hasOwn(props, key) && !hasDefault) {value = false}// 其他情况转换为 trueelse if (opt[1 /* shouldCastTrue */] &&(value === '' || value === hyphenate(key))) {value = true}}}return value
}

验证 props 合法

/*** 验证 props 是否合法*/
function validateProps(props, comp) {const rawValues = toRaw(props)const options = normalizePropsOptions(comp)[0]// 对标准化后的 props 进行遍历,拿到每一个配置 opt,然后执行 validateProp 验证for (const key in options) {let opt = options[key]if (opt == null) continuevalidateProp(key, rawValues[key], opt, !hasOwn(rawValues, key))}
}/*** 验证 prop 是否合法*/
function validateProp(name, value, prop, isAbsent) {const { type, required, validator } = prop// 如果配置了 required 但是没有传值,则报警告if (required && isAbsent) {warn(/* ... */)return}// 虽然没有值但也没有配置 required,直接返回if (value == null && !prop.required) return// 类型检测,只要满足其中一种类型就是合法的,否则报警告if (type != null && type !== true) {let isValid = falseconst types = isArray(type) ? type : [type]const expectedTypes = []}// 只要指定的类型之一匹配,值就有效for (let i = 0; i < types.length && !isValid; i++) {const { valid, expectedType } = assertType(value, types[i])expectedTypes.push(expectedType || '')isValid = valid}if (!isValid) {warn(/* ... */)return}// 如果配置了自定义校验器但是不满足校验器的规则,则报警告if (validator && !validator(value)) {warn(/* ... */)}
}

把 props 变为响应式并且添加到组件实例 instance 上

// 无额外函数

问题:

  1. 为什么 instance.props 要变成响应式?

    因为希望在子组件中监听 props 的变化而进行一些操作。

  2. 为什么用 shallowReactive API?

    因为 props 在更新过程中只会修改最外层属性,shallowReactive 就足够了。

更新 props

/*** 更新组件*/
const updateComponent = (nl, n2, parentComponent, optimized) => {const instance = (n2.component = nl.component)// 根据新旧子组件 vnode 判断是否需要更新子组件if (shouldUpdateComponent(n1, n2, parentComponent, optimized)) {instance.next = n2 // 新的子组件 vnode 赋值给 instance.next// 子组件也可能因为数据变化被添加到更新队列里了,移除它们防止对一个子组件重复更新 invalidateJob(instance.update)// 执行子组件的副作用渲染函数instance.update()}// 不需要更新,只复制属性else {n2.component = n1.componentn2.el = n1.el}
}/*** 更新组件 - 初始化渲染副作用*/
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {// 创建响应式的副作用渲染函数instance.update = effect(function componentEffect() {// 渲染组件if (!instance.isMounted) {// ...}// 更新组件else {let { next, vnode } = instance // next 表示新的组件 vnode// 更新组件 vnode 节点信息if (next) {updateComponentPreRender(instance, next, optimized)} else {next = vnode}// 渲染新的子树 vnodeconst nextTree = renderComponentRoot(instance)// 缓存旧的子树 vnodeconst prevTree = instance.subTree// 更新子树 vnodeinstance.subTree = nextTree// 组件更新核心逻辑,根据新旧子树 vnode 做 patchpatch(prevTree, nextTree,hostParentNode(prevTree.el), // 如果在 teleport 组件中父节点可能已经改变,所以容器直接找旧树 DOM 元素的父节点getNextHostNode(prevTree), // 考节点在 fragment 的情况可能改变,所以直接找旧树 DOM 元素的下一个节点instance, parentSuspense, isSVG)// 缓存更新后的 DOM 节点next.el = nextTree.el}}, prodEffectOptions)
}/*** 更新组件 - 在渲染前做一些操作*/
const updateComponentPreRender = (instance, nextVNode, optimized) => {nextVNode.component = instanceconst prevProps = instance.vnode.propsinstance.vnode = nextVNodeinstance.next = nullupdateProps(instance, nextVNode.props, prevProps, optimized)updateSlots(instance, nextVNode.children)
}/*** 更新 Props - 把父组件渲染时求得的 props 新值更新到子组件实例的 instance.props 中*/
function updateProps(instance, rawProps, rawPrevProps, optimized) {const { props, attrs, vnode: { patchFlag } } = instanceconst rawCurrentProps = toRaw(props)const [options] = normalizePropsOptions(instance.type)if ((optimized || patchFlag > 0) && !(patchFlag & 16/* FULL_PROPS */)) {// 只更新动态 props 节点if (patchFlag & 8/* PROPS */) {const propsToUpdate = instance.vnode.dynamicPropsfor (let i = 0; i < propsToUpdate.length; i++) {const key = propsToUpdate[i]const value = rawProps[key]if (options) {if (hasOwn(attrs, key)) {attrs[key] = value}else {const camelizedKey = camelize(key)props[camelizedKey] = resolvePropValue(options, rawCurrentProps, camelizedKey, value)}} else {attrs[key] = value}}}}else {// 全量 props 更新setFullProps(instance, rawProps, props, attrs)// 因为新的 props 是动态的,把那些不在新的 props 中但存在于旧的 props 中的值设置为 undefinedlet kebabKeyfor (const key in rawCurrentProps) {if (!rawProps || (!hasOwn(rawProps, key) && ((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey)))) {if (options) {if (rawPrevProps && (rawPrevProps[key] !== undefined || rawPrevProps[kebabKey] !== undefined)) {props[key] = resolvePropValue(options, rawProps || EMPTY_OBJ, key, undefined)}} else {delete props[key]}}}}if ((process.env.NODE_ENV === 'production') && rawProps) {validateProps(props, instance.type)}
}

emit

/*** 自定义事件的派发* @param {Object} instance - 执行 $emit 的组件实例* @param {string} event - 事件名称* @param  {...any} args - 事件传递的参数*/
function emit(instance, event, ...args) {const props = instance.vnode.props || EMPTY_OBJ// 获取事件名称 - 把传递的 event 首字母大写,然后在前面加上 onlet handlerName = `on${capitalize(event)}`// 根据事件名称在 props 中找到对应的回调函数let handler = props[handlerName]// 如果没有对应的回调函数 且 事件是以 update: 开头,则尝试将事件名改为 - 形式再查找对应的回调函数if (!handler && event.startsWith('update:')) {handlerName = `on${capitalize(hyphenate(event))}`handler = props[handlerName]}// 如果有对应的回调函数,则执行if (handler) {callWithAsyncErrorHandling(handler, instance, 6/* COMPONENT_EVENT_HANDLER */, args)}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/152820.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为---OSPF网络虚连接(Virtual Link)简介及示例配置

OSPF网络虚连接&#xff08;Virtual Link&#xff09;简介 为了避免区域间的环路&#xff0c;OSPF规定不允许直接在两个非骨干区域之间发布路由信息&#xff0c;只允许在一个区域内部或者在骨干区域和非骨干区域之间发布路由信息。因此&#xff0c;每个ABR都必须连接到骨干区域…

QT基础学习

2创建项目 2.1使用向导创建 打开Qt Creator 界面选择 New Project或者选择菜单栏 【文件】-【新建文件或项目】菜单项 弹出New Project对话框&#xff0c;选择Qt Widgets Application&#xff0c; 选择【Choose】按钮&#xff0c;弹出如下对话框 设置项目名称和路径&#xf…

N 字形变换

将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 “PAYPALISHIRING” 行数为 3 时&#xff0c;排列如下&#xff1a; P A H N A P L S I I G Y I R 之后&#xff0c;你的输出需要从左往右逐行读取&#xff0…

网络参考模型与标准协议(一)

OSI参考模型 OSI 模型(Open Systems Interconnection Model)&#xff0c;由国际化标准组织ISO (TheInternational Organization for Standardization )收录在ISO 7489标准中并于1984年发布。 OSI参考模型又被称为七层模型&#xff0c;由下至上依次为: 物理层: 在设备之间传输比…

Linux编辑器-gcc/g++使用

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟练使用gcc/g编译器 > 毒鸡汤&#xff1a;真正…

75基于matlab的模拟退火算法优化TSP(SA-TSP),最优路径动态寻优,输出最优路径值、路径曲线、迭代曲线。

基于matlab的模拟退火算法优化TSP(SA-TSP)&#xff0c;最优路径动态寻优&#xff0c;输出最优路径值、路径曲线、迭代曲线。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 75matlab模拟退火算法TSP问题 (xiaohongshu.com)

s28.CentOS、Ubuntu、Rocky Linux系统初始化脚本v6版本

CentOS、Ubuntu、Rocky系统初始化脚本 Shell脚本源码地址&#xff1a; Gitee&#xff1a;https://gitee.com/raymond9/shell Github&#xff1a;https://github.com/raymond999999/shell 可以去上面的Gitee或Github仓库代码拉取脚本。 版本功能v6版更新内容1.由于CentOS 6…

【金融分析】Python:病人预约安排政策 | 金融模拟分析

目录: 说明(Instructions) 问题描述(Problem Description) 仿真设置(Simulation Setting) 仿真过程的 Python 代码

WMS仓库管理系统库位功能

后端 &#xfeff;using Infrastructure.Attribute; using Model.Dto.WarehouseManagement; using Model.Page; using Model.WarehouseManagement; using Repository; using Service.Interface.WarehouseManagement; using SqlSugar;namespace Service.WarehouseManagement {[…

如何通过cpolar内网穿透工具实现远程访问本地postgreSQL

文章目录 前言1. 安装postgreSQL2. 本地连接postgreSQL3. Windows 安装 cpolar4. 配置postgreSQL公网地址5. 公网postgreSQL访问6. 固定连接公网地址7. postgreSQL固定地址连接测试 前言 PostgreSQL是一个功能非常强大的关系型数据库管理系统&#xff08;RDBMS&#xff09;,下…

uniapp开发小程序,包过大解决方案

1、首先和大家说一下 微信小程序 主包限制不能超过2M 分包一共不能超过8M 然后具体解决优化步骤如下&#xff0c; 将主包进行分包 在pages.json 下subPackages里面进行配置分包 分包配置完 配置过的文件都需要进行修改对应的路径 2 、 在运行的时候 一定要勾选 压缩代码 有…

2311d导入c的语义不同

原文 以下D代码正常工作: enum X "X"; import core.stdc.stdio; void main(){puts(X); }但是,如果该X枚举是C文件中#define的结果,则会出现错误: //x.c #define X "X" //D代码 import x; import core.stdc.stdio; void main(){puts(X); //错误 }错误:不…

Android Termux安装MySQL,内网穿透实现公网远程访问

文章目录 前言1.安装MariaDB2.安装cpolar内网穿透工具3. 创建安全隧道映射mysql4. 公网远程连接5. 固定远程连接地址 前言 Android作为移动设备&#xff0c;尽管最初并非设计为服务器&#xff0c;但是随着技术的进步我们可以将Android配置为生产力工具&#xff0c;变成一个随身…

【电路笔记】-欧姆定律

欧姆定律 文章目录 欧姆定律1、概述2、AC电路的等效性2.1 输入电阻2.2 输入电感2.3 输入电容 3、欧姆定律的局部形式3.1 介绍和定义3.2 德鲁德模型(Drude Model)3.3 局部形式表达式 4、电阻和宏观欧姆定律5、总结 电流、电压和电阻之间的基本关系被称为欧姆定律&#xff0c;可能…

国家开放大学 平时作业训练题

试卷代号&#xff1a;1428 风险沟通 参考试题&#xff08;开卷&#xff09; 一、单项选择题&#xff08;每题2分&#xff0c;共40分&#xff09; 1.关于组织传播&#xff0c;下列描述中不正确的是( )。 A.是组织成员之间、组织内部机构之间的信息交流和沟通 B.决策应变是…

cpu飙高问题,案例分析(一)

一、复习知识点&#xff1a; CPU性能指标&#xff1a; load average&#xff1a;负载&#xff0c;linux查看的时候&#xff0c;通常显示如下&#xff1a; load average后面有三段数字&#xff1a;代表了系统1分钟&#xff0c;5分钟&#xff0c;15分钟平均负载。 形象的类别可…

使用npm发布自己的组件库

在日常开发中&#xff0c;我们习惯性的会封装一些个性化的组件以适配各种业务场景&#xff0c;突发奇想能不能建一个自己的组件库&#xff0c;今后在各种业务里可以自由下载安装自己的组件。 一. 项目搭建 首先直接使用vue-cli创建一个vue2版本的项目&#xff0c;并下载好ele…

Ps:陷印

在准备图像进行专业印刷之前&#xff0c;陷印 Trap是一个重要的步骤。 在彩色印刷中&#xff0c;多种颜色的墨水通常分别印刷。陷印是一种叠印技术&#xff0c;它可避免打印时印版的微小偏差或移动而使打印图像出现微小的缝隙。 进行陷印处理以纠正未对齐现象 A. 未对齐现象&am…

原始值的响应式方案

原始值指的是 Boolean、Number、BigInt、String、Symbol、undefined 和 null 等类型的值。在JavaScript 中&#xff0c;原始值是按值传递的&#xff0c;而非按引用传递。这意味着&#xff0c;如果一个函数接收原始值作为参数&#xff0c;那么形参与实参之间没有引用关系&#x…

代码逻辑修复与其他爬虫ip库的应用

在一个项目中&#xff0c;由于需要设置 http_proxy 来爬虫IP访问网络&#xff0c;但在使用 requests 库下载文件时遇到了问题。具体表现为在执行 Python 脚本时&#xff0c;程序会阻塞并最终超时&#xff0c;无法正常完成文件下载。 解决方案 针对这个问题&#xff0c;我们可以…