GUI应用程序架构的十年变迁:MVC,MVP,MVVM,Unidirectional,Clean

[toc]

笔者的编程基础与软件工程相关文章索引
十年前,Martin Fowler撰写了GUI Architectures一文,至今被奉为经典。本文所谈的所谓架构二字,核心即是对于对于富客户端的代码组织/职责划分。纵览这十年内的架构模式变迁,大概可以分为MV与Unidirectional两大类,而Clean Architecture则是以严格的层次划分独辟蹊径。从笔者的认知来看,从MVC到MVP的变迁完成了对于View与Model的解耦合,改进了职责分配与可测试性。而从MVP到MVVM,添加了View与ViewModel之间的数据绑定,使得View完全的无状态化。最后,整个从MV到Unidirectional的变迁即是采用了消息队列式的数据流驱动的架构,并且以Redux为代表的方案将原本MV*中碎片化的状态管理变为了统一的状态管理,保证了状态的有序性与可回溯性。

笔者在撰写本文的时候也不可避免的带了很多自己的观点,在漫长的GUI架构模式变迁过程中,很多概念其实是交错复杂,典型的譬如MVP与MVVM的区别,笔者按照自己的理解强行定义了二者的区分边界,不可避免的带着自己的主观想法。另外,鉴于笔者目前主要进行的是Web方面的开发,因此在整体倾向上是支持Unidirectional Architecture并且认为集中式的状态管理是正确的方向。但是必须要强调,GUI架构本身是无法脱离其所依托的平台,下文笔者也会浅述由于Android与iOS本身SDK API的特殊性,生搬硬套其他平台的架构模式也是邯郸学步,沐猴而冠。不过总结而言,它山之石,可以攻玉,本身我们所处的开发环境一直在不断变化,对于过去的精华自当应该保留,并且与新的环境相互印证,触类旁通。

Introduction

Make everything as simple as possible, but not simpler — Albert Einstein

Graphical User Interfaces一直是软件开发领域的重要组成部分,从当年的MFC,到WinForm/Java Swing,再到WebAPP/Android/iOS引领的智能设备潮流,以及未来可能的AR/VR,GUI应用开发中所面临的问题一直在不断演变,但是从各种具体问题中抽象而出的可以复用的模式恒久存在。而这些模式也就是所谓应用架构的核心与基础。对于所谓应用架构,空谈误事,不谈误己,笔者相信不仅仅只有自己想把那一团糟的代码给彻底抛弃。往往对于架构的认知需要一定的大局观与格局眼光,每个有一定经验的客户端程序开发者,无论是Web、iOS还是Android,都会有自己熟悉的开发流程习惯,但是笔者认为架构认知更多的是道,而非术。当你能够以一种指导思想在不同的平台上能够进行高效地开发时,你才能真正理解架构。这个有点像张三丰学武,心中无招,方才达成。笔者这么说只是为了强调,尽量地可以不拘泥于某个平台的具体实现去审视GUI应用程序架构模式,会让你有不一样的体验。譬如下面这个组装Android机器人的图:

超级产品经理").val(this.id).text(this.name));
});

而以Angular 1声明式的方式进行编写,那么是如下的标记模样:

{{item.name}}

而在iOS和Android开发中,近年来函数响应式编程(Functional Reactive Programming)也非常流行,参阅笔者关于响应式编程的介绍可以了解,响应式编程本身是基于流的方式对于异步操作的一种编程优化,其在整个应用架构的角度看更多的是细节点的优化。以RxSwift为例,通过响应式编程可以编写出非常优雅的用户交互代码:

let searchResults = searchBar.rx_text
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest { query -> Observable in
if query.isEmpty {
return Observable.just([])
}

    return searchGitHub(query)        .catchErrorJustReturn([])}.observeOn(MainScheduler.instance)

searchResults
.bindTo(tableView.rx_itemsWithCellIdentifier("Cell")) {
(index, repository: Repository, cell) in
cell.textLabel?.text = repository.name
cell.detailTextLabel?.text = repository.url
}
.addDisposableTo(disposeBag)

