浅谈MVC,MVP,MVVM渐进变化及React与Vue比较
前言
因为没有明确的界定,这里不讨论正确与否,只表达个人对前端MV*架构模式理解看法,再比较React和Vue两种框架不同.
写完之后我知道这文章好水,特别是框架对比部分都是别人说烂的,而我也是打算把这作为长期文章来写,慢慢梳理深入,每次有新的理解就更新文章,我挺期待之后到了超过字数限制不得不写成系列文章的那一天.
2018/04/28 新增声明式渲染 && 生命周期对比 && 状态(State) OR 属性(Data) && Props组件通信
2018/05/02 新增状态管理
2018/06/07 新增mobx状态管理
2018/11/06 补充更新机制和状态管理对比
MVC
MVC全名是Model View Controller,把应用程序分成三部分分别是:
- Model(业务模型): 用于管理应用程序数据处理逻辑的部分,通过观察者模式(Pub&Sub / Events)发送消息给View;
- View(视图界面): 用于处理数据显示的部分,注册并接收Model的数据更新视图,通常视图是依据模型数据创建的;
- Controller(控制器): 用于连接模型和视图控制应用程序的流程(事件绑定等),通常控制器负责响应View的事件(路由,键盘,鼠标等),调用Model的接口进行操作;
(这些简单的东西我就懒得特意画图了,直接百度图片找张清晰的拿来用的..)
(更多内容请自行查阅,本节到此为止了.)
流程
- 当用户在视图界面中发生交互事件,View捕获到这个操作会把处理的权利交移给Controller;
- Controller会对来自View数据进行预处理并决定调用Model的相关暴露接口;
- Model执行相关的业务逻辑更改数据之后会通知有关的View重新渲染;
- View收到通知后从Model请求最新的数据,然后重新渲染相关视图界面;
还有一种情况: MVC允许在不改变视图外观的情况下改变视图对用户输入的响应方式,只要用不同种类的controller实例替换即可。例如改变URL触发hashChange事件,用户不经过View直接到达Controller最后再影响回View.
优点:
- 耦合性低,MVC 分层有助于管理复杂的应用程序,同时也让应用程序的测试更加容易;
- 重用性高,多个视图能共享一个模型,可以做到多视图同时更新;
- 生命周期成本低,MVC使开发和维护用户接口的技术含量降低;
- 部署快,只需要部署对应部分代码而不是完整项目;
- 可维护性高,分离视图层和业务逻辑层也使得应用更易于维护和修改;
- 有利软件工程化管理,可以使用控制器来联接不同的模型和视图去完成用户的需求;
缺点:
- 没有明确的定义,完全理解MVC并不是很容易,现存就有很多对MVC不同解读实现的方式;
- 不适合小型,中等规模的应用程序,花费大量时间将MVC应用到规模并不是很大的应用程序通常会得不偿失;
- 增加系统结构和实现的复杂性,对于简单的界面,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率;
- 视图与控制器间的过于紧密的连接,视图没有控制器的存在,其应用是很有限的,反之亦然,导致测试困难(依据模型数据创建部分);
- 视图对模型数据的低效率访问,依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据;
- 观察者模式由于事件触发的隐式行为可能导致很难查找问题的来源并影响其解决;
MVP
MVP全名是Model-View-Presenter,从经典的模式MVC演变而来,分两种情况:
Passive View(被动视图)
Presenter占据绝对主导地位,掌控著Model和View,而后两者之间互不联系.
- Model(业务模型): 用于管理应用程序数据处理逻辑的部分,通过观察者模式(Pub&Sub / Events)发送消息给Presenter;
- View(视图界面): 用于处理数据显示的部分,传递事件和提供相关接口给Presenter;
- Presenter(派发器): 作为中间层同步控制著Model数据修改和View视图变化;
(这些简单的东西我就懒得特意画图了,直接百度图片找张清晰的拿来用的..)
(更多内容请自行查阅,本节到此为止了.)
流程:
- 当用户在视图界面中发生交互事件,View捕获到这个操作会把处理的权利交移给Presenter进行处理;
- Presenter需要时候可以获取Model其中的数据,并对Model进行操作更新;
- Model数据变化之后会通知Presenter;
- Presenter收到通知后会执行View提供的相关接口重新渲染相关视图界面;
MVC和MVP(Passive View)区别:
- 后者View和Model完全解耦,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部;
- 前者Controller只能通过Model间接触发View自行更新视图,后者View不再负责更新视图,而是提供接口给Presenter执行;
Supervising Controller(监督控制器)
Presenter依旧占据主导地位,但是会把一部分简单的视图逻辑(如双向绑定)交还给View和Model进行处理,自身负责其他复杂的视图逻辑.
- Model(业务模型): 用于管理应用程序数据处理逻辑的部分,通过观察者模式(Pub&Sub / Events)发送消息给Presenter或者View;
- View(视图界面): 用于处理数据显示的部分和接管部分简单的视图逻辑,同步简单的视图和模型的状态,传递事件和提供相关接口给Presenter;
- Presenter(派发器): 作为中间层同步控制著Model数据修改和View视图变化;
MVC和MVP(Supervising Controller)区别:
1, 视图支持Presenter和View两种途径更新;
优点:
1, 模型与视图高度分离,我们可以修改视图而不影响模型;
2, 可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部;
3, 可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑;
4, 如果把逻辑放在Presenter中,就可以脱离用户接口来测试这些逻辑(单元测试);
缺点:
1, 由于对视图的渲染放在了Presenter中,所以View和Presenter的交互会过于频繁并且难以维护;
MVVM
MVVM全名是Model-View-ViewModel,本质上就是MVC的改进版,也可以说是MVP的改良版,把应用程序分成三部分分别是:
- Model(业务模型): 用于管理应用程序数据;
- View(视图界面): 通过使用模板语法来声明式的将数据渲染进DOM;
- ViewModel(视图模型): 包含了领域模型(Domain Model)和视图的状态(State),核心就是双向绑定技术(Two-Way-Data-Binding),View和Model之间数据同步操作交由给内部的Binder/Data-binding engine处理;
MVP和MVVM区别: 它使用 数据绑定(Data Binding)
、依赖属性(Dependency Property)
、命令(Command)
、路由事件(Routed Event)
来搞定与view层的交互, 当ViewModel对Model进行更新的时候,会通过数据绑定更新到View.
(这些简单的东西我就懒得特意画图了,直接百度图片找张清晰的拿来用的..)
(更多内容请自行查阅,本节到此为止了.)
优点:
- 双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然;
- 解放MVP大量手动同步状态的问题,提高了代码的可维护性;
- 简化测试,Model正确就能保证View输出;
缺点:
- 大型项目的绑定数据较多会提高维护成本;
- View里的数据绑定无法检测断点,只能从Model下手;
React VS Vue
两个框架是现在最热门的选择之一,它们既类似又不同.
- 使用 Virtual DOM
- 提供了响应式 (Reactive) 和组件化 (Composable) 的视图组件。
- 将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库。
React就是MVC里的V,只专注视图层,而Vue算是MVVM框架,双向绑定是特色之一.
介绍
我们先看看它们自己的官方介绍:
React
React is a JavaScript library for building user interfaces.
- Declarative: React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes. Declarative views make your code more predictable, simpler to understand, and easier to debug.
- Component-Based: Build encapsulated components that manage their own state, then compose them to make complex UIs. Since component logic is written in JavaScript instead of templates, you can easily pass rich data through your app and keep state out of the DOM.
- Learn Once, Write Anywhere: We don't make assumptions about the rest of your technology stack, so you can develop new features in React without rewriting existing code. React can also render on the server using Node and power mobile apps using React Native.
翻译:
React是一个用于构建用户界面的Javascript库.
- 声明式: React让你无痛创建交互式UI界面,为你的App应用程序里的每个状态设计简单的视图,并且当你的数据改变之后会进行高效地更新和正确地渲染对应组件,声明式视图让你的代码更可预测、更易于理解和更容易调试.
- 组件化: 构建封装组件管理它们自己的内部状态,然后组合它们去构建复杂UI界面.因为组件逻辑写在Javascript而不是模板里,你能轻松注入丰富的数据到你的App并且状态脱离在Dom之外.
- 只需学习一次,就能用到任何地方,我们不对你的其余技术栈作出假设,所以你能在React里开发新的特性而不需要重写你的现有代码.React也能使用Nodejs进行服务器渲染和使用React Native进行移动端的丰富开发.
Vue
Vue (pronounced /vjuː/, like view) is a progressive framework for building user interfaces. Unlike other monolithic frameworks, Vue is designed from the ground up to be incrementally adoptable. The core library is focused on the view layer only, and is easy to pick up and integrate with other libraries or existing projects. On the other hand, Vue is also perfectly capable of powering sophisticated Single-Page Applications when used in combination with modern tooling and supporting libraries.
翻译:
Vue.js (读音 /vjuː/,类似于 view) 是一套构建用户界面的渐进式框架。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。Vue 的核心库只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与单文件组件和 Vue 生态系统支持的库结合使用时,Vue 也完全能够为复杂的单页应用程序提供驱动。
声明式渲染
React(官方写法)
React 组件实现一个 render() 方法,它接收输入数据并返回显示的内容。此示例使用类似XML的语法,称为 JSX 。输入数据可以通过 this.props 传入组件,被 render() 访问。
class HelloMessage extends React.Component { render() { return ( <div> Hello {this.props.name} </div> ); } } ReactDOM.render( <HelloMessage name="Taylor" />, mountNode );
Vue(官方写法)
Vue.js 的核心是一个允许采用简洁的模板语法来声明式的将数据渲染进 DOM 的系统
<div id="app"> {{ message }} </div> // --------省略-------- var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })
HTML
React JSX
他是 JavaScrip 的一种扩展语法。 React 官方推荐使用这种语法来描述 UI 信息。JSX 可能会让你想起某种模板语言,但是它具有 JavaScrip 的全部能力,从本质上讲,JSX 只是为 React.createElement(component, props, ...children) 函数提供的语法糖。
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
- 它是类型安全的,在编译过程中就能发现错误。
- 使用 JSX 编写模板更加简单快速。
JSX 对使用React 不是必须的。
Vue Templates
Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,在应用状态改变时,Vue 能够智能地计算出重新渲染组件的最小代价并应用到 DOM 操作上。
事实上 Vue 也提供了渲染函数,甚至支持 JSX。然而,默认推荐的还是模板。
- 对于很多习惯了 HTML 的开发者来说,模板比起 JSX 读写起来更自然。这里当然有主观偏好的成分,但如果这种区别会导致开发效率的提升,那么它就有客观的价值存在。
- 基于 HTML 的模板使得将已有的应用逐步迁移到 Vue 更为容易。
- 这也使得设计师和新人开发者更容易理解和参与到项目中。
- 你甚至可以使用其他模板预处理器,比如 Pug 来书写 Vue 的模板。
对比
个人感觉两者其实上手速度都挺快,相比之下JSX除了修改部分属性名字跟普通HTML变化不算大,Templates额外添加很多自定义功能帮助开发者做更多的事,框架痕迹也比较重.
我们可以把组件区分为两类:一类是偏视图表现的 (presentational)推荐使用模板,一类则是偏逻辑的 (logical)推荐使用 JSX 或渲染函数。
//Jsx写法 <div className="list"> <ol> { todos.map(item =><li>{todo.text}</li>) } </ol> </div>
//Templates写法 <div class="list"> <ol> <li v-for="todo in todos"> {{ todo.text }} </li> </ol> </div>
CSS
React
React 中推荐通过 CSS-in-JS 的方案实现的 (比如 styled-components、glamorous 和 emotion),虽然在构建时将 CSS 提取到一个单独的样式表是支持的,但 bundle 里通常还是需要一个运行时程序来让这些样式生效。当你能够利用 JavaScript 灵活处理样式的同时,也需要权衡 bundle 的尺寸和运行时的开销
var styleObj = { color:"blue", fontSize:40, fontWeight:"normal" }; --------省略-------- <h1 style={styleObj} className="alert-text">Hello</h1>
Vue
Vue 设置样式的默认方法是单文件组件里类似 style 的标签。让你可以在同一个文件里完全控制 CSS,将其作为组件代码的一部分。
<style scoped> @media (min-width: 250px) { .list-container:hover { background: orange; } } </style>
这个可选 scoped
属性会自动添加一个唯一的属性 (比如 data-v-21e5b78) 为组件内 CSS 指定作用域,编译的时候 .list-container:hover 会被编译成类似 .list-container[data-v-21e5b78]:hover。
最后,Vue 的单文件组件里的样式设置是非常灵活的。通过 vue-loader,你可以使用任意预处理器、后处理器,甚至深度集成 CSS Modules——全部都在 <style> 标签内。
对比
各有好坏吧,React侵入式做法不太喜欢,Vue组件式做法倒也还行,个人而言更倾向独立css样式外部引入易于管理.
状态(State) OR 属性(Data)
React
State是私有的,并且由组件本身完全控制。
不要直接修改 state(状态),setState() 代替;
// 错误 this.state.comment = 'Hello'; // 正确 this.setState({comment: 'Hello'});
state(状态)更新会被合并,React 为了优化性能,有可能会将多个setState()调用合并为一次更新;
componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ comments: response.comments }); }); }
state(状态) 更新可能是异步的,不能依赖他们的值计算下一个state(状态);
// 错误 this.setState({ counter: this.state.counter + this.props.increment, }); // 正确 //另一种 setState() 的形式,它接受一个函数而不是一个对象。这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数 this.setState((prevState, props) => ({ counter: prevState.counter + props.increment }));
Vue
当一个 Vue 实例被创建时,它向 Vue 的响应式系统中加入了其 data 对象中能找到的所有的属性。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
只有当实例被创建时 data 中存在的属性是响应式的
// 有效 data: { visitCount1: 0 } --------省略-------- // 触发任何视图的更新 vm.visitCount1 = 2 // 不会触发任何视图的更新 vm.visitCount2 = 2
对比
React是属于单向控制,即只能是通过改变State从而改变视图,我们可以利用JS方法像表单等场景模拟双向绑定的效果,实际还是由State去触发视图更新
Vue是属于双向绑定,原理是通过 Object.defineProperty 监听劫持data对象的getter/setter属性来实现的
Props组件通信
一个组件可以选择将数据向下传递,作为其子组件的 props(属性).
React
父组件单向控制
//父组件传递数据 <Child num={this.state.num} /> //--------省略-------- //子组件读取数据 <h2>It is {this.props.num}.</h2>
子组件自主控制
//父组件传递数据 <Child num={this.state.num} /> //--------省略-------- constructor(props) { super(props); this.state = { //初始化成state num: this.props.num, }; }
父子组件双向控制
//传递修改函数 class Father extends Component { construtor(props) { super(props); this.state = { num: 1, }; } onChangeState(val) { this.setState(val); } render() { <Child num={this.state.num} onClicked={this.onChangeState.bind(this)} />; } } //调用修改函数添加入参 class Child extends Component { render() { <button onClicked={() => this.props.onClicked({num: 2})}> It is {this.props.num}. </button>; } }
Vue
父组件单向控制
//父组件传递数据 <child message="hello!"></child> //--------省略-------- //子组件要显式地用 props 选项声明它预期的数据 Vue.component('child', { // 声明 props props: ['message'], // 就像 data 一样,prop 也可以在模板中使用 // 同样也可以在 vm 实例中通过 this.message 来使用 template: '<span>{{ message }}</span>' })
子组件自主控制
//父组件传递数据 <child message="hello!"></child> //--------省略-------- //1, 定义一个局部变量,并用 prop 的值初始化它: props: ['message'], data: function () { return { msg: this.message } } template: '<span>{{ msg }}</span>' //2, 定义一个计算属性,处理 prop 的值并返回: props: ['message'], computed: { msg: function () { return this.message } } template: '<span>{{ msg }}</span>'
父子组件双向控制
//父组件传递数据 <child v-bind:message="message" v-on:update:message="message = $event"></child> //--------可用.sync 修饰符替代-------- //后面传递的message是变量,非字符串 //<child :message.sync="message"></child> //--------省略-------- //子组件 props: ['message'], data: function () { return { msg: this.message } } watch: { //监听msg变化自动通信父组件更改 msg(val) { this.$emit('update:message', newMsg) }, },
生命周期对比
React
React生命周期 | 作用 |
---|---|
getDefaultProps | 作用于组件类,只调用一次,返回对象用于设置默认的props,对于引用值,会在实例中共享 |
getInitialState | 作用于组件的实例,在实例创建时调用一次,用于初始化每个实例的state,此时可以访问this.props。 |
componentWillMount | 在完成首次渲染之前调用,此时仍可以修改组件的state |
render | 必选的方法,创建虚拟DOM,该方法具有特殊的规则: 1) 只能通过this.props和this.state访问数据; 2) 可以返回null、false或任何React组件; 3) 只能出现一个顶级组件(不能返回数组); 4) 不能改变组件的状态; 5) 不能修改DOM的输出; |
componentDidMount | 真实的DOM被渲染出来后调用,在该方法中可通过this.getDOMNode()访问到真实的DOM元素。此时已可以使用其他类库来操作这个DOM。在服务端中,该方法不会被调用。 |
componentWillReceiveProps | 组件接收到新的props时调用,并将其作为参数nextProps使用,此时可以更改组件props及state |
shouldComponentUpdate | 组件是否应当渲染新的props或state,返回false表示跳过后续的生命周期方法,通常不需要使用以避免出现bug。在出现应用的瓶颈时,可通过该方法进行适当的优化。在首次渲染期间或者调用了forceUpdate方法后,该方法不会被调用 |
componentWillUpdate | 接收到新的props或者state后,进行渲染之前调用,此时不允许更新props或state。 |
componentDidUpdate | 完成渲染新的props或者state后调用,此时可以访问到新的DOM元素。 |
componentWillUnmount | 组件被移除之前被调用,可以用于做一些清理工作,在componentDidMount方法中添加的所有任务都需要在该方法中撤销,比如创建的定时器或添加的事件监听器 |
componentDidCatch | 16.x新增,捕获全局异常来进行页面的友好提示 |
Vue生命周期 | 作用 |
---|---|
beforeCreate | 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用 |
created | 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见 |
beforeMount | 在挂载开始之前被调用:相关的 render 函数首次被调用。该钩子在服务器端渲染期间不被调用 |
mounted | el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内. 注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted |
beforeUpdate | 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。该钩子在服务器端渲染期间不被调用 |
updated | 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之,注意 updated 不会承诺所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以用 vm.$nextTick 替换掉 updated:该钩子在服务器端渲染期间不被调用 |
activated | keep-alive 组件激活时调用。该钩子在服务器端渲染期间不被调用 |
deactivated | keep-alive 组件停用时调用。该钩子在服务器端渲染期间不被调用 |
beforeDestroy | 实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。 |
destroyed | Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁 |
errorCaptured | 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播 |
React示意图
Vue示意图
再渲染性能
React
在React里渲染机制是以组件单位更新的,也就是说当数据发生改变,当前视图包括其中的组件子组件和底下的子组件都会一起更新,这种违反性能的机制肯定是有问题的,所以React提供了生命周期shouldComponentUpdate方法让你决定当前组件是否更新,还有一个PureComponent方法会自动检测到state或者props发生变化时,才会调用render方法.但是只是浅比较,如果搭配ImmutableJs持久化数据据说性能大大的提升.除此之外还能节省大量的手动比较的代码和时间,
简单描述过程
- 调用render函数利用JS生成虚拟Dom树,直到数据state/props发生改变的时候,render函数会被再次调用渲染出另外一棵虚拟Dom树;
比较前后两棵Dom树同层级的节点区别,非同层级节点包括所属子节点整个直接删除重新创建;
- 不同的节点类型,直接替换.
- 相同节点类型当中的DOM节点,替换属性.
- 相同类型当中的组件节点,继续递归比较所属子节点.
- DOM节点的递归children,继续递归比较children.
- 列表比较.赋予唯一的key作比较.
- 更新视图中差异地方;
Vue
在 Vue 应用中,组件的依赖是在渲染过程中自动追踪的,所以系统能精确知晓哪个组件确实需要被重渲染,因为Vue是使用 Object.defineProperty对绑定属性进行数据劫持的,所以比起React组件式更新它能够精确接收到哪些组件才是需要渲染的.
- Vue 将遍历此data对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter.
- 每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖.
- 在属性被访问和修改时通知对应的组件.
- 对应的组件再次调动渲染函数,生成虚拟Dom树对比,实现更新.
路由
React-Router
这是React-Router3的模板写法,实际到了React-Router4的API和思想都有些大的差异
import React from 'react' import { render } from 'react-dom' // 首先我们需要导入一些组件... import { Router, Route, Link } from 'react-router' // 然后我们从应用中删除一堆代码和 // 增加一些 <Link> 元素... const App = React.createClass({ render() { return ( <div> <h1>App</h1> {/* 把 <a> 变成 <Link> */} <ul> <li><Link to="/about">About</Link></li> <li><Link to="/inbox">Inbox</Link></li> </ul> {/* 接著用 `this.props.children` 替换 `<Child>` router 会帮我们找到这个 children */} {this.props.children} </div> ) } }) // 最后,我们用一些 <Route> 来渲染 <Router>。 // 这些就是路由提供的我们想要的东西。 React.render(( <Router> <Route path="/" component={App}> <Route path="about" component={About} /> <Route path="inbox" component={Inbox} /> </Route> </Router> ), document.body)
Vue-Router
Vue-Router3的模板写法
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter) // 1. 定义(路由)组件。 // 可以从其他文件 import 进来 const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } // 2. 定义路由 // 每个路由应该映射一个组件。 其中"component" 可以是 // 通过 Vue.extend() 创建的组件构造器, // 或者,只是一个组件配置对象。 // 我们晚点再讨论嵌套路由。 const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ] // 3. 创建 router 实例,然后传 `routes` 配置 // 你还可以传别的配置参数, 不过先这么简单著吧。 const router = new VueRouter({ routes // (缩写)相当于 routes: routes }) // 4. 创建和挂载根实例。 // 记得要通过 router 配置参数注入路由, // 从而让整个应用都有路由功能 const app = new Vue({ router }).$mount('#app') // 现在,应用已经启动了!
状态管理
状态管理库有很多种,我只是举出我用过的例子,并不是必须的.下面只会展示最基本的代码,想跑完整流程还得看文档.
Redux
Redux 可以用这三个基本原则来描述:
- 单一数据源: 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
- State 是只读的: 唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
- 使用纯函数来执行修改: 为了描述 action 如何改变 state tree ,你需要编写 reducers。
Actions: 把数据从应用传到store的有效载荷。它是store数据的唯一来源.
/* * action 类型 */ export const ADD_TODO = 'ADD_TODO'; /* * action 创建函数 */ export function addTodo(text) { return { type: ADD_TODO, text } }
Reducers: 指定了应用状态的变化如何响应actions并发送到store的,记住actions只是描述了有事情发生了这一事实,并没有描述应用如何更新state。
import { combineReducers } from 'redux' import { ADD_TODO, } from './actions' function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [ ...state, { text: action.text, completed: false } ] default: return state } } const todoApp = combineReducers({ todos }) export default todoApp
Store: 就是把Actions和Reducers联系到一起的对象.
- 维持应用的 state;
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器;
- 通过 subscribe(listener) 返回的函数注销监听器。
import { createStore } from 'redux' import todoApp from './reducers' let store = createStore(todoApp)
单向数据流
(这些简单的东西我就懒得特意画图了,直接百度图片找张清晰的拿来用的..)
- 定义Action描述对象;
- 通过dispatcher触发Action对象;
- Reducer响应变化更新到Store状态管理对象;
- 注入Store状态的组件视图更新;
- 界面交互触发Action再次跑相应流程;
进阶:
- Action利用中间件发起异步请求;
- Reducer逻辑拆分;
- 组件注入部分Store状态;
等等,Redux 中文文档,(更多内容请自行查阅,本节到此为止了.)
Mobx
另一种实现方式,具体可看Mobx4.X状态管理入门
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
Mutation: 必须是同步函数,更改Vuex的store中的状态的唯一方法是提交mutatio
// mutation-types.js export const SOME_MUTATION = 'SOME_MUTATION' // store.js import Vuex from 'vuex' import { SOME_MUTATION } from './mutation-types' const store = new Vuex.Store({ state: { ... }, mutations: { // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名 [SOME_MUTATION](state) { // mutate state } } })
Action 类似于mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态;
- Action 可以包含任意异步操作;
actions: { incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) } }
Getter: 从store中的state中派生出一些状态
getters: { // ... doneTodosCount: (state, getters) => { return getters.doneTodos.length } }
State: 包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在.
const app = new Vue({ el: '#app', // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件 store, components: { Counter }, template: ` <div class="app"> <counter></counter> </div> ` })
Module: Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态
单向数据流
(官网来的)
Vuex
对比
- Redux的Action可以是异步或者同步函数返回Javascript对象,Vuex分为同步函数不限制格式的mutation和可包含异步函数的action
- React直接dispatch action触发状态更新,Vuex是dispatch action提交mutation再commmit触发状态更新
Redux指定了应用状态的变化如何响应actions并发送到store的reducer,且必须为纯函数.Vuex实际上是使用mutation更新状态
- Redux: (dispatch)action -> (reducer)store
- Vuex: (dispatch)action -> (commit)mutation -> (mutate)store
- Vuex可以用getter派生出一些状态,就像计算函数会被缓存起来只有依赖变化之后才会重新计算.
(更多内容请自行查阅,本节到此为止了.)
官方脚手架
React 官方提供了 create-react-app,诟病的地方比较多.
- 它不允许在项目生成时进行任何配置,而 Vue 支持 Yeoman-like 定制。
- 它只提供一个构建单页面应用的单一模板,而 Vue 提供了各种用途的模板。
- 它不能用用户自建的模板构建项目,而自建模板对企业环境下预先建立协议是特别有用的。
更多人选择自己搭建或者使用民间打包库.
Vue 提供了 Vue-cli 脚手架,能让你非常容易地构建项目,包含了 Webpack,Browserify,甚至 no build system,但是有些设置例如Scss预处理器等自定义配置需要自己搞,总的来说相当实用.
入门难度
React正常来说需要搭配Jsx和Es6语法和构建环境;
Vue可以直接引入js库就能开发,而且内置的功能属性比React多得多