共计 15707 个字符,预计需要花费 40 分钟才能阅读完成。
前言
在这个
卷神
辈出的时代,只是纯熟Vue
的胖头鱼,曾经被毒打过屡次了,面试中曾被质疑:“你竟然不会 React?”我无语凝噎,不知说啥是好。新公司
Vue
和React
我的项目都有不少,做了几个后,发现两者之间还是有很多共通之处,重点是先了解思维,而后把握对应的写法,上手起来也就快了很多。这篇文章尝试将
Vue
中一些常见的性能在React
中实现一遍,如果你恰巧是Vue
转React
,或者React
转Vue
,期待对你有些帮忙。如果你是一名相熟
React
和Vue
的同学跪求轻喷(手动求生)
每个性能,都有对应的 Vue 和 React 版本实现,也有对应的截图或者录屏
Vue 仓库
React 仓库
1. v-if
咱们先从最常见的显示暗藏开始,Vue 中解决一个元素的显示暗藏个别会用
v-if
或者v-show
指令,只不过v-if
是“真正”的条件渲染,切换过程中条件块内的事件监听器和子组件会适当地被销毁和重建。而v-show
就简略了,只是 css 款式上的管制。
v-if 源代码点这里
Vue
<template>
<div class="v-if">
<button @click="onToggleShow"> 切换 </button>
<div v-if="isShow"> 前端胖头鱼 显示进去啦 </div>
</div>
</template>
<script>
export default {
name: 'vif',
data () {
return {isShow: true}
},
methods: {onToggleShow () {this.isShow = !this.isShow}
}
}
</script>
React
vif 源代码点这里
import React, {useState} from "react"
export default function Vif (){const [ isShow, setIsShow] = useState(true)
const onToggleShow = () => {setIsShow(!isShow)
}
return (
<div className="v-if">
<button onClick={onToggleShow}> 切换 </button>
{/* 也能够用三目运算符 */}
{/* { isShow ? <div> 前端胖头鱼 显示进去啦 </div> : null} */}
{isShow && <div> 前端胖头鱼 显示进去啦 </div>}
</div>
)
}
预览
2. v-show
同上,这次咱们通过
v-show
来实现显示暗藏的性能,同时察看 DOM 的款式变动
留神: 这里为啥显示的时候不设置为 block
是因为有些元素自身不是块级元素,如果强行设置为 block
有可能导致谬误的款式。
Vue
v-show 源代码点击这里
<template>
<div class="v-show">
<button @click="onToggleShow"> 切换 </button>
<div v-show="isShow"> 前端胖头鱼 显示进去啦 </div>
</div>
</template>
<script>
export default {
name: 'vshow',
data () {
return {isShow: true}
},
methods: {onToggleShow () {this.isShow = !this.isShow}
}
}
</script>
React
vShow 源代码点这里
import React, {useState} from "react"
export default function VShow (){const [ isShow, setIsShow] = useState(true)
const onToggleShow = () => {setIsShow(!isShow)
}
return (
<div className="v-show">
<button onClick={onToggleShow}> 切换 </button>
{<div style={{ display: isShow ? '':'none'}}> 前端胖头鱼 显示进去啦 </div>
}
</div>
)
}
预览
3. v-for
个别状况下,渲染一个列表在
Vue
中应用v-for
指令,v-for 指令须要应用item in items
模式的非凡语法,其中items
是源数据数组,而item
则是被迭代的数组元素的别名。当然了,每个元素都须要设置惟一的key
Vue
v-for 源代码点这里
<template>
<div class="v-for">
<div
class="v-for-item"
v-for="item in list"
:key="item.id"
>
{{item.name}}
</div>
</div>
</template>
<script>
export default {
name: 'vfor',
data () {
return {
list: [
{
id: 1,
name: '前端',
},
{
id: 2,
name: '后端',
},
{
id: 3,
name: 'android',
},
{
id: 4,
name: 'ios',
},
]
}
}
}
</script>
React
在
React
没有v-for
指令,咱们能够采纳map
遍历的形式实现相似性能
vFor 源代码点这里
import React, {useState} from "react"
export default function VFor (){const [ list, setList] = useState([
{
id: 1,
name: '前端',
},
{
id: 2,
name: '后端',
},
{
id: 3,
name: 'android',
},
{
id: 4,
name: 'ios',
},
])
return (
<div className="v-for">
{list.map((item) => {return <div className="v-for-item" key={ item.id}>{item.name}</div>
})
}
</div>
)
}
预览
4. computed
当某个变量须要依赖其余变量求值时,应用计算属性会十分不便,并且
Vue
的计算属性是基于它们的响应式依赖进行缓存的,依赖值未发生变化,不会从新计算,达到缓存的作用。
咱们来看一个简略的加法例子:num3
由 num1
和num2
相加所得,同时按钮每点一次 num1
加 10,num3
也会跟着一直加 10
Vue
computed 源代码点这里
<template>
<div class="computed">
<button @click="onAdd">+10</button>
<div> 计算结果:{{num3}}</div>
</div>
</template>
<script>
export default {
name: 'computed',
data () {
return {
num1: 10,
num2: 10,
}
},
computed: {num3 () {return this.num1 + this.num2}
},
methods: {onAdd () {this.num1 += 10}
}
}
</script>
React
React
没有计算属性,然而咱们能够通过useMemo
这个 hook 来实现,和Vue
computed 不太一样的中央在于,咱们必须 手动保护依赖
computed 源代码点这里
import React, {useMemo, useState} from "react"
export default function Computed (){const [ num1, setNum1] = useState(10)
const [num2, setNum2] = useState(10)
const num3 = useMemo((a, b) => {return num1 + num2}, [num1, num2])
const onAdd = () => {setNum1(num1 + 10)
}
return (
<div className="computed">
<button onClick={onAdd}>+10</button>
<div> 计算结果:{num3}</div>
</div>
)
}
预览
5. watch
有时候咱们须要监听数据变动而后执行异步行为或者开销较大的操作时,在 Vue 中能够应用
watch
来实现
咱们来模仿一个这样的场景并且通过 watch
来实现:抉择 boy
或者girl
,选中后发送申请,显示申请后果。(这里通过 setTimeout 模仿异步申请过程)
Vue
watch 源代码点这里
<template>
<div class="watch">
<div class="selects">
<button
v-for="(item, i) in selects"
:key="i"
@click="onSelect(item)"
>
{{item}}
</button>
</div>
<div class="result">
{{result}}
</div>
</div>
</template>
<script>
export default {
name: 'watch',
data () {
return {
fetching: false,
selects: [
'boy',
'girl'
],
selectValue: ''
}
},
computed: {result () {return this.fetching ? '申请中' : ` 申请后果:选中 ${this.selectValue || '~'}`
}
},
watch: {selectValue () {this.fetch()
}
},
methods: {onSelect (value) {this.selectValue = value},
fetch () {if (!this.fetching) {
this.fetching = true
setTimeout(() => {this.fetching = false}, 1000)
}
}
}
}
</script>
React
React
中要实现监听某些数据的变动执行响应的动作,能够应用useEffect
watch 源代码点这里
import React, {useState, useMemo, useEffect} from "react"
import './watch.css'
export default function Watch() {const [fetching, setFetching] = useState(false)
const [selects, setSelects] = useState([
'boy',
'girl'
])
const [selectValue, setSelectValue] = useState('')
const result = useMemo(() => {return fetching ? '申请中' : ` 申请后果:选中 ${selectValue || '~'}`
}, [fetching])
const onSelect = (value) => {setSelectValue(value)
}
const fetch = () => {if (!fetching) {setFetching(true)
setTimeout(() => {setFetching(false)
}, 1000)
}
}
useEffect(() => {fetch()
}, [selectValue])
return (
<div className="watch">
<div className="selects">
{selects.map((item, i) => {return <button key={ i} onClick={() => onSelect(item) }>{item}</button>
})
}
</div>
<div className="result">
{result}
</div>
</div>
)
}
预览
6. style
有时候不免要给元素动静增加款式
style
,Vue
和React
都给咱们提供了不便的应用形式。
在应用上根本大同小异:
相同点:
CSS property 名能够用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名
不同点:
- Vue 能够通过数组语法绑定多个款式对象,React 次要是单个对象的模式(这点 Vue 也能够)
- React 会主动增加”px”(这点 Vue 不会主动解决) 后缀到内联款式为数字的属性,其余单位手动须要手动指定
- React 款式不会主动补齐前缀。如需反对旧版浏览器,需手动补充对应的款式属性。Vue 中当 v-bind:style 应用须要增加浏览器引擎前缀的 CSS property 时,如 transform,Vue.js 会主动侦测并增加相应的前缀。
Vue
style 源代码点这里
<template>
<div class="style" :style="[style, style2]"></div>
</template>
<script>
export default {
name: 'style',
data () {
return {
style: {
width: '100%',
height: '500px',
},
style2: {backgroundImage: 'linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%)',
borderRadius: '10px',
}
}
}
}
</script>
React
style 源代码点这里
import React from "react"
export default function Style (){
const style = {
width: '100%',
height: '500px',
}
const style2 = {backgroundImage: 'linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%)',
borderRadius: '10px',
}
return (<div className="style" style={ { ...style, ...style2} } ></div>
)
}
预览
7. class
如何动静地给元素增加 class?Vue 中我本人比拟喜爱用数组的语法(当然还有对象的写法),React 中也能够应用一些第三方包如 classnames 起到更加便捷增加 class 的成果。
上面咱们看下不借助任何库,如何实现按钮选中的成果
Vue
class 源代码点这里
<template>
<button :class="buttonClasses" @click="onClickActive">{{buttonText}}</button>
</template>
<script>
export default {
name: 'class',
data () {
return {isActive: false,}
},
computed: {buttonText () {return this.isActive ? '已选中' : '未选中'},
buttonClasses () {
// 通过数组模式保护 class 动静列表
return ['button', this.isActive ? 'active' : '']
}
},
methods: {onClickActive () {this.isActive = !this.isActive}
}
}
</script>
<style scoped>
.button{
display: block;
width: 100px;
height: 30px;
line-height: 30px;
border-radius: 6px;
margin: 0 auto;
padding: 0;
border: none;
text-align: center;
background-color: #efefef;
}
.active{background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
color: #fff
}
</style>
React
class 源代码点这里
import React, {useMemo, useState} from "react"
import './class.css' // 此处款式与下面是一样的
export default function Class (){const [ isActive, setIsActive] = useState(false)
const buttonText = useMemo(() => {return isActive ? '已选中' : '未选中'}, [isActive])
const buttonClass = useMemo(() => {
// 和 Vue 中不太一样的是咱们须要手动 join 一下,变成 'button active' 模式
return ['button', isActive ? 'active' : ''].join(' ')
}, [isActive])
const onClickActive = () => {setIsActive(!isActive)
}
return (<div className={ buttonClass} onClick={onClickActive}>{buttonText}</div>
)
}
预览
8.provide/inject
Vue 和 React 中对于全局状态的治理都有各自好的解决方案,比方 Vue 中的
Vuex
,React 中的redux
和Mobx
,当然小型我的项目中引入这些有点 大材小用 了, 有没有其余解决方案呢?
Vue 中能够应用 provide/inject
React 中则能够应用 Context
假如全局有有一个用户信息 userInfo 的变量,须要在各个组件中都能便捷的拜访到,在 Vue 和 React 中该如何实现呢?
Vue
Vue
中借用provide/inject
能够将顶层状态,传递至任意子节点,假如咱们再 app.vue 中申明了一个userInfo
数据
provide 源代码点这里
app.vue
<template>
<div id="app">
<div class="title"> 我是 Vue 栗子 </div>
<router-view/>
</div>
</template>
<script>
export default {
name: 'app',
// 申明数据
provide () {
return {
userInfo: {name: '前端胖头鱼'}
}
}
}
</script>
provide.vue
<template>
<div class="provide-inject">{{userInfo.name}}</div>
</template>
<script>
export default {
name: 'provideInject',
// 应用数据
inject: ['userInfo']
}
</script>
React
React
中要实现相似的性能,能够借助 Context,将全局状态共享给任意子节点
provide 源代码点这里
context/index.js
import {createContext} from "react";
export const UserInfoContext = createContext({
userInfo: {name: ''}
})
app.js
import {UserInfoContext} from './context/index'
function App() {
return (
<BrowserRouter>
// 留神这里
<UserInfoContext.Provider
value={{userInfo: { name: '前端胖头鱼'} }}
>
<div className="title"> 我是 React 栗子 </div>
<Routes>
<Route path="/v-if" element={<Vif />} />
<Route path="/v-show" element={<VShow />} />
<Route path="/v-for" element={<VFor />} />
<Route path="/computed" element={<Computed />} />
<Route path="/watch" element={<Watch />} />
<Route path="/style" element={<Style />} />
<Route path="/class" element={<Class />} />
<Route path="/slot" element={<Slot />} />
<Route path="/nameSlot" element={<NameSlot />} />
<Route path="/scopeSlot" element={<ScopeSlot />} />
<Route path="/provide" element={<Provide />} />
</Routes>
</UserInfoContext.Provider>
</BrowserRouter>
);
}
provide.js
import React, {useContext} from "react"
import {UserInfoContext} from '../context/index'
export default function Provide() {
// 通过 userContext,应用定义好的 UserInfoContext
const {userInfo} = useContext(UserInfoContext)
return (<div class="provide-inject">{ userInfo.name}</div>
)
}
预览
9. slot(默认插槽)
插槽是
Vue
中十分实用的性能,我把他了解成”坑位“,期待着你从里面把他填上,而这个”坑位“能够分成默认坑位
、具名坑位
、作用域坑位
,咱们通过一个实战例子来看看React
中如何实现等同的性能。
假如咱们要实现一个简略的 dialog
组件, 基本功能是题目能够传字符串,内容局部能够齐全自定义,应该怎么实现呢?
Vue
slot 源代码点这里
dialog 组件
<template>
<div class="dialog" v-show="visible">
<div class="dialog-mask" @click="onHide"></div>
<div class="dialog-body">
<div class="dialog-title" v-if="title">{{title}}</div>
<div class="dialog-main">
// 留神这里放了一个默认插槽坑位
<slot></slot>
</div>
<div class="dialog-footer">
<div class="button-cancel" @click="onHide"> 勾销 </div>
<div class="button-confirm" @click="onHide"> 确定 </div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "dialog",
props: {
title: {
type: String,
default: "",
},
visible: {
type: Boolean,
default: false,
},
},
methods: {onHide () {this.$emit('update:visible', false)
}
}
};
</script>
默认插槽组件
dialog
<template>
<div class="slot">
<button @click="onToggleVisible"> 切换 dialog</button>
<Dialog
:visible.sync="visible"
title="默认插槽"
>
// 这里会替换到 <slot></slot> 的地位处
<div class="slot-body"> 前端胖头鱼 </div>
</Dialog>
</div>
</template>
<script>
import Dialog from './components/dialog.vue'
export default {
name: 'slot',
components: {Dialog,},
data () {
return {visible: false}
},
methods: {onToggleVisible () {this.visible = !this.visible}
}
}
React
要在 React 中同样实现下面的性能应该怎么办呢?
React
可没有啥插槽啊!别急,尽管React
中没有插槽的概念,然而却能够通过props.children
获取到组件外部的子元素,通过这个就能够实现默认插槽的性能
slot 源代码点这里
dialog
import React, {useState, useEffect} from "react"
import './dialog.css'
export default function Dialog(props) {
// 原谅我用 visible - 1 这种傻叉的形式先实现了, 重点不是在这里
const {children, title = '', visible = -1} = props
const [visibleInner, setVisibleInner] = useState(false)
const onHide = () => {setVisibleInner(false)
}
useEffect(() => {setVisibleInner(visible > 0)
}, [visible])
return (<div className="dialog" style={ { display: visibleInner ? 'block' : 'none'}}>
<div className="dialog-mask" onClick={onHide}></div>
<div className="dialog-body">
{title ? <div className="dialog-title">{ title}</div> : null }
<div className="dialog-main">
{/* 留神这里,通过 children 实现默认插槽性能 */}
{children}
</div>
<div className="dialog-footer">
<div className="button-cancel" onClick={onHide}> 勾销 </div>
<div className="button-confirm" onClick={onHide}> 确定 </div>
</div >
</div >
</div >
)
}
默认插槽组件
import React, {useState, useEffect} from "react"
import Dialog from './components/dialog'
export default function Slot() {const [visible, setVisible] = useState(-1)
const onToggleVisible = () => {setVisible(Math.random())
}
return (
<div className="slot">
<button onClick={onToggleVisible}> 切换 dialog</button>
<Dialog
visible={visible}
title="默认插槽"
>
{/* 留神这里,会被 Dialog 组件的 children 读取并且替换掉 */}
<div className="slot-body"> 前端胖头鱼 </div>
</Dialog>
</div>
)
}
预览
10. name slot(具名插槽)
当组件外部有多个动静内容须要内部来填充的时候,一个默认插槽曾经不够用了,咱们须要给插槽取个名字,这样内部才能够”循序渐进“到指定地位。
咱们来丰盛一下 Dialog
组件,假如 title
也能够反对动静传递内容呢?
Vue
Vue 中通过
<slot name="main"></slot>
模式先进行插槽的申明,再通过v-slot:main
模式进行应用,一个萝卜一个坑也就填起来了
nameSlot 源代码点这里
Dialog 革新
<template>
<div class="dialog" v-show="visible">
<div class="dialog-mask" @click="onHide"></div>
<div class="dialog-body">
<div class="dialog-title" v-if="title">{{title}}</div>
<!-- 留神这里,没有传 title 属性,时候通过插槽进行内容承接 -->
<slot name="title" v-else></slot>
<div class="dialog-main">
<!-- 申明 main 局部 -->
<slot name="main"></slot>
</div>
<div class="dialog-footer">
<div class="button-cancel" @click="onHide"> 勾销 </div>
<div class="button-confirm" @click="onHide"> 确定 </div>
</div>
</div>
</div>
</template>
// ... 其余中央和上面试一样的
nameSlot
React
后面通过
props.children
属性能够读取组件标签内的内容算是和Vue
默认插槽实现了一样的性能,然而具名插槽如何实现呢?React
好玩的其中一个点,我感觉是属性啥玩意都能够传、字符串
、数字
、函数
、连 DOM
也能够传。所以实现具名插槽也很简略,间接当属性传递就能够
nameSlot 源代码点这里
Dialog 革新
import React, {useState, useEffect} from "react"
import './dialog.css'
export default function Dialog(props) {
// 原谅我用 visible - 1 这种傻叉的形式先实现了, 重点不是在这里
const {title, main, visible = -1} = props
const [visibleInner, setVisibleInner] = useState(false)
const onHide = () => {setVisibleInner(false)
}
useEffect(() => {setVisibleInner(visible > 0)
}, [visible])
return (<div className="dialog" style={ { display: visibleInner ? 'block' : 'none'}}>
<div className="dialog-mask" onClick={onHide}></div>
<div className="dialog-body">
{/* { title ? <div className="dialog-title">{ title}</div> : null } */}
{/* 留神这里,间接渲染 title 就能够了 */}
{title}
<div className="dialog-main">
{/* 留神这里,通过 children 实现默认插槽性能 */}
{/* {children} */}
{/* 这一这里不是 children 了,是 main */}
{main}
</div>
<div className="dialog-footer">
<div className="button-cancel" onClick={onHide}> 勾销 </div>
<div className="button-confirm" onClick={onHide}> 确定 </div>
</div >
</div >
</div >
)
}
nameSlot
import React, {useState} from "react"
import Dialog from './components/dialog'
import './slot.css'
export default function NameSlot() {const [visible, setVisible] = useState(-1)
const onToggleVisible = () => {setVisible(Math.random())
}
return (
<div className="slot">
<button onClick={onToggleVisible}> 切换 dialog</button>
<Dialog
visible={visible}
// 留神这里,间接传递的 DOM
title={<div className="dialog-title"> 默认插槽 </div>}
// 留神这里,间接传递的 DOM
main={<div className="slot-body"> 前端胖头鱼 </div>}
>
</Dialog>
</div>
)
}
预览
能够看到具名插槽,
React
间接用属性反而更简洁一些
11. scope slot(作用域插槽)
有了
默认插槽
、具名插槽
最初当然少不了作用域插槽啦!有时让插槽内容可能拜访子组件中才有的数据是很有用的,这也是作用域插槽的意义所在
假如:Dialog
组件外部有一个 userInfo: {name: '前端胖头鱼'}
数据对象,心愿应用 Dialog
组件的内部插槽也能拜访到,该怎么做呢?
Vue
scopeSlot 源代码点这里
Dialog
<template>
<div class="dialog" v-show="visible">
<div class="dialog-mask" @click="onHide"></div>
<div class="dialog-body">
<div class="dialog-title" v-if="title">{{title}}</div>
<!-- 留神这里,通过绑定 userInfo 内部能够进行应用 -->
<slot name="title" :userInfo="userInfo" v-else></slot>
<div class="dialog-main">
<!-- 留神这里,通过绑定 userInfo 内部能够进行应用 -->
<slot name="main" :userInfo="userInfo"></slot>
</div>
<div class="dialog-footer">
<div class="button-cancel" @click="onHide"> 勾销 </div>
<div class="button-confirm" @click="onHide"> 确定 </div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "dialog",
// ...
data () {
return {
userInfo: {name: '前端胖头鱼'}
}
},
// ...
};
</script>
scopeSlot
<template>
<div class="slot">
<button @click="onToggleVisible"> 切换 dialog</button>
<Dialog
:visible.sync="visible"
>
<template v-slot:title>
<div class="dialog-title"> 作用域插槽 </div>
</template>
<!-- 留神这里 -->
<template v-slot:main="{userInfo}">
<!-- 留神这里 userInfo 是 Dialog 组件外部的数据 -->
<div class="slot-body"> 你好{{userInfo.name}}</div>
</template>
</Dialog>
</div>
</template>
React
还是那句话,React 中万物皆可传,相似实现具名插槽中咱们间接传递 DOM,同样咱们也能够传递函数,将
Dialog
组件外部的userInfo
数据通过函数传参的形式给到内部应用
scopeSlot 源代码点这里
Dialog 革新
import React, {useState, useEffect} from "react"
import './dialog.css'
export default function Dialog(props) {
// 原谅我用 visible - 1 这种傻叉的形式先实现了, 重点不是在这里
const {title, main, visible = -1} = props
const [visibleInner, setVisibleInner] = useState(false)
const [userInfo] = useState({name: '前端胖头鱼'})
const onHide = () => {setVisibleInner(false)
}
useEffect(() => {setVisibleInner(visible > 0)
}, [visible])
return (<div className="dialog" style={ { display: visibleInner ? 'block' : 'none'}}>
<div className="dialog-mask" onClick={onHide}></div>
<div className="dialog-body">
{/* 作用域插槽,当函数应用,并且把数据传递进去 */}
{title(userInfo) }
<div className="dialog-main">
{/* 作用域插槽,当函数应用,并且把数据传递进去 */}
{main(userInfo) }
</div>
<div className="dialog-footer">
<div className="button-cancel" onClick={onHide}> 勾销 </div>
<div className="button-confirm" onClick={onHide}> 确定 </div>
</div >
</div >
</div >
)
}
scopeSlot
import React, {useState} from "react"
import Dialog from './components/dialog'
import './slot.css'
export default function ScopeSlot() {const [visible, setVisible] = useState(-1)
const onToggleVisible = () => {setVisible(Math.random())
}
return (
<div className="slot">
<button onClick={onToggleVisible}> 切换 dialog</button>
<Dialog
visible={visible}
// 通过函数来实现插槽
title={() => <div className="dialog-title"> 作用域插槽 </div> }
// 接管 userInfo 数据
main={(userInfo) => <div className="slot-body"> 你好{userInfo.name}</div> }
>
</Dialog>
</div>
)
}
预览