首页
关于
翻译
留言
统计
搜索
1
以太坊简介
757 阅读
2
搭建 OpenAI 代理
716 阅读
3
第 4 章 创建您的第一个 React 组件
567 阅读
4
如何读懂编译后的 JavaScript 代码
545 阅读
5
第 9 章 使用 Jest 测试 React 应用
487 阅读
JavaScript
TypeScript
后端
Web
移动
运维
杂项
登录
Search
标签搜索
React
翻译
Vue
组件
Angular
工程化
库
Hook
框架
优化
路由
Node.js
Flash
部署
算法
可视化
Debug
测试
兼容
Web3
Flying
累计撰写
267
篇文章
累计收到
2
条评论
首页
栏目
JavaScript
TypeScript
后端
Web
移动
运维
杂项
页面
关于
翻译
留言
统计
搜索到
55
篇
React
相关的结果
2019-01-05
使用 React 高阶组件
高阶组件是 React 中一个很重要且较复杂的概念,主要用来实现组件逻辑的抽象和复用,在很多第三方库(如 Redux)中都被使用到。即使开发一般的业务项目,如果能合理地使用高阶组件,也能显著提高项目的代码质量。本章将详细介绍高阶组件的概念及应用。
2019年01月05日
228 阅读
0 评论
3 点赞
2018-12-25
第 12 章 使用 Redux 完善 Flux 应用
上一章带你实现了一个基于 Flux 架构的完整的 React 应用。在本章中,你将对这个应用进行一些修改,以便它使用 Redux 库来实现 Flux 架构。本章的组织结构如下:
2018年12月25日
296 阅读
0 评论
11 点赞
2018-12-10
第 11 章 准备使用 Flux 轻松维护 React 应用
我们决定在我们的 React 应用中实现 Flux 架构的原因是我们希望有一个更容易维护的数据流。上一章实现了 AppDispatcher、TweetActionCreators 和 TweetStore。让我们快速记住它们的用途:TweetActionCreators:创建和调度 actionAppDispatcher:将所有的 action 调度到所有 storesTweetStore:用于存储和管理应用数据数据流中唯一缺失的部分如下:使用 TweetActionCreators 创建 action 并启动数据流使用 TweetStore 获取数据这里有几个重要的问题要问:数据流在我们应用的哪里开始?我们的数据是什么?如果我们回答了这些问题,我们将知道从哪里开始重构我们的应用以适应 Flux 架构。Snapterest 允许用户接收和收集最新的推文。我们的应用唯一关心的数据是 tweets。因此,我们的数据流从接收新推文开始。应用的哪个部分负责接收新推文?你可能还记得我们的 Stream 组件有以下 componentDidMount()方法:componentDidMount() { SnapkiteStreamClient.initializeStream(this.handleNewTweet); }是的,目前,我们在渲染 Stream 组件后初始化了一个新的推文流。等等,你可能会问:“我们不是知道 React 组件应该只关心渲染用户界面吗?”你是正确的。不幸的是,目前,Stream 组件负责两件不同的事情:渲染 StreamTweet 组件初始化数据流显然,这是未来潜在的维护问题。让我们借助 Flux 来解耦这两个不同的关注点。解耦 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() 函数来初始化一个新的推文流,就像在 Stream 组件的 componentDidMount() 方法中一样。除了一个关键的区别:每当 SnapkiteStreamClient 收到一条新消息时,它都会调用 TweetActionCreators.receiveTweet 方法将一条新消息作为参数传递给它:SnapkiteStreamClient.initializeStream(receiveTweet);记住,receiveTweet 函数接受的参数是 tweet:function receiveTweet(tweet) { // ... create and dispatch 'receive_tweet' action }然后,这条消息将作为 receiveTweet() 函数创建的新 action 对象的属性分发。然后,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 函数中放入一个日志语句。例如,运行以下代码: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命令。然后,在浏览器中打开 ~/snapterest/build/index.html ,你会看到下面的文本呈现在页面上:I am about to learn the essentials of React.js.现在打开 JavaScript 控制台,你会看到如下输出:[Snapkite Stream Client] Socket connectedI've received a new tweet and now will dispatch it together with a new action.应用收到的每一条新消息都会打印出这条日志消息。即使我们没有渲染任何 React 组件,我们的 Flux 架构仍然存在:我们的应用收到一条新的推文。它创建并调度一个新的 action。没有 store 注册到 dispatcher,所以没有人接收新的 action;因此,什么也没有发生。现在你可以清楚地看到 React 和 Flux 是两个完全不依赖的东西。然而,我们确实想渲染我们的 React 组件。毕竟,我们在前 10 章中已经花了很多精力来创建它们!要做到这一点,我们需要将 TweetStore 存储到 action。你能猜到我们应该在哪里使用它吗?这里有一个提示:在一个 React 组件中,它需要一条 tweet 来渲染自己——我们的旧 Stream 组件。重构 Stream 组件现在有了 Flux 架构,我们将重新思考我们的 React 组件如何获取它们需要渲染的数据。如你所知,一个 React 组件通常有两个数据源:调用另一个库,例如,调用 jQuery.ajax() 方法,或者在我们的例子中,SnapkiteStreamClient.initializeStream()通过 props 对象从父组件接收数据我们希望 React 组件不使用任何外部库来接收数据。相反,从现在开始,他们将从 store 获取相同的数据。记住这个计划,让我们重构我们的 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 组件获取初始消息的方式。让我们更新它的初始状态:state = { tweet: TweetStore.getTweet(), };代码方面,这可能看起来只是一个小变化,但却是一个重大的架构改进。我们现在使用 getTweet() 方法从 TweetStore 中获取数据。在上一章中,我们讨论了 store 如何在 Flux 中暴露公共方法,以便应用的其他部分从它们中获取数据。getTweet() 方法是这些公共方法中的一个例子,它们被称为 _getter_。你可以 store 中获取数据,但你不能像那样直接在 store 中设置数据。store 没有公共的 setter 方法。它们在设计时特意考虑了这种限制,以便当你使用 Flux 编写应用时,数据只能朝一个方向流动。当你需要维护你的 Flux 应用时,这将给你带来很多好处。现在我们知道了如何获取初始的推文,但如何获取后面所有的新推文呢?我们可以创建一个定时器,并反复调用 TweetStore.getTweet(); 然而,这并不是最好的解决方案,因为它假设我们不知道 TweetStore 何时更新了一条新的推文。然而,我们确实知道这一点。怎么做?在上一章中,我们实现了 TweetStore 对象的 addChangeListener() 方法,如下所示:addChangeListener(callback) { this.on('change', callback); }我们也实现了 removeChangeListener() 方法:removeChangeListener(callback) { this.removeListener('change', callback); }没错,我们可以让 TweetStore 告诉我们它什么时候改变了数据。为此,我们需要调用它的 addChangeListener()方法,并传入一个回调函数,TweetStore 会在收到每条新消息时调用该函数。问题是在 Stream 组件中,我们应该在哪里调用 TweetStore.addChangeListener() 方法呢?由于我们只需要为每个组件的生命周期添加一次 change 事件监听器到 TweetStore,因此 componentDidMount() 是一个完美的候选。将下面的 componentDidMount() 方法添加到 Stream 组件中:componentDidMount() { TweetStore.addChangeListener(this.onTweetChange); }我们在这里将 change 事件监听器 this.tweetchange 添加到 TweetStore。现在当 TweetStore 改变它的数据时,它会触发 this.onTweetChange 方法。我们很快就会创建这个方法。不要忘记,在卸载 React 组件之前,我们需要删除所有事件监听器。为此,将以下 componentWillUnmount() 方法添加到 Stream 组件:componentWillUnmount() { TweetStore.removeChangeListener(this.onTweetChange); }删除事件监听器与添加它非常相似。只需调用 TweetStore.removeChangeListener() 方法并传入 this.onTweetChange 方法作为参数。现在是时候在 Stream 组件中创建 onTweetChange 方法了:onTweetChange = () => { this.setState({ tweet: TweetStore.getTweet(), }); };如你所见,它使用 TweetStore.gettweet() 方法更新了组件的状态,并将一条新消息存储在 TweetStore 中。我们需要在 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() 方法从 TweetStore 获取的最新推文。然后,我们在 TweetStore 中监听变化。当 TweetStore 改变它的推文时,我们使用 getTweet() 方法将组件的状态更新为从 TweetStore 获取的最新推文。当组件即将卸载时,我们停止监听 TweetStore 中的变化。这就是 React 组件与 Flux store 交互的方式。在继续使我们的应用的其余部分 Flux 增强之前,让我们看看当前的数据流:app.js:接收新的推文并为每条推文调用 TweetActionCreatorsTweetActionCreators:创建并派发一个带有新推文的新 actionAppDispatcher:它将所有的 action 调度到所有的 storesTweetStore:它注册 dispatcher,并在 dispatcher 接收到每个新 action 时派发 change 事件Stream:监听 TweetStore 中的变化,从 TweetStore 获取一条新的推文,用一条新的推文 更新状态,并重新渲染你能看到我们现在如何扩展 React 组件、action 创建者和 store 的数量,同时仍然能够维护 Snapterest 吗?使用 Flux,它将永远是单向的数据流。不管我们要实现多少新特性,心智模型都是一样的。从长远来看,当我们需要维护我们的应用时,我们将受益匪浅。我是否提到过我们将在应用中更多地使用 Flux 呢?接下来,让我们来做这个。创建 CollectionStoreSnapterest 不仅存储最新的推文,还存储用户创建的推文集合。让我们用 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 是一个更大的 store,但它具有与 TweetStore 相同的结构。首先,我们导入依赖项并给 CHANGE_EVENT 变量指定一个 change 事件名称:import AppDispatcher from '../dispatcher/AppDispatcher'; import { EventEmitter } from 'events'; const CHANGE_EVENT = 'change';然后,定义数据和 4 个修改这些数据的私有方法: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; }如你所见,我们将推文集合存储在一个对象中,该对象最初为空,我们还将集合名称最初设置为 new。然后,创建三个私有函数来修改 collectionTweets:addTweetToCollection():顾名思义,它将 tweet 对象添加到 collectionTweets 对象中removeTweetFromCollection():从 collectionTweets 对象中删除 tweet 对象removeAllTweetsFromCollection():通过将 collectionTweets 设置为空对象,删除其所有 tweet 对象然后,我们定义了一个私有函数 setCollectionName,它会修改 collectionName,把现有的集合名改成新的集合名。这些函数被认为是私有的,因为它们在 CollectionStore 模块之外不可访问。例如,你不能在任何其他模块中这样访问它们:CollectionStore.setCollectionName('impossible');如前文所述,这样做是为了在应用中强制执行单向数据流。我们创建了 emitChange() 方法来触发 change 事件。然后创建 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 调度 action,但与 CollectionStore 模块中的 TweetStore 不同,我们可以处理多个 actions。实际上,我们可以处理与消息集合相关的 4 个 actions。add_tweet_to_collection:向集合中添加一条推文remove_tweet_from_collection:从集合中删除一条推文remove_all_tweets_from_collection:从集合中删除所有推文set_collection_name:设置集合名称请记住,所有的数据存储都会接收到所有的 actions,所以 CollectionStore 也会接收到 receive_tweet action,但我们在这个数据存储中忽略了它,就像 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 创建器函数。创建 CollectionActionCreators转到 ~/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 中处理的每个 action,我们都有一个 action 创建器函数:addTweetToCollection():创建并调度 add_tweet_to_ collection action,包含一条新的推文removeTweetFromCollection():创建并调度 remove_ tweet_from_collection action,该 action 的参数是必须从集合中删除的消息 IDremoveAllTweetsFromCollection():创建和调度 remove_all_tweets_from_collection actionsetCollectionName():创建并调度 add_tweet_to_ collection action,并使用新的集合名称现在当我们创建了 CollectionStore 和 CollectionActionCreators 模块后,我们可以采用 Flux 架构开始重构 React 组件。重构 Application 组件从哪里开始重构 React 组件呢?让我们从组件层次结构中最顶层的 React 组件——应用开始。目前,我们的 Application 组件存储和管理消息集合。让我们删除此功能,因为它现在由集合存储管理。删除 constructor()、addTweetToCollection()、removeTweetFromCollection () 以及 Application 组件的 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 组件只有 render()方法,用于渲染流和 Collection 组件。由于它不再管理推文的集合,我们也不需要向流和 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 组件管理最新的推文,Collection 组件管理推文集合,而 Application 组件不再需要管理任何东西,所以它变成了一个容器组件,用额外的 HTML 标记封装了 Stream 和 Collection 组件。事实上,你可能已经注意到,我们当前版本的 Application 组件很适合作为一个函数式 React 组件: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 组件。用下面的代码替换现有的 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 章使用 Jest 测试 React 应用时创建了 CollectionUtils 模块,本章我们将使用它。CollectionStore 是我们获取数据的地方。接下来,你应该能够发现这四个方法的熟悉模式:在初始状态下,我们将推文集合设置为当时存储在 CollectionStore 中的内容。你可能还记得,CollectionStore 提供了 getCollectionTweets() 方法来从中获取数据。在 componentDidMount() 方法中,我们添加 change 事件监听器。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 模块获取集合中的一些推文,传递给子组件 CollectionControls 和 TweetList 的属性更少。重构 CollectionControls 组件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() action 创建函数,该函数创建并调度该 action 到 store。接着,CollectionStore 从集合中删除所有消息并派发 change 事件。接下来,让我们重构 CollectionRenameForm 组件。重构 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(); };这里的重要区别在于,当用户提交表单时,我们将创建一个新的 action,在集合存储中设置一个新的名称: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 组件TweetList 组件渲染一个推文列表。每条推文都是一个 Tweet 组件,用户可以单击它将其从集合中删除。你觉得它可以使用 CollectionActionCreators 吗?是可以的。让我们把 CollectionActionCreators 模块添加进去:import CollectionActionCreators from '../actions/CollectionActionCreators';然后,创建 removeTweetFromCollection() 回调函数,当用户点击推文图片时调用该函数:removeTweetFromCollection = (tweet) => { CollectionActionCreators.removeTweetFromCollection(tweet.id); };如你所见,它使用 removeTweetFromCollection() 函数创建了一个新 action,将消息 ID 作为参数传递给它。最后,我们需要确保 removeTweetFromCollection()真的被调用了。在 getTweetElement()方法中,找到下面这行代码:const { tweets, onRemoveTweetFromCollection } = this.props;现在用下面的代码替换它:const { tweets } = this.props; const onRemoveTweetFromCollection = this.removeTweetFromCollection;我们已经完成了这个组件。重构之旅的下一站是 StreamTweet 组件。重构 StreamTweet 组件StreamTweet 渲染一个推文图像,用户可以点击它将其添加到推文集合中。你可能已经猜到了,我们将在用户单击推文图像时创建并调度一个新的 action。首先,将 CollectionActionCreators 模块导入 StreamTweet 组件:import CollectionActionCreators from '../actions/CollectionActionCreators';然后,新增 addTweetToCollection() 方法:addTweetToCollection = (tweet) => { CollectionActionCreators.addTweetToCollection(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 应用中所需要做的所有工作。如果你比较一下没有使用 Flux 和使用 Flux 的 React 应用,你会很快发现使用 Flux 时,理解你的应用如何工作的是多么容易。你可以访问 https://facebook.github.io/flux了解更多关于 Flux 的信息。我认为现在是检查一切是否正常的好时机。让我们构建并运行 Snapterest!转到 ~/snapterest,在终端窗口中执行以下命令:npm start请确保运行的是我们在第 2 章中为你的项目安装强大的工具的 Snapkite 引擎应用。现在在浏览器中打开 ~/snapterest/build/index.html 文件。你将看到新推文出现在左侧,每次一条。点击一条推文,将其添加到右侧的集合中。它有效果吗?检查 JavaScript 控制台是否有错误。没有错误吗?恭喜你已经将 Flux 架构集成到我们的 React 应用中!总结在本章中,我们使用 Flux 架构完成了应用的重构。你了解了如何将 React 与 Flux 结合,以及 Flux 所提供的优势。下一章,我们将使用 Redux 库进一步简化应用的架构。
2018年12月10日
333 阅读
0 评论
9 点赞
2018-11-29
第 10 章 使用 Flux 增强您的 React 架构
构建 web 应用的过程有一个特性,它在某种程度上反映了生命本身的演变过程——它永远不会结束。与构建桥梁不同,构建 web 应用没有表示开发过程结束的自然状态。你或你的团队决定何时停止开发过程并发布已经构建的内容。
2018年11月29日
184 阅读
0 评论
9 点赞
2018-11-09
第 9 章 使用 Jest 测试 React 应用
到目前为止,你已经创建了许多 React 组件。其中一些非常简单,但也有一些足够复杂。有了这些构建后,你可能已经获得了一定的信心,这让你相信无论用户界面有多复杂,你都可以使用 React 构建它,而不会有任何重大缺陷。这是一个很好的信心。毕竟,这就是我们投入时间学习 React 的原因。然而,许多自信的 React 开发人员都陷入了不编写单元测试的陷阱。
2018年11月09日
487 阅读
0 评论
9 点赞
1
...
7
8
9
...
11