原创

Vue 和 React 的异同

真正的学习通常发生在实践的过程中,为了了解而去进行的学习往往容易脱离真实环境,不够彻底。

新工作新业务的驱动,导致我需要重新学习 React 和 TypeScript,所以,这是我对 Vue 和 React 异同点的一次总结,我希望不仅仅局限于使用层面。

叙述结构按照组件化进行描述,即:异同点会在同一个话题进行总结。

数据流

Vue

Vue 是基于 Object.defineproperty 及观察者模式实现的双向绑定,包括对所有 “类 DOM 原生元素” 的数据流包装,各种双向控件的 v-model 就是一种表现。 前端框架里 Directive 的实现最早来自于 angular.js,但在以 angular.js 为代表的 MVC 时代,Controller 往往才是主角,相当于 Vue 中的组件的 Script 部分。

至于数据流的单双向问题,更多取决于开发者的业务实现,在 Vue2.0 之后,官方提倡优先以单向数据流为设计原则。

由于基于 Object.defineproperty 所以 Vue.js 有条件在核心就支持 computed 等相关数据动态计算功能,而 React 中是不存在的。

React

React 本身不存在整循环的数据绑定,它更专注于 State => View 的最优更新这件事。

所以,任何事件产生的数据 Input 需通过我们自组织的业务逻辑来完成 State 的更新,继而 Diff 计算后进行 View 更新; 并且,React 依然提供了允许我们手动管理 View 更新与否的能力,而在 Vue 中这一点是被内部自动优化的。

小结

Vue 靠 Object.defineproperty 对象的拦截知道何时更新视图,React 靠 setState 的代理模式来做到同样的事。 假设丢弃或不使用 Vue 中 v-model 类似的 API,你会发现单事件流来说和 React 其实没什么区别。

Vue 的组件系统中,我们往往假设数据是可变的,所以也往往都是直接改变数据,正因为如此,我们可以得到最细粒度更新的效果。

React 提倡数据不可变,每一次我们应该创建一个新的 State 覆盖之前的,至于最终更新所需的资源靠 Diff 算法来优化。

两种拦截模式背后进行了怎样复杂的计算?

Vue 在不同环境下会降级为不同的异步 API 作为一个数据捕获周期,如Promise.resolve().then, MutationObserver, setTimeout

React 根据版本不同,有稍微不同的更新策略,大致是维护一个状态队列,将所有状态的更改推进队列,按照不同的事务类型进行不同周期的批量更新。

So

  • React 中的state 等价于 Vue 中的data;
  • React 中的setState 等价于 Vue 中的直接赋值;
  • React 中的setState.callback 等价于 Vue 中的$nextTick;
  • React 中的render 等价于 Vue 中的render/template;

更详细可以参考官方文档。

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
// Vue 中的双向绑定 export default { name: 'some-component', template: `<input name="count" v-model="count" />`, data() { return { count: 1 } } } // Vue 中实现单向数据流 export default { name: 'some-component', template: `<input name="count" :value="count" @input="handleChange" />`, data() { return { count: 1 } }, methods: { handleChange(event) { this.count = event.target.value } } } // React 中实现单向数据流 class SomeComponent extends React.Component { constructor() { this.state = { count: 1 } } handleChange(event) { this.setState({ count: event.target.value }) } render() { return ( <div> <input value={this.state.count} onChange={this.handleChange} /> </div> ) } }

事件流

Vue

按照我的理解,Vue 对于原生 DOM 元素的原生事件都有进行一层包装,简单说就是将所有 dispatchEvent 转化为 基于中介者模式的$emit, 最终导致了事件传播形式的统一,开发者只需要关注框架的事件派发标准就可以。

并且,Vue 中对于各 DOM 元素绑定的事件会被转换为真实 DOM 的事件绑定,顺序为:

TemplateEvent => DomEvent + Decorate => CompoentEvent

各种修饰符,就是在 “包装转化的这一层” 对事件进行了不同程度的:冒泡处理、拦截处理等等...

比如一个例子:

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
<!-- 事件捕获 + 停止传播 --> <div class="box" @click.capture.stop="handleClick"></div> <!-- 转换后的伪代码 --> <script> box.addEventListener('click', event => { event.stopPropagation() handleClick(event) }, false) </script>

React

在 React 中,为了兼顾 浏览器兼容性、事件统一机制的高性能,React 内置了一套 SyntheticEvent 用于统一事件管理的系统。

简单说,从头到尾,React 只为最顶级元素绑定了一大套事件,我们为某某元素绑定的事件都相当于在 React 的 SyntheticEvent 里 “注册”; 当事件发生时,我们注册的事件会相应地被触发。

我们为 “类原生 DOM” 组件/元素 绑定的事件并不会真正地转化为真正的 Dom 事件,而是被挂载在 SyntheticEvent 系统的不同周期内进行触发和处理; 这些挂载的事件中得到的 Event 对象是经过 SyntheticEvent 包装后的复合对象,由原生事件产生,由 SyntheticEvent 加工得到的。

