用React和Vue创建两个完全相同的app,发现了这些不同

348 阅读11分钟

在工作中使用 Vue 一段时间后,对它的工作原理有了相当深入的了解。然而,我很想知道篱笆另一边的草地是什么样的(React)。

我已经阅读了 React 文档,也观看了一些教程视频,虽然它们都很棒,但我真正想知道的是 React 与 Vue 到底有什么不同。这里的「不同」不是指它们是否具有虚拟 DOM 或者它们如何渲染页面。我希望有人能直接解释代码并告诉我会发生什么!我想找到一篇能解释这些差异的文章,那样 Vue、React (或者 Web 开发)的新手就可以更好地理解两者之间的差异了。

但我找不到任何解决这个问题的资源。所以我意识到必须靠自己来解决这个问题,发现它们之间的相似之处和不同之处。在这样做时,我想记录下整个过程,所以最终就有了这样一篇文章。

在这里插入图片描述
我决定构建一个标准的 Todo 列表应用,允许用户添加、删除列表中的项目。两个应用都使用默认的 CLI (React 的 create-react-app,Vue 的 vue-cli) 来构建。顺便说一下,CLI 表示命令行界面。

因为这篇文章的篇幅已经超出了我的预期,所以让我们首先快速了解下这两个应用:

在这里插入图片描述
Vue vs React:势均力敌

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

在这里插入图片描述
您会看到它们的结构也几乎相同。唯一的区别是React应用程序具有三个CSS文件,而Vue应用程序没有任何CSS文件。这是因为,在create-react-app中,React组件将具有一个随附文件来保存其样式,而Vue CLI采用了一种全面的方法,即在实际的组件文件中声明样式。

最终,它们都达到了相同的目的,没有什么可说的,您可以继续在React或Vue中以不同的方式构造CSS。这实际上取决于个人喜好-您将听到开发人员社区就CSS的结构如何构建进行大量讨论。现在,我们将仅遵循两个CLI中列出的结构。

但是在进一步介绍之前,让我们快速看一下典型的Vue和React组件的外观:

在这里插入图片描述
左边是Vue组件,右边是React组件。那就让我们深入了解细节吧!

我们如何改变数据?

首先,“改变数据”甚至意味着什么?听起来有点技术含量是吗?实际上,这只是意味着更改我们已存储的数据。因此,如果我们想将一个人的名字的值从John更改为Mark,我们就需要“改变数据”。因此,这就是React和Vue之间的关键区别所在。Vue本质上创建了一个可以自由更新数据的数据对象,而React创建了一个状态对象,在状态对象中执行更新需要更多的工作。现在,React实际上增加了很多额外的工作,我们将对此进行一些介绍。但是首先,让我们看一下Vue 中的数据对象和React中的状态对象

在这里插入图片描述
在这里插入图片描述
左侧是Vue数据对象,右侧是React状态对象。

您可以看到我们已经将相同的数据传递给了两者,但是它们只是被不同地标记。因此,将初始数据传递到我们的组件中非常相似。但是正如我们已经提到的,在两个框架之间,如何更改此数据的方法有所不同。

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

在Vue中,我们通过调用this.name来引用它。我们还可以通过调用this.name ='John'来进行更新。这将会把我的名字改为John。

在React中,我们将通过调用this.state.name引用相同的数据。现在,这里的主要区别是我们不能简单地编写this.state.name ='John',因为React具有适当的限制来防止这种简单,轻松的改变数据方式的产生。因此,在React中,我们将按照this.setState({name:'John'})的方式来改变状态数据。

尽管这基本上与我们在Vue中实现的功能相同,但是还有很多需要写的,因为每当数据更新时,Vue都会默认结合其自己的setState版本。简而言之,React需要setState,然后再更新数据,而Vue如果要更新数据对象中的值,则不需要这样做。那么,为什么React会为此而烦恼,为什么需要setState?我们来看下Revanth Kumar进行的解释:

