第 13 章:使用 Flux 构建可扩展的 UI

Flux 架构通过单向数据流和显式状态管理,帮助开发者构建高可扩展性、易维护的现代 Web UI。本章将系统介绍 Flux 的核心原理、组件分层与代码实践,并结合 Go 微服务实现云原生前端架构。

引言

在上一章中,我们初步了解了 React 框架及其单向数据流理念。本章将进一步探讨 Flux 架构模式,重点介绍如何通过 Flux 实现复杂 UI 的可扩展性和高性能,并结合 Go 微服务构建完整的前后端系统。

本章主要内容包括:

  • Flux 架构模式与核心组件解析
  • 使用 Flux 构建高流量场景下的应用程序
  • 代码分层与实践示例
  • 总结与参考文献

Flux 架构模式简介

Flux 并非具体实现或库,而是一种架构模式,类似于 MVC。Flux 基于单向数据流,强调将数据和状态从视图中分离出来,通过 dispatcher、store、action、source 等组件实现显式的数据流管理。

Flux 核心组件说明

Flux 架构包含以下核心组件:

  • dispatcher(分派器):负责将载荷广播到注册的回调函数,协调系统中的数据流。
  • store(数据仓库):存储应用状态,响应 dispatcher 派发的事件并触发视图渲染。
  • view(视图):通常为 React 组件,负责渲染 UI 并响应 store 状态变化。
  • action(动作):由用户或系统事件触发,描述状态变更的意图。
  • source(数据源):与后端服务通信,获取或提交数据,解耦 store 与实际数据。

下图展示了 Flux 架构的数据流关系:

图 13.1  Flux 中的数据流
图 13.1 Flux 中的数据流

数据始终单向流动:action → dispatcher → store → view。

Flux 架构的优势与复杂性

Flux 通过显式分层和单向数据流,提升了系统的可维护性和可扩展性。虽然组件数量较多,但每个组件职责单一,便于故障排查和功能扩展。相比“魔术”式的双向绑定框架,Flux 更适合构建复杂、可测试的云原生前端系统。

创建 Flux 应用程序

本节以僵尸爆发监控应用为例,展示如何通过 Flux 架构优化 React 应用的状态管理和组件设计。

App 组件重构示例

通过 Flux 分离状态管理,App 组件变得简洁易维护:

// filepath: App.jsx
import React from 'react';
import Outbreaks from "./Outbreaks"
import OutbreakStore from '../stores/OutbreakStore'
import OutbreakActions from '../actions/OutbreakActions'

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = OutbreakStore.getState();
  }

  componentDidMount() {
    OutbreakStore.listen(this.storeChanged);
    OutbreakStore.fetchOutbreak();
  }
  componentWillUnmount() {
    OutbreakStore.unlisten(this.storeChanged);
  }

  storeChanged = (state) => {
    this.setState(state);
  };

  addOutbreak = (event) => {
    OutbreakActions.addOutbreak();
  }

  render() {
    const outbreaks = this.state.outbreaks;
    return (
      <div>
        <button onClick={this.addOutbreak}>New Report</button>
        <Outbreaks outbreaks={outbreaks}/>
      </div>
    );
  }
}

组件挂载时监听 store 状态变化,初始数据通过 fetchOutbreak 获取,事件处理通过 action 分发。

Action 层实现示例

Action 层定义所有状态变更的意图:

// filepath: OutbreakActions.js
import alt from '../lib/alt';
import uuid from 'node-uuid';

class OutbreakActions {
  updateOutbreak(outbreak) { return outbreak; }
  fetchOutbreak() { return []; }
  deleteOutbreak(outbreak) { return outbreak; }
  changeOutbreak(outbreak) { return outbreak; }
  outbreakFailed(errorMessage) { return errorMessage; }
  addOutbreak() {
    var d = new Date();
    var datestring = ("0" + d.getDate()).slice(-2) + "-" +
      ("0" + (d.getMonth() + 1)).slice(-2) + "-" +
      d.getFullYear() + " " + ("0" + d.getHours()).slice(-2) + ":" +
      ("0" + d.getMinutes()).slice(-2);
    var newOutbreak = {
      id: uuid.v4(),
      origin: "Station Gamma",
      severity: "Yellow",
      description: "This was bad",
      date: datestring
    };
    return newOutbreak;
  }
}

export default alt.createActions(OutbreakActions);

Action 只描述意图,不直接操作状态。

Source 层实现示例

Source 层负责与后端服务或模拟数据通信:

// filepath: OutbreakSource.js
import uuid from 'node-uuid';
import OutbreakActions from '../actions/OutbreakActions'

var mockData = [
  {
    id: uuid.v4(),
    origin: "Station Gamma",
    severity: "Yellow",
    description: "This was bad",
    date: "03-04-2016 12:00"
  }
];

