夜影如歌

如何使用 Flux 框架来写qml applictions
#### 为什么有这个问题? MVC ? MVVM ? #### Flux 是什么? Flux...
扫描右侧二维码阅读全文
12
2018/06

如何使用 Flux 框架来写qml applictions

为什么有这个问题?

MVC ? MVVM ?

Flux 是什么?

Flux:
What the Flux? (On Flux, DDD, and CQRS) — Jack Hsu

QuickFlux:

Action-Dispatcher Design Pattern for QML

[A cartoon guide to Flux — Code Cartoons]()

[Flux: Qt Quick with unidirectional data flow]()

Revised QML Application Architecture Guide with Flux

如何使用 Flux ?

下面以项目eve为例进行阐述

eve 是类似 SparkQML 的一种支持qml语法的浏览器,其目标是结合 C++ /Java这种工程级的高级语言,以及python,javascript等这种脚本语言各自的优点,打造分布式,插件式应用运行框架,实现类似 Chrome,FireFox等浏览的功能。在eve上,可根据qml应用地址的站点地址方便访问,且这些应用可以方便的制作成桌面型app,运行效果与 eve上浏览的效果一样。目前,2.1.0 版本仅支持 qml 应用。

项目结构
eve
├── apps  (放置应用程序目录)
├── configures  (eve 配置文件)
├── core    (eve c++ 源码目录)
├── eve.pro
├── main.cpp    (eve 桌面发布时的运行入口)
├── main.qml    (eve 站点发布时的运行入口)
├── qml         (eve qml站点源码)
├── res
└── tests

2018-06-12_18-36-02.png

qml 文件夹下的内容:

qml
├── components  (eve 自己所使用的组件)
├── flux    (以flux方式写的qml代码)
│   ├── actions (动作定义)
│   ├── middlewares (中间件)
│   └── stores  (模型)
└── views   (eve 用户界面就放在这个目录下)
    ├── MainWindow.qml 
    └── pages

flux 文件夹下的内容:

flux
├── actions
│   ├── ActionTypes.qml (动作名称定义)
│   └── AppActions.qml  (动作类型声明)
├── middlewares
│   ├── AppMiddleware.qml   (App相关)
│   └── NavigationMiddleware.qml    (导航相关事物)
└── stores
    ├── AppStore.qml    (eve 应用相关模型)
    ├── MainStore.qml   (eve 主存储模型,singleton)
    ├── RootStore.qml   (根存储模型)
    └── SettingStore.qml    (eve 设置相关模型)

结构大概就是这样。

实施步骤

按照上面目录结构创建eve工程以后,程序启动流程:main.cpp -- > main.qml ---> MainWindow.qml ---> 显示界面。

2018-06-12_19-04-18.png

关于 C++ 部分的代码就不说了。这里就讲一下 与 flux 有关的几个文件的写法。

main.qml

Item {
    MiddlewareList {
        applyTarget: AppActions

        AppMiddleware {
        }

        NavigationMiddleware {
            explorer: mainwindow1
        }
    }

    MainWindow {
        id: mainwindow1
    }
}

注意,上面 applyTarget 的绑定对象,是 AppActions 类型的对象,表示 AppActions.qml 中定义的所有动作信号,都可以通过 AppActions 所指定的 Dispatcher(分发器) 分发给这两个中间件:AppMiddleware 和 NavigationMiddleware。

  • AppMiddleware.qml
Middleware {

    filterFunctionEnabled: true

    ...

    function funTest(message) {
        console.log("funTest")
        next(ActionTypes.funTest, message);
    }

}

那么这个AppActions对象所使用的分发器是什么类型?在哪里定义?

1.0 版本仅支持一个 singleton 类型的分发器 AppDispatcher,而 1.1 版本增加了一个支持对象化的 Dispatcher。这使得 qml应用嵌套使用 flux 框架成为可能。这么说吧,如果 一个flux应用A和另一个flux应用B都使用AppDispatcher作为分发器,如果需要从A跳转到B,那么跳转后,B 代替 A 接管AppDisptcher的使用权,B 仍然可以正常使用的,但是由于 B 中并没有包括 A 的动作定义,于是 A 的所有动作都无法发送到A的 Middlewares,更不用说 Stores了。如果想让A和B都能正常分发,就需要分别配置一个Dispatcher 对象了。

