每个 Bug 都值得认真对待:分享一个 debug 的案例,推荐给前端实习生参考

简介: 每个 Bug 都值得认真对待:分享一个 debug 的案例,推荐给前端实习生参考

前言


最早听说 wangEditor 编辑器还是在 17 年,当时读王福朋大佬的 深入理解javascript原型和闭包 时提到的,作为国产开源项目,历经近十年依然在不断的技术迭代并使用 ts vdom 重构,真的很强。 近期有个文字编辑需求,用 textarea 效果总是不尽如人意,于是就上了富文本编辑器。


Bug 初现


首先安装下面两个包:


"@wangeditor/editor": "^5.1.0",
"@wangeditor/editor-for-vue": "^1.0.2",然后浅看下文档:


这种项目一般是不依赖框架的,不过为了方便用户使用,官方还提供了 vue、react 组件,本着能不动手尽量 copy 的原则,我们用他的 vue 组件,然后意料之外的出现了警告:


vue.esm.js?4f66:5023 [Vue warn]: $attrs is readonly
vue.esm.js?4f66:5023 [Vue warn]: $listeners is readonly


1687782370404.png


这个警告虽然目前看来并没有造成流程阻断,但确是个很大的隐患,一定要在上线前 debug 掉。


Debug & 修复


首先让我们熟练的打开 谷歌🦴,输入vue.esm.js?4f66:5023 [Vue warn]: $attrs is readonly 或者 vue.esm.js?4f66:5023 [Vue warn]: $listeners is readonly都行


1687782359117.png


很顺利的就初步定位到了原因,很可能是由于 @wangeditor/editor-for-vue 组件内部引入的 vue 版本和我们项目本地的不同导致的冲突;为了确认是这个原因,让我们找到它的源码,从中看到它竟然使用的是 vue2.7.5 的版本,竟然是跟 vue 官方同步的节奏,然而我的项目中 vue 还是 2.6.14 的版本,看来是这个原因引起的。


1687782349004.png


解决方案:使 vue 版本保持一致即可,为了这个小插件来升级我的项目 vue 版本不太合适,还是降级下吧,看了下它的版本更新记录,发现固定到 1.0.0 的版本即可,从 package.json 中把这俩插件版本锁定


"@wangeditor/editor": "5.1.0",
"@wangeditor/editor-for-vue": "1.0.0"


重新装包,启动,发现已经不再报警告了。


1687782340446.png


思考


为什么引入两个不同的 vue版本,会出现这个警告呢?


虽然这只是个小小的 bug, 不过其背后的执行逻辑还是引起了我的好奇,为什么引入两个不同的 vue版本,会出现这个警告呢?让我们从源码中来寻找答案:🤔


