styles是什么?

2024-08-08 @1uokun

CSS-in-JS是什么?

在聊styles是什么之前,我们先简单了解CSS-in-JS是什么?

CSS-in-JS是一种将CSS样式直接编写在JavaScript代码中的技术,使得开发者能够在JavaScript文件中定义和使用样式。

常见的CSS-in-JS库包括:

  • Styled-Components: 允许你使用ES6的模板字符串语法定义样式。

  • Emotion: 提供了高性能和灵活的API来写CSS。

  • JSS: 通过JavaScript对象定义样式,并将其动态注入到DOM中。

而React-Native根据官网描述,

在React-native中,样式是通过JavaScript来实现的,所有的核心组件都接受名为style的属性。

为应对实际开发中组件的样式会越来越复杂,建议使用StyleSheet.create来集中定义组件的样式。

via

举一个基础案例:

const Button = ({ children }) => {
  return (
    <TouchableHighlight style={styles.myButton}>
      {children}
    </TouchableHighlight>
  )
}

const styles = StyleSheet.create({
  myButton: {
    height: 47,
    backgroundColor: '#108ee9',
  },
})

这就是天生的且只能支持CSS-in-JS了。

其中StyleSheet.create的设计是类似于react-jss,属于CSS-in-JS中的JSS(JavaScript Style Sheets)一类,他们有着以下的优势:

动态值

创建一个 useStyles hook函数,接收一个props对象,并返回StyleSheet.create的对象。

这样样式中就可以使用来自组件props对象:

const Button = ({children, ...props}) => {
  const styles = useStyles(props)
  return (
    <TouchableHighlight style={styles.myButton}>
      {children}
    </TouchableHighlight>
  )
}

// create useStyles hooks
const useStyles = (props) =>
  StyleSheet.create({
    myButton: {
      height: props.height, // 来自组件的 `height` prop
      backgroundColor: '#108ee9',
    },
  })

主题化

使用Context统一维护所有主题变量(ThemeContext),即用ThemeContext.Provider包装你的应用程序并将theme对象传递给ThemeContext.Provider

随后创建一个 useTheme hook函数,返回theme对像;同时将theme对象也入参到 useStyles hook函数中。

const Button = ({ children, ...props }) => {
  const theme = useTheme()
  const styles = useStyles({...props, theme})
  return (
    <TouchableHighlight style={styles.myButton}>
      {children}
    </TouchableHighlight>
  )
}

export const App = () => {
  const theme = {
    colorPrimary: '#108ee9',
  }
  return (
    <ThemeContext.Provider theme={theme}>
      <Button height={47}>I am a button</Button>
    </ThemeContext.Provider>
  )
}

// create ThemeContext & useTheme hooks
const ThemeContext = React.createContext({})
const useTheme = () => {
  return React.useContext(ThemeContext)
}


// useStyles hooks
const useStyles = ({...props, theme}) =>
  StyleSheet.create({
    myButton: {
      height: props.height, // 来自组件的 height prop
      backgroundColor: theme.colorPrimary, // 来自ThemeContext变量
    },
  })

CSS-in-JS 的困境

当我们想在外部为Button组件添加padding样式时,需要重新设计Button组件,改动内部的源码:

export const App = () => {
  return (
-    <Button>I am a button</Button>
+    <Button padding={7}>I am a button</Button>
  )
}

const useStyles = (props) =>
  StyleSheet.create({
    myButton: {
      height: props.height,
+      padding: props.padding
    },
  })

大致方案为:

  1. 添加足量的style类props

  2. 添加足量的token css变量

这是可以遇见的徒增维护成本。那么如何实现像CSS那样可以用外部样式覆盖呢?

styles是什么?

通过styles一个prop来支持实现CSS样式覆盖的能力(以下为简易后的代码,实际会做一些性能优化和边际保护),避免过多的非组件核心逻辑的props设计。

/**
 * @params   styles = { myButton: { padding } }
 * const baseStyles = { myButton: { height } }
 *
 * @return { myButton: [{ height }, { padding }] }
 * **/
const useStyles = ({...props, styles}) => {
  const baseStyles = StyleSheet.create({
    myButton: {
      height: props.height,
    },
  })

  // 类似 lodash.mergesWidth
  return _.mergeWidth(baseStyles, styles)
}


<Button styles={{ myButton: { padding: 7 } }}>
    I am a button
</Button>
  • Q:为此styles是什么,为什么@ant-design/react-native的每个组件都被设计了这么一个属性呢?

  • A:styles就是支持组件相同的样式集合,能覆盖组件的所有样式,但是也强化了类似CSS样式类名的存在。

实战

<Picker> 为例,

第一步,我们需要聚焦Picker的样式类名(文档中有提供)

interface PickerViewStyle {
  wrappper: ViewStyle
  wheelWrapper: ViewStyle

   // item style
  itemWrap: ViewStyle
  itemStyle: TextStyle

  // 遮罩层
  mask: ViewStyle
  maskTop: ViewStyle
  maskMiddle: ViewStyle
  maskBottom: ViewStyle
}

interface PickerStyle extends Partial<PickerViewStyle> {
  // modal 相关的样式
  modal: ViewStyle
  container: ViewStyle
  header: ViewStyle
  headerItem: ViewStyle
  actionText: TextStyle
  title: TextStyle
  okText: TextStyle
  dismissText: TextStyle
}

可以借助React Developer Tools浏览器插件定位DOM信息

第二步,设置styles属性

// 建议使用 useMemo 缓存变量
const styles = useMemo(
  () => ({
    itemActiveStyle: {
      color: '#108ee9',
      fontWeight: 'bold',
    },
    mask: {
      paddingHorizontal: 10,
    },
    maskMiddle: {
      backgroundColor: 'rgba(51,51,51,0.1)',
      borderRadius: 10,
    },
  }),
  [],
)

<Picker 
  styles={styles}
>
...
</Picker>
基础样式定制样式

结尾

讨论区: https://github.com/ant-design/ant-design-mobile-rn/discussions/1368


不满足业务? 需要定制,联系我,低至 $99 /天 @1uokun