写点什么

我用 Vue 和 React 构建了相同的应用程序,这是他们的差异

2018 年 8 月 18 日

在工作中使用了 Vue 之后,我已经对它有了相当深入的了解。同时,我也对 React 感到好奇。我阅读了 React 的文档,也看了一些教程视频,虽然它们很棒,但我真正想知道的是 React 与 Vue 有哪些区别。这里所说的区别,并不是指它们是否都具有虚拟 DOM 或者它们如何渲染页面。我真正想要做的是对它们的代码进行并排比较,并搞清楚在使用这两个框架开发应用时究竟有哪些差别。

我决定构建一个标准的待办事项应用程序,用户可以添加和删除待办事项。我分别使用它们默认的 CLI(React 的 create-react-app 和 Vue 的 vue-cli)来创建这个应用。先让我们看一下这两个应用的外观:



两个应用程序的 CSS 代码几乎完全相同,但代码存放的位置存在差别。



它们的结构也几乎完全相同,唯一的区别是 React 有三个 CSS 文件,而 Vue 则没有。这是因为 React 组件需要一个附带的文件来保存样式,而 Vue 采用包含的方式,将样式声明在组件文件中。

从理论上讲,你可以使用老式的 style.css 文件来保存整个页面的样式,这完全取决于你自己。不管怎样,还是展示一下.vue 文件中的 CSS 代码长什么样:



看完样式方面的问题,现在让我们深入了解其他细节!

我们如何改变数据?

我们说“改变数据”(mutate data),实际上就是指修改已经保存好的数据。比如,如果我们想将一个人的名字从 John 改成 Mark,我们就要“改变数据”。这就是 React 和 Vue 的关键区别之一。Vue 创建了一个数据对象,我们可以自由地更新数据对象,而 React 创建了一个状态对象,要更新状态对象,需要做更多琐碎的工作。下面是 React 的状态对象和 Vue 的数据对象之间的对比:



React 的状态对象



Vue 的数据对象

从图中可以看到,我们传入的是相同的数据,它们只是标记的方式不一样。但它们在如何改变这些数据方面却有很大的区别。

假设我们有一个数据元素 name:'Sunil'。

在 Vue 中,我们通过 this.name 来引用它。我们也可以通过 this.name='John'来更新它,这样会把名字改成 John。

在 React 中,我们通过 this.state.name 来引用它。关键的区别在于,我们不能简单地通过 this.state.name='John'来更新它,因为 React 对此做出了限制。在 React 中,我们需要使用 this.setState({name:'John'}) 的方式来更新数据。

在了解了如何修改数据之后,接下来让我们通过研究如何在待办事项应用中添加新项目来深入了解其他细节。

我们如何创建新待办事项?

React:

复制代码
createNewToDoItem = () => {
this.setState( ({ list, todo }) => ({
list: [
...list,
{
todo
}
],
todo: ''
})
);
};

Vue:

复制代码
createNewToDoItem() {
this.list.push(
{
'todo': this.todo
}
);
this.todo = '';
}

React 是怎么做到的?

在 React 中,input 有一个叫作 value 的属性。我们通过几个与创建双向绑定相关的函数来自动更新 value。React 通过为 input 附加 onChange 函数来处理双向绑定。

复制代码
<input type="text"
value={this.state.todo}
onChange={this.handleInput}/>

只要 input 的值发生变化,就会执行 handleInput 函数。这个函数会将状态对象中 todo 字段的值改为 input 中的值。这个函数看起来像这样:

复制代码
handleInput = e => {
this.setState({
todo: e.target.value
});
};

现在,只要用户按下页面上的 + 按钮,createNewToDoItem 就会调用 this.setState,并传入一个函数。这个函数有两个参数,第一个是状态对象的 list 数组,第二个是 todo(由 handleInput 函数更新)。然后函数会返回一个新对象,这个对象包含之前的整个 list,然后将 todo 添加到 list 的末尾。

最后,我们将 todo 设置为空字符串,它也会自动更新 input 中的值。

Vue 是怎么做到的?

在 Vue 中,input 有一个叫作 v-model 的属性。我们可以用它来实现双向绑定。

复制代码
<input type="text" v-model="todo"/>

v-model 将 input 绑定到数据对象 toDoItem 的一个 key 上。在加载页面时,我们将 toDoItem 设置为空字符串,比如 todo:’’。如果 todo 不为空,例如 todo:’add some text here',那么 input 就会显示这个字符串。我们在 input 中输入的任何文本都会绑定到 todo。这实际上就是双向绑定(input 可以更新数据对象,数据对象也可以更新 input)。

