c

样式进阶

在第2章节中,我们研究了向应用添加样式的两种不同方式: 老式的single CSS文件和inline-styles。 在这一章节,我们将看看其他一些方法。

Ready-made UI libraries

【现成的 UI 库】

为应用定义样式的一种方法是使用现成的“ UI 框架”。

一个广泛流行的 UI 框架是由 Twitter 创建的Bootstrap ,它可能仍然是最流行的框架。 最近,进入这个领域的新 UI 框架数量激增。 事实上,选择的范围是如此之广,以至于几乎没有希望创建一个详尽的选项清单。

许多 UI 框架为 web 应用开发人员提供现成的主题和“组件” ,如按钮、菜单和表格。 我们将组件写在引号中,因为在这里我们不讨论 React 组件。 通常,UI 框架是通过在应用中包含 CSS 样式表和框架的 JavaScript 代码来使用的。

有许多 UI 框架具有响应友好版本,其中框架的“组件”已经转换为 React 组件。 有几个不同的React Bootstrap版本,像reactstrapReact-Bootstrap

接下来我们将仔细研究两个 UI 框架,Bootstrap 和MaterialUI。 我们将使用这两个框架来为我们在课程教材的React-router 部分中创建的应用添加类似的样式。

React Bootstrap

让我们首先来看一下通过react-Bootstrap包的帮助引导程序。

让我们用如下命令来安装这个包:

npm install --save react-bootstrap

然后,我们在应用的 public/index.html文件中的 head 标签内部添加一个链接,用于加载 Bootstrap 的 CSS 样式表:

<head>
  <link
    rel="stylesheet"
    href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
    crossorigin="anonymous"
  />
  // ...
</head>

当我们重新加载应用时,我们注意到它已经看起来有点时髦了:

fullstack content

在 Bootstrap 中,应用的所有内容通常都渲染在一个容器中。 实际上,这是通过给应用的根 div 元素 container class 属性来实现的:

const App = () => {
  // ...

  return (
    <div className="container">      // ...
    </div>
  )
}

我们注意到,这已经对应用的外观产生了影响。 内容不再像以前那样接近浏览器的边缘:

fullstack content

接下来,让我们对Notes 组件进行一些更改,以便它将便笺列表渲染为table。 React Bootstrap 为此提供了一个内置的Table组件,因此不需要单独定义 CSS 类。

const Notes = (props) => (
  <div>
    <h2>Notes</h2>
    <Table striped>      <tbody>
        {props.notes.map(note =>
          <tr key={note.id}>
            <td>
              <Link to={`/notes/${note.id}`}>
                {note.content}
              </Link>
            </td>
            <td>
              {note.user}
            </td>
          </tr>
        )}
      </tbody>
    </Table>
  </div>
)

这个应用的外观非常时髦:

fullstack content

请注意 React Bootstrap 组件必须与库分开导入,如下所示:

import { Table } from 'react-bootstrap'

Forms

【表单】

让我们在 Bootstrap forms的帮助下改进Login 视图中的表单。

React Bootstrap 为创建表单提供了内置的组件(尽管缺少相关的文档) :

let Login = (props) => {
  // ...
  return (
    <div>
      <h2>login</h2>
      <Form onSubmit={onSubmit}>
        <Form.Group>
          <Form.Label>username:</Form.Label>
          <Form.Control
            type="text"
            name="username"
          />
          <Form.Label>password:</Form.Label>
          <Form.Control
            type="password"
          />
          <Button variant="primary" type="submit">
            login
          </Button>
        </Form.Group>
      </Form>
    </div>
)}

我们需要导入的组件数量增加了:

import { Table, Form, Button } from 'react-bootstrap'

切换到 Bootstrap 表单后,我们改进的应用如下:

fullstack content

Notification

现在登录表单已经更好了,让我们来看看如何改进应用的通知功能:

fullstack content

让我们在用户登录到应用时为通知添加一条消息。 我们将把它存储在App 组件状态的消息变量中:

const App = () => {
  const [notes, setNotes] = useState([
    // ...
  ])

  const [user, setUser] = useState(null)
  const [message, setMessage] = useState(null)
  const login = (user) => {
    setUser(user)
    setMessage(`welcome ${user}`)    setTimeout(() => {      setMessage(null)    }, 10000)  }
  // ...
}

我们将把消息作为 Bootstrap Alert 组件来渲染。 再一次,React Bootstrap 库为我们提供了一个匹配的React component :

<div className="container">
  {(message &&    <Alert variant="success">      {message}    </Alert>  )}  // ...
</div>

Navigation structure

【导航结构】

最后,让我们改变应用的导航菜单,使用 Bootstrap 的导航栏组件。 React Bootstrap 库为我们提供了匹配内置组件。 通过反复试验,我们最终得到了一个可行的解决方案,尽管文档晦涩难懂:

<Navbar collapseOnSelect expand="lg" bg="dark" variant="dark">
  <Navbar.Toggle aria-controls="responsive-navbar-nav" />
  <Navbar.Collapse id="responsive-navbar-nav">
    <Nav className="mr-auto">
      <Nav.Link href="#" as="span">
        <Link style={padding} to="/">home</Link>
      </Nav.Link>
      <Nav.Link href="#" as="span">
        <Link style={padding} to="/notes">notes</Link>
      </Nav.Link>
      <Nav.Link href="#" as="span">
        <Link style={padding} to="/users">users</Link>
      </Nav.Link>
      <Nav.Link href="#" as="span">
        {user
          ? <em>{user} logged in</em>
          : <Link to="/login">login</Link>
        }
    </Nav.Link>
    </Nav>
  </Navbar.Collapse>
</Navbar>

最终的布局有一个非常干净和令人愉快的外观:

fullstack content

如果浏览器的视口变窄,我们会注意到菜单会“折叠” ,点击“汉堡包”按钮就可以展开:

fullstack content

引导程序和大多数现有的 UI 框架产生响应式设计,这意味着产生的应用可以在各种不同的屏幕尺寸上渲染良好的效果。

开发工具可以在不同移动客户端的浏览器中模拟使用我们的应用:

fullstack content

你可以在这里找到应用的完整代码点击这里

Material UI

作为我们的第二个例子,我们将研究MaterialUIReact库,它实现了谷歌开发的Material design视觉语言。

使用如下命令安装库

npm install --save @material-ui/core

然后向 public/index.html文件中的 head 标签添加如下行。如下代码加载了Google的Roboto 字体。

<head>
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
  // ...
</head>

现在,让我们使用 MaterialUI 对前面使用 bootstrap 所做的代码进行相同的修改。

Container内渲染整个应用的内容:

import Container from '@material-ui/core/Container'

const App = () => {
  // ...
  return (
    <Container>
      // ...
    </Container>
  )
}

让我们从Notes 组件开始,我们将便笺列表渲染为一个table :

