样式方面的正确做法和错误做法

本页介绍了使用样式以在整个代码库中实现一致性的最佳实践,以及我们在设计 API 时遵循的原则。

正确做法

请遵循以下最佳实践:

正确做法:使用样式实现视觉效果,使用修饰符实现行为

使用 Styles API 进行视觉配置(背景、内边距、边框),并为点击逻辑、手势检测或无障碍功能等行为保留修饰符。

正确做法:在设计系统中公开样式参数

对于您自己的自定义设计系统组件,您应在修饰符参数后公开 Style 对象。

@Composable
fun GradientButton(
    modifier: Modifier = Modifier,
    // ✅ DO: for design system components, expose a style modifier to consumers to be able to customize the components
    style: Style = Style
) {
    // Consume the style
}

正确做法:将基于视觉效果的参数替换为样式

考虑将可组合项上的参数替换为单个 Style 参数。 例如:

// Before
@Composable
fun OldButton(background: Color, fontColor: Color) {
}

// After
// ✅ DO: Replace visual-based parameters with a style that includes same properties
@Composable
fun NewButton(style: Style = Style) {
}

正确做法:优先使用样式实现动画

使用内置 animate 块进行基于状态的样式设置,并使用动画来提高性能,而不是使用修饰符。

正确做法:利用“后写优先”原则

利用 style 属性会覆盖而非堆叠这一事实。 使用此属性可替换默认组件边框或背景,而无需使用多个参数。

错误做法

不建议采用以下模式:

错误做法:使用样式实现互动逻辑

请勿尝试在样式中处理 onClick 或手势检测。样式仅限于基于状态的视觉配置,因此不应处理业务逻辑;相反,它们只应根据状态具有不同的视觉效果。

错误做法:将默认样式作为默认参数提供

样式参数应始终使用 style: Style = Style 进行声明:

@Composable
fun BadButton(
    modifier: Modifier = Modifier,
    // ❌ DON'T set a default style here as a parameter
    style: Style = Style { background(Color.Red) }
) {
}

如需添加“默认”参数,请将传入的参数样式与定义的默认样式合并:

@Composable
fun GoodButton(
    modifier: Modifier = Modifier,
    // ✅ Do: always pass it as a Style, do not pass other defaults
    style: Style = Style
) {
    // ...
    val defaultStyle = Style { background(Color.Red) }
    // ✅ Do Combine defaults inside with incoming parameter
    Box(modifier = modifier.styleable(styleState, defaultStyle, style)) {
      // your logic
    }
}

错误做法:向基于布局的可组合项提供样式参数

虽然您可以向任何可组合项提供样式,但基于布局的可组合项或屏幕级可组合项不应接受样式,因为从消费者的角度来看,不清楚样式在此级别上的作用。 样式是为组件设计的,不一定是为布局设计的。

错误做法:在 Composition 中创建样式

CompositionLocals 在定义样式时读取,而不是在使用样式时读取。当实际使用样式时,CompositionLocal 的状态可能已更改,从而导致样式不准确。

// DON'T - Create styles in Composition that access composition locals in this way - this will likely lead to issues when style is used / accessed, as it would not get updated when the value changes.
@Composable
fun containerStyle(): Style {
    val background = MaterialTheme.colorScheme.background
    val onBackground = MaterialTheme.colorScheme.onBackground
    return Style {
        background(background)
        contentColor(onBackground)
    }
}

// Do: Instead, Create StyleScope extension functions for your subsystems to access themed composition Locals
val StyleScope.colors: JetsnackColors
    get() = JetsnackTheme.LocalJetsnackTheme.currentValue.colors

val StyleScope.typography: androidx.compose.material3.Typography
    get() = JetsnackTheme.LocalJetsnackTheme.currentValue.typography
val StyleScope.shapes: Shapes
    get() = JetsnackTheme.LocalJetsnackTheme.currentValue.shapes
// Access CompositionLocals
val button = Style {
    background(colors.brandSecondary)
    shape(shapes.small)
}

正确做法:为子系统值更改创建一个样式

例如,如果要在深色模式和浅色模式之间切换,请查询现有主题值(通过 CompositionLocal)以动态更改 Style

// Do: Use CompositionLocals or themed values to create a single style
val buttonStyle = Style {
    background(colors.brandSecondary)
    shape(shapes.small)
}

正确做法:当组件在主题定义之间存在根本差异时,替换整个样式

如果样式对象在主题级别上存在根本差异,您可以替换整个样式对象。

例如,如果您要创建一个应用,该应用针对每个产品/页面或产品/服务提供不同的主题,并且样式的许多属性都不同,那么在主题级别上替换整套样式是可以接受的。

// DO Switch out whole styles when many properties differ - if Product A and Product B are two white labelled apps that provide different Themes.
val productBThemedButton = Style {
    shape(shapes.small)
    background(colors.brandSecondary)
    // other properties are fundamentally different
}

val productAThemedButton = Style {
    shape(shapes.large)
    background(colors.brand)
    // other properties are fundamentally different
}