其直观的效果大概如下图所示:![超级产品经理](https://v1cdn.imspm.com/imspm.com超级产品经理2016072200di2bocvbk3m.gif)  Last name: 

因为不同的应用中,form的提交地址可能不一致,那么整个form组件是不可直接重用的,即Non-Fractal Architectures。而form中的input组件是可以进行直接复用的,如果将input看做一个单独的GUI架构,即是所谓的Fractal Architectures,form就是所谓的Orchestrators,将可重用的组件编排组合,并且设置应用特定的一些信息。

Reference

Overview

  1. Martin Fowler-GUI Architectures

  2. Comparison-of-Architecture-presentation-patterns

MV*

  1. THE EVOLUTION OF ANDROID ARCHITECTURE

  2. the-evolution-of-android-architecture

  3. android-architecture

  4. ios-architecture-patterns

  5. Albert Zuurbier:MVC VS. MVP VS. MVVM

MVC

  1. Model-View-Controller (MVC) in iOS: A Modern Approach

  2. 为什么我不再使用MVC框架

  3. difference-between-mvc-mvp-mvvm-swapneel-salunkhe

MVP

  1. presentation-model-and-passive-view-in-mvp-the-android-way

  2. Repository that showcases 3 Android app architectures

MVVM

  1. approaching-android-with-mvvm

Unidirectional Architecture

  1. unidirectional-user-interface-architectures

  2. Facebook: MVC Does Not Scale, Use Flux Instead [Updated]

  3. mvvm-mvc-is-dead-is-unidirectional-a-mvvm-mvc-killer

  4. flux-vs-mvc-design-patterns

  5. jedux:Redux architecture for Android

  6. writing-a-todo-app-with-redux-on-android

  7. state-streams-and-react

Viper/Clean Architecture

  1. Uncle Bob:the-clean-architecture

  2. Android Clean Architecture

  3. A sample iOS app built using the Clean Swift architecture

  4. Introduction to VIPER

MV*:Fragmentary State 碎片化的状态与双向数据流

MVC模式将有关于渲染、控制与数据存储的概念有机分割,是GUI应用架构模式的一个巨大成就。但是,MVC模式在构建能够长期运行、维护、有效扩展的应用程序时遇到了极大的问题。MVC模式在一些小型项目或者简单的界面上仍旧有极大的可用性,但是在现代富客户端开发中导致职责分割不明确、功能模块重用性、View的组合性较差。作为继任者MVP模式分割了View与Model之间的直接关联,MVP模式中也将更多的ViewLogic转移到Presenter中进行实现,从而保证了View的可测试性。而最年轻的MVVM将ViewLogic与View剥离开来,保证了View的无状态性、可重用性、可组合性以及可测试性。总结而言,MV*模型都包含了以下几个方面:

  1. Models:负责存储领域/业务逻辑相关的数据与构建数据访问层,典型的就是譬如Person、PersonDataProvider。

  2. Views:负责将数据渲染展示给用户,并且响应用户输入

  3. Controller/Presenter/ViewModel:往往作为Model与View之间的中间人出现,接收View传来的用户事件并且传递给Model,同时利用从Model传来的最新模型控制更新View

MVC:Monolithic Controller

相信每一个程序猿都会宣称自己掌握MVC,这个概念浅显易懂,并且贯穿了从GUI应用到服务端应用程序。MVC的概念源自Gamma, Helm, Johnson 以及Vlissidis这四人帮在讨论设计模式中的Observer模式时的想法,不过在那本经典的设计模式中并没有显式地提出这个概念。我们通常认为的MVC名词的正式提出是在1979年5月Trygve Reenskaug发表的Thing-Model-View-Editor这篇论文,这篇论文虽然并没有提及Controller,但是Editor已经是一个很接近的概念。大概7个月之后,Trygve Reenskaug在他的文章Models-Views-Controllers中正式提出了MVC这个三元组。上面两篇论文中对于Model的定义都非常清晰,Model代表着an abstraction in the form of data in a computing system.,即为计算系统中数据的抽象表述,而View代表着capable of showing one or more pictorial representations of the Model on screen and on hardcopy.,即能够将模型中的数据以某种方式表现在屏幕上的组件。而Editor被定义为某个用户与多个View之间的交互接口,在后一篇文章中Controller则被定义为了a special controller ... that permits the user to modify the information that is presented by the view.,即主要负责对模型进行修改并且最终呈现在界面上。从我的个人理解来看,Controller负责控制整个界面,而Editor只负责界面中的某个部分。Controller协调菜单、面板以及像鼠标点击、移动、手势等等很多的不同功能的模块,而Editor更多的只是负责某个特定的任务。后来,Martin Fowler在2003开始编写的著作Patterns of Enterprise Application Architecture中重申了MVC的意义:Model View Controller (MVC) is one of the most quoted (and most misquoted) patterns around.,将Controller的功能正式定义为:响应用户操作,控制模型进行相应更新,并且操作页面进行合适的重渲染。这是非常经典、狭义的MVC定义,后来在iOS以及其他很多领域实际上运用的MVC都已经被扩展或者赋予了新的功能,不过笔者为了区分架构演化之间的区别,在本文中仅会以这种最朴素的定义方式来描述MVC。

根据上述定义,我们可以看到MVC模式中典型的用户场景为:

  1. 用户交互输入了某些内容

  2. Controller将用户输入转化为Model所需要进行的更改

  3. Model中的更改结束之后,Controller通知View进行更新以表现出当前Model的状态

超级产品经理

后来Anvil这样的受React启发的组件式框架以及Jedux这样借鉴了Redux全局状态管理的框架也将Unidirectional 架构引入了Android开发的世界。### MVC1. 声明View中的组件对象或者Model对象
private Subscription subscription;private RecyclerView reposRecycleView;private Toolbar toolbar;private EditText editTextUsername;private ProgressBar progressBar;private TextView infoTextView;private ImageButton searchButton;
1. 将组件与Activity中对象绑定,并且声明用户响应处理函数
    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    progressBar = (ProgressBar) findViewById(R.id.progress);    infoTextView = (TextView) findViewById(R.id.text_info);    //Set up ToolBar    toolbar = (Toolbar) findViewById(R.id.toolbar);    setSupportActionBar(toolbar);    //Set up RecyclerView    reposRecycleView = (RecyclerView) findViewById(R.id.repos_recycler_view);    setupRecyclerView(reposRecycleView);    // Set up search button    searchButton = (ImageButton) findViewById(R.id.button_search);    searchButton.setOnClickListener(new View.OnClickListener() {        @Override        public void onClick(View v) {            loadGithubRepos(editTextUsername.getText().toString());        }    });    //Set up username EditText    editTextUsername = (EditText) findViewById(R.id.edit_text_username);    editTextUsername.addTextChangedListener(mHideShowButtonTextWatcher);    editTextUsername.setOnEditorActionListener(new TextView.OnEditorActionListener() {        @Override        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {            if (actionId == EditorInfo.IME_ACTION_SEARCH) {                String username = editTextUsername.getText().toString();                if (username.length() > 0) loadGithubRepos(username);                return true;            }            return false;        }

});

