当初上手使用的时候就直接使用的 Vue,简单方便且直接。但最近在思考为什么三大框架得以挤掉 jQuery 变得流行,至于流行的原因众说纷纭,但我认为真正的原因只有两个:
- 提供一种简单方便的开发范式,你不需要知道框架的运行原理就可以快速方便的开发出一个网站
- 第二个原因则是最重要的:因为数据和 DOM 的同步并不简单。
简单方便的开发范式
先说第一个:无论是 Vue、React还是Angular实际上都默认提供了一种简单方便并且可维护的项目组织结构方式:组件。
无论是 Vue的单文件组件还是 React 的tsx都鼓励将一个完整的网站、页面的不同组成部分拆分成一个组件,再像搭积木一样将它们拼接起来。这样所构建的网站可维护性良好、更容易构建出来超大规模项目 — — 超过数十万行代码的 js 项目。
你再也不需要使用类似于jQuery一样类似于意大利面条一样的开发方式了。当然,事实上在 MVVM 框架出现之前有各种各样的方法将项目拆分,但的确很少有像 jsx 一样优雅的项目组织方式了。
万幸我没有经历过那样一个时代。
数据与 DOM 的同步问题
JavaScript 的主要运行环境之一是浏览器,其责任是负责视图的渲染与用户的交互。随着前端页面的复杂程度的增长,js 必须承担起更大的责任。数据的改变与DOM的同步问题变成了一个非常头痛的问题。
这里我想做一个简单的todo-list来说明这个问题。
class Todo {
constructor(msg) {
this.msg = msg;
this.dom = null;
this.createTodoItem();
}
createTodoItem() {
const todo = document.createElement('div');
todo.innerText = this.msg;
this.dom = todo;
}
remove() {
this.dom.remove();
this.dom = null;
}
destroy() {
this.remove();
}
}
class Diff {
constructor(option, index, data) {
this.op = option;
this.index = index;
this.data = data;
}
}
class TodoWrapper {
constructor(wrapper) {
if (typeof wrapper === 'string') {
wrapper = document.querySelector(wrapper);
}
this.wrapper = wrapper;
this.container = null;
this.todos = [];
this.todoDoms = [];
this.createContainer();
}
render(diff) {
switch (diff.op) {
case 'add':
this.addTodoItemDom(diff.index, diff.data);
break;
case 'remove':
this.removeTodoItemDom(diff.index, diff.data);
break;
}
}
addTodoItemDom(index, todo) {
const sibling = this.todoDoms[index];
if (this.todoDoms.indexOf(todo) >= 0) {
return;
}
if (!sibling) {
this.container.appendChild(todo.dom);
} else {
this.container.insertBefore(todo.dom, sibling);
}
}
removeTodoItemDom(index) {
const todo = this.todoDoms[index];
todo.remove();
this.todoDoms.splice(index, 1);
}
addTodoItem(index, todo) {
if (this.todos.indexOf(todo) >= 0) {
return;
}
this.todos.splice(index, 0, todo);
const diff = new Diff('add', index, todo);
this.render(diff);
}
removeTodoItem(index, todo) {
if (this.todos.indexOf(todo) >= 0) {
return;
}
this.todos.splice(index, 1, todo);
const diff = new Diff('remove', index, todo);
this.render(diff);
}
createContainer() {
const container = document.createElement('div');
container.classList.add('todo-container');
this.wrapper.appendChild(container);
this.container = container;
}
destroy() {
this.wrapper = null;
this.container = null;
}
}
如此复杂的操作也是MVVM出现的原因。我们不需要再关注 DOM 的同步问题,只需要关注数据的改变即可。当数据改变的时候,我们的视图会随之变化。
用Vue举例
// in .vue
<template>
<div class="todo-wrapper">
<template v-for="todo in todoList">
<Todo :msg="todo.msg" />
</template>
</div>
</template>
<script>
export default Vue.extend({
name: 'todoList',
components: {
Todo,
}
data() {
return {
todoList: [],
}
}
})
</script>
是不是简单多了?
三大框架的流行使得现在出现很多脱离了框架就不会进行网站开发的“偏科生”
之前有一篇特别有名的文章 2015前端组件化框架之路 · Issue #19 · xufei/blog · GitHub 推荐在学习框架的同时读一读,大概可以理解在15年那个分叉路口的风起云涌,也大致能理解为什么现在三大框架三足鼎立。
有些框架被历史放弃,有些则激流勇进。
被神化的MVVM
不久之前 Vue 在 github 的 star 数量超过了 react。但其实随着我接触前段时间的增长,我发现我更加喜欢 react 的开发方式,即使我最先接触的是 Vue。
用于比较 Vue 与 React 区别的文章有很多,我在这里只想谈谈我自己的使用感受。
就使用方式而言,Vue 是传一个配置文件给构造函数,而 React 则是配合 babel 的 jsx 转换自己写一个组件的类。
ES6提供了标准的 Class,这让构建一个 React 组件时继承方式变得简单,当然,Vue 也提供了自己的 mixin 方式来实现,但总觉得有些别扭。
当然,我并不是说Vue不好。
什么是 MVVM 呢?
就我自己而言的理解来说,数据驱动,使开发人员的关注点从视图分离,不需要注重数据与视图同步的过程。
就实现方式而言,Vue通过es5所提供的 getter 和 setter 方法来实现依赖手机与视图更新,而React 则是通过手动触发。
就效果而言,Vue的实现方式可以实现精准dom更新(因为Vue是深度绑定,如果是数组则会遍历整个数组使其响应化),而React则因为使用了数据更新的浅拷贝换取性能优势。
这里仅以 Vue 为例
相信对于 Vue 有一定了解的人都听说过 Watcher, Vue 实际上是使用观察者模式来实现 MVVM(不是发布订阅模式)
在Vue初始化的时候实际上会进行一次渲染以触发数据的getter进行依赖收集。由于依赖收集与视图的更新依赖于getter/setter,为了达到这个目的,Vue需要深度遍历Data的每一个属性使其响应化(深度遍历每一个属性为其绑定上getter/setter,如果是数组,则会遍历整个数组)。
当页面的变量达到非常大的时候,对于性能的需求与内存的需求都是一个不小的数字。
我没有深入研究过React,不过相信过程都是差不多的,都需要进行依赖收集。
MVVM的确是前端工程化的利器,但还是需要用客观的思想去看待问题,有时候自己瞎想,当工程规模更大的时候、更大的时候、更大的时候,MVVM 或许就会成为限制前端发展的瓶颈。