免费下载案例集|20+数字化领先企业人才培养实践经验 了解详情
写点什么

我用 React 和 Vue 构建了同款应用,来看看哪里不一样

  • 2019-10-08
  • 本文字数:6196 字

    阅读完需:约 20 分钟

我用React和Vue构建了同款应用,来看看哪里不一样

前几天,我们曾和大家探讨了 React、Angular、Vue.js 三大框架的优缺点。在留言中,许多朋友提到 Vue 在国内的市场应用广泛,最近 Vue 3.0 Pre-Alpha 的发布也让它成为前端圈关注的焦点。而 React 则易于学习,有网友评论,不关注框架本身提供的 API,更容易让开发者形成靠谱的代码风格。理论上的梳理或许还不够直观,今天的文章作者就进行了这样一场实验,他也在好奇,如果用 React 和 Vue 构建同样一款应用,开发过程与效果会是怎样的?


我在工作中使用 Vue 有一段时间了,对它的了解也相当深刻,但我很想知道围墙另一边的草是什么样的——这里所说的草指的就是 React。


我阅读了很多 React 文档并观看了一些教学视频,它们的确很棒,但我真正想知道的是 React 与 Vue 有何不同。我所说的“不同”并不是它们是否都有虚拟 DOM,或者它们是怎样渲染页面之类的问题。我希望的是有人从代码层面给出解释。我希望能有一篇文章涵盖这样的内容,帮助那些对 Vue 或 React(乃至整个 Web 开发工作)都不熟悉的人更好地理解两者之间的区别。


可惜我找不到现成的答案。因此我意识到我得自己动手解决这个问题才能看清楚两者之间的异同。于是我记录下了整个对比过程,终于完成了这样一篇文章,填补了这方面的空白。



哪个更好看?


我决定尝试构建一款比较典型的 To Do 类应用,允许用户在列表中添加和删除项目。两款应用都是使用默认 CLI 构建的(React 用 create-react-app,Vue 则是 vue-cli)。顺带一提,CLI 的意思是命令行界面。🤓


好了,开头的部分比我想象的还要长,我们可以切入正题了。先来大致看一下两款应用的外观:



React vs Vue。不可移动的对象遇到了不可抗拒的力量!


两个应用程序的 CSS 代码完全相同,但代码所处的位置有所不同。记住这一点,接下来让我们看一下两个应用程序的文件结构:



你会发现它们的结构也几乎相同。唯一的区别是 React 应用有三个 CSS 文件,而 Vue 应用没有任何 CSS 文件。这是因为在 create-react-app 中,每个 React 组件都会附带一个文件来保存其样式,而 Vue CLI 采用了一种包含式的方法,具体来说是在组件文件中声明样式。


最后他们俩都达成了同样的目标,也没什么可多说的,因为在 React 或 Vue 中你都不能改变 CSS 的结构。这确实取决于个人喜好。开发人员社区关于 CSS 的结构化方式这个话题有大量的讨论,尤其是 React 这块,因为有许多 CSS-in-JS 解决方案,诸如样式组件和 emotion 等。顺便说一句,CSS-in-JS 就是字面上的意思。虽然这些都很有用,但这里我们只用两边的 CLI 给出的结构。


在进一步深入之前,我们先来看一下典型的 Vue 和 React 组件长什么样:



左边是 React,右边是 Vue。


看过之后我们来深入了解细节吧!

我们如何突变数据?

首先,“突变数据”到底是什么意思呢?听起来是不是有点高深?其实它基本上就是指更改我们已存储的数据。如果我们想将一个人名的值从 John 更改为 Mark,我们就是在“突变“这份数据。这就是 React 和 Vue 之间的关键区别所在。Vue 本质上创建了一个数据对象,可以在其中自由更新数据,而 React 通过所谓的状态 hook 来处理数据突变。


从下面的图片中可以看到两者的设置,然后我们会具体说明:



左边是 React,右边是 Vue。


于是你看到我们将相同的数据传递给了两者,但是各自的结构有所不同。


在 Vue 中,通常会将组件的所有可变数据放置在 data()函数内,该函数返回一个对象,其中包含你的数据。


在 React 中,至少从 2019 年开始,我们一般会通过一系列 Hooks 处理状态。你可能以前没接触过这种概念,一开始它看起来可能有点奇怪。它的工作机制基本上是这个样子:假设我们要创建一个待办事项列表,我们可能需要创建一个名为 list 的变量,它可能需要一个由字符串或对象组成的数组(比如说给每个 todo 字符串一个 ID 或其他一些东西)。我们需要写的代码是 const [list, setList] = useState([])。这里我们用的就是 React 里面的 Hook,称为 useState。它本质上是让我们能够在组件中保留局部状态。


