【建議收藏】11+實戰技巧,讓你輕鬆從Vue過渡到React

前端胖頭魚發表於2021-12-22

前言

在這個卷神輩出的時代,只是熟練Vue的胖頭魚,已經被毒打過多次了,面試中曾被質疑:“你居然不會React?”我無語凝噎,不知說啥是好。

這篇文章嘗試將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>
  )
}

預覽

v-for.png

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

預覽

computed.gif

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

預覽

watch.gif

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

預覽

class.gif

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

預覽

image.png

9. slot(預設插槽)

插槽是Vue中非常實用的功能,我把他理解成”坑位“,等待著你從外面把他填上,而這個”坑位“可以分成預設坑位具名坑位作用域坑位,我們們通過一個實戰例子來看看React中如何實現同等的功能。

假設我們要實現一個簡單的dialog元件,基本功能是標題可以傳字串,內容部分可以完全自定義,應該怎麼實現呢?

image.png

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>

slot

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

slot

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

預覽

預設插槽.gif

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

<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>
        <div class="slot-body">前端胖頭魚</div>
      </template>
    </Dialog>
  </div>
</template>

<script>
import Dialog from './components/dialog.vue'

export default {
  name: 'nameSlot',
  components: {
    Dialog,
  },
  data () {
    return {
      visible: false
    }
  },
  methods: {
    onToggleVisible () {
      this.visible = !this.visible
    }
  }
}
</script>

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直接用屬性反而更簡潔一些

具名插槽.gif

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

預覽

作用域插槽.gif

相關文章