Redian新闻
>
因为一个写法,我翻烂了 vue 源码,这是它的问题吧,我要不要提 pr!

因为一个写法,我翻烂了 vue 源码,这是它的问题吧,我要不要提 pr!

公众号新闻

推荐关注↓

作者:老骥farmer

https://juejin.cn/post/7245171528122990650

问题背景

我已经老了。。。。面对现在的观众不知该如何表达。既然这样的话

那......

直接上代码吧:


<template>
  <div>
    <div class="test" :style="[is ? {backgroundColor:'red'} : '',bg]">这是测试页面</div>
  </div>

</template>
<script setup>
import { ref } from 'vue'
const is=ref(true)
const bg = ref({'background-color': 'yellow'})
setInterval(() => {
       is.value=!is.value
}, 5000);
</
script>
<style>
.test{
  height500px;
  width500px;
  text-align: center;
  line-height500px;
  font-size40px;
}
</style>

事情就发在昨天,在我们单位的办公大厅里,有一个产品向我走来。他主动介绍自己,他对我说,“老骥:你这个页面有问题,很大很大的问题,现在我是特地来告诉你,对我来说,还得辛苦你给我解决问题”

我很慌乱.....

因为此时我的正在吃早饭,嘴里还有个茶叶蛋

我慌忙的咽了下去,提醒焦急的产品:

我知道你很急,但.....

请你不要着急!!

我得一点一点的排查问题。

具体业务问题就不交代了,复现代码请见开头

具体现象如下,请细品

Kapture 2023-06-14 at 17.10.50.gif

首先我设置了一个定时器,定时器中通过一个变量控制者绑定的style 在以上代码中,虽然定时器在不停的执行,

但是,由于bg这个值是个常量,理论上来说他的页面背景应该一直呈现黄色

有人问,为啥你要设置成黄色?

额,这不是重点,可能因为我们是黄种人

然而现实情况却在黄色和没有颜色之间徘徊,这是为什么?

问题探究过程

抱着好奇的态度我首先怀疑的是我的我对于vuestyle动态的值的绑定是不是理解的不透彻

探究vue文档

我怀着忐忑的心情,找到了vue文档,在文档中我只需要确认两点:

  • 1、style绑定数据的规则
  • 2、style的驼峰写法规则

style绑定数据的规则&style的驼峰写法规则

在他的官方文档中我们可以发现

<div :style="[baseStyles, overridingStyles]"></div>

他的绑定朴实无华,并且根据我翻看源码得出结论,数组后方的变量覆盖前方的变量

源码如下:

export function normalizeStyle(
  value: unknown
): NormalizedStyle | string | undefined 
{
  // 判断样式数组的情况
  if (isArray(value)) {
    // 最后格式化之后的样式对象
    const res: NormalizedStyle = {}
    // 对当前数组进行遍历,此处就可以预示着,在初始值后方的数组内容会覆盖前方的
    for (let i = 0; i < value.length; i++) {
      // 拿到数组中的每一项
      const item = value[i]
      // 如果是个字符串,那就表示这个样式需要解析
      const normalized = isString(item)
        ? parseStringStyle(item)
        // 否则防止是个多维数组,递归调用最终将所有的都放在res中
        : (normalizeStyle(item) as NormalizedStyle)
      if (normalized) {
        for (const key in normalized) {
          res[key] = normalized[key]
        }
      }
    }
    return res
    // 其他情况暂且不看
  } else if (isString(value)) {
    return value
  } else if (isObject(value)) {
    return value
  }
}

同样是通过上述源码中内容可以发现,他并没有对于类似background-color以及 backgroundColor做统一的格式处理,这个所谓的normalizeStyle其实就是将绑定的值,做一个集成处理,方便在后续绑定的时候做统一的处理循环绑定。

此时我们先排除了代码的写法错误,接下来我的排查方向其实应该就是vue源码中的蛛丝马迹

于是我首先将问题定位在了源码中的的模板解析错误

查看模板解析

我们知道vue的模板的的编译结果是可以在浏览器中查看的,具体查看方式有两种

vue-devtools 中可以直接查看编译结果

image.png

从源码中我们可以看到他先调用上方的normalizeStyle方法对绑定样式做处理,在调用createElementVNode 去创建vnode

