知海

学习 React:概述

React开始-简介

学习 React:概述

引言

欢迎阅读 React 的官方文档!本章节将介绍你在日常开发中会反复使用到的 React 核心概念,涵盖从组件化思维到状态管理的完整知识体系。通过本章节的学习,你将掌握 React 开发中最常用的 80% 的内容。

本章学习目标

  • 如何创建和嵌套组件
  • 如何使用 JSX 添加标签和样式
  • 如何在界面中显示数据
  • 如何进行条件渲染和列表渲染
  • 如何响应事件并更新界面
  • 如何在组件间共享数据

创建和嵌套组件

React 应用程序由组件(Component)构成。组件是用户界面的一部分,具有独立的逻辑和外观表现形式。组件可以小至一个按钮,大至整个页面。

React 组件本质上是一个返回标签的 JavaScript 函数:

js 复制代码
function MyButton() {
  return (
    <button>我是一个按钮</button>
  );
}

声明了 MyButton 组件后,你可以将其嵌套到其他组件中使用:

js {5} 复制代码
export default function MyApp() {
  return (
    <div>
      <h1>欢迎来到我的应用</h1>
      <MyButton />
    </div>
  );
}

需要注意,<MyButton /> 以大写字母开头,这是 React 识别组件的关键约定:React 组件必须以大写字母开头,而原生 HTML 标签则必须使用小写字母。

js 复制代码
function MyButton() {
  return (
    <button>
      我是一个按钮
    </button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>欢迎来到我的应用</h1>
      <MyButton />
    </div>
  );
}

export default 关键字用于指定文件中的主要组件。如果你对 JavaScript 的模块语法尚不熟悉,可以参考 MDNjavascript.info 的相关文档。

使用 JSX 编写标签

上述代码中使用的标签语法称为 JSX。虽然 JSX 并非 React 的强制要求,但大多数 React 项目都采用这一语法,因为它提供了极大的便利性。我们推荐的所有本地开发工具都开箱即用地支持 JSX。

JSX 比 HTML 的语法更加严格,具体表现在:

  • 所有标签必须正确闭合,例如 <br />
  • 组件不能返回多个并列的 JSX 标签,必须将它们包裹在共享的父级中,可以使用 <div>...</div>,或者使用空标签 <>...</>(Fragment)作为包裹:
js {3,6} 复制代码
function AboutPage() {
  return (
    <>
      <h1>关于</h1>
      <p>你好。<br />最近怎么样?</p>
    </>
  );
}

如果你有大量现成的 HTML 需要迁移为 JSX,可以使用在线转换工具辅助完成。

添加样式

在 React 中,通过 className 属性为元素指定 CSS 类名。它的用法与 HTML 的 class 属性相同:

js 复制代码
<img className="avatar" />

随后,你可以在独立的 CSS 文件中编写对应的样式规则:

css 复制代码
/* styles.css */
.avatar {
  border-radius: 50%;
}

React 并不限制你如何引入 CSS 文件,最简单的方案是使用原生 HTML 的 <link> 标签。如果项目使用了构建工具或框架,请查阅相应文档了解推荐的 CSS 引入方式。

显示数据

JSX 允许你将标签嵌入 JavaScript 代码中,而使用大括号 {} 则可以从标签中"返回"到 JavaScript 的语境中,嵌入变量并将其展示给用户。例如,下面的代码会渲染 user.name 的值:

js {3} 复制代码
return (
  <h1>
    {user.name}
  </h1>
);

你也可以在 JSX 的属性中嵌入 JavaScript 表达式,此时必须使用大括号而非引号。例如,className="avatar" 将字符串 "avatar" 传给了 className;而 src={user.imageUrl} 会读取 JavaScript 变量 user.imageUrl 的值并将其作为 src 属性:

js {3,4} 复制代码
return (
  <img
    className="avatar"
    src={user.imageUrl}
  />
);

大括号内还可以放置更复杂的表达式,例如字符串拼接:

