雙節旅遊人如山,不如家中程式碼閒。
學以致用加班少,王者榮耀家中玩。
小編日常工作中使用的是Vue
,對於React
只是做過簡單的瞭解,並沒有做過深入學習。趁著這個雙節假期,小編決定好好學一學React
,今天這篇文章就是小編在學習React
之後,將React
與Vue
的用法做的一個對比,通過這個對比,方便使用Vue
的小夥伴可以快速將Vue
中的寫法轉換為React
的寫法。
本文首發於公眾號【前端有的玩】,玩前端,面試找工作,就在【前端有的玩】,歡迎關注
插槽,在React
中沒找到??
在使用Vue
的時候,插槽是一個特別常用的功能,通過定義插槽,可以在呼叫元件的時候將外部的內容傳入到元件內部,顯示到指定的位置。在Vue
中,插槽分為預設插槽,具名插槽和作用域插槽。其實不僅僅Vue
,在React
中其實也有類似插槽的功能,只是名字不叫做插槽,下面我將通過舉例來說明。
預設插槽
現在專案需要開發一個卡片元件,如下圖所示,卡片可以指定標題,然後卡片內容可以使用者自定義,這時候對於卡片內容來說,就可以使用插槽來實現,下面我們就分別使用Vue
和React
來實現這個功能
Vue
實現
首先實現一個
card
元件,如下程式碼所示<template> <div class="card"> <div class="card__title"> <span>{{ title }}</span> </div> <div class="card__body"> <slot></slot> </div> </div> </template> <script> export default { props: { title: { type: String, default: '' } } } </script>
可以看到上面我們使用了
<slot></slot>
,這個就是元件的預設插槽,在使用元件的時候,傳入的內容將會被放到<slot></slot>
所在位置在外部使用定義的
card
元件<template> <div> <my-card> <div>我將被放在card元件的預設插槽裡面</div> </my-card> </div> </template> <script> import MyCard from '../components/card' export default { components: { MyCard } } </script>
如上程式碼,就可以使用元件的預設插槽將外部的內容應用到元件裡面指定的位置了。
React
實現
雖然在React
裡面沒有插槽的概念,但是React
裡面也可以通過props.children
拿到元件標籤內部的子元素的,就像上面程式碼<my-card>
標籤內的子元素,通過這個我們也可以實現類似Vue
預設插槽的功能,一起看看程式碼。
使用
React
定義Card
元件import React from 'react' export interface CardProps { title: string, children: React.ReactNode } export default function(props: CardProps) { return ( <div className="card"> <div className="card__title"> <span>{props.title}</span> </div> <div className="card__body"> {/**每個元件都可以獲取到 props.children。它包含元件的開始標籤和結束標籤之間的內容 */} {props.children} </div> </div> ); }
- 在外部使用
Card
元件
import React from 'react' import Card from './components/Card' export default function () { return ( <div> <Card title="標題"> <div>我將被放在card元件的body區域內容</div> </Card> </div> ); }
- 在外部使用
具名插槽
繼續以上面的Card
元件為例,假如我們現在需求發生了變化,元件的title
也可以使用插槽,這時候對於Vue
就可以使用具名插槽了,而React
也是有辦法實現的哦。
Vue實現
Vue
的具名插槽主要解決的是一個元件需要多個插槽的場景,其實現是為<slot>
新增name
屬性來實現了。
- 我們就上面的需求對
card
元件進行修改
<template>
<div class="card">
<div class="card__title">
<!--如果傳入了title,則使用title屬性,否則使用具名插槽-->
<span v-if="title">{{ title }}</span>
<slot v-else name="title"></slot>
</div>
<div class="card__body">
<!--對於內容區域依然使用預設插槽-->
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ''
}
}
}
</script>
card
元件修改完之後,我們再去調整一下使用card
元件的地方
<template>
<div>
<my-card>
<!--通過v-slot:title 使用具名插槽-->
<template v-slot:title>
<span>這裡是標題</span>
</template>
<div>我將被放在card元件的預設插槽裡面</div>
</my-card>
</div>
</template>
<script>
import MyCard from '../components/card'
export default {
components: {
MyCard
}
}
</script>
React實現
React
連插槽都沒有, 更別提具名插槽了,但是沒有不代表不能模擬出來。對於React
的props
,我們不僅僅可以傳入普通的屬性,還可以傳入一個函式,這時候我們就可以在傳入的這個函式裡面返回JSX
,從而就實現了具名插槽的功能。
- 對原有的
Card
元件進行修改
import React from 'react'
export interface CardProps {
title?: string,
// 加入了一個renderTitle屬性,屬性型別是Function
renderTitle?: Function,
children: React.ReactNode
}
export default function(props: CardProps) {
const {title, renderTitle} = props
// 如果指定了renderTtile,則使用renderTitle,否則使用預設的title
let titleEl = renderTitle ? renderTitle() : <span>{title}</span>
return (
<div className="card">
<div className="card__title">{titleEl}</div>
<div className="card__body">
{/**每個元件都可以獲取到 props.children。它包含元件的開始標籤和結束標籤之間的內容 */}
{props.children}
</div>
</div>
);
}
- 這時候就可以在外部自定義
title
了
import React from 'react'
import Card from './components/Card'
export default function () {
return (
<div>
<Card renderTitle={
() => {
return <span>我是自定義的標題</span>
}
}>
<div>我將被放在card元件的body區域內容</div>
</Card>
</div>
);
}
作用域插槽
有時讓插槽內容能夠訪問子元件中才有的資料是很有用的,這個就是Vue
提供作用域插槽的原因。我們繼續使用上面的Card
元件為例,現在我基於上面的卡片元件開發了一個人員資訊卡片元件,使用者直接使用人員資訊卡片元件就可以將人員資訊顯示到介面中,但是在某些業務模組需要自定義人員資訊顯示方式,這時候我們就需要使用到作用域插槽了。
Vue
實現
- 實現使用者資訊卡片元件,裡面使用了作用域插槽
<template>
<custom-card title="人員資訊卡片">
<div class="content">
<!--這裡使用了作用域插槽,將userInfo傳出去了-->
<slot name="userInfo" :userInfo="userInfo">
<!--如果沒有使用插槽,則顯示預設內容-->
<span>姓名: {{ userInfo.name }}</span>
<span>性別: {{ userInfo.sex }}</span>
<span>年齡: {{ userInfo.age }}</span>
</slot>
</div>
</custom-card>
</template>
<script>
import CustomCard from '../card'
export default {
components: {
CustomCard
},
data() {
return {
userInfo: {
name: '張三',
sex: '男',
age: 25
}
}
}
}
</script>
- 在外部使用人員資訊元件
<template>
<div>
<user-card>
<template v-slot:userInfo="{ userInfo }">
<div class="custom-user">
<ul>
<li>姓名: {{ userInfo.name }}</li>
<li>年齡: {{ userInfo.age }}</li>
</ul>
</div>
</template>
</user-card>
</div>
</template>
<script>
import UserCard from '../components/user-card'
export default {
components: {
UserCard
}
}
</script>
React
實現
在具名插槽那一小節我們通過給元件傳入了一個函式,然後在函式中返回JSX
的方式來模擬了具名插槽,那麼對於作用域插槽,我們依然可以使用函式的這種方式,而作用域插槽傳遞的引數我們可以使用給函式傳參的方式來替代
實現人員資訊卡片元件
import React, { useState } from 'react' import Card from './Card' interface UserCardProps { renderUserInfo?: Function } export interface UserInfo { name: string; age: number; sex: string; } export default function(props: UserCardProps) { const [userInfo] = useState<UserInfo>({ name: "張三", age: 25, sex: "男", }); const content = props.renderUserInfo ? ( props.renderUserInfo(userInfo) ) : ( <div> <span>姓名: {userInfo.name}</span> <span>年齡: {userInfo.age}</span> <span>性別: {userInfo.sex}</span> </div> ); return <Card title="人員資訊"> {content} </Card> }
在外部使用人員資訊卡片元件
import React from 'react' import UserCard, { UserInfo } from "./components/UserCard"; export default function () { return ( <div> <UserCard renderUserInfo={(userInfo: UserInfo) => { return ( <ul> <li>姓名: {userInfo.name}</li> </ul> ); }} ></UserCard> </div> ); }
Context
, React
中的provide/inject
通常我們在專案開發中,對於多元件之間的狀態管理,在Vue
中會使用到Vuex
,在React
中會使用到redux
或者Mobx
,但對於小專案來說,使用這些狀態管理庫就顯得比較大材小用了,那麼在不使用這些庫的情況下,如何去完成資料管理呢?比如面試最常問的祖孫元件通訊。在Vue
中我們可以使用provide/inject
,在React
中我們可以使用Context
。
假設有這樣一個場景,系統現在需要提供一個換膚功能,使用者可以切換皮膚,現在我們分別使用Vue
和React
來實現這個功能。
Vue
中的provide/inject
在Vue
中我們可以使用provide/inject
來實現跨多級元件進行傳值,就以上面所說場景為例,我們使用provide/inject
來實現以下
首先,修改App.vue
內容為以下內容
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
data() {
return {
themeInfo: {
theme: 'dark'
}
}
},
provide() {
return {
theme: this.themeInfo
}
}
}
</script>
然後在任意層級的子元件中像下面這樣使用
<template>
<div :class="`child-${theme.theme}`">
</div>
</template>
<script>
export default {
inject: ['theme']
}
</script>
這樣就可以實現theme
在所有子元件中進行共享了
React
中的Context
在Vue
中我們使用provide/inject
實現了元件跨層級傳值功能,在React
中也提供了類似的功能即Context
,下面我們使用Context
來實現相同的功能。
在專案src
目錄下新建context
目錄,新增MyContext.js
檔案,然後新增以下內容
import {createContext} from 'react'
// 定義 MyContext,指定預設的主題為`light`
export const MyContext = createContext({
theme: 'light'
})
MyContext
提供了一個Provider
,通過Provider
可以將theme
共享到所有的子元件。現在我們在所有的元件的共同父元件比如App.js
上面新增MyContext.Provider
將theme
共享出去
import { MyContext } from '@/context/MyContext';
export default function() {
const [theme, setTheme] = useState('dark')
return (
<MyContext.Provider
value={{
theme
}}
>
<Children></Children>
</MyContext.Provider>
)
}
然後這時候就可以直接在所有的子元件裡面使用定義的主題theme
了
import React, { useContext } from 'react'
import { MyContext } from '@/context/MyContext';
export default function() {
const {theme} = useContext(MyContext)
return <div className={`child-${theme}`}>
}
沒有了v-model
,但也不影響使用
我們知道React
和Vue
都是單向資料流的,即資料的流向都是由外層向內層元件進行傳遞和更新的,比如下面這段React
程式碼就是標準的單向資料流.
import React, { useState } from "react";
export default function(){
const [name] = useState('子君')
return <input value={name}></input>
}
在vue
中使用v-model
如上程式碼,我們在通過通過value
屬性將外部的值傳遞給了input
元件,這個就是一個簡單的單向資料流。但是在使用Vue
的時候,還有兩個比較特殊的語法糖v-model
和.sync
,這兩個語法糖可以讓Vue
元件擁有雙向資料繫結的能力,比如下面的程式碼
<template>
<input v-model="name"/>
</template>
<script>
export default {
data() {
return {
name:'子君'
}
}
}
</script>
通過v-model
,當使用者修改input
的值的時候,外部的name
的值也將同步被修改。但這是Vue
的語法糖啊,React
是不支援的,所以React
應該怎麼辦呢?這時候再想想自定義v-model
,v-model
實際上是通過定義value
屬性同時監聽input
事件來實現的,比如這樣:
<template>
<div class="custom-input">
<input :value="value" @input="$_handleChange"/>
</div>
</template>
<script>
export default {
props:{
value:{
type: String,
default: ''
}
},
methods:{
$_handleChange(e) {
this.$emit('input', e.target.value)
}
}
}
</script>
在react
尋找v-model
替代方案
同理,React
雖然沒有v-model
語法糖,但是也可以通過傳入屬性然後監聽事件來實現資料的雙向繫結。
import React, { useState } from 'react'
export default function() {
const [name, setName] = useState('子君')
const handleChange = (e) => {
setName(e.target.value)
}
return <div>
<input value={name} onChange={handleChange}></input>
</div>
}
小編剛開始使用react
,感覺沒有v-model
就顯得比較麻煩,不過麻煩歸麻煩,程式碼改寫也要寫。就像上文程式碼一樣,每一個表單元素都需要監聽onChange
事件,越發顯得麻煩了,這時候就可以考慮將多個onChange
事件合併成一個,比如像下面程式碼這樣
import React, { useState } from 'react'
export default function () {
const [name, setName] = useState('子君')
const [sex, setSex] = useState('男')
const handleChange = (e:any, method: Function) => {
method(e.target.value)
}
return <div>
<input value={name} onChange={(e) => handleChange(e, setName)}></input>
<input value={sex} onChange={(e) => handleChange(e, setSex)}></input>
</div>
}
沒有了指令,我感覺好迷茫
在Vue
中我們一般繪製頁面都會使用到template
,template
裡面提供了大量的指令幫助我們完成業務開發,但是在React
中使用的是JSX
,並沒有指令,那麼我們應該怎麼做呢?下面我們就將Vue
中最常用的一些指令轉換為JSX
裡面的語法(注意: 在Vue中也可以使用JSX
)
v-show
與v-if
在Vue
中我們隱藏顯示元素可以使用v-show
或者v-if
,當然這兩者的使用場景是有所不同的,v-show
是通過設定元素的display
樣式來顯示隱藏元素的,而v-if
隱藏元素是直接將元素從dom
中移除掉。
看一下
Vue
中的v-show
與v-if
的用法<template> <div> <span v-show="showName">姓名:{{ name }}</span> <span v-if="showDept">{{ dept }}</span> </div> </template> <script> export default { data() { return { name: '子君', dept: '銀河帝國', showName: false, showDept: true } } } </script>
將
v-show
,v-if
轉換為JSX
中的語法在
Vue
中指令是為了在template
方便動態運算元據而存在的,但是到了React
中我們寫的是JSX
,可以直接使用JS
,所以指令是不需要存在的,那麼上面的v-show
,v-if
如何在JSX
中替代呢import React, { useState } from 'react' export default function() { const [showName] = useState(false) const [showDept] = useState(true) const [userInfo] = useState({ name:'子君', dept: '銀河帝國' }) return ( <div> {/**模擬 v-show */} <span style={{display: showName ? 'block' : 'none'}}>{userInfo.name}</span> {/**模擬 v-if */} {showDept ? <span>{userInfo.dept}</span>: undefined} </div> ) }
v-for
v-for
在Vue
中是用來遍歷資料的,同時我們在使用v-for
的時候需要給元素指定key
,key
的值一般是資料的id
或者其他唯一且固定的值。不僅在Vue
中,在React
中也是存在key
的,兩者的key
存在的意義基本一致,都是為了優化虛擬DOM
diff
演算法而存在的。
在
Vue
中使用v-for
<template> <div> <ul> <li v-for="item in list" :key="item.id"> {{ item.name }} </li> </ul> </div> </template> <script> export default { data() { return { list: [ { id: 1, name: '子君' }, { id: '2', name: '張三' }, { id: '3', name: '李四' } ] } } } </script>
在
React
中使用v-for
的替代語法在
react
中雖然沒有v-for
,但是JSX
中可以直接使用JS
,所以我們可以直接遍歷陣列import React from 'react' export default function() { const data = [ { id: 1, name: "子君", }, { id: "2", name: "張三", }, { id: "3", name: "李四", }, ]; return ( <div> <ul> { data.map(item => { return <li key={item.id}>{item.name}</li> }) } </ul> </div> ) }
v-bind
與v-on
v-bind
在Vue
中是動態繫結屬性的,v-on
是用於監聽事件的,因為React
也有屬性和事件的概念,所以我們在React
也能發現可替代的方式。
在
Vue
中使用v-bind
與v-on
<template> <div> <!--:value是v-bind:value的簡寫, @input是v-on:input的簡寫--> <input :value="value" @input="handleInput" /> </div> </template> <script> export default { data() { return { value: '子君' } }, methods: { handleInput(e) { this.value = e.target.value } } } </script>
在
React
中尋找替代方案在
Vue
中,作者將事件和屬性進行了分離,但是在React
中,其實事件也是屬性,所以在本小節我們不僅看一下如何使用屬性和事件,再瞭解一下如何在React
中自定義事件開發一個
CustomInput
元件import React from 'react' export interface CustomInputProps { value: string; //可以看出 onChange是一個普通的函式,也被定義到了元件的props裡面了 onChange: ((value: string,event: React.ChangeEvent<HTMLInputElement>) => void) | undefined; } export default function(props: CustomInputProps) { function handleChange(e: React.ChangeEvent<HTMLInputElement>) { // props.onChange是一個屬性,也是自定義的一個事件 props.onChange && props.onChange(e.target.value, e) } return ( <input value={props.value} onChange={handleChange}></input> ) }
使用
CustomInput
元件import React, { useState } from 'react' import CustomInput from './components/CustomInput' export default function() { const [value, setValue] = useState('') function handleChange(value: string) { setValue(value) } return ( <div> <CustomInput value={value} onChange={handleChange}></CustomInput> </div> ) }
總結
剛開始從Vue
轉到React
的時候,其實是有點不適應的,但是當慢慢的習慣之後,就會發現Vue
和React
是存在很多共性的,可以參考的去學習。當然無論Vue
還是React
,上手比較快,但是想深入學習還是需要下功夫的,後續小編將會對Vue
和React
的用法在做更深入的介紹,敬請期待。