最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue?3開發(fā)者轉(zhuǎn)型React保姆級教程(含實際項目案例)

 更新時間:2026年05月20日 08:53:27   作者:張海潮  
從Vue轉(zhuǎn)到React并不難,兩者作為主流前端框架,核心思想(組件化、聲明式編程)一致,但在語法、API設(shè)計和最佳實踐上存在差,這篇文章主要介紹了Vue3開發(fā)者轉(zhuǎn)型React保姆級教程的相關(guān)資料,需要的朋友可以參考下

本文基于實際項目案例,對比 Vue 3 與 React 的核心差異,幫助 Vue 開發(fā)者快速上手 React。

前言

作為 Vue 3 開發(fā)者,當(dāng)我第一次接觸 React 時,最大的感受是:很多概念都有對應(yīng),但寫法和思維方式完全不同

本文將結(jié)合一個倉庫瀏覽器頁面的實戰(zhàn)案例,講解從 Vue 3 到 React 需要掌握的核心概念。

項目背景

我們正在構(gòu)建一個倉庫瀏覽器頁面:

  • 左側(cè):可折疊的文件樹
  • 右側(cè):代碼高亮 / Markdown 預(yù)覽

先看一下兩個框架的代碼對比:

Vue 3 版本

<template>
  <div class="file-tree">
    <div 
      v-for="node in data" 
      :key="node.id"
      @click="handleClick(node)"
    >
      {{ node.name }}
    </div>
  </div>
</template>

<script setup lang="ts">
interface Props {
  data: FileNode[]
  selectedId: string | null
}

const props = defineProps<Props>()
const emit = defineEmits<{
  select: [node: FileNode]
  toggle: [id: string]
}>()

const handleClick = (node: FileNode) => {
  if (node.type === 'folder') {
    emit('toggle', node.id)
  } else {
    emit('select', node)
  }
}
</script>

React 版本

import React from 'react'

interface FileTreeProps {
  data: FileNode[]
  selectedId: string | null
  onToggle: (id: string) => void
  onSelect: (node: FileNode) => void
}

export const FileTree: React.FC<FileTreeProps> = ({
  data,
  selectedId,
  onToggle,
  onSelect,
}) => {
  const handleClick = (node: FileNode) => {
    if (node.type === 'folder') {
      onToggle(node.id)
    } else {
      onSelect(node)
    }
  }

  return (
    <div className="file-tree">
      {data.map((node) => (
        <div
          key={node.id}
          onClick={() => handleClick(node)}
        >
          {node.name}
        </div>
      ))}
    </div>
  )
}

核心差異:

Vue 3React
<template>JSX
defineProps函數(shù)參數(shù)解構(gòu)
defineEmits函數(shù)參數(shù)(回調(diào)函數(shù))
v-forArray.map()
@clickonClick

一、useCallback 是什么?

1.1 問題場景

在倉庫瀏覽器中,我們有一個 useFileTree Hook 管理狀態(tài):

// useFileTree.ts
export function useFileTree() {
  const [selectedFile, setSelectedFile] = useState<FileNode | null>(null)
  const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set())

  // 這個函數(shù)每次渲染都會重新創(chuàng)建
  const selectFile = (file: FileNode) => {
    if (file.type === 'file') {
      setSelectedFile(file)
    }
  }

  const toggleFolder = (folderId: string) => {
    setExpandedFolders((prev) => {
      const next = new Set(prev)
      if (next.has(folderId)) {
        next.delete(folderId)
      } else {
        next.add(folderId)
      }
      return next
    })
  }

  return {
    selectedFile,
    expandedFolders,
    selectFile,    // 每次渲染都是新函數(shù)
    toggleFolder,  // 每次渲染都是新函數(shù)
  }
}

問題: selectFiletoggleFolder 每次渲染都會生成新的函數(shù)引用,導(dǎo)致使用它們的子組件不必要地重新渲染。

1.2 Vue 3 的解決方案

Vue 3 中,你可能不需要特別處理這個問題,因為:

  • refreactive 是響應(yīng)式的
  • 模板編譯器會自動優(yōu)化

但如果你確實需要緩存函數(shù),Vue 3 也提供了 computedwatch。

1.3 React 的解決方案:useCallback

import { useState, useCallback } from 'react'

