React 是一个优秀的用于构建用户界面的库。如果我们想将其与另一个负责接收数据的库集成,该怎么办?在上一章中,我们概述了 Snapterest web 应用应该能够执行的五项任务。我们的方案其中四个与用户界面有关,还有一个与接收数据有关:实时从 Snapkite-engine 服务器接收推文。
在本章中,你将学习如何将 React 与外部 JavaScript 库集成,以及 React 组件生命周期方法是什么,同时完成接收数据的重要任务。
在 React 组件中使用另一个库
正如我们在本书前面所讨论的,我们的 Snapterest web 应用将使用实时推文流。在第 2 章为你的项目安装强大的工具中,你安装了 Snapkite-engine 库,该库连接到 Twitter 流 API,过滤传入的推文,并将其发送到我们的客户端应用。相反的,我们的客户端应用需要一种连接到实时流并监听新推文的方式。
幸运的是,我们不需要自己实现这个功能,因为我们可以重用另一个名为 snapkite-stream-client
的 Snapkite 模块。让我们安装此模块:
- 转到
~/snapterest
目录并运行以下命令:npm install --save snapkite-stream-client
- 这将安装
snapkite-stream-client
模块,并将其作为依赖项添加到package.json
。 - 现在,我们准备在一个 React 组件中重用
snapkite-stream-client
模块。
在上一章中,我们创建了带有两个子组件的 Application
组件:Stream
和 Collection
。在本章中,我们将创建 Stream
组件。
让我们从创建 ~/snapterest/source/components/Stream.js
文件开始:
import React, { Component } from 'react';
import SnapkiteStreamClient from 'snapkite-stream-client';
import StreamTweet from './StreamTweet';
import Header from './Header.react';
class Stream extends Component {
state = {
tweet: null,
};
componentDidMount() {
SnapkiteStreamClient.initializeStream(this.handleNewTweet);
}
componentWillUnmount() {
SnapkiteStreamClient.destroyStream();
}
handleNewTweet = (tweet) => {
this.setState({
tweet: tweet,
});
};
render() {
const { tweet } = this.state;
const { onAddTweetToCollection } = this.props;
const headerText = 'Waiting for public photos from Twitter...';
if (tweet) {
return (
<StreamTweet
tweet={tweet}
onAddTweetToCollection={this.props.onAddTweetToCollection}
/>
);
}
return <Header text={headerText} />;
}
}
export default Stream;
首先,我们将导入 Stream
组件所依赖的以下模块:
React
和ReactDOM
:这是 React 库的一部分StreamTweet
和Header
:这些是 React 组件snapkite-stream-client
:这是一个实用程序库
接下来我们将定义 React 组件。让我们来看看 Stream 组件的实现:
componentDidMount()
componentWillUnmount()
handleNewTweet()
render()
我们已经熟悉了 render()
方法。render()
方法是 React API 的一部分。你已经知道,任何 React 组件都必须在至少使用 render()
方法。让我们看看 Stream
组件的 render()
方法:
render() {
const { tweet } = this.state;
const { onAddTweetToCollection } = this.props;
const headerText = 'Waiting for public photos from Twitter...';
if (tweet) {
return (
<StreamTweet
tweet={tweet}
onAddTweetToCollection={this.props.onAddTweetToCollection}
/>
);
}
return (
<Header text={headerText} />
);
}
如你所见,我们创建了一个新的 tweet
常量,该常量引用了 tweet
属性,该属性是组件状态对象的一部分。然后我们将检查该变量是否引用了实际的 tweet
对象,如果引用了,我们的 render()
方法将返回 StreamTweet
组件,否则将返回 Heade
r 组件。
StreamTweet
组件渲染流中的标头和最新推文,而 header
组件仅渲染标头。
你是否注意到,我们的 Stream
组件本身不渲染任何内容,而是返回其他两个组件中的一个来进行实际渲染?Stream
组件的目的是封装应用的逻辑,并将渲染委托给其他 React 组件。在 React 中,你应该至少有一个封装应用的逻辑,并存储和管理应用状态的组件。这通常是根组件或组件层次结构中的高级组件之一。如果可能,所有其他 React 子组件都应该没有状态。如果你将所有 React 组件看做视图,那么我们的 Stream
组件就是 ControllerView
组件。
我们的 Stream
组件将源源不断的接收新推文,每次接收到新推文时,它都需要重新渲染它的子组件。为了做到这一点,我们需要将当前推文存储在组件的状态中。一旦我们更新其状态,React 将调用 render()
方法并重新渲染其所有子组件。为此,我们将实现 handleNewTweet()
方法:
handleNewTweet = (tweet) => {
this.setState({
tweet: tweet,
});
};
handleNewTweet(
) 方法获取一个推文对象,并将其设置为组件状态的 tweet
属性的新值。
新推文是从哪里来的,什么时候来的?让我们来看看 componentDidMount()
方法:
componentDidMount() {
SnapkiteStreamClient.initializeStream(this.handleNewTweet);
}
该方法调用 SnapkiteStreamClient
对象的 initializeStream()
属性,并传递 this.handleNewTweet
回调函数作为其参数。SnapkiteStreamClient
是一个带有 API 的外部库,我们使用它来初始化推文流。对于 SnapkiteStreamClient
接收到的每条新推文,都将调用 this.handleNewTweet
方法。
为什么我们将该方法命名为 componentDidMount()
?我们没有。React 做了。事实上,componentDidMount()
方法是 React API 的一部分。这是 React 组件的生命周期方法之一。它只在 React 完成组件的初始渲染后立即调用一次。此时,React 已经创建了一个 DOM 树,它由我们的组件表示。现在我们可以使用另一个 JavaScript 库访问该 DOM。
componentDidMount()
是将 React 库与另一个 JavaScript 库集成的完美场所,这是我们使用外部 SnapkiteStreamClient
库连接到推文流的地方。
现在我们知道什么时候初始化 React 组件中的外部 JavaScript 库,但相反的,什么时候应该取消初始化并清理我们在 componentDidMount()
方法中所做的一切呢?在卸载组件之前清理所有内容是个好主意。为此,React API 为我们提供了另一种组件生命周期方法 —— componentWillUnmount()
:
componentWillUnmount() {
SnapkiteStreamClient.destroyStream();
}
在 React 卸载组件之前,React 调用 componentWillUnmount()
方法。正如你在 componentWillUnmount()
方法中看到的,你正在调用 SnapkiteStreamClient
对象的 destroyStream()
方法。destroyStream()
方法断开了与 SnapkiteStreamClient
的连接,我们可以安全地卸载 Stream
组件。
你可能想知道组件生命周期方法是什么,以及我们为什么需要它们。
了解 React 组件的生命周期方法
想想 React 组件的作用?我们知道它使用 render()
方法描述了要渲染的内容。然而有时只使用 render()
方法是不够的,因为如果我们想在组件渲染之前或之后做什么呢?如果我们希望能够决定是否应该调用组件的 render()
方法呢?
看起来我们所描述的是一个渲染 React 组件的过程。该过程具有不同的阶段,例如,渲染前、渲染中和渲染后。在 React 中,这个过程被称为组件的生命周期。每个 React 组件都要经过这个过程。我们想要的是一种方法来连接到该过程中,并在该过程的不同阶段调用我们自己的函数,以便更好地控制它。为此,React 提供了许多方法,当组件生命周期过程中的某个阶段发生时,我们可以使用这些方法获得通知。这些方法称为组件的生命周期方法。它们以可预测的顺序调用。
所有 React 组件的生命周期方法可分为三个阶段:
- 挂载:当组件插入到 DOM 中时,会发生此阶段
- 更新:当组件被重新渲染到虚拟 DOM 中以确定实际 DOM 是否需要更新时,就会出现此阶段
- 卸载:从 DOM 中删除组件时会发生此阶段
在 React 的术语中,将组件插入 DOM 称为“挂载”,而从 DOM 中移除组件称为“卸载”。
了解 React 组件生命周期方法的最佳方法是查看它们的运行情况。让我们创建在本章前面讨论过的 StreamTweet
组件,该组件将实现 React 的大部分生命周期方法。
转到 ~/snapterest/source/components/
并新建 StreamTweet.js
文件:
import React, { Component } from 'react';
import Header from './Header';
import Tweet from './Tweet';
class StreamTweet extends Component {
// 此处定义其他组件生命周期方法
render() {
console.log('[Snapterest] StreamTweet: Running render()');
const { headerText } = this.state;
const { tweet, onAddTweetToCollection } = this.props;
return (
<section>
<Header text={headerText} />
<Tweet tweet={tweet} onImageClick={onAddTweetToCollection} />
</section>
);
}
}
export default StreamTweet;
正如你所看到的,StreamTweet
组件除了 render()
方法还没有生命周期方法。我们将在前进过程中逐一创建并讨论它们。
这四种方法在组件的挂载阶段调用,如下图所示:
从上图中可以看到,调用的方法如下:
constructor()
componentWillMount()
render()
componentDidMount()
在本章中,我们将讨论这四种方法中的两种(render()
除外)。当组件插入到 DOM 中时,它们只调用一次。让我们仔细看看他们。
挂载方法
现在让我们看看一些有用的挂载方法。
componentWillMount 方法
第二个调用 componentWillMount()
方法。在 React 将组件插入 DOM 之前立即调用它。在 StreamTweet
组件的 constructor()
之后添加以下代码:
componentWillMount() {
console.log('[Snapterest] StreamTweet: 1. Running componentWillMount()');
this.setState({
numberOfCharactersIsIncreasing: true,
headerText: 'Latest public photo from Twitter'
});
window.snapterest = {
numberOfReceivedTweets: 1,
numberOfDisplayedTweets: 1
};
}
我们用这个方法做了很多事。首先,我们记录正在调用该方法的事实。事实上,为了演示,我们将记录该组件的每个组件生命周期方法。当你在 web 浏览器中运行此代码时,你应该能打开 JavaScript 控制台,并看到这些日志消息按预期的升序打印。
接下来,我们使用 this.setState()
方法更新组件的状态:
- 设置
numberOfCharactersIsIncreasing
属性为true
- 设置
headerText
属性为Latest public photo from Twitter
因为这是该组件将渲染的第一条推文,所以我们知道,文字数量肯定会从零增加到第一条推文中的文字数量。因此,我们将其设置为真。我们还将默认文本 Latest public photo from Twitter
分配给标题。
正如你所知道的,调用 this.setState()
方法会触发组件的 render()
方法,因此在组件的挂载阶段,render()
方法似乎会被调用两次。然而在这种情况下,React 知道尚未渲染任何内容,因此它只调用 render()
方法一次。
最后,在该方法中,我们使用以下两个属性定义 snapterest
全局对象:
numberOfReceivedTweets
:统计所有已接收推文的数量numberOfDisplayedTweets
:仅统计显示了的推文数
我们将 numberOfReceivedTweets
设置为 1
,因为我们知道 componentWillMount()
方法只在收到第一条推文时调用一次。我们还知道,我们的 render()
方法将在第一条推文中调用,因此我们也将 numberOfDisplayedTweets
设置为 1
:
window.snapterest = {
numberOfReceivedTweets: 1,
numberOfDisplayedTweets: 1,
};
这个全局对象不是 React 或我们的 web 应用逻辑的一部分;我们可以删除它,一切仍将按预期工作。在上述代码中,window.snapterest
是一个方便的用于跟踪我们在任何时间点处理了多少推文的工具。我们使用全局对象 window.snapterest
仅用于演示目的。我强烈建议你不要将自己的属性添加到现实项目中的全局对象中,因为你可能会覆盖现有属性,并且又或许你的属性稍后可能会被你不拥有的其他 JavaScript 代码覆盖。如果你稍后决定在生产环境中部署 Snapterest,请确保删除全局对象 window.snapterest
和 StreamTweet
组件中的相关代码。
在 web 浏览器中查看 Snapterest 几分钟后,你可以打开 JavaScript 控制台并键入 Snapterest.numberOfReceivedTweets
和 snapterest.numberOfDisplayedTweets
命令。这些命令将输出数字,这些数字将帮助你更好地了解新推文的速度,以及有多少推文没有显示。在下一个组件生命周期方法中,我们将向 wondow.snapterest
对象中添加更多属性。
componentDidMount 方法
在 React 将组件插入 DOM 后,立即调用 componentDidMount()
方法。更新后的 DOM 现在可供访问,这意味着该方法是初始化其他需要访问该 DOM 的 JavaScript 库的最佳位置。
在本章前面,我们使用 componentDidMount()
方法创建了 Stream
组件,该方法初始化外部 snapkite-stream-client
JavaScript 库。
让我们看看这个组件的 componentDidMount()
方法。在 componentWillMount()
方法之后,将以下代码添加到 StreamTweet
组件:
componentDidMount() {
console.log('[Snapterest] StreamTweet: 2. Running componentDidMount()');
const componentDOMRepresentation = ReactDOM.findDOMNode(this);
window.snapterest.headerHtml = componentDOMRepresentation.children[0].outerHTML;
window.snapterest.tweetHtml = componentDOMRepresentation.children[1].outerHTML;
}
此处我们使用 ReactDOM.findDOMNode()
方法引用表示 StreamTweet
组件的 DOM。我们给它传了 this
参数来引用当前组件(在本例中为 StreamTweet
)。componentDOMRepresentation
常量引用我们可以遍历的 DOM 树,从而访问其各种属性。为了更好地理解这个 DOM 树的样子,让我们仔细看看 StreamTweet
组件的 render()
方法:
render() {
console.log('[Snapterest] StreamTweet: Running render()');
return (
<section>
<Header text={this.state.headerText} />
<Tweet
tweet={this.props.tweet}
onImageClick={this.props.onAddTweetToCollection}
/>
</section>
);
}
使用 JSX 的最大好处之一就是只需查看组件的 render()
方法,就可以轻松地确定组件将拥有多少子元素。此处我们可以看到父 <section>
元素有两个子组件:<Header/>
和 <Tweet/>
。
因此,当我们使用 DOM API children 属性遍历生成的 DOM 树时,我们可以确保它也有两个子元素:
componentDOMRepresentation.children[0]
:<Header/>
组件的 DOM 表示componentDOMRepresentation.children[1]
:<Tweet/>
组件的 DOM 表示
每个元素的 outerHTML
属性获取表示每个元素 DOM 树的 HTML 字符串。正如我们在本章前面讨论的那样,为了方便起见,我们将此 HTML 字符串分配给全局对象 window.snapterest
。
如果你正在使用另一个 JavaScript 库,例如 jQuery 和 React,那么可以使用 componentDidMount()
方法将两者集成。
如果你想发送 AJAX 请求,或者使用 setTimeout()
或 setInterval()
函数设置计时器,那么也可以在该方法中执行 this.props
。通常,componentDidMount()
应该是将 React 库与非 React 库和 API 集成的首选组件生命周期方法。
到目前为止,在本章中,你已经学习了 React 组件为我们提供的基本挂载方法。我们在 StreamTweet
组件中使用了这三个组件。我们还讨论了 StreamTweet
组件的 render()
方法。这就是我们需要了解的全部内容,以了解 React 最初将如何渲染 StreamTweet
组件。在第一次渲染时,React 将执行以下方法序列:
componentWillMount()
render()
componentDidMount()
这被称为 React 组件的挂载阶段。它只执行一次,除非我们卸载组件并再次挂载它。
接下来,让我们讨论 React 组件的卸载阶段。
卸载方法
现在让我们来看看一种流行的卸载方法。
componentWillUnmount 方法
React 在此阶段仅提供一个方法,即 componentWillUnmount()
。在 React 从 DOM 中移除组件并销毁它之前,会立即调用该方法。该方法对于清理在组件安装或更新阶段创建的任何数据非常有用。这正是我们在 StreamTweet
组件所作的。将此代码添加到 StreamTweet
组件的 componentDidMount()
方法后面:
componentWillUnmount() {
console.log('[Snapterest] StreamTweet: 3. Running componentWillUnmount()');
delete window.snapterest;
}
在 componentWillUnmount()
方法中,我们使用 delete
运算符删除了全局对象 window.snapterest
:
delete window.snapterest;
删除 window.snapterest
将保持我们的全局对象干净。如果你在 componentDidMount()
方法中创建了任何其他 DOM 元素,那么 componentWillUnmount()
方法是删除它们的好地方。你可以将 componentDidMount()
和 componentWillUnmount()
方法视为将 React 组件与另一个 JavaScript API 集成的两步机制:
- 在
componentDidMount()
方法中初始化它。 - 在
componentWillUnmount()
方法中终止它。
这样的话,需要使用 DOM 的外部 JavaScript 库将与 React 渲染的 DOM 保持同步。
这就是高效卸载 React 组件所需的全部知识。
总结
在本章中,我们创建了 Stream 组件,并学习了如何将 React 组件与外部 JavaScript 库集成。你还了解了 React 组件的生命周期方法。我们还详细讨论了挂载和卸载方法,并开始实现 StreamTweet 组件。
下一章我们将了解组件生命周期的更新方法。我们还将实现 Header 和 Tweet 组件,并学习如何设置组件的默认属性。
评论 (0)