const Notes = ({notes}) => (
  <div>
    <h2>Notes</h2>

    <TableContainer component={Paper}>
      <Table>
        <TableBody>
          {notes.map(note => (
            <TableRow key={note.id}>
              <TableCell>
                <Link to={`/notes/${note.id}`}>{note.content}</Link>
              </TableCell>
              <TableCell>
                {note.name}
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  </div>
)

表格看起来是这样的:

fullstack content

Material UI 的一个不那么令人愉快的特性是,每个组件都必须单独导入。 便笺页面的导入列表非常长:

import {
  Container,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableRow,
  Paper,
} from '@material-ui/core'

Form 表单

接下来让我们在Login 视图中更好地使用TextFieldButton组件来创建登录表单:

const Login = (props) => {
  const history = useHistory()

  const onSubmit = (event) => {
    event.preventDefault()
    props.onLogin('mluukkai')
    history.push('/')
  }

  return (
    <div>
      <h2>login</h2>
      <form onSubmit={onSubmit}>
        <div>
          <TextField label="username" />
        </div>
        <div>
          <TextField  label="password" type='password' />
        </div>
        <div>
          <Button variant="contained" color="primary" type="submit">
            login
          </Button>
        </div>
      </form>
    </div>
  )
}

最终的结果是:

fullstack content

与 bootstrap 不同的是,MaterialUI 并不为表单本身提供组件。 这里的表单是一个普通的 HTML form元素。

请记住导入表单中使用的所有组件。

Notification

在登录中显示的通知可以通过使用Alert来完成,这个组件与 bootstrap 的等价组件非常相似:

<div>
  {(message &&    <Alert severity="success">      {message}    </Alert>  )}</div>

Alert 组件尚未包含在 MaterialUI 核心包中,因此我们必须安装lab包才能使用它:

npm install --save @material-ui/lab

然后我们可以像这样导入组件

import { Alert } from '@material-ui/lab'

是相当时尚的:

fullstack content

Navigation structure

【导航结构】

我们可以使用 AppBar 组件来实现导航。

如果我们使用文档中的示例代码

<AppBar position="static">
  <Toolbar>
    <IconButton edge="start" color="inherit" aria-label="menu">
    </IconButton>
    <Button color="inherit">
      <Link to="/">home</Link>
    </Button>
    <Button color="inherit">
      <Link to="/notes">notes</Link>
    </Button>
    <Button color="inherit">
      <Link to="/users">users</Link>
    </Button>  
    <Button color="inherit">
      {user
        ? <em>{user} logged in</em>
        : <Link to="/login">login</Link>
      }
    </Button>                
  </Toolbar>
</AppBar>

我们的确有导航系统,但看起来更好

fullstack content

我们可以从文档中documentation找到一个更好的 。 我们可以使用component props来定义 MaterialUI 组件的根元素是如何渲染的。

通过定义

<Button color="inherit" component={Link} to="/">
  home
</Button>

Button 组件渲染为这样,它的根组件是 react-router-dom Link ,它接收它的路径作为 prop 字段 to

导航条的代码如下

<AppBar position="static">
  <Toolbar>
    <Button color="inherit" component={Link} to="/">
      home
    </Button>
    <Button color="inherit" component={Link} to="/notes">
      notes
    </Button>
    <Button color="inherit" component={Link} to="/users">
      users
    </Button>   
    {user
      ? <em>{user} logged in</em>
      : <Button color="inherit" component={Link} to="/login">
          login
        </Button>
    }                              
  </Toolbar>
</AppBar>

看起来我们也希望如此

fullstack content

这个应用的代码可以在这里here找到。

Closing thoughts

【封闭的思想】

React-bootstrap 和 MaterialUI 之间的区别并不大,这取决于你觉得哪个更好看。

我自己并没有使用很多MaterialUI,但我的第一印象是积极的。 它的文档比起React引导程序要好一点。

根据追踪不同 npm 流行程度的 https://www.npmtrends.com/ 数据库, MaterialUI 在2018年底超过了 react-bootstrap:

fullstack content

在前面的两个示例中,我们借助于 React-integration 库使用了 UI 框架。

与使用React Bootstrap库不同,我们可以直接使用 Bootstrap,方法是为应用的 HTML 元素定义 CSS 类。 不用Table 组件定义表:

<Table striped>
  // ...
</Table>

我们可以使用一个常规的 HTMLtable 并添加所需的 CSS 类:

<table className="table striped">
  // ...
</table>

从这个例子来看,使用 React Bootstrap 库的好处并不明显。

除了使前端代码更加紧凑和可读,使用 React UI 框架库的另一个好处是它们包含了使特定组件工作所需的 JavaScript。 一些引导程序组件需要一些讨厌的依赖项(JavaScript 依赖项 JavaScript dependencies) ,我们不希望在 React 应用中包含这些。

通过集成库而不是“直接”使用 UI 框架的一些潜在缺点是,集成库可能具有不稳定的 API 和糟糕的文档。 与其他 UI 框架相比,Semantic UI React的情况要好得多,因为它是一个官方的 React 集成库。

还有一个问题是,首先是否应该使用 UI 框架库。 这取决于每个人形成自己的意见,但对于缺乏 CSS 和网页设计知识的人来说,他们是非常有用的工具。

Other UI frameworks

【其他 UI 框架】

这里有一些其他的 UI 框架供您考虑。 如果您没有在列表中看到您最喜欢的 UI 框架,请对课程材料提出PR。

Styled components

【样式组件】

还有一些我们还没有看过的React 应用的样式。

样式化组件库提供了一种有趣的方法,可以通过在 ES6中引入的带标记的模板文字定义样式。

让我们借助样式化组件对应用的样式进行一些更改。 首先,用如下命令安装包:

npm install --save styled-components

让我们用样式定义两个组件:

import styled from 'styled-components'

const Button = styled.button`
  background: Bisque;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid Chocolate;
  border-radius: 3px;
`

const Input = styled.input`
  margin: 0.25em;
`

上面的代码创建了ButtonInput HTML 元素的样式版本,然后将它们赋值给ButtonInput 变量。

定义样式的语法非常有趣,因为 CSS 规则是在backticks中定义的。

我们定义的样式化组件与常规的buttoninput 元素工作方式完全相同,它们可以以相同的方式使用:

const Login = (props) => {
  // ...
  return (
    <div>
      <h2>login</h2>
      <form onSubmit={onSubmit}>
        <div>
          username:
          <Input />        </div>
        <div>
          password:
          <Input type='password' />        </div>
        <Button type="submit" primary=''>login</Button>      </form>
    </div>
  )
}

让我们为这个应用创建一些样式化的组件,它们是div 元素的样式化版本:

const Page = styled.div`
  padding: 1em;
  background: papayawhip;
`

const Navigation = styled.div`
  background: BurlyWood;
  padding: 1em;
`

const Footer = styled.div`
  background: Chocolate;
  padding: 1em;
  margin-top: 1em;
`

让我们使用应用中的组件:

const App = () => {
  // ...

  return (
    <Page>      <Navigation>        <Link style={padding} to="/">home</Link>
        <Link style={padding} to="/notes">notes</Link>
        <Link style={padding} to="/users">users</Link>
        {user
          ? <em>{user} logged in</em>
          : <Link style={padding} to="/login">login</Link>
        }
      </Navigation>
      <Switch>
        <Route path="/notes/:id">
          <Note note={note} />
        </Route>
        <Route path="/notes">
          <Notes notes={notes} />
        </Route>
        <Route path="/users">
          {user ? <Users /> : <Redirect to="/login" />}
        </Route>
        <Route path="/login">
          <Login onLogin={login} />
        </Route>
        <Route path="/">
          <Home />
        </Route>
      </Switch>
      
      <Footer>        <em>Note app, Department of Computer Science 2020</em>
      </Footer>    </Page>  )
}

产生的应用的外观如下:

fullstack content

样式化组件在最近一段时间内一直受到欢迎,很多人认为这是定义样式到 React 应用的最佳方式。