当前位置: 首页 > 科技观察

我在React和Vue中构建了相同的应用程序,让我们比较一下

时间:2023-03-16 16:41:49 科技观察

前言几年前,我决定尝试分别在React和Vue中构建一个相当标准的ToDo应用程序。这两个应用程序都是使用默认CLI构建的(React的create-react-app和Vue的vue-cli)。我想尽量保持中立,并使用这样的示例向您展示这两种技术如何执行特定任务。当ReactHooks发布时,我将这篇文章更新为“2019版”,将类组件替换为函数式Hooks。随着Vue3及其组合API的发布,是时候将本文更新为“2020年版”了。2019版:我用React和Vue构建了同一个app,看看有什么不同我们先来看看两个app的外观:两个app的CSS代码完全一样,只是代码位于不同的地方地方。牢记这一点,让我们看一下它们的文件结构:你会发现它们的结构也几乎相同。唯一的区别是React应用程序有两个CSS文件,而Vue应用程序没有任何CSS文件。这是因为在create-react-app中,默认情况下每个React组件都带有一个单独的文件来保存其样式,而VueCLI使用单个文件来包含默认组件的HTML、CSS和JavaScript。最终他们都实现了相同的目标,没有什么好说的,因为你不能在React或Vue中改变文件结构。选择哪一个真的取决于个人喜好。开发社区中有很多关于CSS结构的讨论,尤其是React,因为有许多CSS-in-JS解决方案,如样式化组件和情感。顺便说一下,CSS-in-JS的字面意思。虽然这些很有用,但这里我们只使用两侧CLI给出的结构。在继续之前,让我们先看看一个典型的Vue和React组件长什么样:我们如何改变数据?首先,“变异数据”到底是什么意思?听起来是不是有点高级?其实基本上就是指改变我们存储的数据。如果我们想将一个人的名字的值从John更改为Mark,我们就是在“改变”数据。这就是React和Vue之间的关键区别所在。Vue本质上创建了一个数据对象,其中数据可以自由更新,而React通过所谓的状态挂钩处理数据突变。从下图中你可以看到两者的设置,然后我们将指定:Reactstate:Vuestate:所以你看到我们将相同的数据传递给两者,但是每个的结构不同。在React中,至少从2019年开始,我们一般通过一系列的Hooks来处理状态。您之前可能没有接触过这个概念,一开始可能会觉得有点奇怪。它基本上是这样工作的:假设我们要创建一个待办事项列表,我们可能需要创建一个名为list的变量,它可能需要接收一个字符串或对象数组(比如为每个待办事项字符串一个ID或其他东西)。我们需要写的代码是const[list,setList]=useState([])。这里我们使用了React中的Hook,叫做useState。它本质上允许我们在组件中保留本地状态。此外,您可能已经注意到我们在useState()中传递了一个空数组[]。放在那里是我们希望列表最初设置的内容,这里我们想要一个空数组。但是从上图可以看出,我们传递了数组中的一些数据,这些数据最终成为了列表的初始化数据。想知道setList的作用是什么?稍后会详细介绍!在Vue中,通常将组件的所有突变数据放在setup()函数中,该函数返回一个对象,其中包含要公开的数据和函数(您希望在应用程序中使用的那些)。您会注意到应用程序中的每条状态数据(即我们希望能够改变的数据)都包含在ref()函数中。这个ref()函数是我们从Vue导入的,以允许我们的应用程序在这些数据更改/更新时进行更新。简而言之,如果要在Vue中创建变异数据,请将变量分配给ref()函数并将默认数据放入其中。如何在应用中引用突变数据?假设我们有一些名为name的数据,它被赋予了Sunil值。在React中,因为我们使用useState()创建较小的状态,所以我们很可能使用const[name,setName]=useState('Sunil')创建了一些东西。在应用程序中,我们将简单地调用名称来引用同一块数据。这里的主要区别是我们不能简单地写name='John',因为React有一些限制来防止这种简单和肆无忌惮的突变。在React中,我们会写setName('John')。这里使用了setName。在const[name,setName]=useState('Sunil')中,它创建了两个变量,一个变为constname='Sunil',第二个constsetName被分配了一个函数,该函数使nameRecreate具有新值。在Vue中,它位于setup()函数内部,称为constname=ref('Sunil')。在应用程序中,我们将通过调用name.value来引用它。如果我们想使用在ref()函数中创建的值,我们将在变量上查找.value而不是简单地调用变量。换句话说,如果我们想要保存状态的变量的值,我们将寻找name.value而不是name。如果你想更新name的值,你可以通过更新name.value来实现。例如,假设我想把我的名字从Sunil改成John,我可以写name.value="John"来完成。其实这里React和Vue都在做同样的事情,就是创建可以更新的数据。每次Vue更新包装在ref()函数中的数据时,默认情况下,Vue基本上会结合自己的name和setName版本。React要求您使用内部值调用setName()来更新状态,如果您尝试更新数据对象中的值,Vue会假设您这样做了。那么,为什么React会费心将值与函数分开并使用useState()呢?这是因为React想要在状态改变时重新运行某些生命周期钩子。在我们的例子中,当你调用setName()时,React会知道一些状态已经改变,所以它可以运行它们的生命周期钩子。如果你直接改变状态,React将不得不做更多的工作来跟踪变化和运行生命周期挂钩等。现在我们已经弄清楚了数据突变,让我们看看如何将新项目添加到两个待办事项应用程序。我们如何创建新的待办事项?反应:constcreateNewToDoItem=()=>{constnewId=generateId();constnewToDo={id:newId,text:toDo};setList([...list,newToDo]);setToDo("");};在React中你是怎么做到的?在React中,我们的输入字段有一个名为value的属性。每次通过onChange事件侦听器更改其值时,该值都会自动更新。JSX(基本上是HTML的变体)看起来像这样:将更新状态。handleInput函数如下所示:constandleInput=(e)=>{setToDo(e.target.value);};现在,只要用户按下页面上的+按钮添加新项目,就会触发createNewToDoItem函数。让我们再次看一下这个函数,看看发生了什么:constcreateNewToDoItem=()=>{constnewId=generateId();constnewToDo={id:newId,text:toDo};setList([...list,newToDo]);setToDo("");};本质上,newId函数正在创建一个新ID,该ID将提供给我们的新toDo项目。newToDo变量是一个带有id键的对象,其值由newID确定。它还有一个文本键,其值由toDo确定。此待办事项是输入值更改时要更新的待办事项。setList函数就是这样,我们传入一个包含整个列表和新创建的newToDo的数组。你可能会想...list看起来很奇怪:开头的三个点称为展开运算符,负责将列表中的所有值作为单独的项传递,而不是简单地将所有项作为一个项一起传递大批。感到困惑?那么我强烈建议大家仔细阅读传播运算符的介绍,因为它非常有用!最后我们运行setToDo()并传入一个空字符串。这样我们的输入值为空,我们可以输入一个新的toDo。Vue:functioncreateNewToDoItem(){constnewId=generateId();list.value.push({id:newId,text:todo.value});todo.value="";}在Vue中是怎么做的?在Vue中,我们的输入字段有一个名为v-model的句柄。这允许我们做一些叫做双向绑定的事情。让我们看一下输入字段以了解发生了什么:V-模型将此字段的输入与我们在setup()函数中创建的变量相关联,然后将其作为返回对象中的键公开。到目前为止我们还没有介绍对象返回什么,所以首先,这是我们从ToDo.vue中的setup()函数返回的内容:return{list,todo,showError,generateId,createNewToDoItem,onDeleteItem,displayError};在这里,list、todo和showError是我们的有状态值,其他一切都是我们希望能够在应用程序的其他地方调用的函数。在页面加载时,我们必须将todo设置为一个空字符串,例如:consttodo=ref("")。如果其中已经有一些数据,例如consttodo=ref("addsometexthere"):我们的输入字段将加载alreadyaddsometexthereinside。无论如何,回到空字符串,我们在输入字段中键入的任何文本都必须绑定到todo.value。这实际上是双向绑定——输入字段可以更新ref()值,而ref()值又可以更新输入字段。回顾之前的createNewToDoItem()块,我们可以看到我们将todo.value的内容推入列表数组,然后将前者更新为空字符串。我们还使用了与React示例中相同的newId()函数。如何从列表中删除项目?反应:constdeleteItem=(id)=>{setList(list.filter((item)=>item.id!==id));};你如何在React中做到这一点?由于deleteItem()函数位于ToDo.js中,我可以通过首先将deleteItem()函数作为prop传递来轻松地在ToDoItem.js中引用它,如下所示:这里先把函数传下去,让孩子可以访问。然后在ToDoItem组件内部执行以下操作:deleteItem(item.id)}>-我想引用位于父组件内部的函数,只需参考props.deleteItem。你可能会发现,在代码示例中,我们只写了deleteItem而没有写props.deleteItem。这是因为我们使用了一种称为解构的技术,它允许我们获取道具对象的一部分并将其分配给一个变量。因此,在我们的ToDoItem.js文件中,我们有以下内容:constToDoItem=(props)=>{const{item,deleteItem}=props;}这为我们创建了两个变量,其中一个称为item,它具有相同的props。item同值,deleteItem根据props.deleteItem赋值。我们也可以简单地使用props.item和props.deleteItem来避免解构,但我认为这里值得单独介绍!Vue:functiononDeleteItem(id){list.value=list.value.filter(item=>item.id!==id);}Vue是怎么做的?Vue需要一种稍微不同的方法。在这里我们必须做三件事:首先,在我们要调用函数的元素上:-然后我们必须在子组件中做(本例在ToDoItem.vue中创建一个emit函数作为方法,如下:functiondeleteItem(id){emit("delete",id);}同时,你会发现当我们在ToDo.vue添加ToDoItem.vue的时候,其实引用了一个函数:这是称为自定义事件监听器事件监听器。它监听所有用字符串“delete”触发emit的情况。如果它听到这个,它会触发一个名为onDeleteItem的函数。这个函数在ToDo.vue中,而不是在ToDoItem.vue中。如前所述,此函数仅过滤list.value数组中的id。另请注意,在Vue示例中,我可以像这样在@click侦听器中简单地编写$emit部分:-这样就把步骤从3个减少到2个了,选哪一个完全是个人喜好问题。简而言之,在React中,子组件可以通过props访问父函数(前提是你将props向下传递,这是相当标准的做法,在其他React工作中也很常见);而在Vue中,您需要从子组件发出事件,这些事件通常在父组件内回收。如何传递事件监听器?React:用于简单事件(例如点击事件)的事件侦听器非常适合。以下是为创建新ToDo项的按钮创建点击事件的示例:+这非常简单,类似于内联处理普通JS中的onClick。如Vue部分所述,设置事件监听器以监听Enter键有点复杂。这个需要通过input标签的onKeyPress事件来处理,如下:“enter”被识别为“key”,该函数触发createNewToDoItem函数,如下:constandleKeyPress=(e)=>{if(e.key==="Enter"){createNewToDoItem();}};Vue:是在Vue中编写非常直观。我们只需使用@符号,然后使用我们想要创建的事件侦听器的类型。例如,要添加一个点击事件监听器,我们可以编写如下代码:点击。Vue事件监听器的好处是你还可以绑定很多东西,比如.once,它可以防止事件监听器被多次触发。在编写处理按键的特定事件侦听器时,也有许多快捷方式。我发现在React中编写一个事件侦听器很麻烦,每次按下回车键时都会创建一个新的ToDo项目。在Vue中,我只写:如何将数据传递给子组件?React:在React中,我们将props传递给创建子组件的地方。如:;在这里我们看到传递给ToDoItem组件的两个道具。从这里,我们可以通过this.props在子组件中引用它们。所以要访问item.todo道具,我们只需调用props.item。您可能已经注意到还有一个关键道具(所以从技术上讲,我们实际上传递了三个道具)。这主要在React内部使用,因为它简化了同一组件的多个版本之间的更新和跟踪更改(在我们的例子中,每个待办事项都是ToDoItem组件的副本)。确保您的组件具有唯一键也很重要,否则React会在控制台中发出警告。Vue:在Vue中,我们将props传递给创建子组件的地方。如:这样完成后,我们将它们传入子组件的props数组中,如下所示:props:["todo"]。然后可以在子组件中通过名称引用它们——在本例中名称是todo。如果您不知道将prop键放在哪里,下面是我们子组件中整个导出默认对象的样子:exportdefault{name:"ToDoItem",props:["item"],setup(props,{emit}){functiondeleteItem(id){emit("delete",id);}return{deleteItem,};},};你可能会注意到,在Vue中遍历数据时,我们实际上遍历的是list而不是list.value。遍历后者在这里不起作用。如何将数据发送回父组件?React:我们首先将函数向下传递给子组件,将其作为调用子组件的prop引用。然后我们添加对子组件函数的调用,比如onClick引用props.whateverTheFunctionIsCalled-或者whateverTheFunctionIsCalled(如果使用解构)。然后将触发位于父组件中的功能。我们可以在“如何从列表中删除项目”部分看到完整的过程。Vue:在子组件中,我们只需要写一个函数,返回一个值给父函数即可。在父组件中,我们编写了一个函数来监听值何时发出,然后可以触发函数调用。您可以在“如何从列表中删除项目”部分查看完整过程。终于完成了!我们已经了解了添加、删除和更改数据,将数据作为props从父级传递给子级,以及将数据作为事件侦听器从子级发送到父级。当然,React和Vue之间还有许多其他的小差异和特质,但我希望这篇文章能帮助您了解这两个框架是如何处理事情的。如果您有兴趣分叉本文中使用的样式并想制作您自己的类似样式,请随意这样做!两个应用程序VueToDo的Github链接:https://github.com/sunil-sandhu/vue-todo-2020ReactToDo:https://github.com/sunil-sandhu/react-todo-2020