const OutbreakSource = {
  fetchOutbreak() {
    return {
      remote() {
        return new Promise(function (resolve, reject) {
          resolve(mockData);
        })
      },
      local() { return null; },
      success: OutbreakActions.updateOutbreak,
      error: OutbreakActions.outbreakFailed,
      loading: OutbreakActions.fetchOutbreak
    }
  }
};

export default OutbreakSource;

Source 层可灵活切换为真实 RESTful 服务或模拟数据。

Store 层实现示例

Store 层负责持久化状态并响应 action:

// filepath: OutbreakStore.js
import alt from '../lib/alt';
import OutbreakSource from '../sources/OutbreakSource'
import OutbreakActions from '../actions/OutbreakActions';

class OutbreakStore {
  constructor() {
    this.outbreaks = [];
    this.errorMessage = null;
    this.bindListeners({
      handleUpdateOutbreaks: OutbreakActions.UPDATE_OUTBREAK,
      handleFetchOutbreak: OutbreakActions.FETCH_OUTBREAK,
      handleOutbreakFailed: OutbreakActions.OUTBREAK_FAILED,
      handleChangeOutbreak: OutbreakActions.CHANGE_OUTBREAK,
      handleDeleteOutbreak: OutbreakActions.DELETE_OUTBREAK,
      handleAddOutbreak: OutbreakActions.ADD_OUTBREAK
    });
    this.exportAsync(OutbreakSource)
  }

  handleDeleteOutbreak(outbreak) {
    this.outbreaks = this.outbreaks.filter(target => target.id !== outbreak.id)
  }

  handleAddOutbreak(outbreak) {
    this.outbreaks = this.outbreaks.concat([outbreak]);
  }

  handleChangeOutbreak(outbreak) {
    // 可扩展为复杂状态变更
  }

  handleUpdateOutbreaks(outbreaks) {
    this.outbreaks = outbreaks;
    this.errorMessage = null;
  }

  handleFetchOutbreak() {
    this.outbreaks = [];
  }

  handleOutbreakFailed(errorMessage) {
    this.errorMessage = errorMessage;
  }
}

export default alt.createStore(OutbreakStore, 'OutbreakStore');

Store 通过 exportAsync 绑定 source,实现异步数据获取与状态管理。

组件层响应示例

组件通过 action 触发状态变更,store 监听并更新视图:

// filepath: OutbreakReport.jsx
import React from 'react';
import OutbreakActions from '../actions/OutbreakActions';

export default class OutbreakReport extends React.Component {
  constructor(props) {
    super(props);
    this.state = { editing: false };
  }

  render() {
    if (this.state.editing) {
      return this.renderEdit();
    }
    return this.renderOutbreakReport();
  }

  renderEdit = () => (
    <div>
      <div className="date">{this.props.outbreak.date}</div>
      <div className="origin">{this.props.outbreak.origin}</div>
      <div className="severity">{this.props.outbreak.severity}</div>
      <div className="description">
        <input
          type="text"
          autoFocus={true}
          defaultValue={this.props.outbreak.description}
          onBlur={this.finishEdit}
          onKeyPress={this.checkEnter}
        />
      </div>
    </div>
  );

  edit = () => {
    this.setState({ editing: true });
  };

  checkEnter = (e) => {
    if (e.key === 'Enter') {
      this.finishEdit(e);
    }
  };

  finishEdit = (e) => {
    const value = e.target.value;
    this.setState({ editing: false });
    if (!value.trim()) {
      return;
    }
    this.props.outbreak.description = value;
    OutbreakActions.changeOutbreak(this.props.outbreak);
  }

  onDelete = (e) => {
    OutbreakActions.deleteOutbreak(this.props.outbreak);
  }

  renderOutbreakReport = () => (
    <div>
      <div className="date">{this.props.outbreak.date}</div>
      <div className="origin">{this.props.outbreak.origin}</div>
      <div className="severity">{this.props.outbreak.severity}</div>
      <div className="description" onClick={this.edit}>
        {this.props.outbreak.description}
      </div>
      {this.renderDelete()}
    </div>
  );

  renderDelete = () => (
    <button className="delete-outbreak" onClick={this.onDelete}>X</button>
  );
}

组件交互通过 action 触发,store 统一管理状态,提升可维护性和扩展性。

构建与运行 Flux 应用

安装依赖并启动开发服务器:

npm install
npm run watch
# 访问 http://localhost:8080

构建生产版本并结合 Go 服务运行:

npm run build
go build
./flux-zombieoutbreak
# [negroni] listening on :8100

也可通过 Docker 镜像快速部署:

docker run -p 8100:8100 -e WEBROOT=/pipeline/sourcecloudnativego/flux-zombieoutbreak

总结

本章系统介绍了 Flux 架构的核心原理、组件分层与代码实践,并结合 Go 微服务实现云原生前端架构。通过单向数据流和显式状态管理,开发者可高效构建可扩展、易维护的现代 Web UI。建议结合实际项目,深入理解 Flux 与后端服务的集成模式,为后续复杂系统开发打下坚实基础。

参考文献

文章导航

独立页面

这是书籍中的独立页面。

书籍首页

评论区