绘制用户界面(UI)
React 是一个用于渲染用户界面(UI)的 JavaScript 工具库。UI 是由类似按钮(buttons)、文本框(text)以及图片(images)之类的小零件构成的。React 能够让你将这些“小零件”组合成可重复利用、可嵌套的 组件(components)。从网站到手机应用,屏幕上的所有内容都可以拆解成组件。在本章中,你将学习到如何创建、自定义以及根据条件显示 React 组件。
In this chapter
你的第一个组件
React 应用程序是由被称为“组件(components)”的独立的 UI 片段构成的。一个 React 组件就是一个 JavaScript 函数,并且函数中可以添加 markup。组件可以小刀一个按钮,也可以大到整个页面。下面展示的是一个 Gallery 组件,其中渲染了三个 Profile 组件:
function Profile() {
  return (
    <img
      src="https://i.imgur.com/MK3eW3As.jpg"
      alt="Katherine Johnson"
    />
  );
}
export default function Gallery() {
  return (
    <section>
      <h1>Amazing scientists</h1>
      <Profile />
      <Profile />
      <Profile />
    </section>
  );
}
导入(Import)和导出(exporing)组件
你可以在一个文件中声明多个组件,但是文件变得太大的话就不方便查看了。要解决此问题,你可以将一个组件放到一个单独的文件中并(导出) export 组件,然后在另一个文件中(导入) import 该组件:
import Profile from './Profile.js';
export default function Gallery() {
  return (
    <section>
      <h1>Amazing scientists</h1>
      <Profile />
      <Profile />
      <Profile />
    </section>
  );
}
用 JSX 书写 markup
每个 React 组件就是 JavaScript 函数,函数中可以书写 markup,这些 markup 将由 React 渲染到浏览器中。React 组件使用名为 JSX 的语法扩展来支持 markup。JSX 看上去就像 HTML 一样,但它的语法比较严格,并且可以显示动态信息。
如果我们将现有的 HTML markup 粘贴到 React 组件中,可能会报错:
export default function TodoList() {
  return (
    // This doesn't quite work!
    <h1>Hedy Lamarr's Todos</h1>
    <img
      src="https://i.imgur.com/yXOvdOSs.jpg"
      alt="Hedy Lamarr"
      class="photo"
    >
    <ul>
      <li>Invent new traffic lights
      <li>Rehearse a movie scene
      <li>Improve spectrum technology
    </ul>
  );
}
如果你已有上述类似的 HTML 了,可以使用 converter 工具来修复语法错误:
export default function TodoList() {
  return (
    <>
      <h1>Hedy Lamarr's Todos</h1>
      <img
        src="https://i.imgur.com/yXOvdOSs.jpg"
        alt="Hedy Lamarr"
        className="photo"
      />
      <ul>
        <li>Invent new traffic lights</li>
        <li>Rehearse a movie scene</li>
        <li>Improve spectrum technology</li>
      </ul>
    </>
  );
}
JavaScript in JSX with curly braces
JSX 允许你在 JavaScript 文件中书写类似 HTML 的标记,使渲染逻辑和内容处于同一位置。有时,你需要添加一点 JavaScript 逻辑或者在 markup 中引用一个动态属性。在这种情况下,你可以在 JSX 中使用大花括号为 JavaScript “开一扇窗”:
const person = {
  name: 'Gregorio Y. Zara',
  theme: {
    backgroundColor: 'black',
    color: 'pink'
  }
};
export default function TodoList() {
  return (
    <div style={person.theme}>
      <h1>{person.name}'s Todos</h1>
      <img
        className="avatar"
        src="https://i.imgur.com/7vQD0fPs.jpg"
        alt="Gregorio Y. Zara"
      />
      <ul>
        <li>Improve the videophone</li>
        <li>Prepare aeronautics lectures</li>
        <li>Work on the alcohol-fuelled engine</li>
      </ul>
    </div>
  );
}
将属性(props)传递给组件
React 组件使用 props 参数来互相通信。每个父组件都可以通过 props 将信息传递给子组件。props 可能会让你联想到 HTML 中的属性(attributes),但是你可以通过 props 传递 JavaScript 所支持的不同类型的值,包括对象(objects)、数组(array)、函数(functions)甚至 JSX!
import { getImageUrl } from './utils.js'
export default function Profile() {
  return (
    <Card>
      <Avatar
        size={100}
        person={{
          name: 'Katsuko Saruhashi',
          imageId: 'YfeOqp2'
        }}
      />
    </Card>
  );
}
function Avatar({ person, size }) {
  return (
    <img
      className="avatar"
      src={getImageUrl(person)}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}
function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}
按条件渲染
组件通常需要根据不同的条件显示不同的内容。在 React 中,你可以使用诸如 if 语句、&& 以及 ? : 操作符等控制渲染 JSX 的条件。
在本示例中,JavaScript 的 && 操作符被用于控制在什么条件下渲染复选框(checkmark):
function Item({ name, isPacked }) {
  return (
    <li className="item">
      {name} {isPacked && '✔'}
    </li>
  );
}
export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item
          isPacked={true}
          name="Space suit"
        />
        <Item
          isPacked={true}
          name="Helmet with a golden leaf"
        />
        <Item
          isPacked={false}
          name="Photo of Tam"
        />
      </ul>
    </section>
  );
}
渲染列表
你通常需要按照数据集合来显示多个相似的组件。这是,你可以将 React 和 JavaScript 的 filter() 和 map() 函数一同使用,以便将数据中的数组转换为组件数组。
对于每一个数组项,你需要指定一个 key(键)。通常,你会使用数据库中的 ID 作为 key 使用。key 的作用是让 React 保持对列表中每个条目的追踪,即便列表发生了变化,也依然能够追踪每个条目在列表中的位置。
import { people } from './data.js';
import { getImageUrl } from './utils.js';
export default function List() {
  const listItems = people.map(person =>
    <li key={person.id}>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        known for {person.accomplishment}
      </p>
    </li>
  );
  return (
    <article>
      <h1>Scientists</h1>
      <ul>{listItems}</ul>
    </article>
  );
}
保持组件的功能单一
某些 JavaScript 函数是很 “pure(纯粹的)”。一个“纯粹的”函数有如下特点:
- 只管好自己的事。 在被调用时,不会更改已经存在的任何对象(objects)或变量。
- 相同的输入,相同的输出。 给定相同的输入,“纯粹的”函数是能够始终返回相同结果的。
通过严格的将组件编写为“纯粹的”函数,你就可以避免在代码库膨胀时出现一系列令人费解的错误和不可预测的行为。以下示例展示的是一个“不纯粹的”组件:
let guest = 0;
function Cup() {
  // Bad: changing a preexisting variable!
  guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaSet() {
  return (
    <>
      <Cup />
      <Cup />
      <Cup />
    </>
  )
}
你可以通过传递 prop 而不是修改已存在的变量,让此组件变为“纯粹的”组件:
function Cup({ guest }) {
  return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaSet() {
  return (
    <>
      <Cup guest={1} />
      <Cup guest={2} />
      <Cup guest={3} />
    </>
  );
}