另外,你可能已经注意到我们在 useState()内部传入了一个空数组[]。放在其中的是我们希望 list 最初设置的内容,这里我们希望是一个空数组。但从上图可以看到,我们在数组内传入了一些数据,这些数据最后成了 list 的初始化数据。想知道 setList 是做什么的?稍后会进一步说明!

那么,如何在应用程序中引用可变数据呢?

假设我们有一些数据名为 name,被分配了“Sunil”值。


在 Vue 中,这部分内容位于 data()对象内部,写成 name: ‘Sunil’。在我们的应用程序中,我们将调用 this.name 来引用它。我们还可以调用this.name = 'John’来更新它,把我的名字改为 John。


在 React 中,由于我们使用 useState()创建了较小的状态,因此很可能已经用 const [name, setName] = useState(‘Sunil’)创建了一些东西。在应用程序中,我们将简单地调用 name 来引用同一段数据。这里的主要区别在于我们不能简单地写上 name = ‘John’,因为 React 有一些限制来预防这种简单且无所顾忌的突变。在 React 中,我们要写成 setName(‘John’)。这里用到了 setName。在 const [name, setName] = useState(‘Sunil’)中,它创建两个变量,一个变量变为 const name = ‘Sunil’,而第二个 const setName 被分配了一个函数,该函数使 name 可以用新值重新创建。


实际上,React 和 Vue 在这里做的是同样的事情,也就是创建可以更新的数据。Vue 本质上会在每次更新一条数据时默认结合它自己的 name 和 setName 版本。简单来说,React 要求你使用内部值调用 setName()来更新状态,而如果你曾尝试更新数据对象内部的值,Vue 就会假设你要这么做。那么为什么 React 会费劲地将值与函数分开,还要使用 useState()呢?这里引用 Revanth Kumar 的解释


“这是因为当状态改变时,React 希望重新运行某些生命周期 hooks。当你调用 useState 函数时,它将知道状态已更改。如果你直接改变状态,React 将不得不做更多的工作来跟踪更改以及要运行的生命周期 hooks 等。因此为了简单起见,React 使用 useState。”



Bean 最懂了。


现在我们已经搞明白了数据突变,接下来看看在两个 To Do 应用中添加新项目的方法。

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

React:

const createNewToDoItem = () => {  const newId = Math.max.apply(null, list.map((t) => t.id)) + 1  const newToDo = { id: newId, text: toDo };  setList([...list, newToDo]);  setToDo("");};
复制代码


在 React 中,我们的输入字段有一个名为 value 的属性。每次通过 onChange 事件侦听器更改它的值时,都会自动更新此值。JSX(基本上是 HTML 的变体)如下所示:


<input type="text"        value={toDo}        onChange={handleInput}/>
复制代码


每次更改值时,它都会更新状态。handleInput 函数如下所示:


const handleInput = (e) => {  setToDo(e.target.value);};
复制代码


现在,每当用户按下页面上的+按钮添加新项目时,都会触发 createNewToDoItem 函数。我们再来看一下这个函数,搞清楚具体发生了什么:


const createNewToDoItem = () => {  const newId = Math.max.apply(null, list.map((t) => t.id)) + 1  const newToDo = { id: newId, text: toDo };  setList([...list, newToDo]);  setToDo("");};
复制代码


本质上,newId 函数是在创建一个新 ID,该 ID 将提供给我们的新 toDo 项目。newToDo 变量是一个对象,有一个 id 键,其值由 newID 确定。它还有一个 text 键,其值由 toDo 确定。这个 toDo 就是输入值更改时要更新的那个 toDo。


setList 函数到此为止,然后我们传入一个包含整个 list 以及新创建的 newToDo 的数组。


你可能觉得…list 看起来很奇怪:开头的三个点称为散布运算符,负责将 list 中的所有值作为单独的项目传递,而不是简单地把所有项目打包在一起作为数组传递。感觉有些糊涂吗?那我强烈建议你仔细阅读散布运算符的相关介绍,因为它很有用!


最后我们运行 setToDo()并传入一个空字符串。这样我们的输入值为空,可以输入新的 toDo 了。

Vue:

createNewToDoItem() {  const newId = Math.max.apply(null, this.list.map(t => t.id)) + 1;    this.list.push({ id: newId, text: this.todo });  this.todo = "";}
复制代码


在 Vue 中,我们的 input 字段有一个称为 v-model 的句柄。这使我们能够执行称为双向绑定的操作。下面来看一下 input 字段,搞清楚到底发生了什么:


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


V-Model 将这个字段的输入与我们在数据对象 toDoItem 中的键相关联。页面加载后,我们必须将 toDoItem 设置为空字符串,例如:todo:’’。如果其中已经有一些数据,例如 todo: ‘add some text here’,则我们的输入字段将加载输入字段内已有的 add some text here。那么输入字段为空时,无论我们在输入字段中键入什么文本都将绑定到 todo 的值。这实际上是双向绑定(输入字段可以更新数据对象,反过来数据对象也可以更新输入字段)。


