跳到内容

c

样式进阶

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

Ready-made UI libraries

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

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

许多UI框架为Web应用的开发者提供了现成的主题和 "组件",如按钮、菜单和表格。我们用引号来写组件,因为在这种情况下,我们不是在谈论React组件。通常情况下,UI框架的使用是通过在应用中包含框架的CSS样式表和JavaScript代码。

有许多UI框架都有React友好版本,其中框架的 "组件 "已经被转化为React组件。Bootstrap有几个不同的React版本,比如reactstrapreact-bootstrap

接下来,我们将仔细看看两个UI框架,Bootstrap和MaterialUI。我们将使用这两个框架为我们在教材的React-router部分制作的应用添加类似的样式。

React Bootstrap

让我们首先在react-bootstrap软件包的帮助下看看Bootstrap。

让我们用命令来安装这个包。

npm install react-bootstrap

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

<head>
  <link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
    integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
    crossOrigin="anonymous"
  />
  // ...
</head>

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

fullstack content

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

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

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

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

fullstack content

接下来,让我们对Notes组件做一些修改,使其将笔记列表渲染成一个表格。React Bootstrap为此提供了一个内置的Table组件,所以不需要再单独定义CSS类。

const Notes = ({ notes }) => (
  <div>
    <h2>Notes</h2>
    <Table striped>      <tbody>
        {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表单的帮助下改进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.Group>
        <Form.Group>
          <Form.Label>password:</Form.Label>
          <Form.Control
            type="password"
          />
        </Form.Group>
        <Button variant="primary" type="submit">
          login
        </Button>
      </Form>
    </div>
)}

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

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

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

fullstack content

Notification

现在,登录表单的形状更好了,让我们来看看如何改进我们应用的通知。

fullstack content

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

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组件

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

Navigation structure

最后,让我们改变应用的导航菜单,使用Bootstrap的Navbar 组件。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="me-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 style={padding}>{user} logged in</em>
          : <Link style={padding} to="/login">login</Link>
        }
      </Nav.Link>
    </Nav>
  </Navbar.Collapse>
</Navbar>

得到的布局有一个非常干净和悦目的外观。

fullstack content

如果浏览器的视口变窄,我们注意到菜单会 "折叠",可以通过点击 "汉堡包 "按钮来扩大它。

fullstack content

Bootstrap和大部分现有的UI框架都能产生响应式设计,这意味着所产生的应用能在各种不同的屏幕尺寸上渲染良好的效果。

Chrome开发者工具使得在不同的移动客户端的浏览器中模拟使用我们的应用成为可能。

fullstack content

你可以找到该应用的完整代码这里

Material UI

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

用以下命令安装该库

npm install @mui/material @emotion/react @emotion/styled

然后在public/index.html文件的head标签中添加以下一行。这一行会加载谷歌的Roboto字体。

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

现在让我们用MaterialUI来对我们之前用bootstrap做的代码做同样的修改。

在一个容器中渲染整个应用的内容。

import { Container } from '@mui/material'

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

让我们从Notes组件开始。我们将把笔记的列表渲染成一个表格

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.user}
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  </div>
)

这个表格如下所示:

fullstack content

Material UI的一个不太令人愉快的特点是,每个组件都必须单独导入。笔记页的导入列表相当长。

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

Form

接下来,让我们使用TextFieldButton组件使Login视图中的登录表单变得更好。

const Login = (props) => {
  const navigate = useNavigate()

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

  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

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

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

Notification

登录时显示的通知可以用Alert组件来完成,它与Bootstrap's equivalent组件非常相似。

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

Alert是相当时尚的。

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

我们可以从文档中找到一个更好的方法。我们可以使用组件prop来定义MaterialUI组件的根元素如何被渲染。

通过定义

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

Button组件被渲染,所以它的根元素是react-router-dom's 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

应用的代码可以从这里找到。

Closing thoughts

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

我自己并没有经常使用MaterialUI,但我的第一印象是积极的。它的文档比 react-bootstrap's 好一点。

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

fullstack content

在之前的两个例子中,我们在React-集成库的帮助下使用了UI框架。

我们没有使用React Bootstrap库,而是通过给我们的应用''的HTML元素定义CSS类,直接使用Bootstrap。而不是用Table组件来定义表格。

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

我们可以使用一个普通的HTML table,并添加所需的CSS类。

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

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

除了使前端代码更加紧凑和可读之外,使用React UI框架库的另一个好处是,它们包括使特定组件工作所需的JavaScript。一些Bootstrap组件需要一些不愉快的JavaScript依赖,我们宁愿不在我们的React应用中包含这些依赖。

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

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

Other UI frameworks

这里有一些其他的UI框架供你考虑。如果你在列表中没有看到你喜欢的UI框架,请向教材提出拉动请求。

Styled components

还有一些我们还没有看过的React应用的其他方式

styled components 库提供了一种有趣的方法,通过ES6中引入的标签模板字面来定义样式。

让我们在风格化组件的帮助下对我们应用的风格做一些改变。首先,用命令安装该包。

npm install 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;
`

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

定义样式的语法相当有趣,因为CSS规则是在反斜线内定义的。

我们定义的样式组件与普通的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>
      <Routes>
        <Route path="/notes/:id" element={<Note note={note} />} />
        <Route path="/notes" element={<Notes notes={notes} />} />
        <Route path="/users" element={user ? <Users /> : <Navigate replace to="/login" />} />
        <Route path="/login" element={<Login onLogin={login} />} />
        <Route path="/" element={<Home />} />
      </Routes>

      <Footer>        <em>Note app, Department of Computer Science 2022</em>
      </Footer>    </Page>  )
}

由此产生的应用的外观如下所示。

fullstack content

样式化组件在最近一段时间里持续增长,相当多的人认为它是在React应用中定义样式的最佳方式。