在 Expo 项目中用 `expo/prefer-box-shadow` 规则统一阴影写法:从旧 shadow 属性迁移到现代 `boxShadow`
2026/9/9 20:10:26 网站建设 项目流程

在 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 规范引入的全新阴影样式属性,它把shadowColorshadowOffsetshadowOpacityshadowRadius与 Android 特有的elevation合并为一条简洁的声明。本文以 eslint-plugin-expo 内置的prefer-box-shadow规则 为线索,讲解如何在 Expo / React Native 工程中通过 ESLint 自动约束阴影书写风格,并给出迁移对照、配置方法与适用边界,帮助你写出更一致、更贴近 Web、更利于跨平台复用的阴影代码。

规则定位:从阴影“四件套 + elevation”到一条 boxShadow

在传统 React Native 样式中,给组件加阴影通常要同时声明多个属性,而且 iOS 与 Android 的阴影体系互不相通:shadowColorshadowOffsetshadowOpacityshadowRadius主要作用于 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-likeboxShadowAPI 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', }); } } }, };

据此可以得到三个可推断的工程事实:

  1. 一条属性 = 一条报告。无论一个样式对象里写了shadowColor+elevation两条,还是写全五条,每命中一条旧属性都会产生独立的提示,方便你逐个清理。
  2. 检测对象不限于StyleSheet.create。由于监听的是通用ObjectExpression,规则同样作用于普通对象常量与 JSX 内联样式对象(测试用例对此有明确覆盖,详见下文)。
  3. 规则不做自动修复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/utilsESLintUtils.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 条提示(shadowColorshadowOffsetshadowOpacityshadowRadiuselevation各一条)。

正确的写法(规则放行)

对应的现代写法是用一条字符串把偏移、模糊半径与颜色全部表达出来:

const styles = StyleSheet.create({ container: { boxShadow: '0px 2px 3.84px rgba(0, 0, 0, 0.25)', }, });

其中boxShadow的取值遵循 CSS 风格的语法,逐段拆解上面这条声明对应关系如下:

boxShadow 片段对应旧属性
第 1 段offset-x0pxshadowOffset.width = 0
第 2 段offset-y2pxshadowOffset.height = 2
第 3 段blur-radius3.84pxshadowRadius = 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 显示其peerDependencieseslint >= 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-exportsprefer-box-shadow需要你在项目中显式开启。

通过测试用例理解规则的边界行为

在 src/tests/prefer-box-shadow.test.ts 中可以看到一组值得留意的“有效/无效”边界:

不报错的写法(valid),即只要用的是boxShadow,无论书写位置如何都通过:

  • StyleSheet.create内使用boxShadow
  • 普通对象常量内使用boxShadow
  • <View>内联样式中使用boxShadow

报错的写法(invalid)

  • StyleSheet.create内同时出现五个旧阴影属性,断言 5 个错误;
  • 内联样式中同时出现五个旧阴影属性,断言 5 个错误;
  • 对象内仅出现shadowColorelevation,断言 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-radiusinset关键字及颜色可选项),建议在团队推广该规则时作为唯一语法权威一并同步给开发者。

小结

expo/prefer-box-shadow是一条“小规则、大导向”的 ESLint 约束:它通过监听 AST 中所有对象字面量,对shadowColorshadowOffsetshadowOpacityshadowRadiuselevation五类旧阴影属性逐条上报 "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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询