1. 用户输入之后的更新流程
    progressBar.setVisibility(View.VISIBLE);    reposRecycleView.setVisibility(View.GONE);    infoTextView.setVisibility(View.GONE);    ArchiApplication application = ArchiApplication.get(this);    GithubService githubService = application.getGithubService();    subscription = githubService.publicRepositories(username)            .observeOn(AndroidSchedulers.mainThread())            .subscribeOn(application.defaultSubscribeScheduler())            .subscribe(new Subscriber>() {                @Override                public void onCompleted() {                    progressBar.setVisibility(View.GONE);                    if (reposRecycleView.getAdapter().getItemCount() > 0) {                        reposRecycleView.requestFocus();                        hideSoftKeyboard();                        reposRecycleView.setVisibility(View.VISIBLE);                    } else {                        infoTextView.setText(R.string.text_empty_repos);                        infoTextView.setVisibility(View.VISIBLE);                    }                }                @Override                public void onError(Throwable error) {                    Log.e(TAG, "Error loading GitHub repos ", error);                    progressBar.setVisibility(View.GONE);                    if (error instanceof HttpException                            && ((HttpException) error).code() == 404) {                        infoTextView.setText(R.string.error_username_not_found);                    } else {                        infoTextView.setText(R.string.error_loading_repos);                    }                    infoTextView.setVisibility(View.VISIBLE);                }                @Override                public void onNext(List repositories) {                    Log.i(TAG, "Repos loaded " + repositories);                    RepositoryAdapter adapter =                            (RepositoryAdapter) reposRecycleView.getAdapter();                    adapter.setRepositories(repositories);                    adapter.notifyDataSetChanged();                }

});

### MVP1. 将Presenter与View绑定,并且将用户响应事件绑定到Presenter中
    //Set up presenter    presenter = new MainPresenter();    presenter.attachView(this);    ...    // Set up search button    searchButton = (ImageButton) findViewById(R.id.button_search);    searchButton.setOnClickListener(new View.OnClickListener() {        @Override        public void onClick(View v) {            presenter.loadRepositories(editTextUsername.getText().toString());        }    });
