在本教程中,您将了解Facebook的Flux架构以及如何将其用于处理基于React的应用程序中的数据流。 我们将首先介绍Flux的基础知识并了解其发展的动机,然后我们将通过构建一个简单的虚拟钱包应用程序来实践所学到的知识。
在整个教程中,我假定您以前使用过React,但是没有使用Flux的经验。 如果您已经了解Flux的基础知识并希望获得更深入的了解,则可能会从中得到一些帮助。
如果您是React领域的新手 ,我建议您通过Envato Tuts +上的David East撰写的React入门课程。 这是一门奇妙的课程,可让您立即上手。
什么是助焊剂?
Flux主要是Facebook开发的一种应用程序体系结构概念 ,但是同一术语也指代代表正式实现的库。
Facebook推出Flux是为了解决其庞大代码库中由MVC模式引起的问题。 他们在行动引发级联更新(导致无法预测的结果和难以调试的代码)的问题上挣扎。 如果您以前使用过MVC框架,这听起来可能很熟悉,因为在大多数框架中,所有内容都趋于紧密耦合。 将观察者和双向数据绑定添加到混合中,您会感到头疼。
我的建议是避免尝试在Flux和MVC之间找到共同点。 除了增强您的困惑之外,它无济于事。 Flux尝试以不同的方式解决问题,将其与其他模式进行比较将无济于事。
项目设置
如果您想按照本教程进行操作,请首先确保已安装必需的软件。 完成后, 从我准备与本文一起准备的GitHub存储库中克隆boilerplate
分支。
这是在撰写本文时的软件要求和安装的版本:
- git:2.11
- Node.js:6.9
- NPM:3.10
- 纱:0.22
- 您选择的编辑器
该样板是我们即将建立的小型虚拟钱包应用程序的起点。 它包含Webpack配置,用于将ES6语法转换为纯JavaScript和WDS,以提供文件。 它还具有一些CSS组件样式,因此您可以直接进行编码。
为了安装所有必需的依赖项, cd
进入项目目录并运行yarn
。
在下一节中,您将在集成Flux之前设置应用程序的核心组件。 我没有将它们包括在样板中,因为我相信它将引起更多的混乱。 如果您对构建应用程序不感兴趣,则可以跳过这些步骤并跳至下一部分。
组件设置
首先在js/index.js
包含以下代码,该代码用作应用程序的入口点:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
ReactDOM.render((<App />), document.getElementById('app'));
对于主要的<App />
组件,在js/components
内部创建一个名为App.js
的新文件,并添加以下代码:
import React from 'react';
import AddNewItem from './AddNewItem';
import ItemsList from './ItemsList';
class App extends React.Component {
render() {
return (
<div className="container">
<h1 className="app-title">Flux Wallet</h1>
<AddNewItem />
<ItemsList />
</div>
);
}
}
export default App;
<App />
组件包装了其他两个组件,一个组件用于负责添加新项目的表单,另一个组件用于项目列表。 要创建<AddNewItem />
组件, AddNewItem.js
在js/components
内创建一个新文件AddNewItem.js
并添加以下代码:
import React from 'react';
class AddNewItem extends React.Component {
// Set the initial state.
constructor(props) {
super(props);
this._getFreshItem = this._getFreshItem.bind(this);
this.state = {
item: this._getFreshItem()
};
}
// Return a fresh item.
_getFreshItem() {
return {
description: '',
amount: ''
};
}
// Update the state.
_updateState(event) {
let field = event.target.name;
let value = event.target.value;
// If the amount is changed and it's not a float, return.
if (value && field === 'amount' && !value.match(/^[a-z0-9.\+\-]+$/g)) {
return;
}
this.state.item[field] = value;
this.setState({ item : this.state.item });
}
// Add a new item.
_addNewItem(event) {
// ...
}
render() {
return (
<div>
<h3 className="total-budget">$0</h3>
<form className="form-inline add-item" onSubmit={this._addNewItem.bind(this)}>
<input type="text" className="form-control description" name="description" value={this.state.item.description} placeholder="Description" onChange={this._updateState.bind(this)} />
<div className="input-group amount">
<div className="input-group-addon">$</div>
<input type="text" className="form-control" name="amount" value={this.state.item.amount} placeholder="Amount" onChange={this._updateState.bind(this)} />
</div>
<button type="submit" className="btn btn-primary add">Add</button>
</form>
</div>
)
}
}
export default AddNewItem;
该组件捆绑了一些用于在表单字段更新时更新状态的逻辑,以及一些基本的验证。 让我们通过使用以下代码为项目列表创建js/components/ItemsList.js
内部的最后一个来完成组件设置:
import React from 'react';
class ItemsList extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
};
}
render() {
let noItemsMessage;
// Show a friendly message instead if there are no items.
if (!this.state.items.length) {
noItemsMessage = (<li className="no-items">Your wallet is new!</li>);
}
return (
<ul className="items-list">
{noItemsMessage}
{this.state.items.map((itemDetails) => {
let amountType = parseFloat(itemDetails.amount) > 0 ? 'positive' : 'negative';
return (<li key={itemDetails.id}>{itemDetails.description} <span className={amountType}>{itemDetails.amount}</span></li>);
})}
</ul>
);
}
}
export default ItemsList;
而已! 完成设置项目的组件。 重要的是它们还带有免费样式。
运行yarn start
并等待线束建立。 如果将浏览器指向localhost:8080
,则应该看到没有任何功能的应用程序。
接下来,我们将介绍什么是Flux以及如何使用它为虚拟钱包应用程序添加功能。
通量构建基块
在较高的层次上,Flux分为四个主要部分:动作,调度程序,商店和视图:
- 动作描述了在应用程序中发生的动作。
- 调度程序是回调的单例注册表。 它通过将动作传递给所有订阅它的商店来充当中间人。
- 商店管理针对应用程序特定部分进行更新所需的状态和逻辑。
- 视图是普通的旧React组件。
在Flux中,所有数据都沿一个方向流动:
- 操作是使用所谓的行动创造者方便的类传递给调度员 。
- 调度程序将动作发送(正在调度)到所有订阅它的商店 。
- 最后,如果商店关心收到的(或更多)特定动作,它们会更新状态并向视图发出信号,以便重新渲染。
下面是该过程的直观表示。
动作
数据使用称为动作的普通JavaScript对象沿单一方向“通过线路”发送。 他们的工作是描述应用程序中发生的事件,并将新数据传输到商店。 每个操作必须具有一个类型和一个可选的包含数据的有效负载密钥。 动作类似于以下动作:
{
actionType: "UPDATE_TITLE",
payload: "This is a new title."
}
操作的类型必须由描述性且一致的大写字符串表示,类似于定义常量的通用约定。 它们充当唯一ID,存储将用于标识操作并做出相应响应。
一种常见的做法是在常量对象中定义所有操作类型,然后在整个应用程序中引用该对象以保持一致性。 我们的虚拟钱包将支持一个操作,该操作会将项目添加到列表中(费用和财务收益都将被视为一个项目),因此常量文件将非常小。
在js/constants
文件夹中创建一个index.js
文件,并使用以下代码创建您的第一个操作类型:
export default {
ADD_NEW_ITEM: 'ADD_NEW_ITEM'
}
使用称为操作创建者的便利类帮助程序将操作传递到调度程序,该便捷类帮助程序处理创建操作并将其发送到调度程序的简单任务。 在创建我们的动作创建者之前,让我们看看调度员首先做什么,并了解其在Flux中的作用。
调度员
调度程序用于协调动作创建者和商店之间的通信。 您可以使用它来注册商店的动作处理程序回调,也可以将动作调度到已订阅的商店。
调度程序的API很简单,只有五个可用方法:
-
register()
:注册商店的动作处理程序回调。 -
unregister()
:注销商店的回调。
-
waitFor()
:等待指定的回调先运行。
-
dispatch()
:调度一个动作。
-
isDispatching()
:检查调度程序当前是否正在调度动作。
最重要的是register()
和dispatch()
因为它们用于处理大多数核心功能。 让我们看看它们在幕后的外观和工作方式。
let _callbacks = [];
class Dispatcher {
// Register a store callback.
register(callback) {
let id = 'callback_' + _callbacks.length;
_callbacks[id] = callback;
return id;
}
// Dispatch an action.
dispatch(action) {
for (var id in _callbacks) {
_callbacks[id](action);
}
}
}
当然,这是基本要点。 register()
方法将所有回调存储在私有_callbacks
数组中,然后dispatch()
迭代并使用接收到的操作调用存储的每个回调。
为了简单起见,我们不会编写自己的调度程序。 相反,我们将使用Facebook库中提供的内容。 我鼓励您查看Facebook的GitHub存储库,并查看其实现方式。
在js/dispatcher
文件夹中,创建一个新文件index.js
并添加以下代码片段:
import { Dispatcher } from 'flux';
export default new Dispatcher();
它从flux
库中导入了调度程序(该库先前已使用yarn安装),然后导出了它的新实例。
现在让调度员做好准备,我们可以返回操作并设置应用程序的操作创建者。 在js/actions
文件夹中,创建一个名为walletActions.js
的新文件,并添加以下代码:
import Dispatcher from '../dispatcher';
import ActionTypes from '../constants';
class WalletActions {
addNewItem(item) {
// Note: This is usually a good place to do API calls.
Dispatcher.dispatch({
actionType: ActionTypes.ADD_NEW_ITEM,
payload: item
});
}
}
export default new WalletActions();
WalletActions
类将公开处理三个基本任务的addNewItem()
方法:
- 它接收一个
item
作为参数。 - 它使用分派器分派具有我们先前创建的
ADD_NEW_ITEM
操作类型的操作。
- 然后,它将接收到的
item
作为有效内容连同操作类型一起发送。
在使用该动作创建者之前,让我们看一下什么是商店,以及它们如何适合我们基于Flux的应用程序。
专卖店
我知道,我说过不应该将Flux与其他模式进行比较,但是Flux商店的方式类似于MVC中的模型。 它们的作用是处理逻辑并存储应用程序中特定顶级组件的状态。
所有的Flux存储区都必须定义一个动作处理程序方法,然后将在调度程序中注册该方法。 该回调函数主要由关于接收到的操作类型的switch语句组成。 如果满足特定的操作类型,则它将采取相应措施并更新本地状态。 最后,商店广播事件以向视图发送有关更新状态的视图,以便它们可以相应地更新。
为了广播事件,商店需要扩展事件发射器的逻辑。 有各种事件发射器库可用,但是最常见的解决方案是使用Node的事件发射器。 对于像虚拟钱包这样的简单应用程序,不需要多个商店。
在js/stores
文件夹中,创建一个名为walletStore.js
的新文件,并为我们的应用程序商店添加以下代码:
import { EventEmitter } from 'events';
import Dispatcher from '../dispatcher';
import ActionTypes from '../constants';
const CHANGE = 'CHANGE';
let _walletState = [];
class WalletStore extends EventEmitter {
constructor() {
super();
// Registers action handler with the Dispatcher.
Dispatcher.register(this._registerToActions.bind(this));
}
// Switches over the action's type when an action is dispatched.
_registerToActions(action) {
switch(action.actionType) {
case ActionTypes.ADD_NEW_ITEM:
this._addNewItem(action.payload);
break;
}
}
// Adds a new item to the list and emits a CHANGED event.
_addNewItem(item) {
item.id = _walletState.length;
_walletState.push(item);
this.emit(CHANGE);
}
// Returns the current store's state.
getAllItems() {
return _walletState;
}
// Calculate the total budget.
getTotalBudget() {
let totalBudget = 0;
_walletState.forEach((item) => {
totalBudget += parseFloat(item.amount);
});
return totalBudget;
}
// Hooks a React component's callback to the CHANGED event.
addChangeListener(callback) {
this.on(CHANGE, callback);
}
// Removes the listener from the CHANGED event.
removeChangeListener(callback) {
this.removeListener(CHANGE, callback);
}
}
export default new WalletStore();
我们首先导入存储所需的依赖关系,首先是Node的事件发射器,调度程序,然后是ActionType。 您会注意到,在它下面有一个常量CHANGE
,类似于您先前了解的动作类型。
它实际上不是一个,也不应该混淆。 当商店的数据更改时,它是用于事件触发的常量。 我们将其保留在此文件中,因为它不是应用程序其他部分中使用的值。
初始化后, WalletStore
类通过向调度程序注册_registerToAction()
回调开始。 在后台,此回调将添加到调度程序的_callbacks
数组中。
该方法具有一个单独的switch
语句,用于在分派动作时从分派器接收到的动作类型。 如果满足ADD_NEW_ITEM
操作类型,则它将运行_addNewItem()
方法并传递接收到的有效负载。
_addNewItem()
函数设置该项目的id
,将其推送到现有项目的列表,然后发出CHANGE
事件。 接下来, getAllItems()
和getTotalBudget()
方法是基本的获取方法,我们将使用它们来检索当前商店的状态和总预算。
最后两个方法addChangeListener()
和removeChangeListener()
将用于将React组件链接到WalletStore
以便在商店数据更改时得到通知。
控制器视图
使用React可以使我们将应用程序的各个部分分解为各种组件。 我们可以嵌套它们并构建有趣的层次结构,这些层次结构构成了页面中的工作元素。
在Flux中,位于链顶部的组件倾向于存储生成动作和接收新数据所需的大多数逻辑。 因此,它们称为控制器视图。 这些视图直接挂接到商店中,并且正在侦听商店更新时触发的更改事件。
发生这种情况时,控制器视图将调用setState
方法,该方法将触发render()
方法运行和更新视图,并通过prop将数据发送到子组件。 从那里,React和虚拟DOM发挥了作用,并尽可能高效地更新DOM。
我们的应用程序非常简单,并且不遵守本书中的规则。 但是,根据复杂性,大型应用程序有时可能需要多个控制器视图,以及用于应用程序主要部分的嵌套子组件。
装在一起
我们已经涵盖了Flux的主要部分,但虚拟钱包应用程序尚未完成。 在最后一部分中,我们将回顾从动作到视图的整个流程,并填写完成Flux单向数据流所需的缺少的代码。
调度动作
回到<AddNewItem />
组件,您现在可以包含WalletActions
模块,并使用它在_addNewItem()
方法中生成新的动作。
import React from 'react';
import WalletActions from '../actions/walletActions';
// …
_addNewItem(event) {
event.preventDefault();
this.state.item.description = this.state.item.description || '-';
this.state.item.amount = this.state.item.amount || '0';
WalletActions.addNewItem(this.state.item);
this.setState({ item : this._getFreshItem() });
}
// ...
现在,提交表单后,将分派一个动作,并通知所有商店(在我们的示例中为一个)有关新数据的信息。
聆听商店变化
在您的WalletStore
,当前将一项添加到列表中时,其状态会更改,并且会触发CHANGE
事件,但是没有人在监听。 让我们通过在<ItemsList />
组件内添加一个更改侦听器来结束循环。
import React from 'react';
import WalletStore from '../stores/walletStore';
class ItemsList extends React.Component {
constructor(props) {
super(props);
this.state = {
items: WalletStore.getAllItems()
};
this._onChange = this._onChange.bind(this);
}
_onChange() {
this.setState({ items: WalletStore.getAllItems() });
}
componentWillMount() {
WalletStore.addChangeListener(this._onChange);
}
componentWillUnmount() {
WalletStore.removeChangeListener(this._onChange);
}
render() {
// ...
}
}
export default ItemsList;
更新后的组件将关闭Flux的单向数据流。 请注意,我跳过了整个render()
方法,以节省一些空间。 让我们逐步介绍新功能:
-
WalletStore
模块位于顶部。 - 初始状态将更新为使用商店的状态。
- 新的
_onChange()
方法用于使用来自商店的新数据更新状态。 - 使用React的生命周期挂钩,添加和删除
_onChange()
回调作为商店的更改侦听器回调。
结论
恭喜! 您已经完成了由Flux支持的可运行虚拟钱包应用程序的构建。 您已经了解了所有Flux组件之间如何交互以及如何使用它向React应用添加结构。
当您对Flux技能有信心时,请确保还检查其他Flux实现,例如Alt , Delorean , Flummox或Fluxxor,然后看看哪种方法适合您。
在下面的评论中让我知道您的想法,我很想知道您对Flux的看法,如果您在学习本教程时遇到困难,可以提供帮助。 如果您愿意,还可以通过Twitter @hiskio与我联系 。
翻译自: https://code.tutsplus.com/tutorials/getting-started-with-the-flux-architecture-in-react--cms-28906