作为一个用vue撸了3年多代码的切图仔,最近在使用的react重构一个项目;写惯了vue之后再来写react,会发现两者有大的不同,但是仔细品味一下又会发现,其实两者很像。本文就从vue出发,结合这段时间的开发体验,感性地对比下二者在使用上的异同;注:本文讨论的是vue2.x的版本和react16.13.x版,函数式编程。
模板 template
vue 使用的是接近于 html 的模板语法;
<template>
<div>
{{ msg }}
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
msg: 'hello-vue'
}
}
}
</script>
<style>
</style>
react使用的 jsx 语法,这个语法更像是把 html 标签嵌入到 js 内,函数组件需要返回一个 jsx;
function App() {
const name = 'hello react'
return (
<div className="App">
{name}
</div>
);
}
export default App;
注意
- vue 和 react 的模板语法实际上都是“语法糖”,他们都需要通过编译,生成相应的 render 函数,最终渲染成真实的 dom 元素
- vue 的思路是拥抱前端"三板斧": html、js、css 分开(vue同时也支持 jsx );而 react 拥抱 js,希望把所有的东西都交给 js 去实现;
- vue 的文本插值是“Mustache”语法 (双大括号),而 react 是花括号包裹即可,都支持嵌入表达式;vue 支持在双大括号内输出对象、布尔值等,而 react 只能输出文字和数字;
- 样式上,vue 写样式直接就是
class
,而 react 支持类的写法,class
关键字被占用,所以使用的是className
;vue 的单组件文件支持局部样式表,只需要在 style 标签加 scoped 属性即可,而 react 支持局部变量需要用到 cssModule,把样式当成模块引入,且类名不支持通用的 BEM(可以用驼峰和下划线的方式,强迫症表示很难受);
数据 data
vue 的数据都是挂载在当前实例下的(包括props、data、method、computed等,这也是为什么在单组件文件下通过 this 可以访问到这些属性),声明时要分别定义;data 声明后即是响应式的,改变数据会同步反映到视图上;
<template>
<div>
{{ msg }}
{{ name }}
{{ computedName }}
</div>
</template>
<script>
export default {
name: 'App',
props: ['name'],
data() {
return {
msg: 'hello-vue'
}
},
computed: {
computedName() {
return `${this.name} computed`;
},
}
}
</script>
react 的函数组件的数据都是放在函数内部声明,每次渲染的时候都会执行一次;响应式数据需要利用 hook — useState 主动触发;
import React, { useState } from 'react';
function App({ name }) {
const [state, setstate] = useState('state');
return (
<div className="App">
{name}
{state}
<button onClick={() => {
setstate('state change');
}}>
click me
</button>
</div>
);
}
export default App;
注意
- vue 和 react 都是数据驱动视图的框架,提倡开发者专注数据层逻辑的开发,通过数据变更驱动视图更新;
- vue 在数据声明时就已经做了响应式的处理,而 react 则需要使用 useState 手动触发视图更新。使用下来感觉 vue 的好处是可以直接处理数据,数据是怎么样的,视图就是怎么样的,使用起来很方便;react 需要处理完数据后主动调用 useState ,这样的好处是你永远知道触发视图更新的操作在哪里,而 vue 虽然使用方便,但是也容易出现不符合预期的表现。
侦听器 watcher
vue 提供 watch 属性,用于当数据依赖发生变化时执行相应的逻辑。
<template>
<div>
{{ msg }}
<input type="text" v-model='msg'>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
msg: ''
}
},
watch: {
msg() {
// ...
}
}
}
</script>
react 使用处理副作用的 hook — useEffect;
import React, { useState, useEffect } from 'react';
function App() {
const [state, setstate] = useState('state');
useEffect(() => {
// ...
}, [state])
return (
<div className="App">
{state}
<button onClick={() => {
setstate('state change');
}}>
click me
</button>
</div>
);
}
export default App;
注意
- vue 的 watch 属性只能订阅一个数据源,而 react useEffect 的 hook,可以指定多个依赖;
- useEffect 会在首次渲染的时候执行一次,watch 也支持,不过需要指定 immediate 的 config;如果 useEffect 想在首次渲染时不执行,可以利用 useRef 在组件的整个周期的不变性进行条件控制;
计算属性 computed
vue 提供 computed 属性进行动态求值,每次内部依赖的数据发生变化,计算属性就会重新计算;计算属性的好处是可以减少在模板写过多的逻辑和缓存计算结果(不是每次渲染都重新计算一次,而是依赖变化了才进行计算)
<template>
<div>
{{ msg }}
{{ computedData }}
<input type="text" v-model="msg">
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
msg: ''
}
},
computed: {
computedData() {
return this.msg + this.msg;
}
}
}
</script>
react 可以使用变量声明的方式实现相同的效果,这个操作和 vue 直接在 template 写表达式是一样的,缺点是每次渲染都会执行;如果考虑性能的话,则可以使用 hook — useMemo,并指定依赖项;这样就可以实现 vue computed 一样的效果了。
import React, { useState, useEffect, useMemo } from 'react';
function App() {
const [state, setstate] = useState('state');
// 每次渲染都会重新计算
const computed = `${state}`;
// 依赖的 state 变化了才会重新计算
const computedMemo = useMemo(() => {
return state;
}, [state]);
useEffect(() => {
console.log(state);
}, [state])
return (
<div className="App">
{state}
{computed}
{computedMemo}
<button onClick={() => {
setstate('state change');
}}>
click me
</button>
</div>
);
}
export default App;
注意
- vue 的计算属性的依赖不需要主动指定(编译阶段 vue 内部就帮你关联了),而 react 使用 useMemo 需要主动指定依赖;
事件 event
vue 的事件是通过 v-on 或 @
的方式进行监听,$emit
的方式进行触发的
<template>
<div>
<!-- 监听 button 的点击事件,并 emit 自定义的 clickButton 事件 -->
<button @click="$emit('clickButton')"></button>
</div>
****</template>
react 的事件都是通过 props 传入回调实现的
function App({ handleClickButton }) {
return (
<div className="App">
<button onClick={() => {
handleClickButton();
}}>
click me
</button>
</div>
);
}
export default App;
生命周期
vue 有一系列的生命周期函数,如 mounted、created 等
<script>
export default {
name: 'App',
created() {
// ...
},
mounted() {
// ...
}
}
</script>
react 使用类的写法时也会有各种生命周期函数,如: componentDidMount、componentWillUnmount;但是使用 hook 写 react 组件时没有相应的生命周期函数,但是他提供了 useEffect 的 hook 用于处理副作用;虽然没有一一对应起来,但是我们可以通过一些使用上的技巧实现相应的功能;
function App() {
// 不添加任何依赖(空数组),只会在 render 时执行一次,类似于 vue 的 created 或者 mounted
useEffect(() => {
// ...
}, []);
// 在 useEffect 内返回一个函数,实际上这个函数会在组件被卸载的时候执行
// 结合上一个例子就可以实现 mounted 和 beforeDestroyed 的组合
// 我们通常会这个组合里 addlistener 和 removelistener
useEffect(() => {
// ...
return () => {
// ...
}
}, []);
// 依赖数组都去掉了,这个 effect 每次渲染都会执行,类似于 vue 的 updated
useEffect(() => {
// ...
});
return (
<div className="App">
</div>
);
}
export default App;
插槽 slot
vue 提供了 slot 相关的 api,用于分发不同的内容
<!-- 声明 botton-component.vue-->
<template>
<button type="submit">
<slot></slot>
</button>
</template>
<!-- 使用 -->
<botton-component> slot </botton-component>
react 不显示支持插槽的写法,但是可以通过每个组件自带的 children 属性 或者传入 jsx 属性实现
// children 属性
// 声明
function App({ children }) {
return (
<div className="App">
{children}
</div>
);
}
// 使用
// App 组件的 children 属性实际上就对应了 <div>children</div>
<App>
<div>children</div>
</App>
// jsx 属性
// 声明
function App({ slot }) {
return (
<div className="App">
{slot}
</div>
);
}
// 使用
<App slot={<div>children</div>}></App>
指令 directive
vue 提供了一系列非常好用的指令,比如 v-model 、v-if 等,他实际上做的事就是在 dom 不同的钩子回调中执行不同的回调,进而实现相应的功能;react 本身没有提供这个功能,不过指令能实现的功能都可以用其他方式解决;
<h1 v-if="awesome">Vue is awesome!</h1>
- 条件渲染
// vue
<template>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no </h1>
</template>
// react
function App({ children }) {
return (
{
awesome && <>React is awesome too!</>
}
{
!awesome && <>Oh no </>
}
// 或
{
awesome ? <>React is awesome too!</> : <>Oh no </>
}
);
}
- 列表渲染
// vue
<template>
<div v-for="item in [1, 2, 3]" :key="item">
{{ item }}
</div>
</template>
// react
function App({ children }) {
return (
{
[1, 2, 3].map(item => {
return <div key={item}>{item}</div>
})
}
);
}
插件
vue 官方提供了一系列的插件供开发者使用;而 react 的库大多都是由社区的开发者提供和维护的;像 vue-router 和 react-router、vuex 和 readux 等;
实际开发体验
react 项目初步做下来,会感觉需要自己思考的事情很多,react 的 api 其实很少,它很灵活,只关注核心能力:比如数据驱动视图、jsx等,其他的全权交给开发者处理。
而 vue 不止提供数据驱动、模板编译等能力,还提供了一系列的“标准”甚至“最佳实践”,直接和你说怎么做;vue 的文档写得真的很好,甚至他连“最佳实践”都在文档上;vue 官方提供的插件基本上都是比较好用的。
如果你想从 vue 开发平滑过渡到 react 开发,可以了解一下 “react + dva”,它和 vuex 一样,设计理念都是来源于 elm,如果你很熟悉 vue + vuex 的开发,它可以让你很几乎没有成本、无痛过渡到 react 开发来。
参考
https://zh-hans.reactjs.org/
https://cn.vuejs.org/