1. Presenter中调用Model更新数据,并且调用View中进行重新渲染
public void loadRepositories(String usernameEntered) {    String username = usernameEntered.trim();    if (username.isEmpty()) return;    mainMvpView.showProgressIndicator();    if (subscription != null) subscription.unsubscribe();    ArchiApplication application = ArchiApplication.get(mainMvpView.getContext());    GithubService githubService = application.getGithubService();    subscription = githubService.publicRepositories(username)            .observeOn(AndroidSchedulers.mainThread())            .subscribeOn(application.defaultSubscribeScheduler())            .subscribe(new Subscriber>() {                @Override                public void onCompleted() {                    Log.i(TAG, "Repos loaded " + repositories);                    if (!repositories.isEmpty()) {                        mainMvpView.showRepositories(repositories);                    } else {                        mainMvpView.showMessage(R.string.text_empty_repos);                    }                }                @Override                public void onError(Throwable error) {                    Log.e(TAG, "Error loading GitHub repos ", error);                    if (isHttp404(error)) {                        mainMvpView.showMessage(R.string.error_username_not_found);                    } else {                        mainMvpView.showMessage(R.string.error_loading_repos);                    }                }                @Override                public void onNext(List repositories) {                    MainPresenter.this.repositories = repositories;                }            });    }
### MVVM1. XML中声明数据绑定

...

1. View中绑定ViewModel
    super.onCreate(savedInstanceState);    binding = DataBindingUtil.setContentView(this, R.layout.main_activity);    mainViewModel = new MainViewModel(this, this);    binding.setViewModel(mainViewModel);    setSupportActionBar(binding.toolbar);    setupRecyclerView(binding.reposRecyclerView);
1. ViewModel中进行数据操作

public boolean onSearchAction(TextView view, int actionId, KeyEvent event) {

    if (actionId == EditorInfo.IME_ACTION_SEARCH) {        String username = view.getText().toString();        if (username.length() > 0) loadGithubRepos(username);        return true;    }    return false;}public void onClickSearch(View view) {    loadGithubRepos(editTextUsernameValue);}public TextWatcher getUsernameEditTextWatcher() {    return new TextWatcher() {        @Override        public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {        }        @Override        public void onTextChanged(CharSequence charSequence, int start, int before, int count) {            editTextUsernameValue = charSequence.toString();            searchButtonVisibility.set(charSequence.length() > 0 ? View.VISIBLE : View.GONE);        }        @Override        public void afterTextChanged(Editable editable) {        }    };

}

