| 标题 | React for Vue developers | 
|---|---|
| 中文标题 | 写给Vue用户的React指南 | 
| 出处 | https://sebastiandedeyne.com/react-for-vue-developers/ | 
| 发布日期 | 2019年5月20日 | 
| 翻译日期 | 2021年01月23日 | 
| 关键词 | React, Vue | 
在过去的三年里,我在不同项目中使用 React 和 Vue 。
上个月我写了一篇文章 《为什么我相比vue更喜欢React》。后来我参加了 Adam Wathan 的《Full Stack Radio》(一个podcast访谈)。谈起了从一个Vue开发者的观点看React。
我们在podcast中谈到了很多,但我们谈到大部分内容,可以从下面的代码片段得到启发,这些代码解释他们的相同和不同。
这篇文章是大部分vue特性的简单总结,我会使用2019年Rect Hooks 来实现它们。
如果我遗漏了什么,或者想看到其他比较,或者你想分享你vue和react的想法,可以看我的 Twitter
Template 模板
react的替代: JSX 
Vue 使用HTML字符串和一些特殊的指令Directive 来书写模板。鼓励使用 .vue 后缀的文件来分离模板template、逻辑script、样式style。
基础
<!-- Gretter.vue --><template><p>Hello, {{ name }}!</p></template><script>export default {props: ['name']};</script>
React 使用 JSX ,这是一个 ECMAScript 的一个拓展。
export default function Greeter({ name }) {return <p>Hello, {name}!</p>;}
条件渲染
React的选择: && 操作符 Logical && operator、三元表达式Ternary statements、尽早返回early returns
Vue 使用 v-if , v-else 和 v-else-if 指令进行条件渲染模板的一部分。
<!-- Awesome.vue --><template><article><h1 v-if="awesome">Vue is awesome!</h1></article></template><script>export default {props: ['awesome']};</script>
React 不支持指令,所以要使用js判断返回你想要的内容。
这个 && 操作符提供了简洁succinct 的方式来书写 if  语句。
React
export default function Awesome({ awesome }) {return (<article>{awesome && <h1>React is awesome!</h1>};</article>);}
如果你需要使用 else,可以使用三元表达式。
export default function Awesome({ awesome }) {return (<article>{awesome ? (<h1>React is awesome!</h1>) : (<h1>Oh no 😢</h1>)};</article>}
也可以使用 return 提前结束判断,让两个条件分离,
export default function Awesome({ awesome }) {if (!awesome) {return (<article><h1>Oh no 😢</h1></article>);}return (<article><h1>React is awesome!</h1></article>);}
列表渲染
React的替代: Array.map。
Vue 使用了 v-for 指令来循环数组和对象。
<!-- Recipe.vue --><template><ul><li v-for="(ingredient, index) in ingredients" :key="index">{{ ingredient }}</li></ul></template><script>export default {props: ['ingredients']};</script>
在React中,可以使用js内置的  Arrap.map 来实现数组遍历。
export default function Recipe({ ingredients }) {return (<ul>{ingredients.map((ingredient, index) => (<li key={index}>{ingredient}</li>))}</ul>);}
迭代对象需要一点小技巧,Vue还是使用 v-for 指令来获得对象上的属性key和值value。
<!-- KeyValueList.vue --><template><ul><li v-for="(value, key) in object" :key="key">{{ key }}: {{ value }}</li></ul></template><script>export default {props: ['object'] // E.g. { a: 'Foo', b: 'Bar' }};</script>
我喜欢在React中使用 Object.entries 方法来获取对象里的内容。
export default function KeyValueList({ object }) {return (<ul>{Object.entries(object).map(([key, value]) => (<li key={key}>{value}</li>))}</ul>);}
CSS: class和style绑定
React的选择:手动传递props。
Vue 会自动绑定组件的元素 class 和 style 
<!-- Post.vue --><template><article><h1>{{ title }}</h1></article></template><script>export default {props: ['title'],};</script><!--<post:title="About CSS"class="margin-bottom"style="color: red"/>-->
React中你需要手动传递 className 和 style 属性。注意 style 在react中必须是一个对象,不支持字符串。
export default function Post({ title, className, style }) {return (<article className={className} style={style}>{title}</article>);}{/* <Posttitle="About CSS"className="margin-bottom"style={{ color: 'red' }}/> */}
如果你想传递所有的属性,可以使用 ... 操作符
export default function Post({ title, ...props }) {return (<article {...props}>{title}</article>);}
如果你怀念 Vue 里的 class api,可以看看 这个库 classnames
Props
React 的选择: Props 
React Props 的行为和Vue很相似。一个微小的不同:React组件不会继承未知Unknown 的属性。
<!-- Post.vue --><template><h1>{{ title }}</h1></template><script>export default {props: ['title'],};</script>
react
export default function Post({ title }) {return <h3>{title}</h3>;}
Vue 里使用props,可以使用 : 前缀,这是 v-bind 指令的别名。React使用 {} 作为动态值
<!-- Post.vue --><template><post-title :title="title" /></template><script>export default {props: ['title'],};</script>
react
export default function Post({ title }) {return <PostTitle title={title} />;}
数据Data
基础
React的选择 : useState hook
Vue中 data 用来存储本地组件的状态。
<!-- ButtonCounter.vue --><template><button @click="count++">You clicked me {{ count }} times.</button></template><script>export default {data() {return {count: 0}}};</script>
React 提供了一个 useState 的hook,它返回包含两个元素的数组,对应当前的状态和设置方法。
import { useState } from 'react';export default function ButtonCounter() {const [count, setCount] = useState(0);return (<button onClick={() => setCount(count + 1)}>{count}</button>);}
如果要分配多个状态,你有两种方式:
使用多个 useState ,多次调用。
import { useState } from 'react';export default function ProfileForm() {const [name, setName] = useState('Sebastian');const [email, setEmail] = useState('sebastian@spatie.be');// ...}
或者使用一个对象。
import { useState } from 'react';export default function ProfileForm() {const [values, setValues] = useState({name: 'Sebastian',email: 'sebastian@spatie.be'});// ...}
v-model
v-model 是 Vue中的一个指令,用来传递 value 和监听 input 事件。这看起来Vue实现了双向绑定 two-way bindling,但背后的原理还是 传递props,接收event—— props down,events up 
<!-- Profile.vue --><template><input type="text" v-model="name" /></template><script>export default {data() {return {name: 'Sebastian'}}};</script>
v-model 展开是下面这样:
<template><inputtype="text":value="name"@input="name = $event.target.value"/></template>
react没有 v-model ,你需要始终这样:
import { useState } from 'react';export default function Profile() {const [name, setName] = useState('Sebastian');return (<inputtype="text"value={name}onChange={event => setName(event.target.name)}/>);}
计算属性Computed properties
React 的选择: 变量Variable、可选包装 useMemo 
Vue 使用计算属性有两个原因:
- 避免在HTML中混合逻辑Mixing logic 和标签
 - 在一个组件实例中缓存复杂的计算操作
 
如果不使用计算属性:
(html中含有逻辑和标签混杂)
<!-- ReversedMessage.vue --><template><p>{{ message.split('').reverse().join('') }}</p></template><script>export default {props: ['message']};</script>
React
export default function ReversedMessage({ message }) {return <p>{message.split('').reverse().join('')}</p>;}
使用React,你可以从模板中提取一个计算的结果作为一个变量。
<!-- ReversedMessage.vue --><template><p>{{ reversedMessage }}</p></template><script>export default {props: ['message'],computed: {reversedMessage() {return this.message.split('').reverse().join('');}}};</script>
react
export default function ReversedMessage({ message }) {const reversedMessage = message.split('').reverse().join('');return <p>{reversedMessage}</p>;}
如果考虑性能,计算过程可以用 useMemo hook,它需要:用来返回一个计算结果的回调,一个数组依赖。
下面的例子中, reverseMessage 会在 依赖项 message 变化之后重新计算。
import { useMemo } from 'react';export default function ReversedMessage({ message }) {const reversedMessage = useMemo(() => {return message.split('').reverse().join('');}, [message]);return <p>{reversedMessage}</p>;}
方法Methods
React 的替代:函数Function
Vue 有一个 methods 选项,用来声明组件用到的方法。
<!-- ImportantButton.vue --><template><button onClick="doSomething">Do something!</button></template><script>export default {methods: {doSomething() {// ...}}};</script>
在React中,你可以在组件里声明一个简单的函数。
export default function ImportantButton() {function doSomething() {// ...}return (<button onClick={doSomething}>Do something!</button>);}
事件Events
React的替代: 回调props
事件本质上是子组件触发了某些事情的一个回调。Vue 把事件视为一等公民,所以你可以使用 v-on 或者 @ 来监听:
<!-- PostForm.vue --><template><form><button type="button" @click="$emit('save')">Save</button><button type="button" @click="$emit('publish')">Publish</button></form></template>
React里的事件没有特殊的含义,只是子组件的props回调。
export default function PostForm({ onSave, onPublish }) {return (<form><button type="button" onClick={onSave}>Save</button><button type="button" onClick={onPublish}>Publish</button></form>);}
事件修饰
React:可以考虑HOC高阶组件
Vue有一些修饰符 prevent stop 来改变事件的行为,而不需要修改处理逻辑。
<!-- AjaxForm.vue --><template><form @submit.prevent="submitWithAjax"><!-- ... --></form></template><script>export default {methods: {submitWithAjax() {// ...}}};</script>
React里没有修饰符语法。阻止默认事件和停止冒泡通常需要在回调里处理。
export default function AjaxForm() {function submitWithAjax(event) {event.preventDefault();// ...}return (<form onSubmit={submitWithAjax}>{/* ... */}</form>);}
如果你真的想有一些类似修饰符的东西,你可以考虑高阶组件HOC
function prevent(callback) {return (event) => {event.preventDefault();callback(event);};}export default function AjaxForm() {function submitWithAjax(event) {// ...}return (<form onSubmit={prevent(submitWithAjax)}>{/* ... */}</form>);}
生命周期Lifecycle methods
React的替代: useEffect hook。
免责声明 在类组件Class Component 中,React和Vue都有很相似的生命周期api。使用 useEffect hooks,可以解决大多数生命周期相关的问题。两者是完全不同的思路,因此很难进行比较。这部分内容只是使用几个例子,用来说明效果。
使用生命周期方法常用来建立和销毁第三方库。
<template><input type="text" ref="input" /></template><script>import DateTimePicker from 'awesome-date-time-picker';export default {mounted() {this.dateTimePickerInstance =new DateTimePicker(this.$refs.input);},beforeDestroy() {this.dateTimePickerInstance.destroy();}};</script>
React 使用 useEffect ,你可以声明一个 副作用 side effect 需要渲染之后运行。使用 useEffect 的回调会在完成之后触发。在这个例子中,副作用发生在组件销毁之后。
import { useEffect, useRef } from 'react';import DateTimePicker from 'awesome-date-time-picker';export default function Component() {const dateTimePickerRef = useRef();useEffect(() => {const dateTimePickerInstance =new DateTimePicker(dateTimePickerRef.current);return () => {dateTimePickerInstance.destroy();};}, []);return <input type="text" ref={dateTimePickerRef} />;}
这和Vue在 mounted 中注册一个 beforeDestroy 监听很相似。
<script>export default {mounted() {const dateTimePicker =new DateTimePicker(this.$refs.input);this.$once('hook:beforeDestroy', () => {dateTimePicker.destroy();});}};</script>
useEffect 和 useMemo 一样,接收一个依赖数组作为第二个参数。
如果不设置依赖,会在每次渲染之后触发,会在下次渲染之前清除。这个功能和Vue的 mounted updated beforeUpdate beforeDestroy 很像。
useEffect(() => {// Happens after every render 每次render之后触发return () => {// Optional; clean up before next render 可选,下次渲染之前清楚};});
如果你指定了没有依赖项,触发条件只有组件第一次渲染的时候,因为它没有条件触发。这个功能和Vue的 moutned 和 beforeDestroyed 很像。
useEffect(() => {// Happens on mountreturn () => {// Optional; clean up before unmount};}, []);
如果执行了有一个依赖,会在依赖项发生变化之后触发。我们会在后续的 watchers 章节再回顾。
const [count, setCount] = useState(0);useEffect(() => {// Happens when `count` changesreturn () => {// Optional; clean up when `count` changed};}, [count]);
不要考虑把生命周期的方法全都转换成 useEffect 。最好重新声明一组副作用。
正如 Ryan Florence 提到:
问题不是“副作用何时被触发”,而是:“这个效果和哪个状态同步” useEffect(fn) // all state useEffect(fn, []) // no state useEffect(fn, [these, states]) by @ryanflorence on Twitter
侦听器Watchers
React的替代: useEfeect hook
watcher 概念上和生命周期的方法很像:当 X 发生了,就做 Y事情。React里没有Watcher,但可以使用 useEffect 实现相同的效果。
<!-- AjaxToggle.vue --><template><input type="checkbox" v-model="checked" /></template><script>export default {data() {return {checked: false}},watch: {checked(checked) {syncWithServer(checked);}},methods: {syncWithServer(checked) {// ...}}};</script>
React
import { useEffect, useState } from 'react';export default function AjaxToggle() {const [checked, setChecked] = useState(false);function syncWithServer(checked) {// ...}useEffect(() => {syncWithServer(checked);}, [checked]);return (<inputtype="checkbox"checked={checked}onChange={() => setChecked(!checked)}/>);}
注意 useEffect 在第一次渲染时候会被触发,这和Vue在watcher中设置 immediate 选项一样。
如果你不想在第一次渲染触发,你可以创建一个 ref 变量来控制第一次渲染是否触发。(就是设置一个flag)
import { useEffect, useRef, useState } from 'react';export default function AjaxToggle() {const [checked, setChecked] = useState(false);const firstRender = useRef(true);function syncWithServer(checked) {// ...}useEffect(() => {if (firstRender.current) {firstRender.current = false;return;}syncWithServer(checked);}, [checked]);return (<inputtype="checkbox"checked={checked}onChange={() => setChecked(!checked)}/>);}
插槽和作用域插槽Slots & scoped slots
React的替代:JSX 的props 或者 render props
如果你在一个组件内部渲染一个其他模板,React使用 children 属性。
Vue可以声明一个 <slot/> 表示内部的内容。React你可以渲染 children 。
<!-- RedParagraph.vue --><template><p style="color: red"><slot /></p></template>
React
export default function RedParagraph({ children }) {return (<p style={{ color: 'red' }}>{children}</p>);}
React里的 插槽是指props,我们不需要在模板中声明,只需要接收 jsx的props,然后在render时候进行渲染。
<!-- Layout.vue --><template><div class="flex"><section class="w-1/3"><slot name="sidebar" /></section><main class="flex-1"><slot /></main></div></template><!-- In use: --><layout><template #sidebar><nav>...</nav></template><template #default><post>...</post></template></layout>
React
export default function RedParagraph({ sidebar, children }) {return (<div className="flex"><section className="w-1/3">{sidebar}</section><main className="flex-1">{children}</main></div>);}// In use:return (<Layout sidebar={<nav>...</nav>}><Post>...</Post></Layout>);
Vue 有作用域插槽scoped slots ,给将要渲染的slot传递数据。关键点:将要渲染
常规的slot是在父组件渲染之前就渲染完了。父组件再决定如何处理渲染的片段。
作用域插槽不能在父组件之前渲染,因为他们依赖父组件传递的数据,因此作用域插槽延迟执行。
在js中延迟执行某些事情很简单:用function包裹然后需要的时候再调用。如果你需要在React中使用作用域插槽,传递一个负责渲染的函数就可以。
我们也可以使用 chidren ,或者其他prop, 传递一个函数而不是声明一个模板 ??
<!-- CurrentUser.vue --><template><span><slot :user="user" /></span></template><script>export default {inject: ['user']};</script><!-- In use: --><template><current-user><template #default="{ user }">{{ user.firstName }}</template></current-user></template>
React
import { useContext } from 'react';import UserContext from './UserContext';export default function CurrentUser({ children }) {const { user } = useContext(UserContext);return (<span>{children(user)}</span>);}// In use:return (<CurrentUser>{user => user.firstName}</CurrentUser>);
Provide/inject
React 的替代: createContext 和 useContext hook
provide/inject 允许一个组件和所有的子组件共享状态。React有一个相同的特性:context
<!-- MyProvider.vue --><template><div><slot /></div></template><script>export default {provide: {foo: 'bar'},};</script><!-- Must be rendered inside a MyProvider instance: --><template><p>{{ foo }}</p></template><script>export default {inject: ['foo']};</script>
React
import { createContext, useContext } from 'react';const fooContext = createContext('foo');function MyProvider({ children }) {return (<FooContext.Provider value="foo">{children}</FooContext.Provider>);}// Must be rendered inside a MyProvider instance:function MyConsumer() {const foo = useContext(FooContext);return <p>{foo}</p>;}
自定义指令Custom directives
React替代: Components 组件
React里没有指令。但是很多靠指令解决的问题可以使用 组件来解决。
<div v-tooltip="Hello!"><p>...</p></div>
React
return (<Tooltip text="Hello"><div><p>...</p></div></Tooltip>);
过渡效果Transitions
React的替代:第三方库。
React没有内置的过渡工具。可以考虑使用 react-transition-group,没有提供动画,但是可以通过class类名来编排。
如果你想要一个提供更多帮助的库,考虑 pose ,(注:React Pose for web has been deprecated by Framer Motion.)