main.qml 载入以后,在 AppActions 中定义动作都能发送指定的中间件了。但是 Stores 还没有连接到 分发器 或者 中间件。

AppActions所使用的分发器在 AppActions.qml 中定义:

  • AppActions.qml
ActionCreator {
    dispatcher: MainStore.dispatcher

    /* Create action by signal */

    signal funTest();

    signal navigateTo(var item, var properties)

    ...
}

可以看出,AppActions 继承自 ActionCreator 类,这里 dispatcher 如果不指定,自动会绑定到 AppDispatcher。由于绑定了 一个对象化的分发器 MainStore.dispatcher,因此 eve 可以自由跳转到别的应用程序的同时能够继续无差异运行。 继续往下看。

  • MainStore.qml
pragma Singleton
...

RootStore {
    property alias dispatcher: dispatcher

    Dispatcher {
      id: dispatcher
    }

    ...
}

可见,确实是1.1版本的分发器,而且继承了 RootStore,是一个静态对象。注意 MainStore 并没有进行分发器绑定操作哦。

由于业务处理需要各种 Store,比如 负责配置相关的 SettingStore,负责应用程序本身相关事物的AppStore。那么我怎么将这些Stores 都绑定到同一个 分发器呢? 接着往下扒:

  • RootStore.qml
Store {
//    bindSource: AppDispatcher
    bindSource: MainStore.dispatcher

    property alias app: app
    property alias settings: setting

    ...

    AppStore {
        id: app
    }

    SettingStore {
        id: settings
    }

    Filter {
        type: ActionTypes.funTest
        onDispatched: {
            console.log("RootStore.Filter.funTest")
        }
    }

    ...
}

可见,Store 绑定分发器的方式是通过 bindSource 这个数据成员指定的。默认 Store 会给自己所包含的其它 Store 进行统一绑定,所有这里只需要指定一次就够了,不需要分别对 AppStore 和 SettingStore 进行绑定声明。

这就是整个flux使用的基本情况。

此时,若 MainWindow 中某个地方响应用户操作比如触发了 funTest 信号:

  • MainWindow.qml
            onClicked: {
                console.log("clicked!")
                AppActions.funTest()
            }

首先, AppMiddleware.qml 会响应(因为它对 funTest 进行了改造),然后 调用了next 分发 给 Dispatcher, Dispatcher在等待所有中间件处理完毕以后,再发给相关Store(比如这里RootStore,AppStore中对funTest都进行了Filter,所以都能够响应)。

值得注意的一点,动作的参数智能在 AppActions.qml中声明,比如 addToSearch 所带的参数 item 和 properties。

  • MainWindow.qml
    Action {
        id: actionGo
        Component.onCompleted: { FileUtilsHelper.addToUpdate(this, "action_home.svg") }

        name: "Open the home page"
        onTriggered: {
            console.log("navigate to: home page")

            var properties = {
                index: mainWidget.actionBar.selectedTabIndex,
                type: 1
            }
            console.log("Action_go_home.onTriggered")
            AppActions.navigateTo(this, properties)
        }
    }

这里的 this 是指 actionGo。 但是这个 动作信号 由 Dispatcher 发送到 Middlewares 处理时是这样的:

  • NavigationMiddleware.qml
Middleware {

    filterFunctionEnabled: true

    ...

    function navigateTo(message) {
        var item = message.item
        var properties = message.properties
//        console.log(properties.iconName)

//        next(ActionTypes.addToSearch, message)
    }

    function navigateBack(message) {
        console.log("navigateBack")
    }
}

可见,分发器将 item 和 properties 都 转换成 message 这个 json 对象的数据成员了。由于不需进一步发送到 Stores 进行处理,所以这里没有调用 next。

这就是 Flux 应用到 qml 开发 的 方法了。

最后修改:2018 年 06 月 12 日 08 : 21 PM
如果觉得我的文章对你有用,请随意赞赏

发表评论