如果一定要得到原生事件属性,可通过nativeEvent访问得到;SyntheticEvent 支持大部分 Dom 原生事件,但不是全部; 如果需要支持 SyntheticEvent 之外的事件需要在 React 的生命周期中挂载和卸载事件,也就是原生的window.addEventListener

由于 SyntheticEvent 是共享的。意味着在调用事件回调之后,SyntheticEvent 对象将会被重用,并且所有属性会被置空。这是出于性能因素考虑的。 因此,您无法以异步方式访问事件。

小结

从使用层面看,这俩也没啥区别,hahaha~

              
  • 1
  • 2
  • 3
  • 4
  • 5
<!-- Vue 中事件捕获 + 停止传播 --> <div class="box" @click.capture.stop="handleClick"></div> <!-- React 中事件捕获 + 停止传播 --> <div class="box" onClickCapture={event => event.stopPropagation() && handleClick()}></div>

组件通信

两者的通信机制基本类似。

  • "父 => 子":数据不可变,单向数据流
  • "子 => 父":事件派发(Vue),回调派发(React)

复杂的跨组件通信,两者都有对应的MessageBus/Store的解决方案。

Vue

Vue 内置了基于中介者模式事件派发机制,使用 $emit$on 作为事件额派发器和接收器。

React

在组件通信里,React 的事件系统并不能为其进行服务,所以往往是将 父组件的事件处理 以 props 的形式传递给子组件直接调用。

小结

这两者的区别更多体现在事件传递,感觉 React 回调传递会导致组件间不能彻底解耦,当你为宿主组件编写处理逻辑时还需要了解子组件的 API、内部逻辑。

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
<!-- Vue 父子通信 --> <template> <div class="child" @click="$emit('click-children', 'i am child 1 event data')" /> </template> <template> <div class="parent"> <children @click-children="handleChildrenClick"></children> </div> </template> <!-- React 父子通信 --> <script> function Child(props) { return <div className="child" onClick={props.onChildClick}></div> } class Parent extends React.Component { constructor(props) { super(props) } onChildClick(event) { console.log('onChildClick', event) } render() { return ( <Child onChildClick={this.onChildClick} /> ) } } </script>

Diff 算法

没仔细研究过,研究过也没看懂,所以,不知道。

模板语法

Vue

数据 IO 的类型采用不同前缀体现,当然本质上也都是内置的 Directive。 Directive 提供了大部分 View 层所需的展示逻辑功能。

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
<!-- 数据传递: --> <some-compnent :attrdata="someData" /> <some-compnent strattr="someStr" /> <!-- 事件绑定: --> <some-compnent @event.decorate="eventHandle" /> <!-- 指令绑定: --> <some-compnent v-some-directive:name="directiveAttr" /> <!-- 渲染逻辑: --> <a-compnent v-if="isTrue" /> <b-compnent v-else /> <list-item-component v-for="item in list" :key="item.id">{{ item.name }}</list-item-component> <!-- 插值实例: --> <div class="component-a"> <p>这是一个 A 组件</p> <slot></slot> </div> <component-a> <p>我是给 A 组件的内容</p> </component-a> <!-- output --> <div class="component-a"> <p>这是一个 A 组件</p> <p>我是给 A 组件的内容</p> </div>

React

数据 IO 的类型统一采用花括号{}或属性值""体现。 默认并不存在 Directive,View 层所需的展示逻辑功能都需要自行业务编码。

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
<!-- 数据传递: --> <Compoent data={someData} title="stringTitle" /> <!-- 事件绑定: --> <Compoent onEvent={eventHandle} /> <!-- 渲染逻辑:三目运算输出值、三目运算输出不同组件、输出循环列表 --> <script> render() { return ( <div>User {isLoggedIn ? 'username logged in' : 'not login'}</div> { isLoggedIn ? ( <LogoutButton onClick={this.handleLogoutClick} /> ) : ( <LoginButton onClick={this.handleLoginClick} /> ) } <ul> { numbers.map((number) => <li key={number.toString()}>{number}</li> )} </ul> ) } </script> <!-- 插值逻辑: --> <script> function Box(props) { return ( <div className="box"> <div className="box-inner"> {props.children} </div> </div> ) } function App() { return ( <Box> <div className="login-box"> <p>登陆弹窗</p> </div> </Box> ) } </script>

CSS 方案

Vue

Vue 使用 loader 来抽离和打包 CSS,使用 C3 属性选择器实现作用域隔离, 但 C3 隔离属性在编译期生成,所以会有一些不可逆的弊端,比如无法根据动态组件结构来实现动态的 deep scope。

如 Angular 就有大概 4-5 种方案来实现作用域隔离。

React

貌似 React 的 CSS 作用域方案都通过 All in js 实现。

其他待续...

本文于 2018/9/3 下午 发布在 宁静寺 分类下,当前已被围观 377 次

相关标签:学习Web开发ReactVue

永久地址:https://surmon.me/article/116

版权声明:自由转载-署名-非商业性使用  |  Creative Commons BY-NC 3.0 CN