js 复制代码
const user = {
  name: 'Hedy Lamarr',
  imageUrl: 'https://react.dev/images/docs/scientists/yXOvdOSs.jpg',
  imageSize: 90,
};

export default function Profile() {
  return (
    <>
      <h1>{user.name}</h1>
      <img
        className="avatar"
        src={user.imageUrl}
        alt={'Photo of ' + user.name}
        style={{
          width: user.imageSize,
          height: user.imageSize
        }}
      />
    </>
  );
}
css 复制代码
.avatar {
  border-radius: 50%;
}

.large {
  border: 4px solid gold;
}

上述示例中的 style={{}} 并不是某种特殊语法:外层大括号用于在 JSX 中嵌入 JavaScript 表达式,内层 {} 则是一个普通对象字面量。当样式值依赖 JavaScript 变量时,可以使用 style 属性进行内联样式设置。

条件渲染

React 没有为条件判断创造专门的语法,而是直接使用常规的 JavaScript 表达式。例如,可以用 if 语句实现条件渲染:

js 复制代码
let content;
if (isLoggedIn) {
  content = <AdminPanel />;
} else {
  content = <LoginForm />;
}
return (
  <div>
    {content}
  </div>
);

如果你偏好更紧凑的写法,可以使用条件运算符(三元表达式)? :。与 if 不同的是,它可以嵌入 JSX 内部使用:

js 复制代码
<div>
  {isLoggedIn ? (
    <AdminPanel />
  ) : (
    <LoginForm />
  )}
</div>

当不需要 else 分支时,还可以使用更简洁的逻辑与运算符 &&

js 复制代码
<div>
  {isLoggedIn && <AdminPanel />}
</div>

以上方式也适用于有条件的指定属性值。如果你对这些 JavaScript 语法还不熟练,建议先从 if...else 入手。

渲染列表

在渲染列表时,需要使用 JavaScript 中的 for 循环和数组的 map() 方法。

假设你有一个产品数组:

js 复制代码
const products = [
  { title: 'Cabbage', id: 1 },
  { title: 'Garlic', id: 2 },
  { title: 'Apple', id: 3 },
];

在组件中,使用 map() 将数组转换为 <li> 标签列表:

js 复制代码
const listItems = products.map(product =>
  <li key={product.id}>
    {product.title}
  </li>
);

return (
  <ul>{listItems}</ul>
);

注意 <li> 上有一个 key 属性。对于列表中的每一项,你都应该传一个字符串或数字作为 key,用于唯一标识该项在兄弟节点中的位置。key 值通常来自数据本身,如数据库中的 ID。当列表后续发生插入、删除或排序等变化时,React 需要依赖 key 来判断项目的变更情况。

js 复制代码
const products = [
  { title: '卷心菜', isFruit: false, id: 1 },
  { title: '大蒜', isFruit: false, id: 2 },
  { title: '苹果', isFruit: true, id: 3 },
];

export default function ShoppingList() {
  const listItems = products.map(product =>
    <li
      key={product.id}
      style={{
        color: product.isFruit ? 'magenta' : 'darkgreen'
      }}
    >
      {product.title}
    </li>
  );

  return (
    <ul>{listItems}</ul>
  );
}

响应事件

你可以在组件中声明事件处理函数来响应用户操作:

js {2-4,7} 复制代码
function MyButton() {
  function handleClick() {
    alert('You clicked me!');
  }

  return (
    <button onClick={handleClick}>
      点我
    </button>
  );
}

请注意,onClick={handleClick} 的末尾没有小括号!这里不要调用事件处理函数,而只需要把函数引用传递给事件。当用户点击按钮时,React 会代为调用你传入的函数。

更新界面

通常组件需要"记住"某些信息并展示出来,例如一个按钮被点击的次数。这需要为组件引入 state(状态)

首先,从 React 中引入 useState Hook:

js 复制代码
import { useState } from 'react';

然后在组件内声明一个 state 变量:

js 复制代码
function MyButton() {
  const [count, setCount] = useState(0);
  // ...
}