// js 中相关代码
// 此变量默认为 false
var isUpdatingChildComponent = false;
// 渲染函数
function initRender (vm) {
  /* istanbul ignore else */
  {
    defineReactive$$1(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
      !isUpdatingChildComponent && warn("$attrs is readonly.", vm);
    }, true);
    defineReactive$$1(vm, '$listeners', options._parentListeners || emptyObject, function () {
      !isUpdatingChildComponent && warn("$listeners is readonly.", vm);
    }, true);
    ...
  }
}
}
// 关键
function updateChildComponent (
  vm,
  propsData,
  listeners,
  parentVnode,
  renderChildren
) {
  {
    isUpdatingChildComponent = true;
  }
  // determine whether component has slot children
  // we need to do this before overwriting $options._renderChildren.
  // check if there are dynamic scopedSlots (hand-written or compiled but with
  // dynamic slot names). Static scoped slots compiled from template has the
  // "$stable" marker.
  var newScopedSlots = parentVnode.data.scopedSlots;
  var oldScopedSlots = vm.$scopedSlots;
  var hasDynamicScopedSlot = !!(
    (newScopedSlots && !newScopedSlots.$stable) ||
    (oldScopedSlots !== emptyObject && !oldScopedSlots.$stable) ||
    (newScopedSlots && vm.$scopedSlots.$key !== newScopedSlots.$key) ||
    (!newScopedSlots && vm.$scopedSlots.$key)
  );
  // Any static slot children from the parent may have changed during parent's
  // update. Dynamic scoped slots may also have changed. In such cases, a forced
  // update is necessary to ensure correctness.
  var needsForceUpdate = !!(
    renderChildren ||               // has new static slots
    vm.$options._renderChildren ||  // has old static slots
    hasDynamicScopedSlot
  );
  vm.$options._parentVnode = parentVnode;
  vm.$vnode = parentVnode; // update vm's placeholder node without re-render
  if (vm._vnode) { // update child tree's parent
    vm._vnode.parent = parentVnode;
  }
  vm.$options._renderChildren = renderChildren;
  // update $attrs and $listeners hash
  // these are also reactive so they may trigger child update if the child
  // used them during render
  vm.$attrs = parentVnode.data.attrs || emptyObject;
  vm.$listeners = listeners || emptyObject;
  // update props
  if (propsData && vm.$options.props) {
    toggleObserving(false);
    var props = vm._props;
    var propKeys = vm.$options._propKeys || [];
    for (var i = 0; i < propKeys.length; i++) {
      var key = propKeys[i];
      var propOptions = vm.$options.props; // wtf flow?
      props[key] = validateProp(key, propOptions, propsData, vm);
    }
    toggleObserving(true);
    // keep a copy of raw propsData
    vm.$options.propsData = propsData;
  }
  // update listeners
  listeners = listeners || emptyObject;
  var oldListeners = vm.$options._parentListeners;
  vm.$options._parentListeners = listeners;
  updateComponentListeners(vm, listeners, oldListeners);
  // resolve slots + force update if has children
  if (needsForceUpdate) {
    vm.$slots = resolveSlots(renderChildren, parentVnode.context);
    vm.$forceUpdate();
  }
  {
    isUpdatingChildComponent = false;
  }
}


从上面找到的相关代码,可以看到,updateChildComponent 函数负责给 $attrs$listener 属性赋值, isUpdatingChildComponent 变量默认为 false, 处理前将 isUpdatingChildComponent 置为true,在处理完成后 isUpdatingChildComponent 置为false。

根据 initRender 函数中的判断条件 !isUpdatingChildComponent && warn("$attrs is readonly.", vm)就明白了,只有在更新子组件的时候,attrs/attrs/attrs/listeners 是可写的,否则都会报 $attrs/$listeners is readonly 的警告错误, 而当 @wangeditor/editor-for-vue 插件运行的时候,其内部重新加载了 vue, 导致 isUpdateChildComponent 被重置成 false 了, 因此 initRender 执行时出现 $attrs/$listeners is readonly 的错误警告。


listeners 和 attrs 又是什么 api 呢?


listeners 定义: 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

attrs 定义: 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。


大多数同学们对这两个 api 都不是很熟悉,因为日常真的不常用到,从官网可以看到,这两个 api 是从 2.4.0 版本开始新增的,一般都是创建高阶组件时候可能会用到,日常的组件传值是不建议用的,会影响代码的可读性。

下面我以伪代码为例来讲解下它俩的实际用法:


// parent.vue 父组件中给 index 组件传值 定义了2个 prop
<index age=18 name='zhou'></index>
// index.vue
<index>
    {{age}} // index.vue 只用了 age,那么 name 等其余属性就可以通过 attrs 的形式传给 son 组件使用
    <son v-bind="$attrs" @click="age++"></son> // v-on 定义的事件都可以在son 中通过 $listeners 监听到
</index>
// son.vue
mounted() {
    console.log(this.$attrs) // {name: 'zhou'}
    console.log(this.$listeners) // {click: f}
}


好了,以上就是本文的全部内容了,希望这个 debug 的过程和思路对你有那么点帮助吧。

