23、利用 Flux 库构建应用

利用 Flux 库构建应用

1. 异步操作的分发

异步操作创建器的实现颇具挑战性。因为通常需要让存储(stores)知晓异步操作即将发生,以便更新用户界面(UI)。例如,点击发送 AJAX 请求的按钮时,可能要在实际发送请求前禁用该按钮,防止重复请求。在 Flux 中,实现此功能的唯一方法是分发操作(dispatch an action),因为一切都是单向的。

某些库在一定程度上能提供帮助。例如,可将请求前操作和成功/错误响应操作抽象成更易用的形式,这是常见模式。但即便如此,仍存在一些问题,比如组装请求以获取给定操作所需的所有数据、同步响应并将其传递给存储,以便存储将其转换为视图所需的数据。

或许将异步问题置于 Flux 范围之外是个不错的选择。例如,Facebook 推出的 GraphQL 语言,能简化从后端服务构建复杂数据的过程,且仅返回存储实际需要的数据。这一切都在一次响应中完成,还能节省带宽和延迟。不过,这种方法并非适用于所有人,Flux 实现者需自行选择处理异步性的方式,只要客户端的单向数据流保持完整即可。

2. 存储的分区

在 Flux 架构中,存储分区不当可能是面临的最大设计风险之一。通常情况是,最初存储大致平衡,但随着系统发展,所有新功能都集中到一个存储中,而其他存储的职责变得不明确,导致存储失衡。持有大部分应用状态的存储会变得过于复杂,难以维护。

存储分区的另一个潜在问题是过于细化。虽然单个存储管理的状态足够简单,但复杂之处在于所有这些存储之间的依赖关系。即使依赖关系不多,考虑的存储越多,在思考问题时就越难记住足够的状态。当相关状态集中在一处时,更容易预测会发生什么。

一些 Flux 库,如 Redux,采取了激进的方法,只允许使用单个存储,从而消除了所有可能的混淆源。实际上,这避免了存储分区等设计问题。后续会看到,Redux 使用归约函数(reducer functions)来转换单个存储的状态。

3. 使用 Alt.js

3.1 Alt.js 的核心思想

Alt.js 是一个 Flux 库,它为我们实现了许多样板代码。它完全遵循 Flux 的概念和模式,让我们能从应用的角度关注架构,而不必担心操作常量和 switch 语句。

Alt.js 作为一个用于生产应用的 Flux 库,有以下几个目标:
- 合规性 :Alt.js 并非借鉴 Flux 的思想,而是真正为 Flux 系统设计的。例如,存储、操作和视图的概念都适用,并且严格遵循 Flux 架构的原则,强制执行同步更新回合和单向数据流。
- 自动化样板代码 :Alt.js 能很好地处理一些与实现 Flux 相关的繁琐编程任务,如自动创建操作创建函数和操作常量,还会为我们处理存储操作处理方法,减少对长 switch 语句的需求。
- 无需调度器 :我们的代码无需与调度器交互。调用操作创建函数时,操作会在幕后自动分发给所有存储。存储之间的依赖管理直接在存储内部处理。

3.2 创建存储

下面创建一个简单的应用,为用户显示两个列表:一个是待办事项列表,另一个是已完成事项列表。我们将使用两个存储,每个列表对应一个存储。

3.2.1 Todo 存储
import alt from '../alt';
import actions from '../actions';
class Todo {
  constructor() {
    // 用于创建新待办事项的输入元素的状态
    this.inputValue = '';
    // 初始待办事项列表
    this.todos = [
      { title: 'Build this thing' },
      { title: 'Build that thing' },
      { title: 'Build all the things' }
    ];
    // 设置当相应操作分发时要调用的处理方法
    this.bindListeners({
      createTodo: actions.CREATE_TODO,
      removeTodo: actions.REMOVE_TODO,
      updateInputValue: actions.UPDATE_INPUT_VALUE
    });
  }
  // 使用操作的 "payload" 作为标题创建新的待办事项
  createTodo(payload) {
    this.todos.push({ title: payload });
  }
  // 根据索引(作为操作的 payload 传入)移除待办事项
  removeTodo(payload) {
    this.todos.splice(payload, 1);
  }
  // 更新用户当前在待办事项输入框中输入的值
  updateInputValue(payload) {
    this.inputValue = payload;
  }
}
// "createStore()" 函数将我们的存储类与所有相关的操作分发机制挂钩,返回存储的实例
export default alt.createStore(Todo, 'Todo');

这里的状态是类的任何实例变量,在这个例子中,是 inputValue 字符串和 todos 数组。 bindListeners() 方法用于将操作映射到方法, createStore() 函数实例化存储类并连接分发机制。

