本项目需求功能Vue shop相同,后端API也是一样的,使用的是React三件套:React、React Router和Redux。先对比一下最终效果吧,源码在这里下载。此文主要就这两个项目说一说React与Vue的区别。
一、渲染不同
React推荐通过JSX渲染模板。而Vue是更多时候和对HTML模板进行渲染。本质上,React是在组件JS代码中,通过原生JS语法比如插值、条件,循环等、来实现模板,更加纯粹。React通过js来操作一切:通过js来生成html,所以设计了jsx,甚至使用js来操作css。它自身的API很小,得自己实现,幸好React的社区很活跃,不用什么都造轮子。所以说,React的半门槛更高,更适合JS基础更好的开发工程师。
看下面CartControl组件中JSX模板代码:
render() { return ( <div className="cart-control"> <div className="cart-decrease" title="减" onClick={this.decrement}> <span className="iconfont icon-remove-circle"></span> </div> <input className="cart-count" value={this.state.count} onChange={this.handleChange} /> <div className="cart-add" title="加" onClick={this.increment}> <span className="iconfont icon-add-circle"></span> </div> </div> ) } }
可以看到JSX模板其实就是React节点,作为render方法的返回值使用的。状态及方法更接近原生JS写法,绑定它们时仍然用到this。因为JSX的本质是js,此处的上下文就是组件本身。重要的事再强调一下,JSX的本质是js,得按JS的方式来处理。
需要注意的是:
react元素的事件处理和DOM元素的很相似,但是有一点语法上的不同:
1. React事件的命名采用小驼峰式(camelCase),而不是纯小写。
2. 使用JSX语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
Vue的单文件组件JS代码和HTML、CSS样式都是分离的,对模板的操作通过指令来实现的,比如条件语句就需要v-if来实现对这一点,Vue借鉴了angular的做法,因此更适合有设计基础的开发工程师。Vue自身的API很多,更像是一个是MVVM框架,有后端开发经验的同学理解起来也很容易。换名话说,Vue的学习曲线要平滑得多。
如CartControl组件中HTML模板代码:
<div class="cart-control"> <div class="cart-decrease" title="减" @click.stop.prevent="decrement(product)"> <span class="iconfont icon-remove-circle"></span> </div> <input class="cart-count" :value="count" @input="inputHandler"></input> <div class="cart-add" title="加" @click.stop.prevent="increment(product)"> <span class="iconfont icon-add-circle"></span> </div> </div>
注意到没有,这个模板用到了v-bind和v-on指令,使用的是简写方式,这些指令甚至有自己的修饰符。绑定状态及方法时不用上下this,在Vue的mount过程中,template会被编译成AST语法树。感兴趣的可以深入去了解一下。另外,借助transform-vue-jsx插件,Vue项目是也可以使用JSX。
二、组件写法不同
react是类式的写法,也可以使用函数组件。如下面的代码:
class CartControl extends Component { constructor(props) { super(props); this.state = { count: props.product.quantity || 1 } this.handleChange = this.handleChange.bind(this) this.increment = this.increment.bind(this) this.decrement = this.decrement.bind(this) } static propTypes = { product: PropTypes.object.isRequired, added: PropTypes.number.isRequired, needConfirmed: PropTypes.bool } increment(e) { e.preventDefault() const { product, needConfirmed, added } = this.props let count = this.state.count; let max = count if (needConfirmed) { max = this.state.count + added; } if (max < product.inventory) { this.setState({ count: ++count }); this.watchCount(count) } } ... render() { return ( <div className="cart-control"> <div className="cart-decrease" title="减" onClick={this.decrement}> <span className="iconfont icon-remove-circle"></span> </div> <input className="cart-count" value={this.state.count} onChange={this.handleChange} /> <div className="cart-add" title="加" onClick={this.increment}> <span className="iconfont icon-add-circle"></span> </div> </div> ) } } export default connect(state => state.cart, { willUpdateItem, updateCartItem })(CartControl)
就是es6的写法:类、方法、属性,很纯粹。
注意,事件方法一般要在类组件的构造函数中绑定,否则访问不到this。
而vue是声明式的写法,通过在字变量对象对象中设置mixins、directives、props、data、computed、生命周期方法、watch、methods自定义方法来定义组件。
export default { name: 'CartControl', props: { product: { type: Object, default: function(){} } ... }, data() { return { count: this.product.quantity || 1 } }, watch: { count: function(count) { if (this.needConfirmed) { this.$store.commit('willUpdateCartItem', count) } else { this.$store.commit('updateCartItem', { id: this.product._id, count: count } ) } } }, methods: { increment() { let max = this.count if (this.needConfirmed) { max = this.count + this.added; } if (max < this.product.inventory) { this.count++ } } ... } </script>
所以react组件很纯粹,和typescript整合更容易,vue整合起来稍微复杂。
三、数据流的不同
React一直不支持双向绑定,提倡的是单向数据流,称之为onChange/setState()模式。不能对修改props,如图所示:
Vue也提倡单向数据流,但通过向父组件emit一个事件,或者借助v-model实现父子组件双向绑定了,这对于一些特殊场景,比如处理表表单也是很有用的。另外,新版的Vue也不鼓励组件对自己的props进行任何修改了。如下图:
四、监听数据变化的实现不同
Vue将遍历此data对象所有的property,并使用 Object.defineProperty通过 getter/setter以及一些函数的劫持追踪依赖。详见深入响应式原理。正是因为Vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树,因此可以更快地计算出VDOM差异。
React默认是通过比较引用的方式(diff)进行的,在应用的状态被改变时,全部子组件都会重新渲染。它是为大项目而生的,如果不优化可能导致大量不必要的VDOM的重新渲染,甚至还没有直接操作DOM来得快。
Vue的实现简单很多,Vue使用的是可变数据,可以对data对象随意修改。而React更强调数据的不可变,对状态的修改必须要用setState方法。
React是这样修改的:
handleChange(e) { let val = e.target.value // Should be a positive integer if (/^[1-9]\d*$/.test(val) && val <= this.props.product.inventory) { this.setState({ count: parseInt(val) }) this.watchCount(val) } else { e.target.value = this.state.count } }
vue是这样实现的,
inputHandler(e) { let val = e.target.value // Should be a positive integer if (/^[1-9]\d*$/.test(val) && val < this.product.inventory) { this.count = parseInt(val) } else { e.target.value = this.count } }
五、Redux和Vuex的联系区别
本质上,Redux和Vuex都是对MVVM思想的服务,将数据从视图中抽离的一种方案。它们都使用单一的数据源,用了设计模式中的观察者模式或者说发布/订阅模式。在React中,initState相当于event对象,dispatch相当于trigger方法, dispatch的函数则相当于订阅的事件。在Vuex中,$store相当于event对象,store.commit相当于trigger方法, mutation中的函数名则相当于订阅的事件。Vuex借鉴了Redux,将store作为全局的数据中心进行数据管理。
Redux数据流向:
View –> Actions –> Reducer –> State变化 –> View变化(同步异步一样)
Vuex的数据流向:
View –> Commit –> Mutations –> State变化 –> View变化(同步操作)
View –> Dispatch –> Actions –> Mutations –> State变化 –> View变化(异步操作)
1. 在Redux中,我们每一个组件都需要显示的用connect把需要的props和dispatch连接起来。在Vuex中,$store被直接注入到了组件实例中,因此可以比较灵活的使用;
2. Vuex改进了Redux中的Action和Reducer函数,以Mutations变化函数取代Reducer,无需switch,只需在对应的Mutation函数里改变state值就可以;
3. Vuex只能配合Vue使用,Redux可以整合到任何框架中;
4. 而Redux中没有异步操作的API,得借助redux-thunk来实现,Vuex中可以直接使用Action进行异步操作;
5. 而Redux是没有module化功能,Vuex自带就有。
React和Vue中的路由基本上是一样,Vue Router借鉴了React Router,核心组件、配置和API都差不多。
React和Vue的设计思想是不一样的,前者是映射思维,后者是模板思维。在映射思维的世界观里,js数据是唯一的实质。在模板思维看来,html模板和js数据都是实质,二者是并列的关系;