React 让 web 开发简化了很多。原则上 React 基于组件的模式让代码分解和复用变得更加容易。 然而,开发者并不总是清楚如何跨项目分享他们的组件。在这片文章中,我会展示几种可用的方法。
React 让书写漂亮,并富有表达力的代码更加容易。然而,如果组件不能很好的复用,随着时间的推移代码变得更加零散和更加难以维护。 我曾经看到的代码库中,同样的 UI 有十几种不同的实现!另外一个问题,开发者通常会把 UI 和业务代码耦合在一起,当 UI 需要改变时就变的很困难。
今天,我们将会看到如何创建可共享的 UI 组件,如何构建贯穿整个应用的一致的设计语言。
开始
一开始你需要一个空的 React 项目。最快捷的方式就是 create-react-app,但是,还是需要设置一下 Sass。 我创建了一个应用框架,你可以在 GitHub 克隆它。你也可以在教程的代码仓中找到完整的项目(https://github.com/tutsplus/build-a-reusable-design-system-with-react)。
运行yarn-install
安装所有的依赖,然后通过 yarn start
启动应用。
所有的视觉组件和相应的样式单独保存在 design_system 目录下。任何全局样式和变量保存在 src/styles。
设置设计的基准
最近一次被设计同行鄙视是什么时候,padding 半个像素的错误,或者不能区分各个灰色色调的区别?(我被告知,#eee
和 #efefef
有不同,我打算在一天内找出来)
构建 UI 库其中之一的目的是为了提升设计和开发团队的关系。前端开发者和 API 设计者已经可以很好的沟通并构建很好的 API 协议。 但是,由于某些原因,在跟设计团队沟通时总是逃避。想象一下,对于一个 UI 元素只能存在有限的几个状态。 例如,如果,我们设计一个标题组件,它可以是 h1
和 h6
任何一个标签,可以是粗体、斜体或者有下划线。这个实现起来应该很直接。
网格系统
在着手构建任何设计项目时首先考虑的是需要理解网格是如何构建的。对于很多应用来说,这很随意。这会导致间距系统非常零散,并且开发者很难确定该使用那个间距。 因此需要确定一个合适的间距。当我第一次阅读 4px - 8px 网格系统时就爱上了它。遵守这一规则会简化我们样式的很多问题。
让我们在代码中先设置一个基本的网格系统。我们从设置布局的 app 组件开始。
//src/App.js import React, { Component } from 'react'; import logo from './logo.svg'; import './App.scss'; import { Flex, Page, Box, BoxStyle } from './design_system/layouts/Layouts'; class App extends Component { render() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title"> Build a design system with React</h1> </header> <Page> <Flex lastElRight={true}> <Box boxStyle={BoxStyle.doubleSpace} > A simple flexbox </Box> <Box boxStyle={BoxStyle.doubleSpace} >Middle</Box> <Box fullWidth={false}>and this goes to the right</Box> </Flex> </Page> </div> ); } } export default App;
接下来,我们定义了一些样式和包装组件。
//design-system/layouts/Layout.js import React from 'react'; import './layout.scss'; export const BoxBorderStyle = { default: 'ds-box-border--default', light: 'ds-box-border--light', thick: 'ds-box-border--thick', } export const BoxStyle = { default: 'ds-box--default', doubleSpace: 'ds-box--double-space', noSpace: 'ds-box--no-space' } export const Page = ({children, fullWidth=true}) => { const classNames = `ds-page ${fullWidth ? 'ds-page--fullwidth' : ''}`; return (<div className={classNames}> {children} </div>); }; export const Flex = ({ children, lastElRight}) => { const classNames = `flex ${lastElRight ? 'flex-align-right' : ''}`; return (<div className={classNames}> {children} </div>); }; export const Box = ({ children, borderStyle=BoxBorderStyle.default, boxStyle= BoxStyle.default, fullWidth=true}) => { const classNames = `ds-box ${borderStyle} ${boxStyle} $ {fullWidth ?'ds-box--fullwidth' : ''}` ; return (<div className={classNames}> {children} </div>); };
最后,我们将在 SCSS 中定义样式。
/*design-system/layouts/layout.scss */ @import '../../styles/variables.scss'; $base-padding: $base-px * 2; .flex { display: flex; &.flex-align-right > div:last-child { margin-left: auto; } } .ds-page { border: 0px solid #333; border-left-width: 1px; border-right-width: 1px; &:not(.ds-page--fullwidth){ margin: 0 auto; max-width: 960px; } &.ds-page--fullwidth { max-width: 100%; margin: 0 $base-px * 10; } } .ds-box { border-color: #f9f9f9; border-style: solid; text-align: left; &.ds-box--fullwidth { width: 100%; } &.ds-box-border--light { border: 1px; } &.ds-box-border--thick { border-width: $base-px; } &.ds-box--default { padding: $base-padding; } &.ds-box--double-space { padding: $base-padding * 2; } &.ds-box--default--no-space { padding: 0; } }
有很多在这没有展示。让我们从头开始。variables.scss 定义了全局的变量,比如:颜色和网格的设置。由于我们使用了 4px-8px 网格,我们将用 4px 做为基础值。 父组件是 Page
,它控制着页面的文档流。层级最低元素是 Box
,它定义了内容如何在页面上渲染。它本身就是一个 div
,并在自身的上下文中渲染自己。
现在,我们需要一个 Container
组件,它包含多个 div
。我们选择 flex-box
,所以组件命名为 Flex
。
定义 Type 系统
Type 系统是任何应用的关键组件。通常,我们会定义一个基本的全局样式,在需要的情况下复写它。 这经常会导致设计不一致。让我们看看如何通过设计库来轻松的解决这个问题。
首先,我们会定义一些样式常量和一个 class 容器。
// design-system/type/Type.js import React, { Component } from 'react'; import './type.scss'; export const TextSize = { default: 'ds-text-size--default', sm: 'ds-text-size--sm', lg: 'ds-text-size--lg' }; export const TextBold = { default: 'ds-text--default', semibold: 'ds-text--semibold', bold: 'ds-text--bold' }; export const Type = ({tag='span', size=TextSize.default, boldness=TextBold.default, children}) => { const Tag = `${tag}`; const classNames = `ds-text ${size} ${boldness}`; return <Tag className={classNames}> {children} </Tag> };
接下来,我们会为这些 text 元素定义样式。
/* design-system/type/type.scss*/ @import '../../styles/variables.scss'; $base-font: $base-px * 4; .ds-text { line-height: 1.8em; &.ds-text-size--default { font-size: $base-font; } &.ds-text-size--sm { font-size: $base-font - $base-px; } &.ds-text-size--lg { font-size: $base-font + $base-px; } &strong, &.ds-text--semibold { font-weight: 600; } &.ds-text--bold { font-weight: 700; } }
这是一个简单的 Text
组件,它代表了 UI 的各个状态。我们可以进一步扩展这个功能来处理交互功能,比如:当文本被省略的时候现实一个 tooltip,或者为 email、time 渲染不同的样式等等。