在 Expo 项目中用expo/prefer-box-shadow规则统一阴影写法:从旧 shadow 属性迁移到现代boxShadow
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
boxShadow是 React Native 借鉴 Web 规范引入的全新阴影样式属性,它把shadowColor、shadowOffset、shadowOpacity、shadowRadius与 Android 特有的elevation合并为一条简洁的声明。本文以 eslint-plugin-expo 内置的prefer-box-shadow规则 为线索,讲解如何在 Expo / React Native 工程中通过 ESLint 自动约束阴影书写风格,并给出迁移对照、配置方法与适用边界,帮助你写出更一致、更贴近 Web、更利于跨平台复用的阴影代码。
规则定位:从阴影“四件套 + elevation”到一条 boxShadow
在传统 React Native 样式中,给组件加阴影通常要同时声明多个属性,而且 iOS 与 Android 的阴影体系互不相通:shadowColor、shadowOffset、shadowOpacity、shadowRadius主要作用于 iOS,Android 侧则长期依赖材质体系下的elevation。开发者往往需要写两套、甚至出现“只写了 elevation,iOS 上没有阴影”这类经典问题。
React Native 现在支持了 Web 风格的boxShadow样式属性,它提供了一种更一致、表达能力更强的阴影声明方式,在 iOS、Android 与 Web 上遵循相同的取值语法。eslint-plugin-expo中的expo/prefer-box-shadow规则正是为了推广这一现代 API 而生:它会定位到仍在使用旧阴影属性的样式对象,并给出提示(suggestion)。
规则文件的原始说明
仓库中该规则的官方文档(packages/eslint-plugin-expo/docs/rules/prefer-box-shadow.md)将其定义概括为:
React Native now supports a new web-like
boxShadowAPI that provides a more consistent and powerful way to add shadows to components. This rule encourages the use of the newboxShadowproperty instead of the legacy shadow properties.
在规则元数据中(src/rules/prefer-box-shadow.ts),官方对该规则的描述则进一步点明推荐场景:
Box shadow is a simpler, more consistent way of defining shadows on components. It is recommended for web builds.
也就是说,prefer-box-shadow既是一条代码规范约束,也承载了 Expo 对“跨 Web 一致渲染”这一方向的引导。
规则触发原理:源码如何检测旧阴影属性
待检测的旧属性清单
该规则的核心逻辑非常简单清晰。打开 packages/eslint-plugin-expo/src/rules/prefer-box-shadow.ts,可以看到被判定为“旧写法”的属性被硬编码进一个数组:
const oldShadowProps = [ 'shadowColor', 'shadowOffset', 'shadowOpacity', 'shadowRadius', 'elevation', ];这 5 个属性正是文档中列出的全部旧阴影 API。
基于 AST 的对象属性扫描
规则的实现并不针对某个 API 调用,而是从 AST 层面监听所有ObjectExpression(对象字面量表达式)。每遇到一个对象字面量,它都会遍历该对象的每个属性,只要发现属性满足下面三个条件就上报一次问题:
- 属性类型是普通
Property(即key: value形式,而非展开运算符或方法); - 属性键是
Identifier(标识符),即字面量属性名而非计算属性; - 属性名命中
oldShadowProps列表中的任意一项。
相关代码位于 src/rules/prefer-box-shadow.ts:
return { ObjectExpression(node) { for (const property of node.properties) { if ( property.type === 'Property' && property.key.type === 'Identifier' && oldShadowProps.includes(property.key.name) ) { context.report({ node: property, messageId: 'preferBoxShadow', }); } } }, };据此可以得到三个可推断的工程事实:
- 一条属性 = 一条报告。无论一个样式对象里写了
shadowColor+elevation两条,还是写全五条,每命中一条旧属性都会产生独立的提示,方便你逐个清理。 - 检测对象不限于
StyleSheet.create。由于监听的是通用ObjectExpression,规则同样作用于普通对象常量与 JSX 内联样式对象(测试用例对此有明确覆盖,详见下文)。 - 规则不做自动修复。
meta中只有schema: [](不接受任何配置项)和messages,并未提供fixer,因此它的输出是一条 messageId 为preferBoxShadow、文案为 "prefer box shadow" 的提示信息,需要你手动改写样式。
规则如何被注册与导出
preferBoxShadow在 packages/eslint-plugin-expo/src/rules/index.ts 中以'prefer-box-shadow'为 key 注册,随后由插件入口 src/index.ts 统一导出。规则通过@typescript-eslint/utils的ESLintUtils.RuleCreator创建,当在支持该机制的编辑器/工具中展示规则时,会自动把meta.docs.url指向仓库内对应的文档(docs/rules/prefer-box-shadow.md),方便你一键查看规则说明。
新旧写法对照与迁移映射
错误的写法(规则会提示)
官方文档给出的“错误”示例几乎涵盖了一组典型 iOS + Android 阴影:
const styles = StyleSheet.create({ container: { shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5, }, });这组代码一共会触发5 条提示(shadowColor、shadowOffset、shadowOpacity、shadowRadius、elevation各一条)。
正确的写法(规则放行)
对应的现代写法是用一条字符串把偏移、模糊半径与颜色全部表达出来:
const styles = StyleSheet.create({ container: { boxShadow: '0px 2px 3.84px rgba(0, 0, 0, 0.25)', }, });其中boxShadow的取值遵循 CSS 风格的语法,逐段拆解上面这条声明对应关系如下:
| boxShadow 片段 | 值 | 对应旧属性 |
|---|---|---|
第 1 段offset-x | 0px | shadowOffset.width = 0 |
第 2 段offset-y | 2px | shadowOffset.height = 2 |
第 3 段blur-radius | 3.84px | shadowRadius = 3.84 |
| 第 4 段 颜色 | rgba(0, 0, 0, 0.25) | shadowColor = '#000'+shadowOpacity = 0.25 |
- 颜色可直接写成十六进制(如
#000000),也可以在 Web 版本上借助带透明通道的颜色表达原来的“颜色 + 不透明度”组合; - 不带
inset关键字时为外阴影,这与传统外阴影语义一致; - 多条阴影可用逗号分隔,表达能力超过旧的单层方案。
各书写位置的适用性
从规则的实现方式可以确认,检测不关心对象是经由StyleSheet.create创建、还是普通 JS 对象,抑或是 JSX 上的内联样式:
- 在
StyleSheet.create({ ... })内使用 → 触发提示(见上面错误示例); - 在普通对象常量中使用 → 触发提示;
- 在
<View style={{ ... }} />内联样式中使用 → 触发提示。
对应测试用例位于 src/tests/prefer-box-shadow.test.ts,其中invalid分组不仅覆盖了StyleSheet.create中写满五条旧属性的场景,还覆盖了内联样式与“只写shadowColor+elevation两条”的局部场景;每条旧属性都断言产生一次preferBoxShadow错误,与实现中“逐属性上报”的行为完全吻合。
安装与配置
第一步:安装
根据插件自带的 README,推荐通过 Expo CLI 安装 ESLint 与插件本体:
npx expo install eslint --save-dev npx expo install eslint-plugin-expo --save-dev仓库内 packages/eslint-plugin-expo/package.json 显示其peerDependencies为eslint >= 8.10,并声明node需满足^22.13.0 || ^24.3.0 || ^26.0.0 || >=27.0.0。
第二步:在 eslintrc 中开启规则
在该插件经典的配置方式中,把expo加入plugins字段(eslint-plugin-前缀可省略),再在rules中按需开启。由于prefer-box-shadow属于风格建议类且不做自动修复,从告警升级到阻断通常由团队自行决定,README 示例将其配置为warn:
{ "plugins": ["expo"], "rules": { "expo/prefer-box-shadow": "warn" } }在支持 ESLint 9 flat config 的项目中,等价写法大致如下(以 CommonJS 为例):
const expoPlugin = require('eslint-plugin-expo'); module.exports = [ { plugins: { expo: expoPlugin }, rules: { 'expo/prefer-box-shadow': 'warn', }, }, ];规则不接受任何配置项(schema: []),因此上述开启方式即为全部可配置内容。注意:该规则没有被内置到仓库的eslint-config-expo推荐集中——从 packages/eslint-config-expo/flat/utils/expo.js 与 packages/eslint-config-expo/utils/expo.js 的现有配置看,预置集中只启用了expo/use-dom-exports,prefer-box-shadow需要你在项目中显式开启。
通过测试用例理解规则的边界行为
在 src/tests/prefer-box-shadow.test.ts 中可以看到一组值得留意的“有效/无效”边界:
不报错的写法(valid),即只要用的是boxShadow,无论书写位置如何都通过:
StyleSheet.create内使用boxShadow;- 普通对象常量内使用
boxShadow; <View>内联样式中使用boxShadow。
报错的写法(invalid):
StyleSheet.create内同时出现五个旧阴影属性,断言 5 个错误;- 内联样式中同时出现五个旧阴影属性,断言 5 个错误;
- 对象内仅出现
shadowColor与elevation,断言 2 个错误。
这套用例把“新 API 任意位置放行、旧 API 逐条上报”的规则行为锁定为可回归验证的契约。若你 fork 了仓库并想本地跑通测试,可以在 packages/eslint-plugin-expo 目录下执行该包package.json中声明的test脚本(jest)来运行全部规则测试。
何时不应使用该规则
官方文档为这条规则的启用划定了清晰的前提,本质上是围绕boxShadow的平台与架构限制:
- 未使用 React Native New Architecture时不应开启——
boxShadow是仅在新架构下可用的新 API,旧架构下没有对应实现; - 需要支持 Android 9 以下版本时,外阴影(outset shadows)会失效;
- 需要支持 Android 10 以下版本时,内阴影(inset shadows,即
boxShadow中带inset关键字的写法)会失效; - 需要兼容更旧的 React Native 版本、且无法通过依赖升级获得该 API 时,不应强制迁移。
换言之,如果项目基线已经落在 New Architecture + 较高的 Android 版本之上(例如面向最新 Expo SDK 的新工程),启用该规则几乎没有兼容性顾虑;反之,若仍需兼容 Android 8 或旧架构产物,则应暂缓开启,或仅对 Web/iOS 目标开启并在 Android 端保留兜底样式。
迁移小贴士:一条可复用的换算思路
把旧的四段式阴影换算为boxShadow时,建议按“偏移横轴、偏移纵轴、模糊半径、颜色与透明度”的顺序书写,并将elevation一并吸收进同一条声明,例如:
| 旧写法 | 新写法 |
|---|---|
shadowColor+ 四段式 offset/opacity/radius +elevation | 单条boxShadow(Android 上与材质高度相关的语义差异需在真机预览中确认) |
需要提醒的是,elevation在 Android 上不仅表达“投影”,还同时影响zIndex语义与按压抬升等交互观感。迁移到boxShadow后,若组件依赖elevation带来的层叠顺序或触控层级变化,应在迁移时显式补充zIndex,并在真机上做一次视觉回归。此外,原文档的 Further Reading 部分指向 React Native 官方文档的 View Style Props 中关于boxShadow的章节,那里维护着最新的完整取值语法(含spread-radius、inset关键字及颜色可选项),建议在团队推广该规则时作为唯一语法权威一并同步给开发者。
小结
expo/prefer-box-shadow是一条“小规则、大导向”的 ESLint 约束:它通过监听 AST 中所有对象字面量,对shadowColor、shadowOffset、shadowOpacity、shadowRadius、elevation五类旧阴影属性逐条上报 "prefer box shadow" 提示,且不依赖书写位置(StyleSheet.create、普通对象、内联样式均覆盖),也不提供自动修复。开启它之前,请先确认工程满足 New Architecture 与 Android 9+/10+ 的系统前提;确认后,它就能帮助团队把 iOS、Android、Web 的阴影代码收敛到同一条现代、一致的boxShadow声明上。
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考