跳转到内容

@material-ui/system

风格化系统 & 风格化的功能,来构建强大的设计系统。

快速上手

@material-ui/system 提供了一些底层辅助函数,我们称之为 "style functions",它们可以用于建立强大的设计系统。 以下是一些关键的功能:

  • ⚛️ 可以在组件的属性中直接获取主题(theme)的值。
  • 🦋 鼓励 UI 保持一致性。
  • 🌈 轻松地写入响应式的风格。
  • 🦎 可以和任何主题对象 (theme object) 结合使用。
  • 💅 使用广为流行的 CSS-in-JS 样式方案。
  • 📦 小于 4KB 的压缩包
  • 🚀 足够快不会成为运行时的瓶颈。

值得关注的是,整个库都提供了纯的样式函数(无副作用的),它们拥有这样的类型签名: ({ theme, ...style })=> style仅此而已

演示

快速开始章节的余下部分,我们会使用 styled-components 作为参考案例(来强调这个包的普及性)。 另外一个方案就是 使用 JSS。 以下的例子也都基于 Material-UI 的 默认 主题对象(theme object)

import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
import NoSsr from '@material-ui/core/NoSsr';
import { createMuiTheme } from '@material-ui/core/styles';
import { palette, spacing, typography } from '@material-ui/system';

const Box = styled.div`${palette}${spacing}${typography}`;
// or import Box from '@material-ui/core/Box';

const theme = createMuiTheme();

export default function Demo() {
  return (
    <NoSsr>
      <ThemeProvider theme={theme}>
        <Box
          color="primary.main"
          bgcolor="background.paper"
          fontFamily="h6.fontFamily"
          fontSize={{ xs: 'h6.fontSize', sm: 'h4.fontSize', md: 'h3.fontSize' }}
          p={{ xs: 2, sm: 3, md: 4 }}
        >
          @material-ui/system
        </Box>
      </ThemeProvider>
    </NoSsr>
  );
}

安装

// 使用 npm
npm install @material-ui/system

// 使用 yarn
yarn add @material-ui/system

创建一个组件

若想使用 Box 组件,您得先创建一个。 首先,请将一个 spacingpalette 函数加入样式的参数。

import styled from 'styled-components';
import { spacing, palette } from '@material-ui/system';

const Box = styled.div`${spacing}${palette}`;

export default Box;

此 Box 组件已支持全新的 间距属性颜色属性。 例如,你可以提供一个间距属性:p ,以及一个颜色属性: color

<Box p="1rem" color="grey">给我一些空间!</Box>

您可以用任何有效的 CSS 值来装饰这个组件。

主题

大多数的时候,你会想通过一个主题的值来提高 UI 的一致性。 最好的情况是你已经预设了一组间距和颜色的值。 导入您样式解决方案的 theme provider。

import React from 'react'
import { ThemeProvider } from 'styled-components'

const theme = {
  spacing: 4,
  palette: {
    primary: '#007bff',
  },
};

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      {/* 子组件 */}
    </ThemeProvider>
  )
}

现在,你可以提供一个间距的乘值:

<Box p={1}>4px</Box>
<Box p={2}>8px</Box>
<Box p={-1}>-4px</Box>

以及一个主要颜色(primary color):

<Box color="primary">蓝色</Box>

全部包含

为了使 Box 组件更实用,我们已预置了一套样式函数,下面是一个完整的列表:

如果你已经在使用 @material-ui/core,那么你可以使用 Box 组件(使用内嵌的 JSS):

import Box from '@material-ui/core/Box';

互操作性

@material-ui/system 适用于大多数 CSS-in-JS 库,包括了 JSS,styled-components,还有 emotion。

如果你已经在使用 @material-ui/core,我们推荐你使用 JSS 方案来最小化捆绑包的大小。

JSS

JSS
import React from 'react';
import { styled } from '@material-ui/core/styles';
import { compose, spacing, palette } from '@material-ui/system';

const Box = styled('div')(compose(spacing, palette));

export default function JSS() {
  return (
    <Box color="white" bgcolor="palevioletred" p={1}>
      JSS
    </Box>
  );
}

Styled components