3.2.2 Done 存储
import alt from '../alt';
import actions from '../actions';
import todo from './todo';
class Done {
  constructor() {
    // "done" 状态保存已完成事项的数组
    this.done = [];
    // 绑定此存储的唯一监听器
    this.bindListeners({
      createDone: actions.CREATE_DONE
    });
  }
  // 此操作的 payload 是 "todo" 存储中某个事项的索引。当该事项被点击时调用此方法,将该事项添加到 "done" 数组中
  // 注意,此操作处理方法不会改变 "todo" 存储的状态,因为这是不允许的
  createDone(payload) {
    const { todos } = todo.getState();
    this.done.splice(0, 0, todos[payload]);
  }
}
// 创建存储实例,并将其与 Alt 的分发机制挂钩
export default alt.createStore(Done, 'Done');

这个存储在将事项标记为已完成时,会使用 Todo 存储复制项目数据,但不会改变 Todo 存储,以免违反单向数据流。

这些存储类不是事件发射器,状态改变时不会显式发出任何信号。例如,添加待办事项时视图如何知道状态已改变?由于 createTodo() 方法会自动调用,方法执行完成后,通知机制也会自动触发。

3.3 声明操作创建函数

Alt 可以生成我们需要的函数以及存储中 bindListeners() 调用使用的常量。以下是操作模块的示例:

import alt from './alt';
// 导出一个包含接受 payload 参数的函数的对象。这些是操作创建函数。
// 还会根据传递给 "generateActions()" 的名称创建操作常量
export default alt.generateActions(
  'createTodo',
  'createDone',
  'removeTodo',
  'updateInputValue'
);

这将导出一个包含操作创建函数的对象,函数名与传递给 generateActions() 的字符串相同,并生成存储使用的操作常量。由于操作创建函数非常相似, generateActions() 非常实用,减少了大量样板代码。不过,对于涉及异步操作的更复杂情况,可能需要更多代码。

3.4 监听状态变化

使用 AltContainer React 组件可以将存储数据传递给其他 React 组件。以下是应用主模块的示例:

import React from 'react';
import { render } from 'react-dom';
import AltContainer from 'alt-container';
import todo from './stores/todo';
import done from './stores/done';
import TodoList from './views/todo-list';
import DoneList from './views/done-list';
// 渲染 "AltContainer" 组件。这里将存储与视图绑定在一起
// "TodoList" 和 "DoneList" 组件是 "AltContainer" 的子组件,因此它们将 "todo" 和 "done" 存储作为属性接收
render(
  <AltContainer stores={{ todo, done }}>
    <TodoList/>
    <DoneList/>
  </AltContainer>,
  document.getElementById('app')
);

AltContainer 组件接受一个 stores 属性,它会监听每个存储,并在任何存储的状态改变时重新渲染其子组件。这是让视图监听存储所需的唯一设置,无需到处手动调用 on() listen() 方法。

3.5 渲染视图和分发操作

3.5.1 TodoList 组件
import React from 'react';
import { Component } from 'react';
import actions from '../actions';
export default class TodoList extends Component {
    render() {
      // 从 "todo" 存储中获取要渲染的相关状态
      const { todos, inputValue } = this.props.todo;
      // 渲染用于输入新待办事项的输入框和当前待办事项列表
      // 用户输入并按下回车键时,创建新的待办事项
      // 用户点击待办事项时,将其移动到 "done" 存储中
      return (
        <div>
          <h3>TODO</h3>
          <div>
            <input
              value={inputValue}
              placeholder="TODO..."
              onKeyUp={this.onKeyUp}
              onChange={this.onChange}
              autoFocus
            />
          </div>
          <ul>
            {todos.map(({ title }, i) =>
              <li key={i}>
                <a
                  href="#"
                  onClick={this.onClick.bind(null, i)}
                >{title}</a>
              </li>
            )}
          </ul>
        </div>
      );
    }
    // 点击活动的待办事项时,将其索引作为 payload 传递给 "createDone()" 操作,然后传递给 "removeTodo()" 操作
    onClick(key) {
      actions.createDone(key);
      actions.removeTodo(key);
    }
    // 如果用户输入了文本并按下回车键,使用 "createTodo()" 操作创建新事项,然后使用 "updateInputValue()" 操作清空输入框
    onKeyUp(e) {
      const { value } = e.target;
      if (e.which === 13 && value) {
        actions.createTodo(value);
        actions.updateInputValue('');
      }
    }
    // 文本输入值改变时,更新存储
    onChange(e) {
      actions.updateInputValue(e.target.value);
    }
}

