引出问题

首先我们来这么一个问题, 这里是完整的 jsfiddle demo or codepen demo

给一个元素绑定两个边框样式, 右侧和底部都为1px的红色边框


styleA: {
borderBottom: '1px solid red',
borderRight: '1px solid red'
};

然后用一个按钮(或者任何方式)将样式换成下面的样式, 一个1px的绿色边框,和1px的红色右侧边框。


styleB: {
border: '1px solid green',
borderRight: '1px solid red'
};

我们期望的结果应该是右侧边框是红色的,其余三边的边框是绿色的,但实际结果却是所有边都是绿色的, 这里已经出现了问题, 然后再点击按钮,将样式切换回去, 此时期望的结果应该是跟一开始一样: 右侧和底部都为1px的红色边框, 但实际结果却是只剩下底部的边框是红色的,右侧的边框就像消失了一样。

那么, 右侧的边框样式是不是真的消失了呢? 是不是从第一次切换就消失了呢?(这好像也能符合第一次全都是绿色边框的表现),是CSS的bug吗?

这个style的替换过程是在Vue里帮我们实现的,是跟虚拟节点vNode的渲染有关,接下来让我们去Vue的源码看一下这个问题到底是怎么样造成的。

Vue更新视图机制

首先,vue视图的更新通过updateComponent进行, updateComponent会执行一个update的方法进行更新视图,update会从根节点进行patch操作, patch操作会依次遍历虚拟节点树的所有vnode节点,深度优先的遍历方式。

通常patch操作会update以下几个部分


0: ƒ updateAttrs(oldVnode, vnode)
1: ƒ updateClass(oldVnode, vnode)
2: ƒ updateDOMListeners(oldVnode, vnode)
3: ƒ updateDOMProps(oldVnode, vnode)
4: ƒ updateStyle(oldVnode, vnode)
5: ƒ update(oldVnode, vnode)
6: ƒ updateDirectives(oldVnode, vnode)

这里我们只需要关注第5个方法:updateStyle, 那么这个方法里做了什么呢?
看一下核心逻辑:

可以看到这段代码的主要逻辑是用新的样式覆盖旧的样式,这里的setProp是对element.style进行修改,也就是原生CSSStyleDeclaration对象的实例。

  • 首先将不存在于newStyle中的oldStyle的样式设置为'',
  • 然后再设置与oldStyle中样式值不相等的newStyle的样式,

看起来没什么问题,一切都很符合逻辑,那么是什么造成了上面的现象呢?

一切的罪魁祸首都在这个border样式的简写属性(shorthand property)上。

简写属性有什么特殊的地方呢?
最直接的就是当对一个简写属性赋值,例如:


border: 1px solid green;

这个赋值会被转换为:


borderWidth: "1px"
borderStyle: "solid"
borderColor: "green" borderTop: "1px solid green"
borderTopColor: "green"
borderTopStyle: "solid"
borderTopWidth: "1px" borderRight: "1px solid green"
borderRightColor: "green"
borderRightStyle: "solid"
borderRightWidth: "1px" borderLeft: "1px solid green"
borderLeftColor: "green"
borderLeftStyle: "solid"
borderLeftWidth: "1px" borderBottom: "1px solid green"
borderBottomColor: "green"
borderBottomStyle: "solid"
borderBottomWidth: "1px"

也就是说borderTop, borderLeft, borderRight, borderBottom也都被赋值了.

原因分析

所以,回到上面的那个切换过程,根据updateStyle源码进行分析:

  • styleA切换为styleB时,

    1. 第一个for循环, borderBottom不在 oldStyle 中,被清空,borderRight在 oldStyle 中,保留了下来。
    2. 第二个for循环, border不在 oldStyle 中,设置border的值,注意此时borderTop, borderLeft, borderRight, borderBottom也都被赋值了,然后borderRight与 oldStyle 中保留下来的值相等, 跳过这次赋值。
    3. 最后的结果就是 borderTop, borderLeft, borderRight, borderBottom都显示 border的值。
  • styleB切换回为styleA时,

    1. 第一个for循环, border不在 oldStyle 中,border的值被清空,此时borderTop, borderLeft, borderRight, borderBottom也都被清空,然后borderRight在 oldStyle 中, 跳过这次赋值。
    2. 第二个for循环, borderBottom不在 oldStyle 中,borderBottom被赋值,borderRight与 oldStyle 中保留下来的值相等, 跳过这次赋值
    3. 最后的结果也就是只剩下了borderBottom的值。

解决方案

那么,原理搞清楚了,有什么好的解决方案呢? 这个问题在Vue的github上已经被提过issue了,看下尤雨溪的官方回复

这个问题被定性为了一个wontfix,但也给出了有效的解决方案:

  • 给这个元素一个用样式生成的hash值作为key, 当样式有任何变化的时候,key就会变化,在Vue的更新渲染逻辑中,如果元素的key发生变化,那么oldstyle就是空对象,就不会出现上面的问题了。

