65.9K
CodeProject 正在变化。 阅读更多。
Home

Bobril - II - Bobflux 应用程序架构

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (7投票s)

2015年11月17日

CPOL

5分钟阅读

viewsIcon

30395

downloadIcon

142

对 bobril 的 bobflux 应用程序架构的解释

引言

上一篇文章中,我们学习了如何使用 bobril 创建一个页面(Web 应用程序的视图部分)。在本文中,我们将学习如何通过 bobflux 添加应用程序逻辑。

背景

Bobflux 是一个纯函数式前端应用架构,它基于 flux,并受到 reflux redux 的启发。它由 Karel Steinmetz(GMC Software Technology 的软件开发人员)编写。

Bobflux 的生命周期可以用下图描述:

bobflux lifecycle

  1. 应用程序具有一些 状态
  2. 视图 根据此状态进行渲染
  3. 视图调用由 action creator 创建的 actions 的处理程序
  4. Actions 改变状态

有关更多信息,请参阅 项目页面

开始吧

我们将创建一个简单的 TODO 应用程序。首先,我们需要在计算机上准备好 bobril-build。请按照上一篇文章中的步骤执行 bobril-build 的安装。

现在,您可以重新开始一个新项目,或者使用 bobril-build github 仓库中预定义的骨架 simpleAppbobril-build github repository

以下示例将使用它。要获取最终代码,请下载完整的示例

将 bobflux 添加到应用程序

在应用程序文件夹的根目录下运行以下命令:

npm i
npm i bobflux --save
bb

状态

首先,我们需要准备应用程序的 状态、它的 cursor 以及用于创建默认状态的函数。

要定义状态,请添加文件 src/state.ts,其内容如下:

import * as flux from 'bobflux';

export interface ITodoAppState extends flux.IState {
    todos: string[];
    todoName: string;
}

export const todoAppCursor: flux.ICursor<ITodoAppState> = {
    key: ''
};

export function createDefaultTodoAppState(): ITodoAppState {
    return {
        todos: [],
        todoName: ''
    };
}

State 是存储应用程序数据的地方。状态与上下文的区别在于,上下文存储仅供组件自身使用的数据,而应用程序的其余部分不关心它们。例如,一个部分组件是否折叠的信息(当您不需要从外部管理它时)。

在我们的 todo 应用程序中,我们需要存储 todos 列表以及当前输入的 todo 名称,因此我们必须为这些数据定义 ITodoAppState

Cursor 定义了应用程序状态中状态的路径(键)。在我们的示例中,我们将仅使用根应用程序光标,因此 todoAppCursor.key 中的路径是空的。

在实际应用中,建议定义尽可能多的特定光标,以获得最佳的 bobflux 性能优势。这意味着,例如,创建一个光标,如:

export const todoNameCursor: flux.ICursor<ITodoAppState> = {
    key: 'todoName'
};

为了简单起见,我们的示例仅使用根 todoAppCursor

现在,我们需要初始化 bobflux 并向其提供应用程序状态,因此请将 src/app.ts 更改为如下所示:

import * as b from 'bobril';
import * as flux from 'bobflux';
import * as todoState from './state';
import { mainPage } from './mainPage';

flux.bootstrap(todoState.createDefaultTodoAppState());

b.routes(
    b.route({ handler: mainPage })
);

现在,我们已经准备好了可以通过调用 actions 来修改的应用程序状态。

Actions

Actions 通过在由光标定义的特定子状态上运行的处理程序来更改状态。在我们的 todo 示例中,我们将需要执行两个 actions:

  1. 根据 textbox 的值更改当前 todo 的名称
  2. 将输入的 todo 添加到 todo 列表中

因此,我们将添加文件 src/actions/changeTodoName.ts

import * as flux from 'bobflux';
import { ITodoAppState, todoAppCursor } from '../state';

export const changeTodoName = 
    flux.createAction(todoAppCursor, 
                     (state: ITodoAppState, todoName: string): ITodoAppState => {

        if (todoName === state.todoName)
            return state;

        return flux.shallowCopy(state, copy => { copy.todoName = todoName; });
    });

export default changeTodoName

src/actions/addTodo.ts

import * as flux from 'bobflux';
import { todoAppCursor } from '../state';

export const addTodo = flux.createParamLessAction(todoAppCursor, state => {
    if (!state.todoName || state.todoName.trim().length === 0)
        return state;

    return flux.shallowCopy(state, copy => {
        copy.todos = [...state.todos, state.todoName];
        copy.todoName = '';
    });
});

export default addTodo;

changeTodoName action 由 bobflux 中的 createAction 函数定义,该函数接受将要更改其状态的光标以及用于更改的那个处理程序。

在处理程序的开头,会检查我们是否想要更改任何内容。

  • 如果不是,则返回原始状态实例。
  • 如果是,则创建状态的浅拷贝并返回修改后的副本。

Bobflux 遵循不可变性原则,以保持最佳性能。它将输入状态与输出状态进行比较,如果不同,则调用 b.invalidate 来重新渲染视图。必须注意复制的对象属性。如果存在像 addTodo action 中的 todos 数组这样的引用对象,也必须将其复制。

使用 bobflux 组合页面

现在,我们已经准备好在 todo 应用程序的页面上使用所有内容了。因此,让我们将 src/mainPage.ts 更改为如下所示:

import * as b from 'bobril';
import * as flux from 'bobflux';
import { todoAppCursor } from './state';
import { changeTodoName } from './actions/changeTodoName';
import { addTodo } from './actions/addTodo';
import { button } from './components/button';
import { textbox } from './components/textbox';
import { p } from './components/paragraph';
import { h1 } from './components/header';

export const mainPage = b.createComponent({
    render(_ctx: b.IBobrilCtx, me: b.IBobrilNode): void {
        const state = flux.getState(todoAppCursor);
        me.children = [
            h1({}, 'TODO'),
            p({}, [
                textbox({ value: state.todoName, 
                          onChange: newValue => changeTodoName(newValue) }),
                button({ title: 'ADD', onClick: () => addTodo() })
            ]),
            state.todos.map(item => p({}, item)),
            p({}, `Count: ${state.todos.length}`)
        ];
    }
});

export default mainPage;

组件定义不是本文的主题,因此您可以使用附加源代码中的定义。

您可以看到,页面通过 todoAppCursor 定义的 getState 函数解析当前应用程序状态。可以这样完成,因为 bobflux 在状态的每次更改时都会初始化页面的渲染。

textboxbutton 组件在其 onChangeonClick 回调中使用定义的 actions,因此来自视图的用户交互会触发 actions 调用。

最后,在 render 函数的末尾,将 todos 数组映射到带有 todo 名称的 'p' 标签。

现在,我们可以在浏览器中打开应用程序并查看其工作原理。是的,它确实就是这么简单。

为了调试状态历史,您也可以尝试 bobflux-monitor(请参阅项目页面或示例)。

Bobflux 还包含一些性能辅助工具,例如 createRouteComponent,用于优化组件渲染或在上下文中提供状态等。

要获取更多信息,请参阅项目 github 页面

历史

  • 2017-07-30:修订(bobril-build@0.71.0, boril@7.3.2, TS 2.4.2)
  • 2017-02-01:修订(bobril-build@0.59.2, bobril@5.2.1)
  • 2016-11-04:修订
  • 2016-05-29:添加了新的编码标准
  • 2015-12-16:更改为基于 bobril-build 的 simpleApp
  • 2015-11-15:为 bobflux 1.0.0 版本创建了文章
© . All rights reserved.