这里不能直接在按下回车键时清空 e.target.value ,因为这违背了 Flux 将状态保存在存储中的原则。如果应用的其他部分需要知道文本输入值,只需依赖 Todo 存储即可。

3.5.2 DoneList 组件
import React from 'react';
import { Component } from 'react';
export default class DoneList extends Component {
  render() {
    // 从 "done" 存储中获取唯一需要的状态 "done" 数组
    const { done } = this.props.done;
    // 设置已完成事项的样式为删除线
    const itemStyle = {
      textDecoration: 'line-through'
    }
    // 渲染已完成事项列表,并将 "itemStyle" 应用到每个事项
    return (
      <div>
        <h3>DONE</h3>
        <ul>
          {done.map(({ title }) =>
            <li style={itemStyle}>{title}</li>
          )}
        </ul>
      </div>
    );
  }
}

这个组件比 TodoList 组件简单,因为没有事件处理。

以下是 Alt.js 应用的整体流程图:

graph LR
    A[用户操作] -->|触发操作| B[操作创建函数]
    B -->|分发操作| C[存储]
    C -->|状态更新| D[视图]
    D -->|用户交互| A

4. 使用 Redux

4.1 Redux 的核心思想

Redux 是用于实现 Flux 架构的库,与 Alt.js 不同,它并不追求完全符合 Flux 规范。Redux 的目标是借鉴 Flux 的重要思想,摒弃繁琐的部分。尽管它没有按照官方文档中指定的方式实现 Flux 组件,但如今已成为 React 架构的首选解决方案,证明了简单性总是优于高级功能。

Redux 有以下核心思想:
- 无需调度器 :和 Alt.js 一样,Redux 从其 API 中去除了调度器的概念。这些 Flux 库不暴露调度器组件,说明 Flux 只是一组思想和模式,而非具体实现。Alt 和 Redux 都能分发操作,只是不需要调度器来完成。
- 单一存储 :Redux 摒弃了 Flux 架构需要多个存储的观念,使用单个存储来保存整个应用的状态。乍一看,这可能会让人觉得存储会变得过大、难以理解,但多个存储也可能出现同样的问题,唯一的区别是应用状态被分割到不同的模块中。
- 直接分发到存储 :当只需要关注一个存储时,可以做出一些设计上的妥协,比如将存储和调度器视为同一概念。Redux 正是这样做的,它直接将操作分发到存储。
- 纯归约函数 :多个 Flux 存储的思想是将应用状态分割成几个逻辑上分离的领域。使用 Redux 也能实现这一点,不同之处在于,Redux 使用归约函数将状态分割成不同领域。这些函数负责在操作分发时转换存储的状态,它们是纯函数,因为它们返回新数据,避免引入任何副作用。

4.2 归约函数和存储

下面使用 Redux 实现与 Alt.js 相同的简单待办事项应用。两个库有很多重叠之处,特别是 React 组件本身,不需要做太多更改。Redux 与 Alt 和一般 Flux 的不同之处在于它使用单个存储和改变存储状态的归约函数。

首先创建 Redux 存储的初始状态模块:

import Immutable from 'immutable';
// Redux 存储的初始状态
// 应用状态的 "形状" 包括两个领域 - "Todo" 和 "Done"。每个领域都是 Immutable.js 结构
const initialState = {
  Todo: Immutable.fromJS({
    inputValue: '',
    todos: [
      { title: 'Build this thing' },
      { title: 'Build that thing' },
      { title: 'Build all the things' }
    ]
  }),
  Done: Immutable.fromJS({
    done: []
  })
};
export default initialState;

状态是一个简单的 JavaScript 对象,单个存储通过 Todo Done 两个主要属性进行组织,类似于多个存储,但它们在一个对象中。每个存储属性都是 Immutable.js 数据结构,因为需要将传递给归约函数的状态视为不可变的,这个库能轻松实现不变性。

4.2.1 Todo 归约函数
import Immutable from 'immutable';
import initialState from '../initial-state';
import {
  UPDATE_INPUT_VALUE,
  CREATE_TODO,
  REMOVE_TODO
} from '../constants';
export default function Todo(state = initialState, action) {
  switch (action.type) {
    // 当 "UPDATE_INPUT_VALUE" 操作分发时,设置 Immutable.Map 的 "inputValue" 键
    case UPDATE_INPUT_VALUE:
      return state.set('inputValue', action.payload);
    // 当 "CREATE_TODO" 操作分发时,将新事项添加到 Immutable.List 的末尾
    case CREATE_TODO:
      return state.set('todos',
        state.get('todos').push(Immutable.Map({
          title: action.payload
        }))
      );
    // 当 "REMOVE_TODO" 操作分发时,从 Immutable.List 中删除给定索引的事项
    case REMOVE_TODO:
      return state.set('todos',
        state.get('todos').delete(action.payload));
    default:
      return state;
  }
}

