简体中文
uni-app x中,有3种class样式
这3者的影响关系,引用和覆盖,就是class样式隔离策略。
在实际开发中,有的组件是自己开发的、有的是引入三方组件,不同场景下隔离需求也不一样。
对于组件作者,大多不希望外部的class影响自身的样式,导致自己的组件不正常。
但有的组件作者,没有给组件使用者提供恰当的组件样式自定义方式,导致依赖被外部覆盖class来定制组件样式。
有些插件作者开发的不是组件,而是页面模板,尤其是基于dialogPage的页面插件,也同样存在如何让插件使用者自定义插件内样式的问题。
这是一个有点复杂的多边关系,需要一个明确的默认策略,并提供自定义方案。
在HBuilderX 5.0以前,这个关系并没有梳理清楚,web、各家小程序各有各的策略。
从HBuilderX 5.0起,全平台统一了样式隔离策略。这可能会造成一定的向下兼容问题,开发者需要注意。
HBuilderX 5+,提供了 样式隔离策略2.0
蒸汽模式仅支持样式隔离策略2.0
非蒸汽模式,可在manifest.json源码视图中设置:manifest.json->uni-app-x->styleIsolationVersion: "2"
考虑到向下兼容问题,目前为开发者和插件作者提供了过渡期,非蒸汽模式暂时默认是老策略,即styleIsolationVersion: "1"。需要显式开启2.0策略。
主流插件作者陆续改造完毕后,将彻底废弃老版样式隔离策略。
样式隔离策略2.0的详细介绍如下:
虽然有默认策略,但页面和组件,均支持通过配置样式隔离策略,单独声明自己是否允许被外部影响。
在<script setup>中使用 defineOptions 定义 styleIsolation,取值值域有 isolated | app | app-and-page。
页面的默认值是 app,组件的默认值是 isolated。(注意一个uvue页面,也可以同时被当做组件使用,此时默认值也变成了 isolated)
使用示例:
<script setup lang="uts">
defineOptions({
styleIsolation: 'isolated' //表示隔离,不受外部影响
})
</script>
推荐开发者尽快升级选项式为组合式,但样式隔离策略2.0在非蒸汽模式下也支持选项式写法。
<script>
export default {
styleIsolation: 'isolated' //表示隔离,不受外部影响
}
</script>
本文所指的页面,不区分普通页面还是dialogPage。
本策略调整,会对插件市场的组件使用造成较大影响。
如果组件作者,希望保持向下兼容,即兼容HBuilderX 5以前的样式方案。可以在组件内配置app-and-page,允许全局和页面进行同名class合并。
但注意即便配置了app-and-page,只是尽可能减少向下兼容,和原来也有少许差异,即原本web下页面不能影响组件,配置app-and-page后会被影响。
当组件隔离后,不受外部class影响,那么组件的样式定制,就需要一套新的完善方案。
在web component中,默认也是样式隔离,但设计了一套::part伪元素方案来影响组件的子组件样式。
uni-app x也需要一套完善的方案。
组件的根样式,可以通过父层使用组件时传入style或class来影响。
但很多组件有嵌套子组件,这些子组件也有开放给外部使用者来自定义样式的需求。
有一种方式是把子组件的样式,封装成组件属性。但这种属性封装有较多弊端:
HBuilderX 5.0+,提供了可跨全平台的external-class,即组件把必要的子组件的样式暴露出去,外部可以传入一个class进行覆盖。相比属性封装,这样更优雅且高性能。
举个例子,假使有一个组件switch,它内部有2层结构,根view和圆球view。
根view的样式可以被父层直接设置的class和style影响,那么圆球view可以暴露一个 thumb-class 的扩展class被外部影响。
Switch组件示例源码:
<template>
<view class="uni-switch"> <!-- 这里是外层view -->
<view class="uni-switch-thumb" :class="thumbClass"></view> <!-- 这里是圆球view -->
</view>
</template>
<script lang="uts" setup>
const props = withDefaults(defineProps<SwitchProps>(), {
thumbClass: '' // 定义一个thumbClass属性
})
defineOptions({
externalClasses: ['thumb-class'] // 注册扩展class,有了这个配置,uni-app x 框架就会把没有写在组件里的外部class传入进来。数组方式,支持配置多个external-class
})
</script>
Switch组件的使用示例:
<template>
<switch thumb-class="red-thumb-class"></switch>
</template>
<style>
.red-thumb-class{ /* 此处示例限app平台,web和小程序更为复杂,具体见下*/
background-color:red
}
</style>
上面的代码,会让这个Switch组件的圆球变成红色。
以上只是举例,实际的switch组件要比上述例子复杂。可以参考Switch组件的源码
defineOptions中配置externalClasses,从HBuilderX5.0起支持。
总结下使用external-class需要做的事情:
defineOptions({externalClasses: ['xxx-class']})在HBuilderX 5.0之前,开发者会发现不写这个配置,在非web平台,也可以生效。
这是因为非web平台在HBuilderX 5.0之前,会默认把页面样式合并到组件中,也就是组件在非web平台默认不隔离,可以引用页面class。
从HBuilderX 5.0起,组件样式默认隔离,如果组件不配置app或app-and-page,就只能通过注册externalClasses来实现外部样式的影响。
注意事项:
!important;。如果组件作者有些样式并不想被外部自定义,那么可以写在组件的内联style中。由于class的优先级低于style,所以内联style永远生效(除非class中的样式定义为important)。
还是以Switch组件举例,假使组件源码中圆球view的style写了border-width: 2px,那么外部使用者无法改变圆球view的边框粗细。
<template>
<view class="uni-switch"> <!-- 这里是外层view -->
<view style="border-width: 2px" class="uni-switch-thumb" :class="thumbClass"></view> <!-- 这里是圆球view -->
</view>
</template>
除了external-class,其实组件作者也可以提供external-style。
此时可作为组件属性直接使用,无需在defineOptions中注册externalClasses。
external-style是内联字符串,不受样式隔离策略影响,它永远生效。所以在HBuilderX 5.0之前也是生效的。
但external-style有个需要注意的问题,这种方式会导致子组件所有样式都能被外部影响。
组件作者难以控制哪部分样式不允许被外部修改。可能出现使用者使用不当导致组件变形。
<template>
<view class="uni-switch"> <!-- 这里是外层view -->
<view :style="thumbStyle" class="uni-switch-thumb" :class="thumbClass"></view> <!-- 这里是圆球view -->
</view>
</template>
<script lang="uts" setup>
const props = withDefaults(defineProps<SwitchProps>(), {
thumbClass: '' // 定义一个thumbClass属性
thumbStyle: '' // 定义一个thumbStyle属性
})
defineOptions({
externalClasses: ['thumb-class'] // 注册扩展class,无需注册扩展style
})
</script>
再次提醒,以上介绍的styleIsolation、external-class等内容,均属于样式隔离策略2.0的内容。非蒸汽模式的应用未开启 manifest.json->uni-app-x->styleIsolationVersion: "2" 是不会生效的。