因此,回看之前的 createNewToDoItem() 代码块,我们将 todo 的内容放到 list 数组中,然后将 todo 更新为空字符串。

我们如何删除待办事项?

React:

复制代码
deleteItem = indexToDelete => {
this.setState(({ list }) => ({
list: list.filter((toDo, index) => index !== indexToDelete)
}));
};

React 是怎么做到的?

虽然 deleteItem 函数位于 ToDo.js 中,我仍然可以在 ToDoItem.js 中引用它,就是将 deleteItem() 函数作为 <ToDoItem/> 的 prop 传入:

复制代码
<ToDoItem deleteItem={this.deleteItem.bind(this, key)}/>

这样可以让子组件访问传入的函数。我们还绑定了 this 和参数 key,传入的函数需要通过 key 来判断要删除哪个 ToDoItem。在 ToDoItem 组件内部,我们执行以下操作:

复制代码
<div className=”ToDoItem-Delete” onClick={this.props.deleteItem}>-</div>

我使用 this.props.deleteItem 来引用父组件中的函数。

Vue:

复制代码
this.$on(‘delete’, (event) => {
this.list = this.list.filter(item => item.todo !== event)
})

Vue 是怎么做到的?

Vue 的方式稍微有点不同,我们基本上要做三件事。

首先,我们需要在元素上调用函数:

复制代码
<div class=”ToDoItem-Delete” @click=”deleteItem(todo)”>-</div>

然后我们必须创建一个 emit 函数作为子组件内部的一个方法(在本例中为 ToDoItem.vue),如下所示:

复制代码
deleteItem(todo) {
this.$parent.$emit(‘delete’, todo)
}

然后我们的父函数,也就是 this.$on(’delete’) 事件监听器会在它被调用时触发过滤器函数。

简单地说,React 中的子组件可以通过 this.props 访问父函数,而在 Vue 中,必须从子组件中向父组件发送事件,然后父组件需要监听这些事件,并在它被调用时执行函数。

这里值得注意的是,在 Vue 示例中,我也可以直接将 $emit 部分的内容写在 @click 监听器中,如下所示:

复制代码
<div class=”ToDoItem-Delete” @click=”this.$parent.$emit(‘delete’, todo)”>-</div>

这样可以减少一些代码,不过也取决于个人偏好。

我们如何传递事件监听器?

React:

简单事件(如点击事件)的事件监听器很简单。以下是我们为添加新待办事项的按钮创建 click 事件的示例:

复制代码
<div className=”ToDo-Add” onClick={this.createNewToDoItem}>+</div>

非常简单,看起来很像是使用纯 JS 处理内联的 onClick 事件。而在 Vue 中,需要花费更长的时间来设置事件监听器。input 标签需要处理 onKeyPress 事件,如下所示:

复制代码
<input type=”text” onKeyPress={this.handleKeyPress}/>

只要用户按下了'enter'键,这个函数就会触发 createNewToDoItem 函数,如下所示:

复制代码
handleKeyPress = (e) => {
if (e.key === ‘Enter’) {
this.createNewToDoItem();
}
};

Vue:

在 Vue 中,要实现这个功能非常简单。我们只需要使用 @符号和事件监听器的类型。例如,要添加 click 事件侦听器,我们可以这样写:

复制代码
<div class=”ToDo-Add” @click=”createNewToDoItem()”>+</div>

注意:@click 实际上是写 v-on:click 的简写。在 Vue 中,我们可以将很多东西链接到事件监听器上,例如.once 可以防止事件监听器被多次触发。在编写用于处理按键特定事件侦听器时,还可以使用一些快捷方式。我发现,在 React 中为添加待办事项按钮创建一个事件监听器需要花费更长的时间。而在 Vue 中,我可以简单地写成:

复制代码
<input type=”text” v-on:keyup.enter=”createNewToDoItem”/>

我们如何将数据传给子组件?

React:

在 React 中,当创建子组件时,我们将 props 传给它。

复制代码
<ToDoItem key={key} item={todo} />

我们将 todo props 传给了 ToDoItem 组件。从现在开始,我们可以在子组件中通过 this.props 引用它们。因此,要访问 item.todo,我们只需调用 this.props.todo。

Vue:

在 Vue 中,当创建子组件时,我们将 props 传给它。

复制代码
<ToDoItem v-for="item in this.list"
:todo="item.todo"
:key="list.indexOf(item)"
:id="list.indexOf(item)"
>
</ToDoItem>

然后,我们将它们加入到子组件的 props 数组,如:props:[‘id’,'todo']。然后可以在子组件中通过名字来引用它们,入'id'和'todo'。

我们如何将数据发送回父组件?

React:

我们在调用子组件时将函数作为 prop 传给子组件,然后通过任意方式调用子组件的函数,这将触发位于父组件中的函数。我们可以在“如何删除待办事项”一节中看到整个过程的示例。

Vue:

在我们的子组件中,我们只需写一个函数,让它向父函数发回一个值。在父组件中,我们写了一个函数来监听这个值,然后触发函数调用。我们可以在“如何删除待办事项”一节中看到整个过程的示例。

示例代码链接:

Vue: https://github.com/sunil-sandhu/vue-todo

React: https://github.com/sunil-sandhu/react-todo

英文原文: https://medium.com/javascript-in-plain-english/i-created-the-exact-same-app-in-react-and-vue-here-are-the-differences-e9a1ae8077fd

感谢覃云对本文的审校。

2018 年 8 月 18 日 16:552728
用户头像

发布了 731 篇内容, 共 368.4 次阅读, 收获喜欢 1860 次。

关注

评论

发布
暂无评论
发现更多内容

# LeetCode 215. Kth Largest Element in an Array

liu_liu

算法 LeetCode

奈学:传授“带权重的负载均衡实现算法”独家设计思路

奈学教育

分布式

你不知道的SSD那些事

焱融科技

分布式 存储 SSD nvme

# LeetCode 863. All Nodes Distance K in Binary Tree

liu_liu

算法 LeetCode

杂谈-JSONP探索

卡尔

Java jsonp

从 0 到 1 搭建技术中台之发布系统实践:集泳道、灰度、四端和多区域于一体的设计与权衡

伴鱼技术团队

架构 系统设计 系统架构 系统性思考 架构设计

这是一个测试文档

Geek_073cad

每个人都是领导者的工程团队

hongfei

ARTS - Week Two

shepherd

js algorithm

知识也会生宝宝?

史方远

个人成长 随笔杂谈

MySQL的各种日志

超超不会飞

MySQL

原创 | 使用JUnit、AssertJ和Mockito编写单元测试和实践TDD (十三)编写测试-生命周期方法

编程道与术

Java 编程 TDD 单元测试 JUnit

Vue生态篇(二)

shirley

Vue

程序员修炼的务实哲学

博文视点Broadview

程序员 软件 编程思维 工程师 编程之路

数据产品经理实战-数据门户搭建(上)

第519区

数据中台 开发数据

我为什么开始技术写作?

架构精进之路

技术创作

【Java 25周年有奖征文获奖名单公布!!!】关于Java,你最想赞扬、吐槽、期待的变化是什么?

InfoQ写作平台官方

写作平台 Java25周年 活动专区

patroni 通过服务启动报错

yafeishi

数据库 高可用 AntDB

Vue生态篇(一)

shirley

Java Vue

美团可能会强势涉足 ToB

罗小布

创业 互联网巨头 深度思考 互联网

我常用的浏览器插件

彭宏豪95

chrome 效率工具 浏览器 插件

开源分布式文件系统大检阅

焱融科技

sds 存储 开源项目 焱融科技 文件存储

线程池续:你必须要知道的线程池submit()实现原理之FutureTask!

一枝花算不算浪漫

源码分析 并发编程

Go语言分布式系统配置治理

田晓亮

微服务

Python 自动化办公之"你还在手动操作“文件”或“文件夹”吗?"

JackTian

Python 自动化

Redis持久化了解一波!

不才陈某

redis 程序员 后端

ARTS 第二周打卡

陈文昕

情绪的力量:如何使用情绪来达成目标

七镜花园-董一凡

情绪

一个人,沿着童年的路究竟可以走多远?

zhoo299

童年 NASA 航天

互联网时代的界限管理

非著名程序员

程序员 职场 提升认知 界限管理

我的 windows 利器

玄兴梦影

工具

4月17日 HarmonyOS 开发者日·上海站

4月17日 HarmonyOS 开发者日·上海站

我用Vue和React构建了相同的应用程序,这是他们的差异-InfoQ