这里使用的 switch 语句模式很熟悉,实际上这个函数就像一个存储,但有两个主要区别:一是它是一个函数而不是类,这意味着不是设置状态属性值,而是返回新状态;二是 Redux 处理监听存储和调用归约函数的机制,使用类时需要自己编写很多这样的代码。

4.2.2 Done 归约函数
import Immutable from 'immutable';
import initialState from '../initial-state';
import { CREATE_DONE } from '../constants';
export default function Done(state = initialState, action) {
  switch (action.type) {
    // 当 "CREATE_DONE" 操作分发时,将新事项插入到 Immutable.List 的开头
    case CREATE_DONE:
      return state.set('done',
        state.get('done')
          .insert(0, Immutable.Map(action.payload))
      );
    // 无需操作,原样返回状态
    default:
      return state;
  }
}

这些归约函数不能修改 state 参数,因此使用 Immutable.js 库,方便通过创建新数据来转换现有状态。虽然不是必须使用 Immutable.js 来转换 Redux 存储状态,但它有助于简化代码。

以下是 Redux 应用的整体流程图:

graph LR
    A[用户操作] -->|触发操作| B[操作创建函数]
    B -->|分发操作| C[单一存储]
    C -->|归约函数转换状态| C
    C -->|状态更新| D[视图]
    D -->|用户交互| A

综上所述,Alt.js 和 Redux 都是优秀的 Flux 库,它们各有特点。Alt.js 严格遵循 Flux 规范,能帮助我们减少样板代码;Redux 则采取了更简洁的方式,使用单一存储和纯归约函数。在选择使用哪个库时,需要根据项目的具体需求和团队的技术栈来决定。

5. Alt.js 与 Redux 的对比

5.1 架构设计对比

对比项 Alt.js Redux
存储设计 支持多个存储,每个存储管理特定部分的状态,可能会存在存储间依赖关系复杂的问题 仅使用单个存储来管理整个应用的状态,避免了多存储的依赖问题,但可能使存储变得庞大
调度器 隐藏了调度器的概念,在幕后自动处理操作分发到存储的过程 去除了调度器的概念,直接将操作分发到存储
状态管理 状态是存储类的实例变量,通过方法直接修改 状态通过纯归约函数进行转换,不直接修改原状态,而是返回新状态

5.2 代码实现对比

5.2.1 存储定义
  • Alt.js :通过类定义存储,使用 bindListeners 方法将操作映射到处理方法, createStore 函数实例化存储并连接分发机制。示例代码如下:
import alt from '../alt';
import actions from '../actions';
class Todo {
  constructor() {
    this.inputValue = '';
    this.todos = [
      { title: 'Build this thing' },
      { title: 'Build that thing' },
      { title: 'Build all the things' }
    ];
    this.bindListeners({
      createTodo: actions.CREATE_TODO,
      removeTodo: actions.REMOVE_TODO,
      updateInputValue: actions.UPDATE_INPUT_VALUE
    });
  }
  createTodo(payload) {
    this.todos.push({ title: payload });
  }
  removeTodo(payload) {
    this.todos.splice(payload, 1);
  }
  updateInputValue(payload) {
    this.inputValue = payload;
  }
}
export default alt.createStore(Todo, 'Todo');
  • Redux :使用纯函数(归约函数)来定义存储的状态转换逻辑。示例代码如下:
import Immutable from 'immutable';
import initialState from '../initial-state';
import {
  UPDATE_INPUT_VALUE,
  CREATE_TODO,
  REMOVE_TODO
} from '../constants';
export default function Todo(state = initialState, action) {
  switch (action.type) {
    case UPDATE_INPUT_VALUE:
      return state.set('inputValue', action.payload);
    case CREATE_TODO:
      return state.set('todos',
        state.get('todos').push(Immutable.Map({
          title: action.payload
        }))
      );
    case REMOVE_TODO:
      return state.set('todos',
        state.get('todos').delete(action.payload));
    default:
      return state;
  }
}
5.2.2 操作创建
  • Alt.js :使用 generateActions 方法自动生成操作创建函数和操作常量。示例代码如下:
import alt from './alt';
export default alt.generateActions(
  'createTodo',
  'createDone',
  'removeTodo',
  'updateInputValue'
);
  • Redux :通常手动创建操作创建函数,返回包含操作类型和可选负载的对象。示例代码如下:
export const createTodo = (payload) => ({
  type: 'CREATE_TODO',
  payload
});

5.3 应用场景对比

  • Alt.js :适用于对 Flux 规范要求严格,需要遵循传统 Flux 架构,且项目规模相对较小,存储间依赖关系可以较好管理的应用。
  • Redux :更适合大型项目,尤其是需要可预测状态管理、易于调试和测试,以及需要与 React 紧密结合的应用。

以下是 Alt.js 和 Redux 对比的总结流程图:

graph LR
    A[应用需求] -->|小型、遵循规范| B[选择 Alt.js]
    A -->|大型、可预测性强| C[选择 Redux]
    B -->|多存储管理| D[Alt.js 架构特点]
    C -->|单存储管理| E[Redux 架构特点]

6. 实际项目中的选择建议

6.1 项目规模

  • 小型项目 :如果项目规模较小,功能相对简单,团队对 Flux 规范有一定要求,且希望快速搭建项目,可以选择 Alt.js。它的样板代码自动化功能可以减少开发时间,让开发者更专注于业务逻辑。
  • 大型项目 :对于大型项目,状态管理复杂,需要可维护性和可测试性强的架构,Redux 是更好的选择。其单一存储和纯归约函数的设计,使得状态变化可预测,便于调试和维护。

6.2 团队技术栈

  • 熟悉传统 Flux :如果团队成员对传统 Flux 架构比较熟悉,且希望继续遵循 Flux 规范进行开发,Alt.js 可能更容易上手。
  • 倾向函数式编程 :如果团队成员对函数式编程有一定了解,并且喜欢使用纯函数来处理数据,Redux 的纯归约函数会更符合团队的技术偏好。

6.3 性能和可扩展性

  • 性能敏感 :在对性能要求较高的场景中,Redux 的单一存储和纯函数特性可能会带来更好的性能表现,因为状态变化可预测,减少了不必要的重新渲染。
  • 扩展性需求 :如果项目需要不断扩展功能,Redux 的模块化设计和易于集成的特点,使其更适合应对未来的变化。

以下是选择库的决策列表:
1. 评估项目规模和复杂度。
2. 考虑团队成员的技术栈和偏好。
3. 分析项目对性能和可扩展性的要求。
4. 根据以上因素综合选择 Alt.js 或 Redux。

7. 总结

在构建基于 Flux 架构的应用时,Alt.js 和 Redux 是两个非常实用的库。Alt.js 严格遵循 Flux 规范,通过自动化样板代码帮助开发者更高效地实现 Flux 架构;Redux 则采取了更简洁和可预测的方式,使用单一存储和纯归约函数来管理应用状态。

在实际项目中,需要根据项目规模、团队技术栈、性能和可扩展性等因素来选择合适的库。无论选择哪个库,都能借助 Flux 架构的优势,实现单向数据流和可预测的状态管理,提高应用的可维护性和可测试性。希望通过本文的介绍,能帮助开发者更好地理解 Alt.js 和 Redux 的特点和适用场景,从而做出更合适的选择。

【四轴飞行器】非线性三自由度四轴飞行器模拟器研究(Matlab代码实现)内容概要:本文围绕非线性三自由度四轴飞行器模拟器的研究展开,重点介绍了基于Matlab的建模与仿真方法。通过对四轴飞行器的动力学特性进行分析,构建了非线性状态空间模型,并实现了姿态与位置的动态模拟。研究涵盖了飞行器运动方程的建立、控制系统设计及数值仿真验证等环节,突出非线性系统的精确建模与仿真优势,有助于深入理解飞行器在复杂工况下的行为特征。此外,文中还提到了多种配套技术如PID控制、状态估计与路径规划等,展示了Matlab在航空航天仿真中的综合应用能力。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的高校学生、科研人员及从事无人机系统开发的工程技术人员,尤其适合研究生及以上层次的研究者。; 使用场景及目标:①用于四轴飞行器控制系统的设计与验证,支持算法快速原型开发;②作为教学工具帮助理解非线性动力学系统建模与仿真过程;③支撑科研项目中对飞行器姿态控制、轨迹跟踪等问题的深入研究; 阅读建议:建议读者结合文中提供的Matlab代码进行实践操作,重点关注动力学建模与控制模块的实现细节,同时可延伸学习文档中提及的PID控制、状态估计等相关技术内容,以全面提升系统仿真与分析能力。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值