移动端设计系统落地: RN+H5 组件库与 Sketch 插件

在汽车之家二手车事业部做了近 3 年移动端工程化,最绕的问题不是某个页面,而是 RN App 和 H5 两端的 UI 不一致——同一个按钮,App 里一个样,H5 里又一个样,设计师改一次,两端各改一遍,联调扯皮。这篇记一下我们怎么用设计系统把这事一次性治住。

一、问题根子: 设计令牌没统一

UI 不一致不是设计师不认真,是令牌没沉淀。颜色、间距、圆角、字号散落在各端代码和各份设计稿里,改一处找不到全貌,自然越改越散。

解决思路三步走:先把令牌集中,再让 RN 和 H5 都从同一份令牌生成样式,最后让设计稿和代码令牌同步。闭环了,一致性才守得住。

二、Style Dictionary: 一份令牌,两端产物

Style Dictionary 把令牌(JSON)编译成各端产物。我们定义一份 token 源:

// tokens/color.json
{
  "color": {
    "brand":   { "value": "#175e88", "type": "color" },
    "primary": { "value": "{color.brand.value}", "type": "color" },
    "text":    { "value": "#202832", "type": "color" },
    "muted":   { "value": "#596571", "type": "color" }
  },
  "size": {
    "spacing": { "value": "4", "type": "dimension" },
    "radius":  { "value": "8", "type": "dimension" }
  }
}

配置两套输出:

// style-dictionary.config.js
export default {
  source: ['tokens/**/*.json'],
  platforms: {
    rn: {
      transformGroup: 'js',
      buildPath: 'packages/rn-tokens/',
      files: [{ destination: 'tokens.ts', format: 'javascript/module' }]
    },
    web: {
      transformGroup: 'css',
      buildPath: 'packages/h5-tokens/',
      files: [{ destination: 'tokens.css', format: 'css/variables' }]
    }
  }
};

一条命令 style-dictionary build,RN 拿到 tokens.ts,H5 拿到 tokens.css,两边引用的都是同一份令牌的派生产物。设计师改色,只改 token 源,两端重新构建,自动同步。

三、RN + H5 双实现组件库

令牌统一后,搭组件库。每个组件有 RN 和 H5 两个实现,但 API 一致:

// Button 组件,双端 API 一致
interface ButtonProps {
  variant: 'primary' | 'ghost';
  size: 'sm' | 'md';
  onPress?: () => void;
  label: string;
}
// RN 实现
import { tokens } from '@autohome/rn-tokens';
export const Button: React.FC<ButtonProps> = ({ variant, size, label, onPress }) => (
  <TouchableOpacity style={[styles.base, styles[variant], styles[size]]} onPress={onPress}>
    <Text style={styles.label}>{label}</Text>
  </TouchableOpacity>
);
const styles = StyleSheet.create({
  base: { borderRadius: tokens.size.radius, backgroundColor: tokens.color.primary }
});
// H5 实现
import '@autohome/h5-tokens/tokens.css';
export const Button: React.FC<ButtonProps> = ({ variant, size, label, onPress }) => (
  <button className={`btn btn--${variant} btn--${size}`} onClick={onPress}>{label}</button>
);

业务调用 import { Button } from '@autohome/ui',构建时按端解析到对应实现。令牌从 tokens.ts / tokens.css 读,样式自动对齐。API 一致是关键——业务代码不用关心端,只换实现。

四、Sketch 插件: 设计稿和令牌双向同步

光统一代码令牌还不够,设计稿要是另搞一套,还是脱节。我们开发了一个 macOS Sketch 插件,做两件事:

  1. 抽取:扫描 Sketch 文档里的共享样式(颜色 / 字号 / 间距),生成 token JSON,直接喂给 Style Dictionary
  2. 生成:从 Sketch 符号生成组件骨架代码(RN / H5),设计师新建符号,插件吐出对应组件文件

设计师在 Sketch 里调色,插件把色值写进 token 源,Style Dictionary 重新构建,两端代码自动更新。设计稿改 → 令牌改 → 代码改,闭环了,不用人肉同步。

五、结果与取舍

上线后量化的结果:

指标改善
页面开发时间-30%
设计-开发联调时间-50%
组件体系推广3+ 业务团队

代价是前期搭令牌 + 组件库 + Sketch 插件投入大,小团队不划算。但只要跨 2 个端以上、3 个以上团队复用,设计系统就回本。我们这套后来推广到 3 个以上业务团队,收益是持续的——一次性投入,长期分摊。

小结

多端 UI 不一致的根子是令牌没统一。Style Dictionary 统一令牌派生,双实现组件库统一 API,Sketch 插件打通设计稿和代码。三件套落地后,设计师改一处,两端代码自动同步,联调成本砍半。这是”工程化”这个词真正能落地的样子——不是喊口号,是令牌到代码的链路打通。