前言

在这个卷神辈出的时代,只是纯熟Vue的胖头鱼,曾经被毒打过屡次了,面试中曾被质疑:“你竟然不会React?”我无语凝噎,不知说啥是好。

新公司VueReact我的项目都有不少,做了几个后,发现两者之间还是有很多共通之处,重点是先了解思维,而后把握对应的写法,上手起来也就快了很多。

这篇文章尝试将Vue中一些常见的性能在React中实现一遍,如果你恰巧是VueReact,或者ReactVue,期待对你有些帮忙。

如果你是一名相熟ReactVue的同学跪求轻喷(手动求生)

每个性能,都有对应的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的计算属性是基于它们的响应式依赖进行缓存的,依赖值未发生变化,不会从新计算,达到缓存的作用。

咱们来看一个简略的加法例子:num3num1num2相加所得,同时按钮每点一次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

有时候不免要给元素动静增加款式styleVueReact都给咱们提供了不便的应用形式。

在应用上根本大同小异:

相同点:

CSS property 名能够用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名

不同点:

  1. Vue能够通过数组语法绑定多个款式对象,React次要是单个对象的模式(这点Vue也能够)
  2. React 会主动增加 ”px”(这点Vue不会主动解决) 后缀到内联款式为数字的属性,其余单位手动须要手动指定
  3. 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中的reduxMobx,当然小型我的项目中引入这些有点大材小用了,有没有其余解决方案呢?

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>  )}

预览