早期的前端是由后端开发的,最开始的时候仅仅做展示,点一下链接跳转到另外一个页面去,渲染表单,再用Ajax的方式请求网络和后端交互,数据返回来还需要把数据渲染到DOM上。写这样的代码的确是很简单。在Web交互开始变得复杂时,一个页面往往有非常多的元素构成,像社交网络的Feed需要经常刷新,展示内容也五花八门,为了追求用户体验需要做很多的优化。
当时说到架构时,可能会想前端需要架构吗,如果需要,应该使用什么架构呢?也有可能一下就想起了MVC/MVP/MVVM架构。在无架构的状态下,我们可以写一个HTML文件,HTML、Script和Style都一股脑的写在一起,这种原始的方式适合个人开发Demo用,或者只是做个玩玩的东西。
当我们写的Script和Style越来越多时,就考虑是否将这些片段代码整理放在同一个文件里,所以,我们就把Script写到了JS文件,把Style写到CSS文件。一个项目肯定有许多的逻辑功能,就需要考虑分层,把独立的功能单独抽取出来可以复用的组件。 Anguar和Ember作为MVC架构的框架,采用MVVM双向绑定技术能够快速开发一个复杂的Web应用。可是,我们不用。
React将自己定位为MVC的V部分,仅仅是一个View库,这就给我们很大的自由空间,并且引入了基于组件的架构和基于状态的架构概念。
MVC将Model、Controller和View分离,它们的通信是单向的,View只和Controller通信, Controller只跟Model交互,Model也只更新View,然而前端的重点在View上,导致Controller非常薄,而View却很重,有些前端框架会把Controller当作Router层处理。
MVP在MVC的基础上改进了一下,把Controller替换成了Presenter,并将Model放到最后,整个模型的交互变成了View只能和Presenter之间互相传递消息,Presenter也只能和Model相互通信,View不能直接和Model交互,这样又导致Presenter非常的重,所有的逻辑基本上都写在这里。
MVVM又在MVP的基础上修改了一下,将Presenter替换成了ViewModel,通信方式基本上和MVP一样,并且采用双向绑定,View的变动反应在Model上,Model的变动也反应在View上,所以称为ViewModel,现在大部分的框架都是基于这种模型,如Angular、Ember和Backbone。
从MVC和MVP可以看到,不是View层太重,就是把业务逻辑都写到了Presenter层,MVVM也没有定义状态的数据流。
最早的时候Elm定义了一套模型,Model -> Update -> View,除了Model之外,其它都是纯函数的。之后又有人提出了SAM「State Action Model」模型,SAM主要强调两个原则:1. 视图和模型之间的关系必须是函数式的,2. 状态的变化必须是编程模型的一等公民。另外还有对开发友好的工具,如时光旅行。像Redux和Mobx State Tree都是这种架构模型。
让我们想象一下国家电网,或者更接近我们的经常接触的领域——网络。网络有非常严格的定义,必须是有序的流,因为并不是所有连接到互联网的计算机都与其他计算机直接连接,它们通过路由节点间接连接。只有这样,网络才变得可以理解,因而易于管理。状态管理也是如此,状态的流动必须是有序的。
组件架构你可以将组件视为组成用户界面的一个个小功能。我们要描述Gitchat的用户界面,可以看到Tabbar是一个组件,发现页的达人课是一个组件,Chat也是一个组件。这些组件中都包装在一个容器内,它们彼此独立又互相交互。组件有自己的结构,自己的方法和自己的API,组件也是可重用的。
有些组件还有AJAX的请求,直接从客户端调用服务端,允许动态更新DOM,而无需页面刷新。组件每个都有自己的接口,可以调用服务端并更新其接口。因为组件是独立的,所以一个组件可以刷新而不影响其他组件。React使用称为虚拟DOM的东西,它使用“diffing”算法来检测组件的更改,并且仅渲染这些更改,而不是重新渲染整个组件。在设计组件的时候最好遵循组件的结构中仅存在与单个组件有关的所有方法和接口。
虽然这种组件的架构鼓励可重用性和单一责任,但它往往会导致臃肿。MV*的目的是确保应用程序的每个层次都有各自的职责,而基于组件的架构目的是将所有这些职责封装在一个空间内。当使用许多组件时,可读性可能会降低。
React提供了两种组件,Stateful和Stateless,简单来说,这两种组件的区别就是状态管理,Stateful组件内部封装了State管理,Stateless则是纯函数式的,由Props传递状态。
class App extends Component { state = { welcome: 'hello world' } componentDidMount() { ... } componentWillUnmount() { ... } render() { return ( <div> {this.state.welcome} </div> ) } }