import React from 'react';
import styled from 'styled-components';
import { palette, spacing } from '@material-ui/system';
import NoSsr from '@material-ui/core/NoSsr';

const Box = styled.div`
  ${palette}
  ${spacing}
`;

export default function StyledComponents() {
  return (
    <NoSsr>
      <Box color="white" bgcolor="palevioletred" p={1}>
        Styled components
      </Box>
    </NoSsr>
  );
}

Emotion

import React from 'react';
import styled from '@emotion/styled';
import { palette, spacing } from '@material-ui/system';
import NoSsr from '@material-ui/core/NoSsr';

const Box = styled.div`
  ${palette}
  ${spacing}
`;

export default function Emotion() {
  return (
    <NoSsr>
      <Box color="white" bgcolor="palevioletred" p={1}>
        Emotion
      </Box>
    </NoSsr>
  );
}

响应式(Responsive)

所有 的属性都是响应式的,我们支持了 3 种不同的 API。 如下使用了断点(breakpoints)的主题结构,这些是默认的值,但是你也可以自行定制。

const values = {
  xs: 0,
  sm: 600,
  md: 960,
  lg: 1280,
  xl: 1920,
};

const theme = {
  breakpoints: {
    keys: ['xs', 'sm', 'md', 'lg', 'xl'],
    up: key => `@media (min-width:${values[key]}px)`,
  },
};

Array

<Box p={[2, 3, 4]} />

/**
 * 输出:
 *
 * padding: 16px;
 * @media (min-width: 600px) {
 *   padding: 24px;
 * }
 * @media (min-width: 960px) {
 *   padding: 32px;
 * }
 */

Object

<Box p={{ xs: 2, sm: 3, md: 4 }} />

/**
 * 输出:
 *
 * padding: 16px;
 * @media (min-width: 600px) {
 *   padding: 24px;
 * }
 * @media (min-width: 960px) {
 *   padding: 32px;
 * }
 */

搭配

如果你想对断点值进行分组,可以使用 breakpoints() 助手。

import { compose, spacing, palette, breakpoints } from '@material-ui/system';
import styled from 'styled-components';

const Box = styled.div`
  ${breakpoints(
    compose(
      spacing,
      palette,
    ),
  )}
`;

<Box
  p={2}
  sm={{ p: 3 }}
  md={{ p: 4 }}
/>

/**
 * 输出:
 *
 * padding: 16px;
 * @media (min-width: 600px) {
 *   padding: 24px;
 * }
 * @media (min-width: 960px) {
 *   padding: 32px;
 * }
 */
Collocation API
<Box xs={{ fontSize: 12 }} sm={{ fontSize: 18 }} md={{ fontSize: 24 }}>
  Collocation API
</Box>

定制样式属性

style(options) => style function

你可以使用这个助手来创建你自己的样式函数。

不是所有的 CSS 属性都是被支持的。 若你有这个想法支持新的属性,这也是有可能的。 而改变主题的路径前缀也是有可能性的。

参数

  1. options (Object):
    • options.prop (String):能够触发样式函数的属性。
    • options.cssProperty (String|Boolean [optional]):默认值是 options.prop。 这使用了 CSS 属性。 你可以传入 false 来禁用此选项。 禁用的情况下,这个属性的值会被作为样式对象应用于其本身。 它可以用来 渲染变体(rendering variants)
    • options.themeKey (String [optional]):主题的路径前缀。
    • options.transform (Function [optional]):在输出 CSS 值之前应用一个转换。

返回结果

style function:被创建的样式函数。

例子

你可以创建一个组件来支持一些如 grid-gap 的 CSS 网格属性。 若将 spacing 作为 themeKey 提供,你可以重用该逻辑,从而实现我们在其他 spacing 属性(如 padding)中定义的行为。

import styled from 'styled-components';
import { style } from '@material-ui/system';
import { Box } from '@material-ui/core';

const gridGap = style({
  prop: 'gridGap',
  themeKey: 'spacing',
});

const Grid = styled(Box)`${gridGap}`;
const example = <Grid display="grid" gridGap={[2, 3]}>...</Grid>;

