【事故记录】子组件样式没有被正确应用?关于style写的时候为啥应该嵌套在一个总的容器class里

项目技术栈

vue3 js OA 后台管理类型的项目

场景

代码发布到生产环境后,测试反馈某些页面的图标样式大小错乱,具体表现为:

  1. 首次加载进入到某个使用 SVG 组件的页面内,样式一切正常显示
  2. 点击左侧菜单跳转进入其他页面后,然后点击回退返回前一个页面发现 SVG 子组件的大小错乱
  3. 所有应用 svg 组件的页面都有这种问题
  4. 仅生产环境有此问题,测试环境和本地代码无问题

问题代码展示

  • 有问题的子组件 SvgIcon
xml 复制代码
<template>
  <svg :class="svgClass">
    <use :xlink:href="iconName" />
  </svg>
</template>
<script>
export default {
  name: "SvgIcon",
  props: {
    iconClass: {
      type: String,
      required: true,
    },
    className: {
      type: String,
      default: "",
    },
    size: {
      type: Number,
      default: 16,
    },
  },
  computed: {
    iconName() {
      return `#icon-${this.iconClass}`;
    },
    svgClass() {
      if (this.className) {
        return "svg-icon " + this.className;
      } else {
        return "svg-icon";
      }
    },
  },
};
</script>

<style lang="less" scoped>
@icon-bg: #1890ff;
.svg-icon {
  width: 1.5em;
  height: 1.5em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
  margin: 0 5px;
  cursor: pointer;
}
</style>
  • 使用子组件的父组件(关键代码节选,并非完整页面)

DOM:

xml 复制代码
<template>
  <div class="mainpage">
                 ...
		<SvgIcon v-if="record.selfStatus === 0" className="statusIcon lineMiddle" icon-class="submit-waitSubmit"></SvgIcon>
		<SvgIcon v-if="record.selfStatus === 1" className="statusIcon lineMiddle" icon-class="submit-success"></SvgIcon>
		<SvgIcon v-if="record.selfStatus === 2" className="statusIcon lineMiddle" icon-class="submit-waitEdit"></SvgIcon>
                ...
  </div>
</template>

CSS:

xml 复制代码
<style scoped>
.mainpage {
  width: 100%;
  height: 100%;
}
.statusIcon {
	width: 12px;
	height: 12px;
	display: inline-block;
	vertical-align: top;
	margin-right: 6px;
	margin-left: 0;
}
</style>

问题排查

  1. 关于样式错乱,直接体现为父组件定义了12像素宽高,但是在错乱之后,控制台显示12像素被子组件原本定义的宽高1.5em覆盖。
  • 最开始我是直接通过class 绑定在 SvgIcon标签上,怀疑父子组件样式权重问题,因此通过className props传入父组件的class,保证是处于子组件绑定的class后面,但无效。
  1. 为什么生产环境才有这种毛病,为什么子组件的样式始终在父组件后面
  • 在本地设置环境为production,然后打包代码后发现,打包会有多个chunk文件,而非production环境的打包只有一个chunk
  • 在生产环境下切换页面,然后查看控制台发现,当进入且仅第一次某个页面后,会在html头部的尾部添加其样式引入文件,而非production环境下没有这种引入的过程
  • scoped本来会添加唯一标识的,但SvgIcon具有两个data-v标识,一个是它自己在所有页面共用的一种标识,一个是父组件渗透下来的标识。而子组件选择器样式,和父组件定义的样式层级同级,造成二者权重相冲,也就是说,谁后声明谁就牛逼。而切换页面后,这个子组件定义的宽高样式跑到了父组件定义的样式之后,冲掉了当前页面本该展示的模样。

反思和知识点

  1. 关于样式scoped
  • scoped样式的data-v会给页面的所有dom节点加上,而子组件自身由于也使用了scoped,导入的过程中又会有一个在各父组件里固定的data-v,因此在这个意义上,子组件data-v并不是"唯一"的,会有两个。而只有页面的根节点(即我例子所示最外层容器mainpage),它只具备一个data-v且唯一,也就是说,如果我们页面的所有选择器都挂在这个页面选择器之后,可以保证其样式选择器是有唯一性的,不会被其他data-v选到同一个元素
  • 在本次踩坑之前,我认为scoped绑定的style全都是唯一的,样式也就写的比较随意,没有考虑非要放在mainpage里
  1. 关于css打包 本次踩坑,包括现在我都有一些疑问,关于production环境下的打包会生成多个chunk文件,而其他我自己定义的环境只有一个;每次切换不同的页面,production环境会加载对应的样式,而其他环境则像是打包完一开始全部加载完毕。
  • 在vue.config.js里有一个相关的css配置,此前没有去了解
css 复制代码
css: {
  	loaderOptions: {
  		less: {
  			lessOptions: {
  				modifyVars: {
  					'primary-color': '#3C53FF',
  				},
  				javascriptEnabled: true,
  			},
  		},
  	},
  	// extract: true
  },

这里的extract是我后来配置的,默认情况下这个配置在判断当前env为production的情况下,它是true,其他环境是false.因此产生差异。如果自己主动配置为true,那么任何环境都会打包出css,而不是内联打包到js里面

最终解决方案

按照反思1中所提,将父组件内所有样式全部写在mainpage,即最外层容器class下面.可保证样式选择器前额外带上父组件的classname,权重会大于子组件选择器

xml 复制代码
<style lang='less' scoped>
    .mainpage {
      width: 100%;
      height: 100%;
      .statusIcon {
          width: 12px;
          height: 12px;
          display: inline-block;
          vertical-align: top;
          margin-right: 6px;
          margin-left: 0;
      }
    }
</style>

如果不想改动css写法,也可以考虑按照反思2的点将vueconfig.js里extract设置为false,这样他就不会打包出css,直接内联打包到js脚本里,这样的话也可以保证顺序,子组件样式先生成,而后父组件样式覆盖子组件。

但这种方式感觉是破坏了他逐步加载css的初衷,我最后没有采用

css 复制代码
  css: {
		loaderOptions: {
			less: {
				lessOptions: {
					modifyVars: {
						'primary-color': '#3C53FF',
					},
					javascriptEnabled: true,
				},
			},
		},
		extract: false
	},
相关推荐
小白白一枚1115 小时前
css实现div被图片撑开
前端·css
@蒙面大虾5 小时前
CSS综合练习——懒羊羊网页设计
前端·css
顾菁寒6 小时前
WEB第二次作业
前端·css·html
前端Hardy10 小时前
HTML&CSS:爱上班的猫咪
前端·javascript·css·vue.js·html
聚宝盆_13 小时前
【css flex 多行均分有间隙布局】
前端·css
零希13 小时前
CSS元素类型(二)
前端·javascript·css
煎饼果子呀13 小时前
css-flex布局属性
开发语言·前端·css·html5
沈阳-施立14 小时前
CSS例子: 胶囊按钮
前端·css
前端Hardy14 小时前
超萌!HTML&CSS:打造趣味动画卡通 dog
前端·css·html·css3
Komorebi⁼14 小时前
JavaScript的对象事件监听处理,交互式网页的关键!
开发语言·前端·javascript·css·html