# Unidirectional User Interface Architecture:单向数据流Unidirectional User Interface Architecture架构的概念源于后端常见的CROS/Event Sourcing模式,其核心思想即是将应用状态被统一存放在一个或多个的Store中,并且所有的数据更新都是通过可观测的Actions触发,而所有的View都是基于Store中的状态渲染而来。该架构的最大优势在于整个应用中的数据流以单向流动的方式从而使得有用更好地可预测性与可控性,这样可以保证你的应用各个模块之间的松耦合性。与MVVM模式相比,其解决了以下两个问题:1. 避免了数据在多个ViewModel中的冗余与不一致问题1. 分割了ViewModel的职责,使得ViewModel变得更加Clean## Why not Bidirectional(Two-way DataBinding)?This means that one change (a user input or API response) can affect the state of an application in many places in the code — for example, two-way data binding. That can be hard to maintain and debug.1. easier-reasoning-with-unidirectional-dataflow-and-immutable-dataFacebook强调,双向数据绑定极不利于代码的扩展与维护。从具体的代码实现角度来看,双向数据绑定会导致更改的不可预期性(UnPredictable),就好像Angular利用Dirty Checking来进行是否需要重新渲染的检测,这导致了应用的缓慢,简直就是来砸场子的。而在采用了单向数据流之后,整个应用状态会变得可预测(Predictable),也能很好地了解当状态发生变化时到底会有多少的组件发生变化。另一方面,相对集中地状态管理,也有助于你不同的组件之间进行信息交互或者状态共享,特别是像Redux这种强调Single Store与SIngle State Tree的状态管理模式,能够保证以统一的方式对于应用的状态进行修改,并且Immutable的概念引入使得状态变得可回溯。譬如Facebook在Flux Overview中举的例子,当我们希望在一个界面上同时展示未读信息列表与未读信息的总数目的时候,对于MV*就有点恶心了,特别是当这两个组件不在同一个ViewModel/Controller中的时候。一旦我们将某个未读信息标识为已读,会引起控制已读信息、未读信息、未读信息总数目等等一系列模型的更新。特别是很多时候为了方便我们可能在每个ViewModel/Controller都会设置一个数据副本,这会导致依赖连锁更新,最终导致不可预测的结果与性能损耗。而在Flux中这种依赖是反转的,Store接收到更新的Action请求之后对数据进行统一的更新并且通知各个View,而不是依赖于各个独立的ViewModel/Controller所谓的一致性更新。从职责划分的角度来看,除了Store之外的任何模块其实都不知道应该如何处理数据,这就保证了合理的职责分割。这种模式下,当我们创建新项目时,项目复杂度的增长瓶颈也就会更高,不同于传统的View与ViewLogic之间的绑定,控制流被独立处理,当我们添加新的特性,新的数据,新的界面,新的逻辑处理模块时,并不会导致原有模块的复杂度增加,从而使得整个逻辑更加清晰可控。这里还需要提及一下,很多人应该是从React开始认知到单向数据流这种架构模式的,而当时Angular 1的缓慢与性能之差令人发指,但是譬如Vue与Angular 2的性能就非常优秀。借用Vue.js官方的说法,The virtual-DOM approach provides a functional way to describe your view at any point of time, which is really nice. Because it doesn’t use observables and re-renders the entire app on every update, the view is by definition guaranteed to be in sync with the data. It also opens up possibilities to isomorphic JavaScript applications.Instead of a Virtual DOM, Vue.js uses the actual DOM as the template and keeps references to actual nodes for data bindings. This limits Vue.js to environments where DOM is present. However, contrary to the common misconception that Virtual-DOM makes React faster than anything else, Vue.js actually out-performs React when it comes to hot updates, and requires almost no hand-tuned optimization. With React, you need to implementshouldComponentUpdate everywhere and use immutable data structures to achieve fully optimized re-renders.总而言之,笔者认为双向数据流与单向数据流相比,性能上孰优孰劣尚无定论,最大的区别在于单向数据流与双向数据流相比有更好地可控性,这一点在上文提及的函数响应式编程中也有体现。若论快速开发,笔者感觉双向数据绑定略胜一筹,毕竟这种View与ViewModel/ViewLogic之间的直接绑定直观便捷。而如果是注重于全局的状态管理,希望维护耦合程度较低、可测试性/可扩展性较高的代码,那么还是单向数据流,即Unidirectional Architecture较为合适。一家之言,欢迎讨论。## Flux:数据流驱动的页面Flux不能算是绝对的先行者,但是在Unidirectional Architecture中却是最富盛名的一个,也是很多人接触到的第一个Unidirectional Architecture。Flux主要由以下几个部分构成:1. Stores:存放业务数据和应用状态,一个Flux中可能存在多个Stores1. View:层次化组合的React组件1. Actions:用户输入之后触发View发出的事件1. Dispatcher:负责分发Actions根据上述流程,我们可知Flux模式的特性为:1. Dispatcher:Event Bus中设置有一个单例的Dispatcher,很多Flux的变种都移除了Dispatcher依赖。1. 只有View使用可组合的组件:在Flux中只有React的组件可以进行层次化组合,而Stores与Actions都不可以进行层次化组合。React组件与Flux一般是松耦合的,因此Flux并不是Fractal,Dispatcher与Stores可以被看做Orchestrator。1. 用户事件响应在渲染时声明:在React的render()函数中,即负责响应用户交互,也负责注册用户事件的处理器下面我们来看一个具体的代码对比,首先是以经典的Cocoa风格编写一个简单的计数器按钮:

class ModelCounter

constructor: (@value=1) ->increaseValue: (delta) =>    @value += delta

class ControllerCounter

constructor: (opts) ->    @model_counter = opts.model_counter    @observers = []getValue: => @model_counter.valueincreaseValue: (delta) =>    @model_counter.increaseValue(delta)    @notifyObservers()notifyObservers: =>    obj.notify(this) for obj in @observersregisterObserver: (observer) =>    @observers.push(observer)

class ViewCounterButton