回顾一下前面的 createNewToDoItem****()代码块,可以看到,我们将 todo 的内容推送到 list 数组中,然后将 todo 更新为空字符串。


我们还使用了与 React 示例中相同的 newId()函数。

如何从列表中删除项目?

React:

const deleteItem = (item) => {  setList(list.filter((todo) => todo !== item));};
复制代码


因为 deleteItem()函数位于 ToDo.js 内,我可以很容易地在 ToDoItem.js 里引用它,首先将 deleteItem**()函数作为**的 prop,如下所示:


<ToDoItem deleteItem={deleteItem}/>
复制代码


这里首先将该函数传递下去,使其能被子级访问。然后在 ToDoItem 组件内执行以下操作:


<button className="ToDoItem-Delete" onClick={() => deleteItem(item)}> - </button>
复制代码


我要引用位于父组件内的函数,只需引用 props.deleteItem。你可能发现在代码示例中,我们只写了 deleteItem,而不是 props.deleteItem。这是因为我们使用了一种称为解构的技术,该技术允许我们获取 props 对象的一部分并将其分配给变量。因此在我们的 ToDoItem.js 文件中有以下内容:


const ToDoItem = (props) => {  const { item, deleteItem } = props;}
复制代码


这为我们创建了两个变量,其中一个称为 item,它被赋予与 props.item 相同的值,而 deleteItem 则根据 props.deleteItem 赋值。我们也可以简单地使用 props.item 和 props.deleteItem 来避免解构的操作,但我认为这里值得单独介绍一下!

Vue:

onDeleteItem(item){  this.list = this.list.filter(todo => todo !== item);}
复制代码


Vue 需要的方法稍微有一些不同。这里我们必须做三件事:


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


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


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


deleteItem(item) {    this.$emit('delete', item)}
复制代码


与此同时你会发现,当我们在 ToDo.vue 中添加 ToDoItem.vue 时,我们实际上引用了一个函数


<ToDoItem v-for="todo in list"           :todo="todo"           @delete="onDeleteItem" // <-- this :)          :key="todo.id" />
复制代码


这就是所谓的自定义事件侦听器。它会侦听使用字符串“delete”触发 emit 的所有情况。如果听到此消息,它将触发一个名为 onDeleteItem 的函数。此函数位于 ToDo.vue 内部,而不是在 ToDoItem.vue 中。如前所述,此函数仅过滤 data 对象内的 todo 数组即可删除单击的项目。


在这里还需注意的是,在 Vue 示例中,我可以简单地将 $emit 部分写在 @click 监听器中,如下所示:


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


这样就能把步骤从 3 步减少到 2 步,选哪个完全取决于个人喜好。


简而言之,React 中的子组件可以通过 props 来访问父函数(前提是你要向下传递 props,这是相当标准的做法,其他 React 工作中也非常常见);而在 Vue 中,你需要从子级发射事件,这些事件通常会在父组件内部回收。

怎样传递事件侦听器?

React:

针对简单事件(例如单击事件)的事件侦听器很好做。下面是为创建新的 ToDo 项目的按钮创建 click 事件的示例:


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


这里非常简单,和在一般的 JS 里处理内联 onClick 差不多。如 Vue 部分所述,设置一个事件侦听器来侦听按下 Enter 键的动作有点复杂。这需要由 input 标签处理 onKeyPress 事件,如下:


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


只要识别出已按下“enter”键,此函数就触发了 createNewToDoItem 函数,如下:


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

Vue:

在 Vue 中写起来非常直观。我们只使用 @符号,后面是我们想要做的事件监听器的类型。例如要添加一个 click 事件监听器,我们可以编写以下代码:


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


注意:@click 实际上是 v-on:click 的简写。Vue 事件侦听器很好用的是你还可以绑定很多东西,例如.once,它可以防止事件侦听器被多次触发。在编写处理按键的特定事件侦听器时还有许多捷径。我发现在 React 中创建一个事件侦听器,做到每当按下 enter 键就创建新的 ToDo 项目写起来比较麻烦。在 Vue 中,我只需编写:


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

如何将数据传递给子组件?

React:

在 React 中,我们将 props 传递到子组件的创建位置。如:


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


这里我们看到两个传递给 ToDoItem 组件的 props。从这里开始,我们就可以通过 this.props 在子组件中引用它们。因此要访问 item.todo prop 时,我们只需调用 props.item

Vue:

在 Vue 中,我们将 props 传递到子组件的创建位置。如:


<ToDoItem v-for="item in list"   :item="item"   @delete="onDeleteItem"   :key="item.id" />
复制代码