当然如果你嫌弃vue提供的不清不楚,不头不尾,别急。。。。

我们在浏览器的控制台中也能看到端倪

在浏览器中查看

image.png

如上图所示,在开发环境下,我们利用 sourcemap,可以完美的查看到整个代码结构,以及编译后的源码,包括他的引用链条,并且他还可以打断点!

从上述代码中我们可以清楚的发现,这个常亮的值确实被编译成功了

那既然这样的话,我就开始怀疑是createElementVNode 的问题

排查createElementVNode

function createBaseVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,// vnode类型
  props: (Data & VNodeProps
) | null = null,// 属性
  childrenunknown = null,// 子节点
  patchFlag = 0, // 补丁标记
  dynamicPropsstring[] | null = null,
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
  isBlockNode = false,
  needFullChildrenNormalization = false
{
  // 创建vnode
  const vnode = {
    __v_isVNodetrue,
    __v_skiptrue,
    type,
    props,// 中间包含style内容
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    slotScopeIdsnull,
    children,
    componentnull,
    suspensenull,
    ssContentnull,
    ssFallbacknull,
    dirsnull,
    transitionnull,
    elnull,
    anchornull,
    targetnull,
    targetAnchornull,
    staticCount0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildrennull,
    appContextnull,
    ctx: currentRenderingInstance
  } as VNode
  if (needFullChildrenNormalization) {
    normalizeChildren(vnode, children)
    if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
      ; (type as typeof SuspenseImpl).normalize(vnode)
    }
  } else if (children) {
    // compiled element vnode - if children is passed, only possible types are
    // string or Array.
    vnode.shapeFlag |= isString(children)
      ? ShapeFlags.TEXT_CHILDREN
      : ShapeFlags.ARRAY_CHILDREN
  }


  if (__DEV__ && vnode.key !== vnode.key) {
    warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type)
  }


  if (
    isBlockTreeEnabled > 0 &&
    !isBlockNode &&
    currentBlock &&
    (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
    vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
  ) {
    currentBlock.push(vnode)
  }

  if (__COMPAT__) {
    convertLegacyVModelProps(vnode)
    defineLegacyVNodeProperties(vnode)
  }

  return vnode
}

export { createBaseVNode as createElementVNode }

从以上代码中我们发现,其实createElementVNode主要做的事情只有一个,就是创建vnode 并且vnode中是包含样式信息的

效果图如下:

image.png

从上图中我们可以发现,他确实包含两个属性,那就表示,这个vnode中应该是包含所有的style信息,并没有缺失,那么就只能是样式更新的问题了

样式更新

说起样式更新,我们还得老规矩,从丘处机路过牛家村开始

在样式的更新操作中,避免不了patch 函数,以及diff过程,这个过程的主流程咱就不过多赘述了,讲的人已经够多了,俺嘴皮子磨破,也就那么两句,没啥新意,俺就主要讲讲diff过程中的跟样式有关的内容,在diff的过程中,有很多的类型改变响应的处理函数,而我们的props的处理对应的就是patchProp 函数 代码如下:

export const patchProp: DOMRendererOptions['patchProp'] = (
  el,
  key,
  prevValue,
  nextValue,
  isSVG = false,
  prevChildren,
  parentComponent,
  parentSuspense,
  unmountChildren
) => {
    
    patchStyle(el, prevValue, nextValue)

}

而在patchProp 函数中还有patchStyle函数,用用来专门处理内联样式,代码如下:

export function patchStyle(el: Element, prev: Style, next: Style{
  // 拿到style样式
  const style = (el as HTMLElement).style
  const isCssString = isString(next)
  //如果不是字符窜
  if (next && !isCssString) {
    // 遍历对象 设置style
    for (const key in next) {
      setStyle(style, key, next[key])
    }
    // 老的style删除
    if (prev && !isString(prev)) {
      for (const key in prev) {
        // 优化手段,如果新的节点没有,那么就表示需要删除,如果按照正常思维,
        //应该是先给老的全删了新的全加上
        if (next[key] == null) {
          setStyle(style, key, '')
        }
      }
    }
  } else {
    // 字符串的情况我们暂且不论
    const currentDisplay = style.display
    if (isCssString) {
      if (prev !== next) {
        style.cssText = next as string
      }
    } else if (prev) {
      el.removeAttribute('style')
    }
    if ('_vod' in el) {
      style.display = currentDisplay
    }
  }
}

看到这,我相信大家已经一目了然了

image.png
image.png

根本原因就是在vue内部没有样式写法做标准化统一, 经过测试,vue2也会有这个问题,

所以,我就怀疑这是不是尤大是故意为之,他不允许你这么书写

其实据我粗浅的理解,解决方式非常简单,我们只需要将代码标准化为驼峰写法,或者连字符写法即可,并且vue3源码中也给了我们对应的函数

//将连字符转化为转驼峰 'on-click' => 'onClick'
const camelizeRE = /-(\w)/g

const camelize = cacheStringFunction(str => {
  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
})


//将小驼峰转化为连字符字符串 'onClick' => 'on-click'

onst hyphenateRE = /\B([A-Z])/g const hyphenate = cacheStringFunction((str: string) => str.replace(hyphenateRE, '-$1').toLowerCase() )

而我们只需要在normalizeStyle函数中,处理即可,代码如下:

   if (normalized) {
        for (const key in normalized) {
          res[hyphenate(key)] = normalized[hyphenate(key)]
        }
      }

好了,问题排查完毕,然后需要发出灵魂一问?

vue源码中是刻意不解决这个问题吗?他是一个使用场景的取舍吗?可有告知?


- EOF -


推荐阅读  点击标题可跳转

1、Google 有一个函数,20000 个变量……

2、员工每天“带薪如厕”3~6小时被解雇,官司一直打到高院

3、真刑!3 员工偷公司游戏源码,半年赚了 1.5 亿


关注「程序员的那些事」加星标,不错过圈内事

点赞和在看就是最大的支持❤️

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
俞敏洪:中国孩子的问题,基本上都是家长的问题【2023 Bellevue Neighborhood Walks】邻居们,一起和市执行长在Bellevue的街上走一走《行香子 - 贺吉安一中百年华诞》真刑!3名离职员工窃取公司源码,半年狂赚 1.5 亿元认了吧,认了吧,这就是他的宿命了。《夏日的玫瑰》&合唱《天下有情人》茅奖鲁奖作家的这套读写法,让我悟了!推荐35款 SpringBoot/SpringCloud 开源项目,附源码泪目!中年父亲为救重症儿子,翻烂医书、冒险制药:人一旦有爱,就如同身披铠甲我的无依之旅和有衣之旅五天爬五岳,我翻过了人生的浪浪山社会活力一步步丧失,这是一个严峻的问题3名离职员工窃取公司游戏源码,半年狂赚 1.5 亿,网友:“自立门户也不带这样玩的!”推荐几个写论文和绘图神器!美债上限危机还没结束!投资者准备应对流动性问题吧一个价值30亿的问题:封神烂了吗以后,卷起来解决更贵的问题吧为了早日退休,我翻烂了这本书!“民主太嘈杂了?这就是它的运作方式,因为人人都能发声” #伯恩斯大使的民主对话# 第三集(4)叔叔的问题,阿姨的问题,还是谁的问题真刑!3 员工偷公司游戏源码,半年赚了 1.5 亿每个写作者的书架上,都应该有这5本书一套被翻烂的化学科普天花板,能从小学读到高中毕业!2023回国 梦牵魂萦的上海南京路,外滩(多图)Vue+SpringBoot 集成 PageOffice 实现在线编辑Word、excel文档秋招干货!简历的满分写法,还不快点get起来?拼音要不要提前学?邻居说,她真后悔当初的做法体验丨大家给评评理:这是香港国泰航空的问题,还是我的问题!罗翔毕业致辞刷屏:如果世界接受不了我们的理想,不是我们的问题,是世界的问题战旗仍在飘扬问360智脑五个刁钻问题,下面是它的回复乙肝两对半与 HBV DNA 结果不符,把书翻烂总结了 19 种原因!每日原则:为了分清楚哪些是人手不足的问题,哪些是能力不够的问题“有娃后感情破裂,我要不要离婚?”如何去阅读源码,我总结了18条心法
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。