你还可以同时添加 propcssProperty 来自定义属性名称,并使用 transform 函数来转换值。

import styled from 'styled-components';
import { style } from '@material-ui/system';

const borderColor = style({
  prop: 'bc',
  cssProperty: 'borderColor',
  themeKey: 'palette',
  transform: value => `${value} !important`,
});

const Colored = styled.div`${borderColor}`;
const example = <Colored bc="primary.main">...</Colored>;

compose(...style functions) => style function

将多个不同的样式函数合为一体。

返回结果

style function:被创建的样式函数。

例子

import { style, compose } from '@material-ui/system'

export const textColor = style({
  prop: 'color',
  themeKey: 'palette',
});

export const bgcolor = style({
  prop: 'bgcolor',
  cssProperty: 'backgroundColor',
  themeKey: 'palette',
});

const palette = compose(textColor, bgcolor);

变体

在一个主题中,style() 助手还可将属性映射到样式对象中去。 在这个例子中, variant 属性支持了所有 theme.typography 中呈现的键。

import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
import NoSsr from '@material-ui/core/NoSsr';
import { style, typography } from '@material-ui/system';

const variant = style({
  prop: 'variant',
  cssProperty: false,
  themeKey: 'typography',
});

// ⚠ Text is already defined in the global context:
// https://developer.mozilla.org/en-US/docs/Web/API/Text/Text.
const Text = styled.span`
  font-family: Helvetica;
  ${variant}
  ${typography}
`;

const theme = {
  typography: {
    h1: {
      fontSize: 30,
      lineHeight: 1.5,
    },
    h2: {
      fontSize: 25,
      lineHeight: 1.5,
    },
  },
};

export default function Variant() {
  return (
    <NoSsr>
      <ThemeProvider theme={theme}>
        <div>
          <Text variant="h1" as="div">
            variant=h1
          </Text>
          <Text variant="h1" fontWeight={300} as="div">
            fontWeight=300
          </Text>
          <Text variant="h2" as="div">
            variant=h2
          </Text>
        </div>
      </ThemeProvider>
    </NoSsr>
  );
}

CSS 属性

如果您想要自定义 CSS 的值,您可以使用 css() 助手。 它将会处理 css 属性。

import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
import NoSsr from '@material-ui/core/NoSsr';
import { createMuiTheme } from '@material-ui/core/styles';
import { compose, spacing, palette, css } from '@material-ui/system';

const Box = styled.div`
  ${css(compose(spacing, palette))}
`;

const theme = createMuiTheme();

export default function CssProp() {
  return (
    <NoSsr>
      <ThemeProvider theme={theme}>
        <Box color="white" css={{ bgcolor: 'palevioletred', p: 1, textTransform: 'uppercase' }}>
          CssProp
        </Box>
      </ThemeProvider>
    </NoSsr>
  );
}

它是如何工作的

styled-system 在 解释它是如何工作的 方面做得很好 。 它可以协助为这种 “style function” 概念建立一个心理模型。

实际用例

在实践中,一个 Box 组件可以节省我们很多时间。 在这个例子中,我们演示了如何搭建一个 Banner 组件。

You have lost connection to the internet. This app is offline.

现有技术

@material-ui/system 从不同地方借鉴了一些想法和 API:

  • Tachyons是第一批(2014年)促进了 原子化使用 CSS 模式(Atomic CSS pattern) (或者 Functional CSS)的 CSS 库。
  • 之后相继推出了 Tachyons(2017年)以及 Tailwind CSS。 他们让 Atomic CSS 更受欢迎。
  • Twitter Bootstrap 在 v2,v3,和 v4 中一步步介绍了原子化的类名(atomic class names)。 这种对 “助手类” 分组方式让他们得到启发。
  • 在 React 世界中, Styled System (2017年)是第一批推动样式函数的(style functions)。 它可以做出一个通用的 Box 组件,这种方式可以替换创建一个新元素时原子化的 CSS 以及一些辅助类。
  • Pinterest、GitHub 和 Segment.io 等大型公司都在使用不同风格的相同方法:
  • 实际的操作和对象响应 API(object responsive API)是受到 Smooth-UI 的系统 的启发。