完成此操作后,我们将它们传递到子组件的 props 数组中,如下所示:props:[‘todo’]。然后它们就可以在子组件中用名称引用——这里的名称就是‘todo’

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

React:

我们首先将函数向下传递给子组件,在调用子组件的位置将其作为 prop 引用。然后我们向子组件的函数添加调用,比如说 onClick 就引用 props.whateverTheFunctionIsCalled,或者 whateverTheFunctionIsCalled(如果用解构)。然后将触发位于父组件中的函数。我们可以在“如何从列表中删除项目”部分中查看全过程。

Vue:

在子组件中,我们只需要编写一个将值返回给父函数的函数即可。在父组件中我们编写一个函数,该函数侦听何时发出该值,然后可以触发函数调用。可以在“如何从列表中删除项目”部分中查看全过程。

终于完成了🎉

我们已经研究了如何添加、删除和更改数据,以 props 形式将数据从父级传递到子级,以及以事件侦听器的形式将数据从子级发送到父级。当然,React 和 Vue 之间还有许多其他的小差异和特殊要素,但我希望本文的内容有助于大家理解这两个框架是如何处理事物的。


如果你有兴趣 fork 本文中使用的样式,并想制作自己的类似作品,请自便!👍


两个应用的 Github 链接:


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


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


原文链接:


https://medium.com/javascript-in-plain-english/i-created-the-exact-same-app-in-react-and-vue-here-are-the-differences-2019-edition-42ba2cab9e56


2019-10-08 18:105537

评论 1 条评论

发布
用户头像
读起来极其不通顺,机翻的感觉..
2019-10-09 11:09
回复
没有更多了
发现更多内容

DAPP代币通缩燃烧销毁质押挖矿系统开发(成熟技术)

l8l259l3365

IPQ8072/QCN9074/QCN9274/IPQ9574/IPQ6010/IPQ6018 WIFI6E WIFI7 WPA3 Hardware Comparison

wallyslilly

QCN9074 IPQ8072 QCN9024

软件物料清单管理 | 打开“应用软件盲盒”,预警“开源组件风险”

网安云

开源 网络安全 系统安全 开源软件 软件物料清单

2023年信创云管平台选哪家?咨询电话多少?

行云管家

云计算 云服务 信创 国产化

向上管理:三个技巧,教会你如何与上级、老板高效协作

LigaAI

产品经理 技术管理 职场成长 向上管理 企业号9月PK榜

数字化转型与架构-架构设计篇|软件开发框架帮我们做了什么?

数字随行

数字化转型

如何使用API接口获取商品数据,从申请API接口、使用API接口到实际应用,一一讲解

Noah

API 文档 API 开发

Codigger的项目代码检测工具的特性和优势

知者如C

Java基础面试题【三】线程(1)

派大星

Java 面试题

可观测成熟度模型介绍(二)

乘云数字DataBuff

Vitess全局唯一ID生成的实现方案 | 京东云技术团队

京东科技开发者

MySQL 数据库 企业号9月PK榜 vitess

MySQL事务死锁问题排查 | 京东云技术团队

京东科技开发者

MySQL 数据库 事务 死锁 企业号9月PK榜

香港服务器如何帮助企业在线业务提供卓越性能和稳定性

一只扑棱蛾子

香港服务器

中国平煤神马集团 | 基于融合共享的经营管控多业务(1+2+N)共享中心建设实践

用友BIP

能源 数智化转型 共享中心

高效、透明-企事业数字化的采购管理系统(源程序源代码)

金陵老街

如何教会小白使用API接口获取商品数据

Noah

API 文档 API 开发

Springboot vs Quarkus

Kevin_913

cad设计绘图 AutoCAD 2024中文最新「支持m芯片」

胖墩儿不胖y

Mac软件 CAD绘图 cad工具

中文导航、本地指南一个App搞定,华为手机国庆出境游用Petal Maps就够了!

最新动态

用友出席第六届中国企业论坛,分享央国企数智化转型方案

用友BIP

数智化转型

小程序编译器性能优化之路

百度Geek说

小程序 性能优化 前端 企业号9月PK榜

软件调研、研发、设计、管理、验收文档(全文档整理)

金陵老街

华为云智能化组装式交付方案 ——金融级PaaS业务洞察及Web3实践的卓越贡献

华为云PaaS服务小智

云计算 软件开发 华为云

苹果电脑串口调试软件:serial 直装激活最新版

mac大玩家j

Mac软件 串口调试工具

Databend 开源周报第112期

Databend

代码层面探索前端性能 | 京东云技术团队

京东科技开发者

CSS 性能优化 前端 企业号9月PK榜

JDK安装与配置教程

小齐写代码

算力百川汇蓝海,商海荡漾绘宏图

鲸品堂

算力 算力网络

我用React和Vue构建了同款应用,来看看哪里不一样_大前端_Sunil Sandhu_InfoQ精选文章