“这是因为当状态改变时,React希望重新运行某些生命周期钩子,例如componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate,render,componentDidUpdate。当您调用setState函数时,它就知道状态已更改。如果直接改变状态,React将不得不做更多的工作来跟踪更改以及要运行的生命周期钩子。因此为了方便追踪数据的流向,React使用setState。

在这里插入图片描述
现在我们已经知道如何改变数据,然后我们来看看如何在 Todo 列表应用中添加新项目。

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

React

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

React是如何做到的?

在React中,我们的input元素具有一个名为value的属性。该属性的值通过调用一些函数来自动更新,以创建类似于Vue双向数据绑定的方式(如果您以前从未听说过,在后面“ 如何使用Vue实现有详细的解释”。我们通过在input元素上绑定onChange事件监听来实现双向绑定。(这里input是受控组件)让我们来看下input元素是什么样的,以便可以看到发生了什么:

<input type =“ text” 
       value = { this.state.todo } 
       onChange = { this.handleInput } />

每当input元素的值更改时,都会运行handleInput函数。它将使用input元素中输入的内容来更新位于状态对象内部的todo的属性值(待办事项)。该函数如下所示:

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

现在,每当用户按下页面上的+按钮添加新项时,createNewToDoItem函数实际上都会运行this.setState并将其传递给函数。此函数有两个参数,第一个是状态对象的整个列表数组,第二个是待办事项(由handleInput函数更新)。然后,该函数返回一个新对象,该对象包含之前的整个列表,然后在其末尾添加todo。整个列表是通过使用扩展运算符(...)添加的(如果您以前没有看过,请使用Google搜索ES6语法。

最后,我们设置待办事项todo为空字符串,它会自动更新input元素的值。

Vue:

createNewToDoItem(){ 
    this.list.push(
        { 
            'todo'this.todo 
        } 
    ); 
    this.todo =''; 
}

Vue是如何做到的?

在 Vue 中,input 元素有一个名为 v-model 的指令。这使我们能够执行称为双向数据绑定的操作。让我们来看一下input元素是什么样的:

<input type =“ text” v-model =“ todo” />

V-Model 指令将 input 元素的值绑定到数据对象中的 toDoItem。当页面加载时,我们通过 todo: '' 将 toDoItem 设置为空字符串。如果这里已经有一些数据,例如 todo: "add some text here'',input 元素会使用已有数据"add some text here"作为初始值。无论如何,假设使用空字符串作为初始值,我们在 input 元素输入的任何文本都绑定到 todo 上。这实际上就是双向绑定(input 元素可以更新数据对象,数据对象也可以更改 input 元素的值)。

因此,回顾一下前面的createNewToDoItem()代码块,我们看到它将todo的内容push到列表数组 ,然后将todo更新为空字符串。

我们如何从列表中删除todo?

React:

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

React是如何做到的?

虽然 deleteItem 方法定义在 ToDo.js 文件中,但先将 deleteItem() 方法作为 组件的 prop 传递进去,在 ToDoItem.js 内部引用它也就很容易了,写法如下:

<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:

onDeleteItem(todo){
  this.list = this.list.filter(item => item !== todo);
}

Vue是如何做到的?

Vue中需要一种稍微不同的方法。基本上必须在这里做三件事分为三步:

首先,在元素上我们要调用函数(绑定点击事件):

<div class="ToDoItem-Delete" @click="deleteItem(todo)">-</div>

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

deleteItem(todo) {
    this.$emit('delete', todo)
}

除此之外,当我们在 ToDo.vue 中添加 ToDoItem.vue 时,我们实际引用了一个函数:

<ToDoItem v-for="todo in list" 
          :todo="todo" 
          @delete="onDeleteItem" // <-- 这里
          :key="todo.id" />

这就是所谓的自定义事件监听。它会监听任何由 emit 触发名为 delete 的事件发生的场合。如果监听到,就会触发执行名为 onDeleteItem 的方法。这个方法定义在 ToDoItem.vue 组件内部而不是 ToDoItem.vue 组件。这个方法,正如上面所示,会过滤 data 对象内的 todo 数组并移除点击的项目。

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

<div class="ToDoItem-Delete" @click="this.$emit('delete', todo)">-</div>

这将使步数从3减少到2,而这完全取决于个人喜好。

简而言之,React中的子组件将可以通过this.props访问父函数(前提是您要传递props,这是比较标准的做法,在其他React示例中,您会大量见到),而在Vue中,您必须从children组件发出事件,这些事件通常会在父组件内部收集。(在父组件自定义事件,在子组件$emit进行分发)

我们如何传递事件监听?

React:

简单事件(例如单击事件)的事件侦听很简单。这是我们如何为新创建的ToDo项目的按钮绑定click事件监听的示例:

<div className="ToDo-Add" onClick={this.createNewToDoItem}>+</div>.

这里的实现非常简单,看起来很像使用原生 JS 来处理行内的 onClick 事件。正如 Vue 部分提到的,如果是为按下回车按钮设置事件监听器就需要花费更长的时间了。input 标签通常会处理 onKeyPress 事件,如下:

<input type="text" onKeyPress={this.handleKeyPress}/>.

只要这个方法监听到了回车键按下,它就会调用 createNewToDoItem 函数,如下所示:

handleKeyPress =(e)=> { 
	if(e.key ==='Enter'){ 
		this.createNewToDoItem(); 
	} 
};

在Vue中,它非常简单明了。我们只需要使用@符号,然后绑定相应的事件监听。例如,要添加一个click事件监听,我们可以编写以下代码:

<div class="ToDo-Add" @click="createNewToDoItem()">+</div>

注意:@click 实际上是 v-on:click 的简写。Vue 事件监听另一个很酷的事情是:有很多修饰符可以链接到后面,例如 .once,它可以防止事件监听器被多次触发。在编写用于处理键盘事件侦听器时,也有一些快捷方式。我发现在 React 中为创建新的 ToDo 项绑定一个事件监听器需要花费更长的时间。而在 Vue 中,我能够像下面这样简单实现

<input type="text" v-on:keyup.enter="createNewToDoItem"/>

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

React:

在 React 中,我们在使用子组件的地方通过 prop 传递数据,如下:

<ToDoItem key={key} item={todo} />

上面有两个 props 传递给了 ToDoItem 组件。这样传递之后,就可以在子组件内部通过 this.props 来引用它们了。因此,就可以通过 this.props.item 访问 todo 变量了。

Vue:

在 Vue 中,也是在使用子组件的地方传递数据,如下:

<ToDoItem v-for="todo in list"   
            :todo="todo" :id="todo.id"  
            :key="todo.id"  
            @delete="onDeleteItem" />

这样传递之后,我们会把这些数据传递到子组件的 props 数组中:props: [ 'id', 'todo' ]。然后就可以在子组件中通过它们的名字进行引用了,比如 id 和 todo。

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

React:

我们首先将函数传递给子组件,方法就是在使用子组件时将其作为 prop 传入。然后在形如 onClick 方法中通过 this.props.whateverTheFunctionIsCalled 引用这个函数。这将触发位于父组件中定义的函数。可以在如何从列表中删除 Todo 项一节中看到整个过程的一个示例。

Vue:

在子组件中,我们只需编写一个函数,将一个事件名发送回父组件。在父组件中,我们编写一个函数来监听这个事件,它会触发函数调用。可以在如何从列表中删除 Todo 项一节中看到整个过程的一个示例。

到这里就完成了

我们研究了如何添加、删除和更改数据,以 prop 形式将数据从父组件传入到子组件,以及通过事件侦听形式将数据从子组件发送到父组件。当然,在 React 和 Vue 之间还存在许多其它差异,但希望本文的内容对你理解两个框架如何处理数据打下一个好的基础.

Github链接地址:

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

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

原文链接:medium.com/javascript-…

作者:Sunil Sandhu

译者:peak 本文采用意译非直译