我们决定在 React 应用中实现 Flux 体系结构的原因是我们希望有一个更易于维护的数据流。在上一章中,我们实现了AppDispatcher
、TweetActionCreators
和TweetStore
。让我们快速记住它们的用途:
TweetActionCreators
:创建并分派动作AppDispatcher
:将所有动作发送到所有门店TweetStore
:存储和管理应用数据
我们的数据流中唯一缺失的部分如下:
- 使用
TweetActionCreators
创建动作并启动数据流 - 使用
TweetStore
获取数据
这里有几个重要的问题要问:在我们的应用中,数据流从哪里开始?我们的数据是什么?如果我们回答了这些问题,我们将了解从何处开始重构应用以适应 Flux 体系结构。
Snapterest 允许用户接收和收集最新推文。我们的应用关注的唯一数据是 tweets。因此,我们的数据流从接收新推文开始。目前,我们的应用的哪个部分负责接收新推文?您可能还记得我们的Stream
组件有以下componentDidMount()
方法:
componentDidMount() {
SnapkiteStreamClient.initializeStream(this.handleNewTweet);
}
是的,目前,我们在呈现Stream
组件后启动了一个新的推特流。等等,您可能会问,“我们不是知道 React 组件应该只关心呈现用户界面吗?”您是对的。不幸的是,目前,Stream
组件负责两件不同的事情:
- 呈现
StreamTweet
组件 - 启动数据流
显然,这是未来可能出现的维护问题。让我们借助 Flux 来解耦这两个不同的关注点。
首先,我们将创建一个名为WebAPIUtils
的新实用模块。在~/snapterest/source/utils/
目录中创建WebAPIUtils.js
文件:
import SnapkiteStreamClient from ‘snapkite-stream-client’;
import { receiveTweet } from ‘../actions/TweetActionCreators’;
function initializeStreamOfTweets() {
SnapkiteStreamClient.initializeStream(receiveTweet);
}
export { initializeStreamOfTweets };
在这个实用模块中,我们首先导入SnapkiteStreamClient
库和TweetActionCreators
。然后,我们创建initializeStreamOfTweets()
函数来初始化新 tweet 流,就像Stream
组件的componentDidMount()
方法一样。除了一个关键的区别外:每当SnapkiteStreamClient
收到一条新的 tweet,它就会调用TweetActionCreators.receiveTweet
方法,将一条新 tweet 作为参数传递给它:
SnapkiteStreamClient.initializeStream(receiveTweet);
请记住,receiveTweet
函数期望接收一个tweet
参数:
function receiveTweet(tweet) {
// ... create and dispatch ‘receive_tweet’ action
}
然后,此 tweet 将作为receiveTweet()
函数创建的新操作对象的属性进行调度。
然后,WebAPIUtils
模块导出我们的initializeStreamOfTweets()
函数。
现在我们有了一个模块,该模块带有一种方法,可以在我们的 Flux 体系结构中启动数据流。我们应该在哪里进口和调用它?因为它与Stream
组件是解耦的,事实上,它根本不依赖于任何 React 组件,我们甚至可以在 React 渲染任何东西之前使用它。让我们在app.js
文件中使用它:
import React from ‘react’;
import ReactDOM from ‘react-dom’;
import Application from ‘./components/Application’;
import { initializeStreamOfTweets } from ‘./utils/WebAPIUtils’;
initializeStreamOfTweets();
ReactDOM.render(
<Application/>,
document.getElementById(‘react-application’)
);
如您所见,我们需要做的就是导入并调用initializeStreamOfTweets()
方法:
import { initializeStreamOfTweets } from ‘./utils/WebAPIUtils’;
initializeStreamOfTweets();
我们在调用 React 的render()
方法之前执行此操作:
ReactDOM.render(
<Application/>,
document.getElementById(‘react-application’)
);
事实上,作为一个实验,您可以完全删除代码的ReactDOM.render()
行,并在TweetActionCreators.receiveTweet
函数中放入一条 log 语句。例如,运行以下代码:
function receiveTweet(tweet) {
console.log("I’ve received a new tweet and now will dispatch it together with a new action.");
const action = {
type: ‘receive_tweet’,
tweet
};
AppDispatcher.dispatch(action);
}
现在运行npm start
命令。然后,在 web 浏览器中打开~/snapterest/build/index.html
,您将看到页面上呈现的以下文本:
我即将学习 React.js 的要点。
现在打开 JavaScript 控制台,您将看到以下输出:
[Snapkite Stream Client] Socket connected
I’ve received a new tweet and now will dispatch it together with a new action.
对于我们的应用收到的每个新 tweet,都会打印出此日志消息。尽管我们没有渲染任何 React 组件,但我们的 Flux 架构仍然存在:
- 我们的应用收到一条新的 tweet。
- 它创建并分派一个新操作。
- 没有商店向调度员注册,因此没有人接收新操作;因此,什么也没有发生。
现在你们可以清楚地看到,反应和 Flux 是两个完全不依赖的独立事物。
但是,我们确实希望呈现我们的 React 组件。毕竟,在前面的十章中,我们已经花了很多精力来创建它们!要做到这一点,我们需要把我们的TweetStore
商店付诸行动。你能猜到我们应该在哪里使用它吗?这里有一个提示:在一个 React 组件中,它需要一条 tweet 来呈现我们的老组件Stream
。
现在,随着 Flux 体系结构的就位,我们将重新思考 React 组件如何获得需要渲染的数据。如您所知,对于 React 组件,通常有两个数据源:
- 调用另一个库,例如,调用
jQuery.ajax()
方法,或者在我们的例子中调用SnapkiteStreamClient.initializeStream()
- 通过
props
对象从父组件接收数据
我们希望我们的 React 组件不使用任何外部库来接收数据。相反,从现在起,他们将从商店获得相同的数据。记住这个计划,让我们重构Stream
组件。
下面是它现在的样子:
import React from ‘react’;
import SnapkiteStreamClient from ‘snapkite-stream-client’;
import StreamTweet from ‘./StreamTweet’;
import Header from ‘./Header’;
class Stream extends React.Component {
constructor() {
super();
this.state = {
tweet: null
};
}
componentDidMount() {
SnapkiteStreamClient.initializeStream(this.handleNewTweet);
}
componentWillUnmount() {
SnapkiteStreamClient.destroyStream();
}
handleNewTweet = tweet => {
this.setState({
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={onAddTweetToCollection}
/>
);
}
return (
<Header text={headerText} />
);
}
}
export default Stream;
首先,让我们把去掉componentDidMount()
、componentWillUnmount()
和handleNewTweet()
方法,导入TweetStore
存储:
import React from ‘react’;
import SnapkiteStreamClient from ‘snapkite-stream-client’;
import StreamTweet from ‘./StreamTweet’;
import Header from ‘./Header’;
import TweetStore from ‘../stores/TweetStore’;
class Stream extends React.Component {
state = {
tweet: null
}
render() {
const { tweet } = this.state;
const { onAddTweetToCollection } = this.props;
const headerText = "Waiting for public photos from Twitter...";
if (tweet) {
return (
<StreamTweet
tweet={tweet}
onAddTweetToCollection={onAddTweetToCollection}
/>
);
}
return (
<Header text={headerText} />
);
}
}
export default Stream;
也不再需要导入snapkite-stream-client
模块。
接下来,我们需要改变Stream
组件获取初始 tweet 的方式。让我们更新其初始状态:
state = {
tweet: TweetStore.getTweet()
}
就代码而言,这看起来可能是一个小的变化,但它是一个重大的架构改进。我们现在使用getTweet()
方法从TweetStore
存储中获取数据。在上一章中,我们讨论了存储如何在 Flux 中公开公共方法,以允许应用的其他部分从中获取数据。getTweet()
方法是这些公共方法之一的一个示例,称为getters。
您可以从存储中获取数据,但不能像这样直接在存储中设置数据。商店没有公开的setter方法。它们是特意设计的,考虑到这一限制,因此当您使用 Flux 编写应用时,您的数据只能朝一个方向流动。当您需要维护 Flux 应用时,这将使您受益匪浅。
现在我们知道了如何获得最初的 tweet,但是如何获得稍后将到达的所有其他新 tweet 呢?我们可以创建一个计时器并重复调用TweetStore.getTweet()
;然而,这并不是最好的解决方案,因为它假设我们不知道TweetStore
何时更新新的推文。然而,我们确实知道这一点。
怎样请记住,在上一章中,我们在TweetStore
对象上实现了以下公共方法,即addChangeListener()
方法:
addChangeListener(callback) {
this.on(‘change’, callback);
}
我们还实施了removeChangeListener()
方法:
removeChangeListener(callback) {
this.removeListener(‘change’, callback);
}
对。我们可以让TweetStore
告诉我们它什么时候改变数据。为此,我们需要调用它的addChangeListener()
方法,并向它传递一个回调函数,TweetStore
将为每个新 tweet 调用该函数。问题是在我们的Stream
组件中,我们将TweetStore.addChangeListener()
方法称为何处?
由于每个组件的生命周期只需要向TweetStore
添加一次change
事件监听器,因此componentDidMount()
是一个完美的候选者。在Stream
组件中添加以下componentDidMount()
方法:
componentDidMount() {
TweetStore.addChangeListener(this.onTweetChange);
}
在这里,我们将自己的change
事件侦听器this.onTweetChange
添加到TweetStore
。现在当TweetStore
改变它的数据时,它将触发我们的this.onTweetChange
方法。我们将很快创建此方法。
不要忘记,在卸载 React 组件之前,我们需要删除任何事件侦听器。为此,在Stream
组件中添加以下componentWillUnmount()
方法:
componentWillUnmount() {
TweetStore.removeChangeListener(this.onTweetChange);
}
删除事件侦听器与添加它非常相似。我们调用TweetStore.removeChangeListener()
方法并将this.onTweetChange
方法作为参数传递。
现在,是时候在我们的Stream
组件中创建onTweetChange
方法了:
onTweetChange = () => {
this.setState({
tweet: TweetStore.getTweet()
});
}
如您所见,它使用TweetStore.getTweet()
方法使用TweetStore
中存储的新 tweet 更新组件的状态。
我们需要在Stream
组件中进行最后一项更改。在本章后面,您将了解到我们的StreamTweet
组件不再需要handleAddTweetToCollection()
回调函数;因此,在此组件中,我们将更改以下代码段:
return (
<StreamTweet
tweet={tweet}
onAddTweetToCollection={onAddTweetToCollection}
/>
);
将其替换为以下代码:
return (<StreamTweet tweet={tweet} />);
现在让我们看看我们新重构的Stream
组件:
import React from ‘react’;
import StreamTweet from ‘./StreamTweet’;
import Header from ‘./Header’;
import TweetStore from ‘../stores/TweetStore’;
class Stream extends React.Component {
state = {
tweet: TweetStore.getTweet()
}
componentDidMount() {
TweetStore.addChangeListener(this.onTweetChange);
}
componentWillUnmount() {
TweetStore.removeChangeListener(this.onTweetChange);
}
onTweetChange = () => {
this.setState({
tweet: TweetStore.getTweet()
});
}
render() {
const { tweet } = this.state;
const { onAddTweetToCollection } = this.props;
const headerText = "Waiting for public photos from Twitter...";
if (tweet) {
return (<StreamTweet tweet={tweet}/>);
}
return (<Header text={headerText}/>);
}
}
export default Stream;
让我们回顾一下我们的Stream
组件如何始终拥有最新推文:
- 我们使用
getTweet()
方法将组件的初始 tweet 设置为从TweetStore
获得的最新 tweet。 - 然后,我们聆听
TweetStore
中的变化。 - 当
TweetStore
更改其 tweet 时,我们使用getTweet()
方法将组件的状态更新为从TweetStore
获得的最新 tweet。 - 当组件即将卸载时,我们停止监听
TweetStore
中的更改。
这就是 React 组件如何与焊剂存储器相互作用。
在我们继续增强应用的其余部分之前,让我们先看看当前的数据流:
app.js
:接收新的推文,并为每条推文呼叫TweetActionCreators
TweetActionCreators
:这会创建并发送一条新的推文AppDispatcher
:将所有动作发送到所有门店TweetStore
:向调度器注册,并在从调度器接收到的每个新操作上发出更改事件Stream
:监听TweetStore
中的变化,从TweetStore
获取新的推文,用新的推文更新状态,并重新呈现
您能看到我们现在如何扩展 React 组件、动作创建者和存储的数量,并且仍然能够维护 Snapterest 吗?对于 Flux,它将始终是单向数据流。无论我们将实现多少新功能,它都将是相同的心智模型。从长远来看,当我们需要维护我们的应用时,我们将受益匪浅。
我有没有提到我们将在应用中进一步调整 Flux?接下来,让我们就这样做。
Snapterest 不仅存储最新的推文,还存储用户创建的推文集合。让我们用 Flux 重构这个特性。
首先,让我们创建一个集合存储。导航到~/snapterest/source/stores/
目录并创建CollectionStore.js
文件:
import AppDispatcher from ‘../dispatcher/AppDispatcher’;
import { EventEmitter } from ‘events’;
const CHANGE_EVENT = ‘change’;
let collectionTweets = {};
let collectionName = ‘new’;
function addTweetToCollection(tweet) {
collectionTweets[tweet.id] = tweet;
}
function removeTweetFromCollection(tweetId) {
delete collectionTweets[tweetId];
}
function removeAllTweetsFromCollection() {
collectionTweets = {};
}
function setCollectionName(name) {
collectionName = name;
}
function emitChange() {
CollectionStore.emit(CHANGE_EVENT);
}
const CollectionStore = Object.assign(
{}, EventEmitter.prototype, {
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getCollectionTweets() {
return collectionTweets;
},
getCollectionName() {
return collectionName;
}
}
);
function handleAction(action) {
switch (action.type) {
case ‘add_tweet_to_collection’:
addTweetToCollection(action.tweet);
emitChange();
break;
case ‘remove_tweet_from_collection’:
removeTweetFromCollection(action.tweetId);
emitChange();
break;
case ‘remove_all_tweets_from_collection’:
removeAllTweetsFromCollection();
emitChange();
break;
case ‘set_collection_name’:
setCollectionName(action.collectionName);
emitChange();
break;
default: // ... do nothing
}
}
CollectionStore.dispatchToken = AppDispatcher.register(handleAction);
export default CollectionStore;
CollectionStore
是一家更大的商店,但其结构与TweetStore
相同。
首先,我们导入依赖项,并将change
事件名称分配给CHANGE_EVENT
变量:
import AppDispatcher from ‘../dispatcher/AppDispatcher’;
import { EventEmitter } from ‘events’;
const CHANGE_EVENT = ‘change’;
然后,我们定义了我们的数据和四种改变数据的私有方法:
let collectionTweets = {};
let collectionName = ‘new’;
function addTweetToCollection(tweet) {
collectionTweets[tweet.id] = tweet;
}
function removeTweetFromCollection(tweetId) {
delete collectionTweets[tweetId];
}
function removeAllTweetsFromCollection() {
collectionTweets = {};
}
function setCollectionName(name) {
collectionName = name;
}
如您所见,我们将 tweet 集合存储在一个最初为空的对象中,并且我们还存储最初设置为new
的集合名称。然后,我们创建了三个变异为collectionTweets
的私有函数:
addTweetToCollection()
:顾名思义,它将tweet
对象添加到collectionTweets
对象中removeTweetFromCollection()
:从collectionTweets
对象中删除tweet
对象removeAllTweetsFromCollection()
:通过将collectionTweets
设置为空对象,将tweet
对象从collectionTweets
中移除
然后,我们定义了一个名为setCollectionName
的私有函数,它将现有集合名称更改为新的集合名称。
这些功能被认为是私有的,因为它们在CollectionStore
模块之外无法访问;例如,您无法在任何其他模块中访问它们:
CollectionStore.setCollectionName(‘impossible’);
如前所述,这样做是为了在应用中强制执行单向数据流。
我们创建了发出change
事件的emitChange()
方法。
然后,我们创建CollectionStore
对象:
const CollectionStore = Object.assign(
{}, EventEmitter.prototype, {
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getCollectionTweets() {
return collectionTweets;
},
getCollectionName() {
return collectionName;
}
});
这与TweetStore
对象非常相似,除了两种方法:
getCollectionTweets()
:返回一组推文getCollectionName()
:返回集合名称
这些方法可在CollectionStore.js
文件之外访问,并应在 React 组件中使用,以从CollectionStore
获取数据。
然后,我们创建handleAction()
函数:
function handleAction(action) {
switch (action.type) {
case ‘add_tweet_to_collection’:
addTweetToCollection(action.tweet);
emitChange();
break;
case ‘remove_tweet_from_collection’:
removeTweetFromCollection(action.tweetId);
emitChange();
break;
case ‘remove_all_tweets_from_collection’:
removeAllTweetsFromCollection();
emitChange();
break;
case ‘set_collection_name’:
setCollectionName(action.collectionName);
emitChange();
break;
default: // ... do nothing
}
}
此函数处理由AppDispatcher
分派的动作,但与CollectionStore
模块中的TweetStore
不同,我们可以处理多个动作。事实上,我们可以处理与推特收集相关的四个操作:
add_tweet_to_collection
:这会在收藏中添加一条推文remove_tweet_from_collection
:这将从集合中删除推文remove_all_tweets_from_collection
:这将删除集合中的所有推文set_collection_name
:设置集合名称
记住所有的商店都会接收所有的动作,所以CollectionStore
也会接收receive_tweet
动作,但我们只是在这个商店中忽略它,就像TweetStore
忽略add_tweet_to_collection
、remove_tweet_from_collection
、remove_all_tweets_from_collection
和set_collection_name
一样。
然后,我们用AppDispatcher
注册handleAction
回调,并将dispatchToken
保存在CollectionStore
对象中:
CollectionStore.dispatchToken = AppDispatcher.register(handleAction);
最后,我们将CollectionStore
作为一个模块导出:
export default CollectionStore;
既然我们已经准备好了集合存储,接下来让我们创建 action creator 函数。
导航到~/snapterest/source/actions/
并创建该CollectionActionCreators.js
文件:
import AppDispatcher from ‘../dispatcher/AppDispatcher’;
function addTweetToCollection(tweet) {
const action = {
type: ‘add_tweet_to_collection’,
tweet
};
AppDispatcher.dispatch(action);
}
function removeTweetFromCollection(tweetId) {
const action = {
type: ‘remove_tweet_from_collection’,
tweetId
};
AppDispatcher.dispatch(action);
}
function removeAllTweetsFromCollection() {
const action = {
type: ‘remove_all_tweets_from_collection’
};
AppDispatcher.dispatch(action);
}
function setCollectionName(collectionName) {
const action = {
type: ‘set_collection_name’,
collectionName
};
AppDispatcher.dispatch(action);
}
export default {
addTweetToCollection,
removeTweetFromCollection,
removeAllTweetsFromCollection,
setCollectionName
};
对于我们在CollectionStore
中处理的每个动作,我们都有一个动作创建者函数:
addTweetToCollection()
:创建并发送带有新 tweet 的add_tweet_to_collection
动作removeTweetFromCollection()
:这将创建并发送带有必须从集合中删除的 tweet ID 的remove_tweet_from_collection
操作removeAllTweetsFromCollection()
:创建并发送remove_all_tweets_from_collection
动作setCollectionName()
:创建并发送带有新集合名称的set_collection_name
操作
现在,当我们创建了CollectionStore
和CollectionActionCreators
模块后,我们可以开始重构 React 组件,以采用 Flux 架构。
我们从哪里开始重构我们的组件?让我们从组件层次结构中最顶层的 React 组件Application
开始。
目前,我们的Application
组件存储并管理 tweet 的收集。让我们删除此功能,因为它现在由集合存储管理。
从Application
组件中删除constructor()
、addTweetToCollection()
、removeTweetFromCollection()
和removeAllTweetsFromCollection()
方法:
import React from ‘react’;
import Stream from ‘./Stream’;
import Collection from ‘./Collection’;
class Application extends React.Component {
render() {
const {
collectionTweets
} = this.state;
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-4 text-center">
<Stream onAddTweetToCollection={this.addTweetToCollection}/>
</div>
<div className="col-md-8">
<Collection
tweets={collectionTweets}
onRemoveTweetFromCollection={this.removeTweetFromCollection}
onRemoveAllTweetsFromCollection={this.removeAllTweetsFromCollection}
/>
</div>
</div>
</div>
);
}
}
export default Application;
现在Application
组件只有呈现Stream
和Collection
组件的render()
方法。因为它不再管理 tweet 的收集,所以我们也不需要将任何属性传递给Stream
和Collection
组件。
更新Application
组件的render()
功能,如下所示:
render() {
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-4 text-center">
<Stream/>
</div>
<div className="col-md-8">
<Collection/>
</div>
</div>
</div>
);
}
Flux 架构的采用使得Stream
组件可以管理最新的 tweet,Collection
组件可以管理 tweet 的收集,而Application
组件不再需要管理任何东西,因此它变成了一个包装Stream
和Collection
的容器组件附加 HTML 标记中的组件。
事实上,您可能已经注意到,我们当前版本的Application
组件是一个很好的候选功能组件:
import React from ‘react’;
import Stream from ‘./Stream’;
import Collection from ‘./Collection’;
const Application = () =>(
<div className="container-fluid">
<div className="row">
<div className="col-md-4 text-center">
<Stream />
</div>
<div className="col-md-8">
<Collection />
</div>
</div>
</div>
);
export default Application;
我们的Application
组件现在简单多了,它的标记看起来干净多了。这提高了组件的可维护性。做得好!
接下来,让我们重构Collection
组件。将现有的Collection
组件更换为以下组件:
import React, { Component } from ‘react’;
import ReactDOMServer from ‘react-dom/server’;
import CollectionControls from ‘./CollectionControls’;
import TweetList from ‘./TweetList’;
import Header from ‘./Header’;
import CollectionUtils from ‘../utils/CollectionUtils’;
import CollectionStore from ‘../stores/CollectionStore’;
class Collection extends Component {
state = {
collectionTweets: CollectionStore.getCollectionTweets()
}
componentDidMount() {
CollectionStore.addChangeListener(this.onCollectionChange);
}
componentWillUnmount() {
CollectionStore.removeChangeListener(this.onCollectionChange);
}
onCollectionChange = () => {
this.setState({
collectionTweets: CollectionStore.getCollectionTweets()
});
}
createHtmlMarkupStringOfTweetList() {
const htmlString = ReactDOMServer.renderToStaticMarkup(
<TweetList tweets={this.state.collectionTweets}/>
);
const htmlMarkup = {
html: htmlString
};
return JSON.stringify(htmlMarkup);
}
render() {
const { collectionTweets } = this.state;
const numberOfTweetsInCollection = CollectionUtils
.getNumberOfTweetsInCollection(collectionTweets);
let htmlMarkup;
if (numberOfTweetsInCollection > 0) {
htmlMarkup = this.createHtmlMarkupStringOfTweetList();
return (
<div>
<CollectionControls
numberOfTweetsInCollection={numberOfTweetsInCollection}
htmlMarkup={htmlMarkup}
/>
<TweetList tweets={collectionTweets} />
</div>
);
}
return (<Header text="Your collection is empty" />);
}
}
export default Collection;
我们在这里改变了什么?有几件事。首先,我们导入了两个新模块:
import CollectionUtils from ‘../utils/CollectionUtils’;
import CollectionStore from ‘../stores/CollectionStore’;
我们在第 9 章中创建了CollectionUtils
模块,使用 Jest测试 React 应用,在本章中,我们正在使用它。CollectionStore
是我们获取数据的来源。
接下来,您应该能够发现四种方法的熟悉模式:
- 在初始状态下,我们将 tweet 的集合设置为当时存储在
CollectionStore
中的内容。您可能还记得,CollectionStore
提供了getCollectionTweets()
方法从中获取数据。 - 在
componentDidMount()
方法中,我们将change
事件监听器this.onCollectionChange
添加到CollectionStore
。每当推文集合更新时,CollectionStore
将调用我们的this.onCollectionChange
回调函数通知Collection
组件该更改。 - 在
componentWillUnmount()
方法中,我们移除添加到componentDidMount()
方法中的change
事件侦听器。 - 在
onCollectionChange()
方法中,我们将组件的状态设置为当时CollectionStore
中存储的任何状态。更新组件的状态会触发重新渲染。
Collection
组件的render()
方法现在更简单、更干净:
render() {
const { collectionTweets } = this.state;
const numberOfTweetsInCollection = CollectionUtils
.getNumberOfTweetsInCollection(collectionTweets);
let htmlMarkup;
if (numberOfTweetsInCollection > 0) {
htmlMarkup = this.createHtmlMarkupStringOfTweetList();
return (
<div>
<CollectionControls
numberOfTweetsInCollection={numberOfTweetsInCollection}
htmlMarkup={htmlMarkup}
/>
<TweetList tweets={collectionTweets}/>
</div>
);
}
return (<Header text="Your collection is empty"/>);
}
我们使用CollectionUtils
模块获取集合中的大量 tweet,并将更少的属性传递给子组件:CollectionControls
和TweetList
。
CollectionControls
组件也得到了一些重大改进。让我们先看看重构版本,然后讨论更新的内容和原因:
import React, { Component } from ‘react’;
import Header from ‘./Header’;
import Button from ‘./Button’;
import CollectionRenameForm from ‘./CollectionRenameForm’;
import CollectionExportForm from ‘./CollectionExportForm’;
import CollectionActionCreators from ‘../actions/CollectionActionCreators’;
import CollectionStore from ‘../stores/CollectionStore’;
class CollectionControls extends Component {
state = {
isEditingName: false
}
getHeaderText = () => {
const { numberOfTweetsInCollection } = this.props;
let text = numberOfTweetsInCollection;
const name = CollectionStore.getCollectionName();
if (numberOfTweetsInCollection === 1) {
text = `${text} tweet in your`;
} else {
text = `${text} tweets in your`;
}
return (
<span>
{text} <strong>{name}</strong> collection
</span>
);
}
toggleEditCollectionName = () => {
this.setState(prevState => ({
isEditingName: !prevState.isEditingName
}));
}
removeAllTweetsFromCollection = () => {
CollectionActionCreators.removeAllTweetsFromCollection();
}
render() {
const { name, isEditingName } = this.state;
const onRemoveAllTweetsFromCollection = this.removeAllTweetsFromCollection;
const { htmlMarkup } = this.props;
if (isEditingName) {
return (
<CollectionRenameForm
name={name}
onCancelCollectionNameChange={this.toggleEditCollectionName}
/>
);
}
return (
<div>
<Header text={this.getHeaderText()} />
<Button
label="Rename collection"
handleClick={this.toggleEditCollectionName}
/>
<Button
label="Empty collection"
handleClick={onRemoveAllTweetsFromCollection}
/>
<CollectionExportForm htmlMarkup={htmlMarkup} />
</div>
);
}
}
export default CollectionControls;
首先,我们导入两个附加模块:
import CollectionActionCreators from ‘../actions/CollectionActionCreators’;
import CollectionStore from ‘../stores/CollectionStore’;
请注意,我们不再管理此组件中的集合名称。相反,我们从CollectionStore
模块获得:
const name = CollectionStore.getCollectionName();
然后,我们做一个关键的改变。我们将setCollectionName()
方法替换为新的removeAllTweetsFromCollection()
:
removeAllTweetsFromCollection = () => {
CollectionActionCreators.removeAllTweetsFromCollection();
}
当用户点击Empty Collection
按钮时,调用removeAllTweetsFromCollection()
方法。此用户操作触发removeAllTweetsFromCollection()
操作创建者函数,该函数创建操作并将操作分派到存储区。依次,CollectionStore
从集合中删除所有 tweet 并发出change
事件。
接下来,让我们重构CollectionRenameForm
组件。
CollectionRenameForm
为受控表单组件。这意味着其输入值存储在组件的状态中,更新该值的唯一方法是更新组件的状态。它有一个初始值,它应该从CollectionStore
得到,所以让我们来实现它。
首先,导入CollectionActionCreators
和CollectionStore
模块:
import CollectionActionCreators from ‘../actions/CollectionActionCreators’;
import CollectionStore from ‘../stores/CollectionStore’;
现在,我们需要删除其现有的constructor()
方法:
constructor(props) {
super(props);
const { name } = props;
this.state = {
inputValue: name
};
}
将上述代码替换为以下代码:
state = {
inputValue: CollectionStore.getCollectionName()
}
正如您所看到的,唯一的区别在于,现在我们从CollectionStore
中获得了初始inputValue
。
接下来我们更新handleFormSubmit()
方法:
handleFormSubmit = event => {
event.preventDefault();
const { onChangeCollectionName } = this.props;
const { inputValue: collectionName } = this.state;
onChangeCollectionName(collectionName);
}
使用以下内容更新前面的代码:
handleFormSubmit = event => {
event.preventDefault();
const { onCancelCollectionNameChange } = this.props;
const { inputValue: collectionName } = this.state;
CollectionActionCreators.setCollectionName(collectionName);
onCancelCollectionNameChange();
}
这里的重要区别在于,当用户提交表单时,我们将创建一个新操作,在集合存储中设置一个新名称:
CollectionActionCreators.setCollectionName(collectionName);
最后,我们需要在handleFormCancel()
方法中更改集合名称的来源:
handleFormCancel = event => {
event.preventDefault();
const {
name: collectionName,
onCancelCollectionNameChange
} = this.props;
this.setInputValue(collectionName);
onCancelCollectionNameChange();
}
将前面的代码更改为以下代码:
handleFormCancel = event => {
event.preventDefault();
const {
onCancelCollectionNameChange
} = this.props;
const collectionName = CollectionStore.getCollectionName();
this.setInputValue(collectionName);
onCancelCollectionNameChange();
}
再一次,我们从一个收藏商店获得了收藏名称:
const collectionName = CollectionStore.getCollectionName();
这就是我们需要在CollectionRenameForm
组件中更改的全部内容。接下来让我们重构TweetList
组件。
TweetList
组件呈现 tweet 列表。每个 tweet 都是一个Tweet
组件,用户可以点击该组件将其从集合中删除。你觉得它可以利用CollectionActionCreators
吗?
对。让我们将CollectionActionCreators
模块添加到其中:
import CollectionActionCreators from ‘../actions/CollectionActionCreators’;
然后,我们将创建removeTweetFromCollection()
回调函数,当用户单击 tweet 图像时将调用该函数:
removeTweetFromCollection = tweet => {
CollectionActionCreators.removeTweetFromCollection(tweet.id);
}
如您所见,它通过将 tweet ID 作为参数传递给removeTweetFromCollection()
函数来创建一个新操作。
最后,我们需要确保实际调用了removeTweetFromCollection()
。在getTweetElement()
方法中,找到以下行:
const { tweets, onRemoveTweetFromCollection } = this.props;
现在将其替换为以下代码:
const { tweets } = this.props;
const onRemoveTweetFromCollection = this.removeTweetFromCollection;
我们都用这个组件完成了。StreamTweet
是我们重构之旅的下一步。
StreamTweet
呈现一个推文图像,用户可以点击该图像将其添加到推文集合中。你可能已经猜到,当用户点击 tweet 图像时,我们将创建并发送一个新动作。
首先,将CollectionActionCreators
模块导入StreamTweet
组件:
import CollectionActionCreators from ‘../actions/CollectionActionCreators’;
然后,添加一个新的addTweetToCollection()
方法:
addTweetToCollection = tweet => {
CollectionActionCreators.addTweetToCollection(tweet);
}
当用户点击 tweet 图像时,应调用addTweetToCollection()
回调函数。我们来看一下render()
方法中的这一行:
<Tweet
tweet={tweet}
onImageClick={onAddTweetToCollection}
/>
用以下代码行替换前面的代码:
<Tweet
tweet={tweet}
onImageClick={this.addTweetToCollection}
/>
最后,我们需要替换以下行:
const { tweet, onAddTweetToCollection } = this.props;
改用这个:
const { tweet } = this.props;
StreamTweet
组件现在已完成。
这就是将 Flux 体系结构集成到 React 应用中所需的全部工作。如果您将 React 应用与无 Flux 应用和有 Flux 应用进行比较,您将很快发现,当 Flux 是应用的一部分时,理解应用的工作原理是多么容易。您可以在了解更多关于 Flux 的信息 https://facebook.github.io/flux/ 。
我认为现在是检查一切是否处于完美工作状态的好时机。让我们构建并运行 Snapterest!
导航到~/snapterest
并在终端窗口中运行以下命令:
npm start
确保您正在运行我们在第 2 章为您的项目安装强大工具中安装和配置的 Snapkite 引擎应用。现在在 web 浏览器中打开~/snapterest/build/index.html
文件。您应该会看到新的推文出现在左侧,一次一条。单击一条 tweet 将其添加到右侧显示的集合中。
它有用吗?检查 JavaScript 控制台是否有任何错误。没有错误?
祝贺您将 Flux 体系结构集成到我们的 React 应用中!
在本章中,我们完成了应用的重构,以使用 Flux 体系结构。您了解了如何将反应与 Flux 结合起来,以及 Flux 具有哪些优势。
在下一章中,我们将使用 Redux 库进一步简化应用的体系结构。