目录
相关文章
|
1月前
|
前端开发 算法 Java
【CSS】前端三大件之一,如何学好?从基本用法开始吧!(一):CSS发展史;CSS样式表的引入;CSS选择器使用,附带案例介绍
上下文选择器(迭代选择器):基于祖先或同胞元素选择一个元素 ID和类选择器:基于id#和class的属性值进行选择元素。 属性选择器:基于属性的有无和特征进行选择。 ①上下文选择器: 上下文选择器的语法格式:标签1 标签2{属性:值;} //注意:组合选择器和上下文选择器的区别,组合选择器以逗号隔开, 上下文选择器以空格隔开 ②特殊的上下文选择器 子选择器> : 语法格式:标签1>标签2 解释说明:标签1和标签2
206 1
|
10月前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
368 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
前端开发 JavaScript
前端一键回到顶部案例
本文介绍了如何实现网页中的一键回到顶部功能,包括两种方法:第一种是通过HTML中的锚点跳转实现快速回到顶部;第二种是使用JavaScript的`scrollTo`方法结合`requestAnimationFrame`实现滚动动画效果,让页面滚动更加平滑自然。
470 1
前端一键回到顶部案例
|
前端开发 JavaScript 开发者
前端开发的终极技巧:如何让你的代码既简洁又高效,还能减少bug?
【10月更文挑战第30天】前端开发充满挑战与创新,如何编写简洁高效且少bug的代码是开发者关注的重点。本文介绍五大技巧:1. 模块化,提高代码复用性;2. 组件化,降低代码耦合度;3. 使用现代框架,提高开发效率;4. 统一代码规范,降低沟通成本;5. 利用工具,优化代码质量。掌握这些技巧,让前端开发更高效。
489 1
|
存储 前端开发 Java
验证码案例 —— Kaptcha 插件介绍 后端生成验证码,前端展示并进行session验证(带完整前后端源码)
本文介绍了使用Kaptcha插件在SpringBoot项目中实现验证码的生成和验证,包括后端生成验证码、前端展示以及通过session进行验证码校验的完整前后端代码和配置过程。
2191 0
验证码案例 —— Kaptcha 插件介绍 后端生成验证码,前端展示并进行session验证(带完整前后端源码)
|
JSON 前端开发 JavaScript
socket.io即时通信前端配合Node案例
本文介绍了如何使用socket.io库在Node.js环境下实现一个简单的即时通信前端配合案例,包括了服务端和客户端的代码实现,以及如何通过socket.io进行事件的发送和监听来实现实时通信。
298 2
|
JavaScript 前端开发
前端基础(十)_Dom自定义属性(带案例)
本文介绍了DOM自定义属性的概念和使用方法,并通过案例展示了如何使用自定义属性来控制多个列表项点击变色的独立状态。
196 0
前端基础(十)_Dom自定义属性(带案例)
|
存储 JSON 前端开发
node使用token来实现前端验证码和登录功能详细流程[供参考]=‘很值得‘
本文介绍了在Node.js中使用token实现前端验证码和登录功能的详细流程,包括生成验证码、账号密码验证以及token验证和过期处理。
448 0
node使用token来实现前端验证码和登录功能详细流程[供参考]=‘很值得‘
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
1019 14
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
326 0

热门文章

最新文章

  • 1
    前端如何存储数据:Cookie、LocalStorage 与 SessionStorage 全面解析
  • 2
    前端工程化演进之路:从手工作坊到AI驱动的智能化开发
  • 3
    Vue 3 + TypeScript 现代前端开发最佳实践(2025版指南)
  • 4
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(五):背景属性;float浮动和position定位;详细分析相对、绝对、固定三种定位方式;使用浮动并清除浮动副作用
  • 5
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(六):全方面分析css的Flex布局,从纵、横两个坐标开始进行居中、两端等元素分布模式;刨析元素间隔、排序模式等
  • 6
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(一):CSS发展史;CSS样式表的引入;CSS选择器使用,附带案例介绍
  • 7
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(八):学习transition过渡属性;本文学习property模拟、duration过渡时间指定、delay时间延迟 等多个参数
  • 8
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(九):强势分析Animation动画各类参数;从播放时间、播放方式、播放次数、播放方向、播放状态等多个方面,完全了解CSS3 Animation
  • 9
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(四):元素盒子模型;详细分析边框属性、盒子外边距
  • 10
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(二):CSS伪类:UI伪类、结构化伪类;通过伪类获得子元素的第n个元素;创建一个伪元素展示在页面中;获得最后一个元素;处理聚焦元素的样式
  • 下一篇
    oss云网关配置