useState 返回两个值:当前的 state(count)以及更新它的函数(setCount)。你可以为它们取任何名字,但按照惯例通常命名为 [something, setSomething] 的形式。

在按钮首次渲染时,count 的值为 0,因为你将 0 传给了 useState()。要修改 state,请调用 setCount() 并传入新的值。点击按钮将使计数器递增:

js {5} 复制代码
function MyButton() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      Clicked {count} times
    </button>
  );
}

React 会重新调用组件函数。首次点击后 count 变为 1,再次点击变为 2,以此类推。

如果在同一页面中渲染同一个组件多次,每个组件实例都会拥有各自独立的 state:

js 复制代码
import { useState } from 'react';

export default function MyApp() {
  return (
    <div>
      <h1>独立更新的计数器</h1>
      <MyButton />
      <MyButton />
    </div>
  );
}

function MyButton() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      点了 {count} 次
    </button>
  );
}
css 复制代码
button {
  display: block;
  margin-bottom: 5px;
}

可以看到,每个按钮都"记住"了自己的 count 状态,互不影响。

使用 Hook

use 开头的函数被称为 HookuseState 是 React 提供的内置 Hook 之一。你可以在 React API 参考中找到其他内置 Hook,也可以通过组合现有 Hook 编写自定义 Hook。

Hook 的使用比普通函数更为严格,需要遵循以下规则:只能在组件(或其他 Hook)的顶层调用 Hook。如果需要根据条件或循环调用 useState,正确的做法是将逻辑提取到新的组件中,并在组件内部使用 Hook。

组件间共享数据

在前面的示例中,每个 MyButton 都有自己独立的 count,点击任意按钮只会影响该按钮自身的 count

然而在实际开发中,我们经常需要多个组件共享数据并同步更新

要实现 MyButton 组件之间的状态共享,需要将各按钮的 state "向上"提升到最近的公共父组件中。在此示例中,这个父组件是 MyApp

具体实现步骤如下。

第一步:将 state 从 MyButton 上移到 MyApp 中:

js {2-6,18} 复制代码
export default function MyApp() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <h1>独立更新的计数器</h1>
      <MyButton />
      <MyButton />
    </div>
  );
}

function MyButton() {
  // ... 原 state 逻辑已移出...
}

第二步:将 MyApp 中 state 和事件处理函数通过 props 传递给每个 MyButton

js {11-12} 复制代码
export default function MyApp() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <h1>共同更新的计数器</h1>
      <MyButton count={count} onClick={handleClick} />
      <MyButton count={count} onClick={handleClick} />
    </div>
  );
}

这种方式向下传递的信息被称为 props(属性)。此时 MyApp 拥有 count state 和 handleClick 事件处理函数,并将它们以 prop 的形式传递给了各个按钮。

第三步:修改 MyButton,接收并读取从父组件传入的 props:

js {1,3} 复制代码
function MyButton({ count, onClick }) {
  return (
    <button onClick={onClick}>
      点了 {count} 次
    </button>
  );
}

现在,当点击任意按钮时,触发 MyApp 中的 handleClick 函数,调用 setCount(count + 1) 使 count 递增。新的 count 值会作为 prop 传递到每个按钮,因此所有按钮都能展示最新值并保持同步更新。这就是 React 中的"状态提升"(Lifting State Up)——通过将状态移至公共父组件,实现了多个组件间的数据共享。

js 复制代码
import { useState } from 'react';

export default function MyApp() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <h1>共同更新的计数器</h1>
      <MyButton count={count} onClick={handleClick} />
      <MyButton count={count} onClick={handleClick} />
    </div>
  );
}

function MyButton({ count, onClick }) {
  return (
    <button onClick={onClick}>
      点了 {count} 次
    </button>
  );
}
css 复制代码
button {
  display: block;
  margin-bottom: 5px;
}

下一节

至此,你已经掌握了 React 核心的基础知识。接下来,可以继续学习实战教程,将本章的知识付诸实践,构建你的第一个 React 迷你应用。

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。