export function useFileTree() {
  const [selectedFile, setSelectedFile] = useState<FileNode | null>(null)
  const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set())

  // useCallback 緩存函數(shù)引用
  const selectFile = useCallback((file: FileNode) => {
    if (file.type === 'file') {
      setSelectedFile(file)
    }
  }, [])  // 依賴數(shù)組為空,函數(shù)只創(chuàng)建一次

  const toggleFolder = useCallback((folderId: string) => {
    setExpandedFolders((prev) => {
      const next = new Set(prev)
      if (next.has(folderId)) {
        next.delete(folderId)
      } else {
        next.add(folderId)
      }
      return next
    })
  }, [])  // 同樣只創(chuàng)建一次

  return {
    selectedFile,
    expandedFolders,
    selectFile,    // 引用穩(wěn)定,不會觸發(fā)子組件重渲染
    toggleFolder,  // 引用穩(wěn)定
  }
}

1.4 useCallback 語法詳解

const memoizedFn = useCallback(
  () => {
    // 函數(shù)體
  },
  [dependency1, dependency2]  // 依賴數(shù)組
)

對比 Vue 3:

場景Vue 3React
緩存計算值computeduseMemo
緩存函數(shù)通常不需要useCallback
監(jiān)聽變化watchuseEffect

1.5 什么時候用 useCallback?

推薦使用:

  • 作為 props 傳遞給子組件的回調(diào)函數(shù)
  • 作為 useEffect 的依賴項
  • 需要保持引用穩(wěn)定的函數(shù)

