React.js 概述

React 简介

多年来,对使用 JavaScript 编写用户界面的直接方法的需求不断增加。
React,也称为 React.js,是 Facebook 设计的开源 JavaScript 库,用于构建用户界面或 UI 组件。

React 当然不是唯一的 UI 库。
Preact、Vue、Angular、Svelte、Lit 和许多其他工具也非常适合从可重用元素组合界面。
鉴于 React 的受欢迎程度,考虑到我们将使用它来了解本指南中的一些设计、渲染和性能模式,因此有必要了解它的工作原理。

当前端开发人员谈论代码时,通常是在为 Web 设计界面的上下文中。我们认为界面组合的方式是元素,如按钮、列表、导航等。
React 提供了一种优化和简化的方式来表达这些元素中的接口。它还通过将您的界面组织成三个关键概念——组件、道具和状态,帮助构建复杂和棘手的界面。

因为 React 以组合为中心,所以它可以完美地映射到设计系统的元素。
所以,本质上,为 React 设计实际上是在鼓励你以模块化的方式思考。
它允许您在组合页面或视图之前设计单独的组件,因此可以完全了解每个组件的范围和目的——这个过程称为组件化


术语

  • React / React.js / ReactJS - React 库,由 Facebook 于 2013 年创建

  • ReactDOM - 用于 DOM 和服务器渲染的包

  • JSX - JavaScript 的语法扩展

  • Redux - 集中状态容器

  • Hooks - 一种无需编写类即可使用状态和其他 React 功能的新方法

  • ReactNative - 使用 Javascript 开发跨平台本机应用程序的库

  • Webpack - 在 React 社区中流行的 JavaScript 模块捆绑器。

  • CRA(创建 React 应用程序)- 一种 CLI 工具,用于创建用于引导项目的脚手架 React 应用程序。

  • Next.js - 一个 React 框架,具有许多一流的功能,包括 SSR、代码拆分、性能优化等。


使用 JSX 渲染

我们将在许多示例中使用 JSX。

JSX 是 JavaScript 的扩展,它使用类似 XML 的语法将模板 HTML 嵌入到 JS 中。

它旨在转换为有效的 JavaScript,尽管该转换的语义是特定于实现的。

JSX 在 React 库中越来越受欢迎,但此后也出现了其他实现。


Components, Props, State

组件、道具和状态是 React 中的三个关键概念。

实际上,在 React 中看到或做的所有事情都可以归类为这些关键概念中的至少一个,以下是这些关键概念的快速浏览:

1. Components

组件是任何 React 应用程序的构建块。
它们就像 JavaScript 函数,接受任意输入(Props) 并返回描述应该在屏幕上显示的内容的 React 元素。

首先要理解的是,React 应用程序屏幕上的所有内容都是组件的一部分。
从本质上讲,React 应用程序只是组件内组件中的组件。 所以开发者不用在 React 中构建页面; 他们构建组件。

组件可让您将 UI 拆分为独立的、可重用的部分。 如果您习惯于设计页面,那么以这种模块化方式思考似乎是一个很大的变化。
但是如果你使用设计系统或风格指南呢? 那么这可能不像看起来那么大的范式转变。

定义组件最直接的方式是编写 JavaScript 函数。

function Badge(props) {
  return <h1>Hello, my name is {props.name}</h1>;
}

这个函数是一个有效的 React 组件,因为它接受带有数据的单个 prop(代表属性)对象参数并返回一个 React 元素。
此类组件称为“函数组件”,因为它们实际上是 JavaScript 函数。

除了功能组件,另一种类型的组件是“类组件”。 类组件与函数组件的不同之处在于它是由 ES6 class 定义的,如下所示:

class Badge extends React.Component {
  render() {
    return <h1>Hello, my name is {this.props.name}</h1>;
  }
}

提取组件

为了说明组件可以拆分为更小的组件这一事实,请考虑以下 Tweet 组件:

可以按如下方式实现:

function Tweet(props) {
  return (
    <div className="Tweet">
      <div className="User">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="User-name">
          {props.author.name}
        </div>
      </div>
      <div className="Tweet-text">
        {props.text}
      </div>
      <img className="Tweet-image"
          src={props.image.imageUrl}
          alt={props.image.description}
        />
      <div className="Tweet-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

这个组件可能有点难以操作,因为它是多么的集群,并且重用它的各个部分也很困难。
但是,我们仍然可以从中提取一些组件。

我们要做的第一件事是提取头像:

function Avatar(props) {
    return (
      <img className="Avatar"
        src={props.user.avatarUrl}
        alt={props.user.name}
      />
    );
  }

Avatar 不需要知道它是在 Comment 中呈现的。
这就是为什么我们给它的道具一个更通用的名字:用户而不是作者。

现在我们将注释简化一点:

function Tweet(props) {
  return (
    <div className="Tweet">
      <div className="User">
        <Avatar user={props.author} />
        <div className="User-name">
          {props.author.name}
        </div>
      </div>
      <div className="Tweet-text">
        {props.text}
      </div>
      <img className="Tweet-image"
          src={props.image.imageUrl}
          alt={props.image.description}
        />
      <div className="Tweet-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

接下来我们要做的是一个 User 组件,它在用户名的旁边呈现一个_头像_:

function User(props) {
  return (
    <div className="User">
      <Avatar user={props.user} />
      <div className="User-name">
        {props.user.name}
      </div>
    </div>
  );
}

现在我们将进一步简化 Tweet:

function Tweet(props) {
  return (
    <div className="Tweet">
      <User user={props.author} />
      <div className="Tweet-text">
        {props.text}
      </div>
      <img className="Tweet-image"
          src={props.image.imageUrl}
          alt={props.image.description}
        />
      <div className="Tweet-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

提取组件似乎是一项乏味的工作,但是在为大型应用程序编码时,拥有可重用的组件会使事情变得更容易。
简化组件时要考虑的一个很好的标准是:
如果 UI 的一部分被多次使用(按钮、面板、头像),或者它本身足够复杂(应用程序、FeedStory、评论),那么它是一个很好的候选者 被提取到一个单独的组件

2. Props

Props 是属性的缩写形式,它们只是指 React 中组件的内部数据。
它们写在组件调用中并传递到组件中。
它们还使用与 HTML 属性相同的语法,例如 _ prop=“value”。

关于道具值得记住的两件事:

  • 首先,在构建组件之前确定 prop 的值并将其用作蓝图的一部分。
  • 其次,prop 的值永远不会改变,即 props 一旦被传递到组件中就是只读的。

访问 prop 的方式是通过每个组件都可以访问的“this.props”属性来引用它。

3. State

State 是一个对象,它包含一些可能在组件的生命周期内发生变化的信息。

这意味着它只是存储在组件 Props 中的数据的当前快照。

数据会随着时间的推移而变化,因此管理数据变化方式的技术变得必要,以确保组件在恰当的时间看起来像工程师希望的那样——这称为状态管理

几乎不可能在不了解状态管理的情况下阅读一段关于 React 的内容。

开发人员喜欢阐述这个主题,但在其核心,状态管理并不像听起来那么复杂。

在 React 中,还可以全局跟踪状态,并且可以根据需要在组件之间共享数据。

本质上,这意味着在 React 应用程序中,在新位置加载数据并不像使用其他技术那样昂贵。
React 应用程序在保存和加载哪些数据以及何时加载方面更加智能。这为制作以新方式使用数据的界面提供了机会。

想想 React 组件,比如具有自己的数据、逻辑和表示的微应用程序
每个组件都应该有一个单一的目的。作为一名工程师,您可以决定该目的并完全控制每个组件的行为方式和使用的数据。
您不再受页面其余部分数据的限制。在您的设计中,您可以通过各种方式利用这一点。有机会展示可以改善用户体验或使设计中的区域更具上下文的附加数据。

如何在 React 中添加 State

在设计时,包括 State 是您应该放到任务的最后。 使用 props 和 events 将所有东西设计得尽可能无状态要好得多。 这使得组件更易于维护、测试和理解

添加状态应该通过 Redux 和 MobX 等状态容器或容器/包装器组件来完成。

Redux 是其他反应式框架的流行状态管理系统。 它实现了一个由动作驱动的集中状态机

在下面的示例中,状态的位置可以是 LoginContainer 本身。 让我们为此使用 React Hooks(这将在下一节中讨论):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

const LoginContainer = () => {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");

  const login = async event => {
    event.preventDefault();
    const response = await fetch("/api", {
      method: "POST",
      body: JSON.stringify({
        username,
        password,
      }),
    });
    // 在这里我们可以检查 response.status 登录或显示错误
  };

  return (
    <LoginForm onSubmit={login}>
      <FormInput
        name="username"
        title="Username"
        onChange={event => setUsername(event.currentTarget.value)}
        value={username}
      />
      <FormPasswordInput
        name="password"
        title="Password"
        onChange={event => setPassword(event.currentTarget.value)}
        value={password}
      />
      <SubmitButton>Login</SubmitButton>
    </LoginForm>
  );
};

Props vs State

道具和状态有时会相互混淆,因为它们非常相似。 以下是它们之间的一些主要区别:

Props State
组件之间的数据保持不变 数据是存储在组件的 Props 中的数据的当前快照。 它在组件的生命周期内发生变化
数据是只读的 数据可以是异步的
props中的数据不可修改 可以使用 this.setState 修改 state 中的数据
props 是传递给组件的东西 State 在组件内管理

React 中的其他概念

组件、道具和状态是你在 React 中所做的一切的三个关键概念。 但还有其他概念需要学习:

1. 生命周期

每个React组件都经过三个阶段:安装、渲染和拆卸。
在这三个阶段发生的一系列事件可以称为组件的生命周期。

虽然这些事件部分与组件的状态(其内部数据)相关,但生命周期略有不同。
React 具有根据需要加载和卸载组件的内部代码,并且组件可以存在于该内部代码中的多个使用阶段。

生命周期方法有很多,但最常见的是:

  • render() - 这个方法是 React 中类组件中唯一需要的方法,也是最常用的方法。 顾名思义,它负责处理组件到 UI 的渲染,它发生在组件的安装和渲染过程中。

创建或删除组件时:

  • componentDidMount() 在组件输出渲染到 DOM 后运行

  • 在卸载和销毁组件之前立即调用 componentWillUnmount()

当 props 或 state 更新时:

  • 当接收到新的 props 或 state 时, shouldComponentUpdate() 在渲染之前被调用。

  • componentDidUpdate() 在更新发生后立即调用。 初始渲染不会调用此方法。

2. HOC

高阶组件 Higher-order component (HOC) 是 React 中用于重用组件逻辑的高级技术。

意思是高阶组件是一个函数,它接受一个组件并返回一个新组件。

它们是从 React 的组合性质中出现的模式。 组件将 props 转换为 UI,而高阶组件将一个组件转换为另一个组件,它们往往在第三方库中流行。

3. Context

在典型的 React 应用程序中,数据通过 props 传递,但这对于应用程序中的许多组件所需的某些类型的 props 来说可能很麻烦。

Context 提供了一种在组件之间共享这些类型的数据的方法,而无需显式地通过每个层次结构级别传递 prop。

意思是上下文,我们可以避免通过中间元素传递道具。


React Hooks

Hooks 是让你从功能组件“钩入”React 状态和生命周期特性的函数。

它们让您无需编写类即可使用状态和其他 React 功能。


在 React 中思考 ✔

React 真正令人惊奇的一件事是它如何让您在构建应用程序时思考它们。

使用 React Hooks 构建可搜索产品数据表的思考过程。

第 1 步:从 模拟 开始想象一下

有一个 JSON API 和一个模拟界面:

JSON API 返回一些如下所示的数据:

[
  {category: "Entertainment", retweets: "54", isLocal: false, text: "Omg. A tweet."},
  {category: "Entertainment", retweets: "100", isLocal: false, text: "Omg. Another."},
  {category: "Technology", retweets: "32", isLocal: false, text: "New ECMAScript features!"},
  {category: "Technology", retweets: "88", isLocal: true, text: "Wow, learning React!"}
];

提示:像 Excalidraw 这样的免费工具可用于绘制 UI 和组件的高级模拟

第 2 步:将 UI 分解为层次结构组件

有了模拟后,接下来要做的是在模拟中的每个组件(和子组件)周围绘制框,并命名所有这些框,如下所示。

使用单一职责原则:理想情况下,组件应该具有单一功能
如果它最终增长,则应将其分解为更小的子组件。 使用相同的技术来决定是否应该创建新函数或对象。

上图中看到应用程序中有五个组件。 已经列出了每个组件代表的数据。

  • TweetSearchResults(橙色):完整组件的容器
  • SearchBar(蓝色):用户输入要搜索的内容
  • TweetList(绿色):根据用户输入显示和过滤推文
  • TweetCategory(绿松石色):显示每个类别的标题
  • TweetRow(红色):为每条推文显示一行

既然已经确定了模拟中的组件,接下来要做的就是将它们分类到一个层次结构中。

在模拟中的另一个组件中找到的组件应显示为层次结构中的子组件。

像这样:

- TweetSearchResults
    - SearchBar
    - TweetList
        - TweetCategory
        - TweetRow

第 3 步:实现应用程序。

在去年之前,最快的方法是构建一个版本,该版本接受数据模型并呈现 UI 但具有零交互性,
但是自从引入 React Hooks 以来,实现应用程序的更简单方法是使用 Hooks,如下所示:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
// i. TweetSearchResults
const TweetSearchResults = ({tweets}) => {
  const [filterText, setFilterText] = useState('');
  const [inThisLocation, setInThisLocation] = useState(false);
  return (
    <div>
      <SearchBar
        filterText={filterText}
        inThisLocation={inThisLocation}
        setFilterText={setFilterText}
        setInThisLocation={setInThisLocation}
      />
      <TweetList
        tweets={tweets}
        filterText={filterText}
        inThisLocation={inThisLocation}
      />
    </div>
  );
}

// ii. SearchBar
const SearchBar = ({filterText, inThisLocation, setFilterText, setInThisLocation}) => (
  <form>
    <input
      type="text"
      placeholder="Search..."
      value={filterText}
      onChange={(e) => setFilterText(e.target.value)}
    />
    <p>
      <label>
        <input
          type="checkbox"
          checked={inThisLocation}
          onChange={(e) => setInThisLocation(e.target.checked)}
        />
        {' '}
        Only show tweets in your current location
      </label>
    </p>
  </form>
);

// iii. TweetList
const TweetList = ({tweets, filterText, inThisLocation}) => {
  const rows = [];
  let lastCategory = null;

  tweets.forEach((tweet) => {
    if (tweet.text.toLowerCase().indexOf(filterText.toLowerCase()) === -1) {
      return;
    }
    if (inThisLocation && !tweet.isLocal) {
      return;
    }
    if (tweet.category !== lastCategory) {
      rows.push(
        <TweetCategory
          category={tweet.category}
          key={tweet.category} />
      );
    }
    rows.push(
      <TweetRow
        tweet={tweet}
        key={tweet.text} />
    );
    lastCategory = tweet.category;
  });

  return (
    <table>
      <thead>
        <tr>
          <th>Tweet Text</th>
          <th>Retweets</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
}

// iv. TweetCategory
const TweetCategory = ({category}) => (
  <tr>
    <th colSpan="2">
      {category}
    </th>
  </tr>
);


// v. TweetRow
const TweetRow = ({tweet}) =>  {
  const color = tweet.isLocal ? 'inherit' : 'red';

  return (
    <tr>
      <td><span style="">{tweet.text}</span></td>
      <td>{tweet.retweets}</td>
    </tr>
  );
}

最终的实现将是按照前面所述的层次结构一起编写的所有代码:

- TweetSearchResults
    - SearchBar
    - TweetList
        - TweetCategory
        - TweetRow

开始 React 的方式

有多种方法可以开始使用 React。

  • 直接在网页上加载:这是设置 React 的最简单方法。 将 React JavaScript 添加到您的页面,作为 npm 依赖项或通过 CDN。

  • 使用 create-react-app:create-react-app 是一个旨在快使用 React 的项目,任何需要超出单个页面的 React 应用程序都会发现 create-react-app 非常符合这个需求 容易地。

  • 更严肃的生产应用程序应该考虑使用 Next.js,因为它具有更强大的默认值(如代码拆分)。

  • 代码沙盒:无需安装即可获得 create-react-app 结构的一种简单方法是访问 https://codesandbox.io/s 并选择“React”。

  • Codepen:如果对 React 组件进行原型设计并喜欢使用 Codepen 那么也可以。


结论

React.js 库旨在使构建模块化、可重用用户界面组件的过程变得简单直观。

当阅读一些其他指南时,会发现这个简短的介绍是一个有用的高级概述。


知识点

  • React
  • ReactDOM
  • JSX
  • Redux
  • Hooks
  • ReactNative
  • Webpack
  • CRA
  • Next.js