原文地址:https://segmentfault.com/a/1190000016895358

Vue.js 渲染简写样式存在的问题的更多相关文章

  1. vue.js 渲染完成回调

    vue.js渲染完成后,想触发一些事情,写在哪里呢? 答案是mounted 例子: new Vue({ el:'#demo', data:{ text:'Hello' }, mounted:funct ...

  2. Vue.js 控制css样式

    <script src="https://unpkg.com/vue/dist/vue.js"></script> <style type=" ...

  3. Vue.js 渲染函数, JSX(未掌握,未学完)

    渲染函数 , JSX(没完成学习) 基础: 实例属性:vm.$slots default 属性包括了所有没有被包含在具名插槽中的节点. 渲染函数: render: function(createEle ...

  4. Vue.js 内联样式绑定style

    html <div class="Menu" v-bind:style="{height:clientHeight}"> </div> ...

  5. 前端云原生,以 Kubernetes 为基础设施的高可用 SSR(Vue.js) 渲染微服务初探(开源 Demo)

    背景 笔者在逛掘金的时候,有幸看到掘友狼族小狈开源的 genesis - 一个可以支持 SSR 和 CSR 渲染的微服务解决方案.总体来说思想不错,但是基于 Kubernetes 云原生部署方面一直没 ...

  6. 用Vue.js开发微信小程序:开源框架mpvue解析

    前言 mpvue 是一款使用 Vue.js 开发微信小程序的前端框架.使用此框架,开发者将得到完整的 Vue.js 开发体验,同时为 H5 和小程序提供了代码复用的能力.如果想将 H5 项目改造为小程 ...

  7. 学习Vue.js之vue移动端框架到底哪家强

    官网:https://cn.vuejs.org/. 转载:http://www.cnblogs.com/8899man/p/6514212.html Weex 2016年4月21日,阿里巴巴在Qcon ...

  8. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十七 ║Vue基础:使用Vue.js 来画博客首页+指令(一)

    缘起 书说前两篇文章<十五 ║ Vue前篇:JS对象&字面量&this>和 <十六 ║ Vue前篇:ES6初体验 & 模块化编程>,已经通过对js面向对 ...

  9. vue.js使用详解

    1.什么是vue.jsvue.js是一款数据驱动型的js框架.何为数据驱动型?html视图层定义模板,vue定义数据.html和vue数据,通过标签id关联. 2.vue.js引入<script ...

随机推荐

  1. 全栈JavaScript之路( 二十 )HTML5 插入 html标记 ( 二 )insertAdjacentHTML

    insertAdjacentHTML(),  这种方法也是在IE中最早出现的.如今已纳入html5规范,它接受两个參数,一个是下列的标记之中的一个,一个是要写入的 html 代码文本. beforeb ...

  2. formValidator向struts2 ajax传參验证

    话不多说,直接上代码 页面head里面的formvalidator验证配置 <script type="text/javascript"> $(document).re ...

  3. 《linux 内核全然剖析》 笔记 CODE_SPACE 宏定义分析

    在memory.c里面.遇到一个宏定义,例如以下: #define CODE_SPACE(addr) ((((addr)+4095)&~4095) < \ current->sta ...

  4. Flex布局 Flexbox属性具体解释

    原文:A Visual Guide to CSS3 Flexbox Properties Flex布局官方称为CSS Flexble Box布局模型是CSS3为了提高元素在容器中的对齐.方向.顺序,甚 ...

  5. MySQL出现Ignoring query to other database的问题

    今天使用mysql的时候,输入随意一条命令都会出: Ignoring query to other database 这条错误信息,非常是奇怪. 后来才发现是登录数据库时.少了个-u的參数.. 正确的 ...

  6. 常用框架(一):spring+springMvc+mybatis+maven

    项目说明: (1) 本例采用 maven web 工程做例子讲解 (2) 利用mybaits 提供的代码生成工具自动生成代码(dao接口,sql mapper映射文件,pojo数据库映射类) (3) ...

  7. ubuntu之修改ls显示颜色

    Linux 系统中 ls 文件夹的痛苦我就不说了,为了不伤眼睛,一般 ssh 终端背景都用的黑色,文件夹又是你妈的深蓝色,每次看文件夹都要探头仔细去看.这下彻底解决这个问题.   因为ubuntu下的 ...

  8. 【POJ 1830】 开关问题

    [题目链接] http://poj.org/problem?id=1830 [算法] 列出异或方程组,用高斯消元求解 [代码] #include <algorithm> #include ...

  9. Node.js:创建第一个应用

    ylbtech-Node.js:创建第一个应用 1.返回顶部 1. Node.js 创建第一个应用 如果我们使用PHP来编写后端的代码时,需要Apache 或者 Nginx 的HTTP 服务器,并配上 ...

  10. [牛客挑战赛 30D] 小A的昆特牌 解题报告 (组合数学)

    interlinkage: https://ac.nowcoder.com/acm/contest/375/D description: solution: 我们枚举步兵的数量$x$,还剩下$S-x$ ...