React × Vue

Props 动态样式与 CSS 变量

两边都可以把运行时数据映射到 CSS 自定义属性,差异集中在状态与 style 绑定语法。

核心结论

当样式值来自运行时数据时,可以把少量动态值放进 CSS 自定义属性,把布局、交互态和响应式规则继续留在 CSS 中。React 与 Vue 的主要差异只是 style 对象的生成和绑定语法。

状态到样式

组件关系与数据流

StateComponentDerivedStyle

差异焦点动态样式对象属于 render 结果,每次组件执行都会重新创建。

实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。

flowchart TB
  accTitle: React render 创建动态 style
  accDescr: themeIndex setter 触发 ReactDemo 重新执行,每次 render 都读取 theme 并创建新的 style 对象,再通过 style prop 写入 CSS 变量。
  state(["themeIndex state"]) --> render["ReactDemo 重新执行"]
  render --> derive{{"每次 render 创建 style 对象"}}
  derive --> variables[("CSS variables")]
  variables -->|"style prop"| card["dynamic-demo-card"]
  card -.->|"setThemeIndex"| state
  class state state
  class render,card component
  class derive process
  class variables resource
  classDef state fill:#fffdf8,stroke:#149eca,color:#25221d,stroke-width:1.2px
  classDef component fill:#e4f5fa,stroke:#149eca,color:#25221d,stroke-width:2px,font-weight:600
  classDef process fill:#f4fafb,stroke:#149eca,color:#25221d,stroke-width:1.6px
  classDef resource fill:#eee9df,stroke:#817a70,color:#25221d,stroke-width:1.4px

setter 触发组件重新执行,并在本次 render 中创建新的 style 对象。

React 流程图
React 组件关系与数据流

滚轮缩放 · 按住拖动 · 双击重置 · Esc 关闭

组件关系与数据流

StateComponentDerivedStyle

差异焦点computed 自动追踪 themeIndex,并缓存 theme 与 cardStyle。

实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。

flowchart TB
  accTitle: Vue computed 动态 style
  accDescr: themeIndex ref 变化只使依赖它的 theme 和 cardStyle computed 失效,模板读取 cardStyle 时得到缓存后的 CSS 变量对象。
  state(["themeIndex ref"]) --> theme{{"computed theme"}}
  theme --> cardStyleNode{{"computed cardStyle"}}
  cardStyleNode --> variables[("缓存的 CSS variables")]
  variables -->|":style binding"| card["dynamic-demo-card"]
  card -.->|"直接修改 themeIndex"| state
  class state state
  class card component
  class theme,cardStyleNode process
  class variables resource
  classDef state fill:#fffdf8,stroke:#2f9d6b,color:#25221d,stroke-width:1.2px
  classDef component fill:#e4f4eb,stroke:#2f9d6b,color:#25221d,stroke-width:2px,font-weight:600
  classDef process fill:#f2f8f4,stroke:#2f9d6b,color:#25221d,stroke-width:1.6px
  classDef resource fill:#eee9df,stroke:#817a70,color:#25221d,stroke-width:1.4px

响应式 ref 使两个 computed 按依赖失效,并在模板读取时复用缓存。

Vue 3 流程图
Vue 3 组件关系与数据流

滚轮缩放 · 按住拖动 · 双击重置 · Esc 关闭

完整 Demo 源码

以下代码直接读取在线 Demo 使用的源文件,没有省略或改写。

React 源码

ReactDemo.tsx
import { useState, type CSSProperties } from "react";const themes = [  { name: "海蓝", color: "#1677ff", background: "#eaf3ff", radius: 18 },  { name: "葡萄", color: "#7c3aed", background: "#f4edff", radius: 8 },];type CardStyle = CSSProperties & {  "--demo-accent": string;  "--demo-background": string;  "--demo-radius": string;};export function ReactDemo() {  const [themeIndex, setThemeIndex] = useState(0);  const theme = themes[themeIndex];  const style: CardStyle = {    "--demo-accent": theme.color,    "--demo-background": theme.background,    "--demo-radius": `${theme.radius}px`,  };  return (    <div className="demo-stack">      <section className="dynamic-demo-card" style={style}>        <span className="demo-tag">{theme.name}</span>        <h2>运行时主题</h2>        <p>颜色与圆角都来自组件状态。</p>      </section>      <div className="demo-actions">        {themes.map((item, index) => (          <button            className="demo-button"            type="button"            key={item.name}            onClick={() => setThemeIndex(index)}          >            {item.name}          </button>        ))}      </div>    </div>  );}

Vue 3 源码

VueDemo.vue
<script setup lang="ts">import { computed, ref } from "vue";const themes = [  { name: "海蓝", color: "#1677ff", background: "#eaf3ff", radius: 18 },  { name: "葡萄", color: "#7c3aed", background: "#f4edff", radius: 8 },];const themeIndex = ref(0);const theme = computed(() => themes[themeIndex.value]);const cardStyle = computed(() => ({  "--demo-accent": theme.value.color,  "--demo-background": theme.value.background,  "--demo-radius": `${theme.value.radius}px`,}));</script><template>  <div class="demo-stack">    <section class="dynamic-demo-card" :style="cardStyle">      <span class="demo-tag">{{ theme.name }}</span>      <h2>运行时主题</h2>      <p>颜色与圆角都来自组件状态。</p>    </section>    <div class="demo-actions">      <button        v-for="(item, index) in themes"        :key="item.name"        class="demo-button"        type="button"        @click="themeIndex = index"      >        {{ item.name }}      </button>    </div>  </div></template>

在线观察

切换主题会同时改变强调色、背景与圆角,CSS 结构保持不变。

React

海蓝

运行时主题

颜色与圆角都来自组件状态。

Vue 3

海蓝

运行时主题

颜色与圆角都来自组件状态。

选择规则

  • 离散且有语义的视觉状态优先使用 class,如 is-active。
  • 连续数值、用户颜色或设计令牌适合 CSS 变量。
  • 不要把所有静态 CSS 都搬进 style 对象;伪类、媒体查询和级联仍应由 CSS 表达。
  • 动态值进入 url() 等敏感位置前需要验证,避免把任意输入直接拼入样式。

组件级样式隔离见CSS Modules 与 scoped。