前言
因为没有明确的界定, 这里不讨论正确与否, 只表达个人对前端 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 多得多