前几年我一直在使用 React。最初仅有 React,后来使用 Redux 和 React 的其他库(react-router、react-redux、prop-types 等)配合使用。我喜欢 React 的简单和方便,使用 React 的时光一直都很快乐。我喜欢这个时代,有太多的好工具帮助我们更快更好地开发应用。
近三个月我在用 Vue 构建 Web 应用,在此我想分享一些我作为一名 React 拥护者的 Vue 使用经验。我不想写成一篇 Vue/React 比较的文章,这种文章太多了,包括官方的 Vue 文档(https://vuejs.org/v2/guide/comparison.html)。它只是一些关于切换库的个人观点。
如果你使用过 Vue 和 React,或者像我一样刚刚从 React 切换到 Vue 正在适应,或者只是想多一些了解,我希望这篇文章能对你有帮助。
React 和 Angular 相似的地方
相比 React,Vue 有时更多地被拿来和 Angular 比较。实际上,浏览 Vue 模板时我们首先看到的就是双向绑定和 directive,与 Angular 非常类似:
<div id="app-3">
<span v-if="seen">Now you see mespan>
<ol>
<todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id">
todo-item>
ol>
<button v-on:click="reverseMessage">Reverse Messagebutton>
div>
尽管 Vue 支持 JSX,但通常的方法还是将模板和 JavaScript 分开。虽然React JSX 的语法很像原生语法,并且反映了通常的 JavaScript 语法,但 Vue 的模板语法非常高级,它包含 directive、快捷方式和条件渲染,使得 Vue 更像 Angular。不过,相似性也就到此为止了。
当然,前后端使用同一种模板可能会有很大好处(比如 node.js/Pug + Vue/Pug),而且尽管 Vue 提供的众多 directive 可能很有用,但对于我来说,从 React 的 JSX 切换到 Vue 的模板依然很痛苦。
Redux vs. Vuex
在应用中,React 通常会与某种数据流库结合使用,最流行的就是 Redux。Vue 也有个类似的数据流库,叫做 Vuex,我很高兴地发现它和 Redux 非常相似。实际上,从 Redux 切换到 Vuex 没有任何痛苦,因为与 React 跟 Vue 相比,这两个库有更多的共同点。
主要的区别就是 Redux 严重依赖于状态的不可修改性。原因就是 Redux 从 React 的思想而来(https://redux.js.org/faq/immutable-data#why-is-immutability-required-by-redux),而且尽管 React 本身能处理可改变的数据,但在 React 中的推荐做法是不要修改 props 或 state 的数据(https://reactjs.org/docs/optimizing-performance.html#the-power-of-not-mutating-data),以便 React 获得最好的效率。
在 React 中,组件 state 的变化会触发该组件以下的整个组件子树的重新渲染。为了避免不必要的子组件重新渲染, 我们需要使用 PureComponent,或尽量实现 shouldComponentUpdate。还需要使用不可变的数据结构让 state 的变化更容易被优化。(https://vuejs.org/v2/guide/comparison.html#Optimization-Efforts)
然而 Vuex 完全不关心 state 是否不可修改。
在 Vue 中,组件的依赖会自动在渲染过程中跟踪,因此当 state 发生变化时,系统可以精确地知道哪个组件需要渲染。(https://vuejs.org/v2/guide/comparison.html#Optimization-Efforts)
因此,React/Vue 与组件交互的方式有一些区别,下面我来介绍下这些区别。
Dispatch 和 Commit
Redux 中的数据流十分严格且直接。组件会 dispatch action,而 action 由 action 的创建器函数返回。然后 reducer 会根据收到的 action 返回新的 state。最后,组件会通过 store 监听 state 的变化,并在 connect() 函数的帮助下访问state中的属性。
每个 action 都会通过 action 创建器。尽管理论上来说可以直接从组件中 dispatch 一个 action,但通常不这样做。action 的语法本身就鼓励我们将 action 的逻辑封装在 action 创建器函数中,即使是最简单的 action:
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
尽管 Vuex 的数据流很相似,但它并不严格要求组件与 state 交互的方式。首先,组件可以 dispatch action。这通常是一些异步动作,比如从后台获取数据等。之后,action 会 commit 一个 mutation。mutation 函数与 reducer 的相似之处就是,它是唯一能够改变 state 的东西。但还有另一种方法:组件可以直接 commit 一个 mutation,有时候跳过 action 直接修改数据是很方便的 。
从组件 commit mutation 的行为不仅没有被严格禁止,Vuex 的文档甚至鼓励在异步的情况下直接使用 action(https://vuex.vuejs.org/en/mutations.html#committing-mutations-in-components)。由于我习惯了 React 的更严格的数据流,我更主张严格分离的概念——不管什么情况下,即使是同步或者非常简单的情况,commit mutation 也应该只能由 action 实施。
如果组件只能通过 action 来创建 mutation,那么组件和 mutation 之间就会有一个额外的层,保证组件和 mutation 之间的低耦合,最终使得代码更容易维护和修改。
从 store 中获取数据
为了与 React 组件内部的 store 交互,我们需要使用 connect() 函数。我认为 React/Redux 最让人不爽的一点就是,你不得不判断哪些组件该用 connect(),哪些不该用。使用 connect() 的组件通常被称为容器,而不使用 connect() 的一般称为表现组件,或者“笨”组件。
但 connect() 不能用得太多,因为它的性能很差。但如果只在顶层组件使用的话,需要传递给下层组件的 props 就会迅速增多。这个问题曾多次被讨论(如这里https://redux.js.org/docs/faq/ReactRedux.html#react-multiple-components和这里https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0),但实际上,即使容器组件的数量还算合理,传递给下层的 props 也挺让人头疼的。
我很意外地返现,在 Vue 中我根本不需要考虑这个问题。store 可以从任何 Vue 组件中访问,非常简单:
const Counter = {
template: `{{ count }}`,
computed: {
count () {
return this.$store.state.count
}
}
}
也就是说,从一个组件传递给另一个组件的 props 数量非常少,而且只需要传递那些没有保存在 store 中的数据。不过,在 Vue 中传递 props 的语法却非常不方便:
<template>
<div>
<todo-item :todo="todo">todo-item>
div>
template>
<script>
import TodoItem from './TodoItem.vue'
export default {
components: {
TodoItem
},
data () {
return {
todo: {
text: 'Learn Vue',
isComplete: false
}
}
}
}
script>
这里我们要给子组件(TodoItem)传递 props,但却不能在子组件定义的位置传递,而必须在模板里传递。相比之下,React 中的 props 传递更加自然,是在子组件渲染时完成的:
class TodoList extends React.Component {
render() {
<TodoItem todo={this.props.todo} />
div>
}
}
尽管在 Vue 中传递 props 很不方便,但好处是,由于 store 能在任何组件中访问,实际需要传递的 props 比 React 中少得多,而在 React 中,即使有足够多的容器组件,平均每个组件收到的 props 数量也非常大。
更新:新的 React Context API(https://reactjs.org/docs/context.html)提供了一种在组件树中直接访问数据而不需要在每层手动传递 props。
结论
如开头所说,本文只是一些我在从 React 迁移到 Vue 时发现的一些最重要的问题。这并不是一篇严谨的比较,不能作为选择库的依据。但如果你也像我一样不得不从一个库切换到另一个库,或者只是想了解更多的关于两个库的信息,这篇文章也许会有帮助。
总结一下重点:
Vue 默认不包含 JSX,很强调脚本和模板分离;
Redux 和 Vuex 背后的数据流思想很相似;
Redux 十分依赖于 state 的不可改变性,而 Vuex 不关心 state 是否不可改变;
Vue 允许 dispatch,也允许直接从组件中 commit,但最好还是严格些,只允许 dispatch 会比较好;
任何 Vue 组件都可以直接访问 store。
原文:https://medium.com/@omm2/doing-vue-after-three-years-with-react-3d36d53abbd6
作者:Anya Pavlova,unu GmbH的软件工程师。
译者:弯月,责编:屠敏