不需要使用:

  • 僅在當(dāng)前組件使用的函數(shù)
  • 事件處理函數(shù)(如 onClick

二、組件傳參詳解

2.1 父傳子:Props

Vue 3 寫法:

<!-- 父組件 -->
<template>
  <FileTree 
    :data="fileData" 
    :selected-id="selectedId"
  />
</template>

<script setup>
const fileData = ref([...])
const selectedId = ref('1')
</script>

<!-- 子組件 FileTree.vue -->
<script setup lang="ts">
interface Props {
  data: FileNode[]
  selectedId: string | null
}

const props = defineProps<Props>()
</script>

React 寫法:

// 父組件
import { FileTree } from './components/FileTree'

const App = () => {
  const [fileData] = useState<FileNode[]>([...])
  const [selectedId] = useState<string | null>('1')

  return (
    <FileTree 
      data={fileData} 
      selectedId={selectedId}
    />
  )
}

// 子組件 FileTree.tsx
interface FileTreeProps {
  data: FileNode[]
  selectedId: string | null
}

export const FileTree: React.FC<FileTreeProps> = ({ 
  data, 
  selectedId 
}) => {
  // 使用 data 和 selectedId
}

2.2 子傳父:回調(diào)函數(shù)

Vue 3 寫法:

<!-- 父組件 -->
<template>
  <FileTreeNode 
    @select="handleSelect"
    @toggle="handleToggle"
  />
</template>

<script setup>
const handleSelect = (node: FileNode) => {
  console.log('選中了:', node)
}

const handleToggle = (id: string) => {
  console.log('切換文件夾:', id)
}
</script>

<!-- 子組件 -->
<script setup>
const emit = defineEmits<{
  select: [node: FileNode]
  toggle: [id: string]
}>()

const handleClick = () => {
  emit('select', someNode)
}
</script>

React 寫法:

// 父組件
const App = () => {
  const handleSelect = (node: FileNode) => {
    console.log('選中了:', node)
  }

  const handleToggle = (id: string) => {
    console.log('切換文件夾:', id)
  }

  return (
    <FileTreeNode 
      onSelect={handleSelect}
      onToggle={handleToggle}
    />
  )
}

// 子組件
interface FileTreeNodeProps {
  onSelect: (node: FileNode) => void
  onToggle: (id: string) => void
}

export const FileTreeNode: React.FC<FileTreeNodeProps> = ({
  onSelect,
  onToggle,
}) => {
  const handleClick = () => {
    onSelect(someNode)  // 直接調(diào)用回調(diào)函數(shù)
  }
}

2.3 復(fù)雜數(shù)據(jù)傳遞

在倉庫瀏覽器中,我們需要傳遞嵌套的文件樹數(shù)據(jù):

Vue 3:

<template>
  <FileTreeNode 
    v-for="node in data"
    :key="node.id"
    :node="node"
    :level="0"
    :expanded-folders="expandedFolders"
    :selected-file-id="selectedFileId"
    @toggle="$emit('toggle', $event)"
    @select="$emit('select', $event)"
  />
</template>

React:

export const FileTree: React.FC<FileTreeProps> = ({
  data,
  expandedFolders,
  selectedFileId,
  onToggle,
  onSelect,
}) => {
  return (
    <div>
      {data.map((node) => (
        <FileTreeNode
          key={node.id}
          node={node}
          level={0}
          expandedFolders={expandedFolders}
          selectedFileId={selectedFileId}
          onToggle={onToggle}
          onSelect={onSelect}
        />
      ))}
    </div>
  )
}

2.4 傳遞方式對比總結(jié)

場景Vue 3React
父傳子v-bind:prop:prop直接作為 JSX 屬性
子傳父defineEmits + emit傳遞回調(diào)函數(shù)作為 props
事件命名@event-nameonEventName (駝峰)
插槽<slot>children 或命名插槽 props
Provide/Injectprovide/injectContext API

三、其他 Vue 3 → React 核心概念映射

3.1 響應(yīng)式數(shù)據(jù)

// Vue 3
const count = ref(0)
const user = reactive({ name: '張三' })

// React
const [count, setCount] = useState(0)
const [user, setUser] = useState({ name: '張三' })

// 更新方式
count.value++                    // Vue 3
setCount(count + 1)              // React

user.name = '李四'              // Vue 3 (reactive 自動響應(yīng))
setUser({ ...user, name: '李四' })  // React (需要創(chuàng)建新對象)

3.2 計算屬性 vs useMemo

// Vue 3
const doubleCount = computed(() => count.value * 2)

// React
const doubleCount = useMemo(() => count * 2, [count])

3.3 生命周期

// Vue 3
onMounted(() => {
  // 組件掛載后
})

onUnmounted(() => {
  // 組件卸載前
})

watch(selectedFile, (newVal) => {
  // 監(jiān)聽變化
})

// React
useEffect(() => {
  // 組件掛載后
  return () => {
    // 組件卸載前
  }
}, [])  // 空依賴數(shù)組 = 只執(zhí)行一次

useEffect(() => {
  // selectedFile 變化時執(zhí)行
}, [selectedFile])

3.4 模板 vs JSX

<!-- Vue 3 模板 -->
<template>
  <div v-if="isLoading">加載中...</div>
  <div v-else-if="error">出錯了: {{ error }}</div>
  <ul v-else>
    <li v-for="item in list" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>
// React JSX
return (
  <div>
    {isLoading ? (
      <div>加載中...</div>
    ) : error ? (
      <div>出錯了: {error}</div>
    ) : (
      <ul>
        {list.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    )}
  </div>
)

四、實戰(zhàn):倉庫瀏覽器核心代碼解析

4.1 類型定義 (types.ts)

// 文件節(jié)點接口 - 兩個框架基本一致
export interface FileNode {
  id: string
  name: string
  type: 'file' | 'folder'
  children?: FileNode[]
  content?: string
}

4.2 自定義 Hook (useFileTree.ts)

import { useState, useCallback } from 'react'

export function useFileTree() {
  // 狀態(tài)管理
  const [selectedFile, setSelectedFile] = useState<FileNode | null>(null)
  const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set())

  // 使用 useCallback 緩存函數(shù)
  const selectFile = useCallback((file: FileNode) => {
    if (file.type === 'file') {
      setSelectedFile(file)
    }
  }, [])

  const toggleFolder = useCallback((folderId: string) => {
    setExpandedFolders((prev) => {
      const next = new Set(prev)
      if (next.has(folderId)) {
        next.delete(folderId)
      } else {
        next.add(folderId)
      }
      return next
    })
  }, [])

  return {
    selectedFile,
    expandedFolders,
    selectFile,
    toggleFolder,
  }
}

對應(yīng) Vue 3 組合式函數(shù):

// useFileTree.ts (Vue 3 版本)
import { ref } from 'vue'

export function useFileTree() {
  const selectedFile = ref<FileNode | null>(null)
  const expandedFolders = ref(new Set<string>())

  const selectFile = (file: FileNode) => {
    if (file.type === 'file') {
      selectedFile.value = file
    }
  }

  const toggleFolder = (folderId: string) => {
    if (expandedFolders.value.has(folderId)) {
      expandedFolders.value.delete(folderId)
    } else {
      expandedFolders.value.add(folderId)
    }
  }

  return {
    selectedFile,
    expandedFolders,
    selectFile,
    toggleFolder,
  }
}

4.3 文件樹節(jié)點 (FileTreeNode.tsx)

import React from 'react'
import { Folder, File, ChevronRight, ChevronDown } from 'lucide-react'

interface FileTreeNodeProps {
  node: FileNode
  level: number
  expandedFolders: Set<string>
  selectedFileId: string | null
  onToggle: (id: string) => void
  onSelect: (node: FileNode) => void
}

export const FileTreeNode: React.FC<FileTreeNodeProps> = ({
  node,
  level,
  expandedFolders,
  selectedFileId,
  onToggle,
  onSelect,
}) => {
  const isFolder = node.type === 'folder'
  const isExpanded = expandedFolders.has(node.id)
  const isSelected = selectedFileId === node.id

  const handleClick = () => {
    if (isFolder) {
      onToggle(node.id)
    } else {
      onSelect(node)
    }
  }

  return (
    <div>
      <div
        className={`flex items-center gap-2 p-2 cursor-pointer ${
          isSelected ? 'bg-blue-100' : 'hover:bg-gray-100'
        }`}
        style={{ paddingLeft: `${level * 16}px` }}
        onClick={handleClick}
      >
        {isFolder ? (
          <>
            {isExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
            <Folder size={16} className="text-yellow-500" />
          </>
        ) : (
          <>
            <span className="w-4" />
            <File size={16} className="text-gray-500" />
          </>
        )}
        <span>{node.name}</span>
      </div>

      {/* 遞歸渲染子節(jié)點 */}
      {isFolder && isExpanded && node.children && (
        <div>
          {node.children.map((child) => (
            <FileTreeNode
              key={child.id}
              node={child}
              level={level + 1}
              expandedFolders={expandedFolders}
              selectedFileId={selectedFileId}
              onToggle={onToggle}
              onSelect={onSelect}
            />
          ))}
        </div>
      )}
    </div>
  )
}

五、常見陷阱與最佳實踐

5.1 狀態(tài)不可變性

// ? 錯誤:直接修改
const toggleFolder = (id: string) => {
  expandedFolders.add(id)  // 不會觸發(fā)重渲染!
  setExpandedFolders(expandedFolders)
}

// ? 正確:創(chuàng)建新對象
const toggleFolder = (id: string) => {
  setExpandedFolders((prev) => {
    const next = new Set(prev)
    if (next.has(id)) {
      next.delete(id)
    } else {
      next.add(id)
    }
    return next  // 返回新 Set
  })
}

5.2 依賴數(shù)組

// ? 錯誤:遺漏依賴
useEffect(() => {
  console.log(selectedFile.name)
}, [])  // selectedFile 變化時不會執(zhí)行

// ? 正確:包含所有依賴
useEffect(() => {
  console.log(selectedFile.name)
}, [selectedFile])

// ? 如果依賴是對象的某個屬性
useEffect(() => {
  console.log(selectedFile.name)
}, [selectedFile?.name])

5.3 Key 的重要性

// ? 錯誤:使用 index 作為 key
{list.map((item, index) => (
  <div key={index}>{item.name}</div>
))}

// ? 正確:使用唯一標(biāo)識
{list.map((item) => (
  <div key={item.id}>{item.name}</div>
))}

六、思維導(dǎo)圖:Vue 3 → React 概念映射

Vue 3                          React
─────────────────────────────────────────────
<template>                     JSX
v-bind / :prop                 直接作為屬性
v-on / @event                  onClick={handler}
v-model                        value + onChange
v-if / v-else                  三元表達式
v-for                          Array.map()
ref()                          useState()
reactive()                     useState() + 對象
computed()                     useMemo()
watch()                        useEffect()
onMounted()                    useEffect(() => {}, [])
onUnmounted()                  useEffect 返回的清理函數(shù)
defineProps()                  函數(shù)參數(shù)解構(gòu)
defineEmits()                  傳遞回調(diào)函數(shù)作為 props
provide/inject                 Context API
<slot>                         children prop

七、總結(jié)

從 Vue 3 轉(zhuǎn)型 React,核心變化在于:

  1. 模板語法 → JSX:更靈活但需要手寫更多邏輯
  2. 響應(yīng)式系統(tǒng) → 狀態(tài)管理ref/reactiveuseState
  3. 事件系統(tǒng) → 回調(diào)函數(shù)emit → 傳遞函數(shù)作為 props
  4. 生命周期 → useEffect:需要理解依賴數(shù)組的概念
  5. useCallback/useMemo:性能優(yōu)化的重要工具

學(xué)習(xí)建議:

  • 先理解 React 的單向數(shù)據(jù)流
  • 掌握 useState 和 useEffect
  • 逐步學(xué)習(xí) useCallback、useMemo 等優(yōu)化 Hooks
  • 多看優(yōu)秀開源項目的代碼

參考資料

到此這篇關(guān)于Vue 3開發(fā)者轉(zhuǎn)型React保姆級教程的文章就介紹到這了,更多相關(guān)Vue3開發(fā)者轉(zhuǎn)型React內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React19 Diff 算法的具體實現(xiàn)

    React19 Diff 算法的具體實現(xiàn)

    本文主要介紹了React19 Diff 算法的具體實現(xiàn),該算法用于高效地更新虛擬DOM,它通過優(yōu)化傳統(tǒng)樹diff算法,將復(fù)雜度從O(n^3)降低到O(n),感興趣的可以了解一下
    2026-05-05
  • 詳解如何在React函數(shù)式組件中使用MobX

    詳解如何在React函數(shù)式組件中使用MobX

    MobX 是一個簡潔的狀態(tài)管理庫,它通過透明的函數(shù)響應(yīng)式編程(TFRP)使得狀態(tài)管理變得簡單和可擴展,下面就跟隨小編一起來了解一下如何在React函數(shù)式組件中使用MobX吧
    2024-01-01
  • React.js入門實例教程之創(chuàng)建hello world 的5種方式

    React.js入門實例教程之創(chuàng)建hello world 的5種方式

    React 是近期非常熱門的一個前端開發(fā)框架。應(yīng)用非常廣泛,接下來通過本文給大家介紹React.js入門實例教程之創(chuàng)建hello world 的5種方式 ,需要的朋友參考下吧
    2016-05-05
  • React組件通信淺析

    React組件通信淺析

    這篇文章主要介紹了React組件通信,在開發(fā)中組件通信是React中的一個重要的知識點,本文通過實例代碼給大家講解react中常用的父子、跨組件通信的方法,需要的朋友可以參考下
    2022-12-12
  • react-beautiful-dnd 實現(xiàn)組件拖拽功能

    react-beautiful-dnd 實現(xiàn)組件拖拽功能

    這篇文章主要介紹了react-beautiful-dnd 實現(xiàn)組件拖拽功能,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-08-08
  • React Native之TextInput組件解析示例

    React Native之TextInput組件解析示例

    本篇文章主要介紹了React Native之TextInput組件解析示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • 詳解如何在react中搭建d3力導(dǎo)向圖

    詳解如何在react中搭建d3力導(dǎo)向圖

    本篇文章主要介紹了如何在react中搭建d3力導(dǎo)向圖,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • React數(shù)據(jù)傳遞之組件內(nèi)部通信的方法

    React數(shù)據(jù)傳遞之組件內(nèi)部通信的方法

    這篇文章主要介紹了React數(shù)據(jù)傳遞之組件內(nèi)部通信的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • react+zarm實現(xiàn)底部導(dǎo)航欄的示例代碼

    react+zarm實現(xiàn)底部導(dǎo)航欄的示例代碼

    本文主要介紹了react?+?zarm?實現(xiàn)底部導(dǎo)航欄的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • Webpack 4.x搭建react開發(fā)環(huán)境的方法步驟

    Webpack 4.x搭建react開發(fā)環(huán)境的方法步驟

    這篇文章主要介紹了Webpack 4.x搭建react開發(fā)環(huán)境的方法步驟,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08

最新評論

苏尼特左旗| 凭祥市| 天门市| 民权县| 凤台县| 嘉黎县| 古田县| 门源| 吉安市| 大关县| 五家渠市| 巴南区| 河西区| 汶上县| 武宣县| 大余县| 海安县| 嘉义县| 海南省| 分宜县| 峡江县| 虹口区| 策勒县| 高雄县| 资中县| 桐乡市| 巴彦县| 类乌齐县| 武胜县| 合肥市| 青冈县| 乌拉特中旗| 长武县| 靖安县| 姜堰市| 长治县| 翁源县| 阳原县| 鄱阳县| 弥勒县| 常宁市|