constructor: (opts) ->    @controller_counter = opts.controller_counter    @button_class = opts.button_class or 'button_counter'    @controller_counter.registerObserver(this)render: =>    elm = $("            # {@controller_counter.getValue()}")    elm.click =>        @controller_counter.increaseValue(1)    return elmnotify: =>    $("button.# {@button_class}").replaceWith(=> @render())
上述代码逻辑用上文提及的MVC模式图演示就是:而如果用Flux模式实现,会是下面这个样子:

Store

class CounterStore extends EventEmitter

constructor: ->    @count = 0    @dispatchToken = @registerToDispatcher()increaseValue: (delta) ->    @count += 1getCount: ->    return @countregisterToDispatcher: ->    CounterDispatcher.register((payload) =>        switch payload.type            when ActionTypes.INCREASE_COUNT                @increaseValue(payload.delta)    )

Action

class CounterActions

@increaseCount: (delta) ->    CounterDispatcher.handleViewAction({        'type': ActionTypes.INCREASE_COUNT        'delta': delta    })

View

CounterButton = React.createClass(

getInitialState: ->    return {'count': 0}_onChange: ->    @setState({        count: CounterStore.getCount()    })componentDidMount: ->    CounterStore.addListener('CHANGE', @_onChange)componentWillUnmount: ->    CounterStore.removeListener('CHANGE', @_onChange)render: ->    return React.DOM.button({'className': @prop.class}, @state.value)

)

其数据流图为:## Redux:集中式的状态管理Redux是Flux的所有变种中最为出色的一个,并且也是当前Web领域主流的状态管理工具,其独创的理念与功能深刻影响了GUI应用程序架构中的状态管理的思想。Redux将Flux中单例的Dispatcher替换为了单例的Store,即也是其最大的特性,集中式的状态管理。并且Store的定义也不是从零开始单独定义,而是基于多个Reducer的组合,可以把Reducer看做Store Factory。Redux的重要组成部分包括:1. Singleton Store:管理应用中的状态,并且提供了一个dispatch(action)函数。1. Provider:用于监听Store的变化并且连接像React、Angular这样的UI框架1. Actions:基于用户输入创建的分发给Reducer的事件1. Reducers:用于响应Actions并且更新全局状态树的纯函数根据上述流程,我们可知Redux模式的特性为:1. 以工厂模式组装Stores:Redux允许我以createStore()函数加上一系列组合好的Reducer函数来创建Store实例,还有另一个applyMiddleware()函数可以允许在dispatch()函数执行前后链式调用一系列中间件。1. Providers:Redux并不特定地需要何种UI框架,可以与Angular、React等等很多UI框架协同工作。Redux并不是Fractal,一般来说Store被视作Orchestrator。1. User Event处理器即可以选择在渲染函数中声明,也可以在其他地方进行声明。## Model-View-Update又被称作Elm Architecture,上面所讲的Redux就是受到Elm的启发演化而来,因此MVU与Redux之间有很多的相通之处。MVU使用函数式编程语言Elm作为其底层开发语言,因此该架构可以被看做更纯粹的函数式架构。MVU中的基本组成部分有:1. Model:定义状态数据结构的类型1. View:纯函数,将状态渲染为界面1. Actions:以Mailbox的方式传递用户事件的载体1. Update:用于更新状态的纯函数根据上述流程,我们可知Elm模式的特性为:1. 到处可见的层次化组合:Redux只是在View层允许将组件进行层次化组合,而MVU中在Model与Update函数中也允许进行层次化组合,甚至Actions都可以包含内嵌的子Action1. Elm属于Fractal架构:因为Elm中所有的模块组件都支持层次化组合,即都可以被单独地导出使用## Model-View-IntentMVI是一个基于RxJS的响应式单向数据流架构。MVI也是Cycle.js的首选架构,主要由Observable事件流对象与处理函数组成。其主要的组成部分包括:1. Intent:Observable提供的将用户事件转化为Action的函数1. Model:Observable提供的将Action转化为可观测的State的函数1. View:将状态渲染为用户界面的函数1. Custom Element:类似于React Component那样的界面组件根据上述流程,我们可知MVI模式的特性为:1. 重度依赖于Observables:架构中的每个部分都会被转化为Observable事件流1. Intent:不同于Flux或者Redux,MVI中的Actions并没有直接传送给Dispatcher或者Store,而是交于正在监听的Model1. 彻底的响应式,并且只要所有的组件都遵循MVI模式就能保证整体架构的fractal特性#mvvm、mvc、MVP#

版权声明

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处。如若内容有涉嫌抄袭侵权/违法违规/事实不符,请点击 举报 进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部