《架构师》2018年9月
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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

作者Sunil Sandhu 译者 无明

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

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

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

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

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

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

我们如何改变数据?

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