為什么要從 Vue 轉(zhuǎn)到 React,這篇文章為什么我們放棄了 Vue?不過對于大多數(shù)人來說,用 Vue 還是 React 不是自己說了算,多學一門技術(shù)不是壞處,而且 React 被大廠大量使用,要進入大廠也是必備的技能,筆者原先使用 Vue,由于 React 相關(guān)概念更加簡單,只要會 js 就行,轉(zhuǎn)到 React 只花了幾天時間(已經(jīng)回不去了~)。本文寫給想從 Vue 轉(zhuǎn)到 React 的同學,假設(shè)讀者有一定的 Vue 基礎(chǔ)。
JSX
先介紹 React 唯一的一個語法糖:JSX。
<div class='box' id='content'>
<div class='title'>Hello</div>
<button>Click</button>
</div>
上面的 DOM 結(jié)構(gòu)可以看出,要每個標簽只有 3 個信息:標簽名、屬性、子元素,所以上面等同于下面的 JSON 結(jié)構(gòu):
{
tag: 'div',
attrs: { className: 'box', id: 'content'},
children: [
{
tag: 'div',
arrts: { className: 'title' },
children: ['Hello']
},
{
tag: 'button',
attrs: null,
children: ['Click']
}
]
}
當你寫下這個 React 組件是:
import React from 'react';
function MyComponent(props) {
return <div>{props.hello}</div>
}
最終會被自動工具翻譯成:
import React from 'react';
function MyComponent(props) {
return React.createElement('div', null, props.hello);
}
理解 JSX 語法并不困難,簡單記住一句話,遇到 {} 符號內(nèi)部解析為 JS 代碼,遇到成對的 <> 符號內(nèi)部解析為 html 代碼。React 就是通過這個小小語法糖,實現(xiàn)在 JS 里面寫 HTML,可能有小伙伴會說 HTML 與 JS 分離不是更好嗎?責職分明,混合只會更亂。但當你體驗到代碼自動提示,自動檢查,以及調(diào)試時精確定位到一行代碼的好處時,就清楚 React 和 Vue 的差距了。
語法糖轉(zhuǎn)換
習慣 Vue 的同學都知道很多語法糖,比如 v-if、v-for、v-bind、v-on 等,相比 Vue,React 只有一個語法糖,那就是 jsx/tsx。v-if 這些功能在 React 上都是通過原生 JAVAscript 實現(xiàn)的,慢慢你會發(fā)現(xiàn),其實你學的不是 React,而是 Javascipt,React 賦予你通過 js 完整控制組件的能力,這部分明顯比 Vue 的語法糖更加靈活,糖太多容易引來蟲子(Bug)。
v-if 條件渲染
vue 中寫法是這樣:
<template>
<div>
<h1 v-if="awesome1">Vue is awesome!</h1>
<h1 v-else>else</h1>
<h1 v-if="awesome2">Oh no</h1>
</div>
</template>
<script>
module.exports = {
data: function() {
return {
awesome1: true,
awesome2: false,
}
}
}
</script>
在 React 函數(shù)組件中只需這樣:
import React, { useState } from 'react';
function Index() {
const [awesome1, setAwesome1] = useState(true);
const [awesome2, setAwesome2] = useState(false);
return (
<div>
{awesome1 ? <h1>React is awesome!</h1> : <h1>Oh no</h1>}
{awesome2 && <h1>React is awesome!</h1>}
</div>
);
}
export default Index;
只需使用 js 三目運算符語法即可完成條件渲染的功能。或者使用 && 邏輯,記住下面一句話就能過理解了:
遇到 {} 符號內(nèi)部解析為 JS 代碼,遇到成對的 <> 符號內(nèi)部解析為 HTML 代碼
v-for 列表渲染
Vue 中寫法:
<template>
<ul id="array-rendering">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
</template>
<script>
module.exports = {
data() {
return {
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
}
</script>
React 寫法:
import React, { useState } from 'react';
function Index() {
const [items, setItems] = useState([{ message: 'Foo' }, { message: 'Bar' }]);
return (
<ul id="array-rendering">
{items.map((item, id) => <li key={id}>{item.message}</li>)}
</ul>
);
}
export default Index;
React 通過 js 的數(shù)組語法 map,將數(shù)據(jù)對象映射為 DOM 對象。只需學會 js,無需記住各種指令,如果要做列表過濾,直接使用 items.filter(...).map(...) 鏈式調(diào)用即可,語法上更加靈活,如果為了提高渲染性能,使用 useMemo 進行優(yōu)化即可,類似 Vue 的 computed。
v-model
Vue 中 v-model 是一個數(shù)據(jù)綁定語法糖,本質(zhì)上還是單向數(shù)據(jù)流,下面的子組件通過 update:title 同步 title 參數(shù)。
App.component('my-component', {
props: {
title: String
},
emits: ['update:title'],
template: `
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)">
`
})
React 寫法較為簡單,不需要像 Vue 一樣填鴨代碼,記住各種規(guī)則,所有數(shù)據(jù)和事件通過 props 傳遞就行了:
import React from 'react';
interface Props {
title: string;
onUpdateTitle: (title: string) => void;
}
function MyComponent(props: Props) {
return <input
type='text'
value={props.title}
onInput={e => props.onUpdateTitle(e.target.value)}
/>
}
更加容易整合 typescript 實現(xiàn)類型推斷,需要的邏輯都由 JS 完成,無需記住各種指令、使用方法,參數(shù)命名規(guī)則。
事件處理
Vue 中寫法
<template>
<div id="inline-handler">
<button @click="say('hi')">Say hi</button>
<button @click="say('what')">Say what</button>
</div>
</template>
<script>
module.exports = {
methods: {
say(message) {
alert(message)
}
}
}
</script>
React 寫法:
import React, { useState } from 'react';
function Index() {
const onClick = (message) => () => alert(message);
return (
<div id="inline-handler">
<button onClick={onClick('hi')}>Say hi</button>
<button onClick={onClick('what')}>Say what</button>
</div>
);
}
export default Index;
這里用了函數(shù)柯里化,一般事件處理這樣就行了:
import React from 'react';
function Index() {
const onClick = () => alert('hi');
return (
<div id="inline-handler">
<button onClick={onClick}>Say hi</button>
</div>
);
}
export default Index;
如果需要優(yōu)化緩存事件處理函數(shù),使用 useCallback 即可。可以看到 Vue 中的事件觸發(fā) this.$emit('click') 或者父組件中的代碼 v-on="say('hi')" 都使用了字符串的寫法,這樣非常不利于類型推斷,不利于代碼重構(gòu)。React 的函數(shù)寫法或者 class 寫法都直接使用 js 語法,沒有額外的東西,相比 Vue 更容易通過 IDE 進行重構(gòu)優(yōu)化。React 中無論方法還是變量,都是采用駝峰命名法,也可以自由定制,Vue 中必須混合小寫中隔線、駝峰、字符串組合,不利于統(tǒng)一代碼規(guī)范。
插槽
Vue 中寫法:
<template>
<button class="btn-primary">
<slot></slot>
</button>
</template>
<script>
module.exports = {
methods: {}
}
</script>
React 寫法:
import React from 'react';
function Index() {
return (
<button classNames="btn-primary">
{props.children}
</button>
);
}
export default Index;
React 的插槽寫法沒有 Vue 那么復(fù)雜,也沒有“備用內(nèi)容”、“具名插槽”、“渲染作用域”、“作用域插槽”、“動態(tài)插槽名”,這些概念和特殊情況的處理,一切通過 JS 邏輯搞定就行了,怎么方便怎么來,比如備用內(nèi)容的實現(xiàn):
import React from 'react';
function Index() {
// 默認情況下使用 Summit 作為按鈕文字
return (
<button classNames="btn-primary">
{props.children === null ? 'Summit' : props.children}
</button>
);
}
export default Index;
樣式 & 屬性
這部分 Vue 的寫法實在是太麻煩了。。。每次我都要查查文檔具體怎么用,對象語法、數(shù)組語法、內(nèi)聯(lián)樣式,要記住的有點多,Vue 動態(tài)修改樣式的寫法:
<template>
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>
</template>
<script>
module.exports = {
data() {
return {
isActive: true,
hasError: false
}
}
}
</script>
React 寫法:
import React, { useState } from 'react';
function Index() {
const [isActive, setIsActive] = useState(true);
const [hasError, setHasError] = useState(false);
return (
<div
classNames={`static ${isActive ? 'active':'} ${hasError? 'text-danger':''}`}
></div>
);
}
export default Index;
React 里面直接采用 JS 的模板字符串語法,如果樣式太多,可以使用 classnames 這個 npm 包,優(yōu)雅傳遞各種狀態(tài),使用非常簡單:
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
狀態(tài)管理
Vue 的狀態(tài)管理官方推薦使用 Vuex 也可采用 Redux。
引用官方文檔一段話:
如果你是來自 React 的開發(fā)者,可能會對 Vuex 和 Redux 間的差異表示關(guān)注,Redux 是 React 生態(tài)環(huán)境中最流行的 Flux 實現(xiàn)。Redux 事實上無法感知視圖層,所以它能夠輕松地通過一些簡單綁定和 Vue 一起使用。Vuex 區(qū)別在于它是一個專門為 Vue 應(yīng)用所設(shè)計。這使得它能夠更好地和 Vue 進行整合,同時提供簡潔的 API 和更好的開發(fā)體驗。
這段話其實暴露了 Vuex 的一個缺陷,它和 Vue 強綁定,無法獨立存在,這種一些項目升級和遷移時會有很大的麻煩。Redux 作為 React 的狀態(tài)管理方案之一其實不依賴于 React。
React 周邊的狀態(tài)管理方案特別多,如 Redux、Mobx、Recoil 等,各有各的亮點,其中使用最多的應(yīng)該是 Redux。
Redux 周邊生態(tài)也很豐富,可以更加下圖選擇不同的方案:
- redux-thunk
- redux-promise
- redux-saga
- redux-observable
由于這部分代碼較多,不詳細寫,不過如果你熟悉 Vuex 的概念,轉(zhuǎn)到 Redux 應(yīng)該不難。Vuex + axIOS 的做法和 Redux + redux-thunk 的寫法類似,不過現(xiàn)在 redux-saga 的方案被更多復(fù)雜項目采用,其中很重要的原因是 saga 的概念編寫異步代碼非常優(yōu)雅,且能夠很好地解決靜態(tài)問題(如果采用 Vuex + axios 的寫法會異常復(fù)雜、冗長),高度定制。
如果你要遷移 Vue 到 React,建議采用的方案是 Redux + saga,saga 的概念不是那么容易懂,學習需要一些時間,但當你學會的時候就會明白這種寫法比直接用 Promise 好太多了。
生命周期
Vue 的生命周期這里不再重復(fù),查詢官方文檔即可,React 生命周期如圖:
圖片可以在這里找到。一般情況下 class 寫法主要用到 componentDidMount 和 componentWillUnmount 鉤子,React 的函數(shù)寫法下可以用 useEffect 的執(zhí)行函數(shù)和清理函數(shù)去模擬 mount 和 unmount 過程:
import React, { useRef, useEffect } from 'react';
function Index() {
const ref = useRef(null);
useEffect(() => {
console.log('mounted');
return () => {
console.log('will unmount');
};
}, []);
return <input ref={ref}/>
}
export default Index;
useEffect 的原理這里不多說,可以看看相關(guān)文章:輕松學會 React 鉤子:以 useEffect() 為例。其實從 React hook 中可以看到,React 在慢慢淡化生命周期的概念,減少自己對用戶代碼的侵入,將更多控制權(quán)交給用戶。
原生 DOM 操作
這部分 Vue 和 React 都是采用 ref 寫法,Vue:
<template>
<input ref="input" />
</template>
<script>
module.exports = {
methods: {
focusInput() {
this.$refs.input.focus()
}
},
mounted() {
this.focusInput()
}
}
</script>
React 寫法:
import React, { useRef, useEffect } from 'react';
function Index() {
const ref = useRef(null);
useEffect(() => {
ref.current?.focuse();
}, []);
return <input ref={ref}/>
}
export default Index;
useEffect 是 React hook,在依賴數(shù)組為空的時候效果類似 componentDidMount 的生命周期函數(shù)(類似 Vue 的 mounted)。此外 useRef 不止用在這里,也可以掛載一些其他的東東,實現(xiàn)一些復(fù)雜操作,比如 previousValue 和對象屬性等。






