565 lines
8.2 KiB
Markdown
565 lines
8.2 KiB
Markdown
# React 到 Vue 代码转换快速参考
|
|
|
|
## 🔄 常用转换模式
|
|
|
|
### 1. 组件定义
|
|
|
|
#### React
|
|
```typescript
|
|
import React from 'react';
|
|
|
|
interface Props {
|
|
title: string;
|
|
count: number;
|
|
}
|
|
|
|
export const Component: React.FC<Props> = ({ title, count }) => {
|
|
return <div>{title}: {count}</div>;
|
|
};
|
|
```
|
|
|
|
#### Vue 3
|
|
```vue
|
|
<template>
|
|
<div>{{ title }}: {{ count }}</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
interface Props {
|
|
title: string;
|
|
count: number;
|
|
}
|
|
|
|
defineProps<Props>();
|
|
</script>
|
|
```
|
|
|
|
---
|
|
|
|
### 2. 状态管理
|
|
|
|
#### React useState
|
|
```typescript
|
|
const [count, setCount] = useState(0);
|
|
const [user, setUser] = useState({ name: '', age: 0 });
|
|
|
|
setCount(5);
|
|
setUser({ ...user, name: 'John' });
|
|
```
|
|
|
|
#### Vue ref
|
|
```typescript
|
|
const count = ref(0);
|
|
const user = ref({ name: '', age: 0 });
|
|
|
|
count.value = 5;
|
|
user.value.name = 'John';
|
|
```
|
|
|
|
#### Vue reactive (对象)
|
|
```typescript
|
|
const user = reactive({ name: '', age: 0 });
|
|
|
|
user.name = 'John'; // 不需要 .value
|
|
```
|
|
|
|
---
|
|
|
|
### 3. 副作用处理
|
|
|
|
#### React useEffect
|
|
```typescript
|
|
useEffect(() => {
|
|
// 副作用逻辑
|
|
return () => {
|
|
// 清理逻辑
|
|
};
|
|
}, [dependency]);
|
|
```
|
|
|
|
#### Vue watchEffect
|
|
```typescript
|
|
watchEffect(() => {
|
|
// 副作用逻辑
|
|
return () => {
|
|
// 清理逻辑
|
|
};
|
|
});
|
|
```
|
|
|
|
#### Vue watch
|
|
```typescript
|
|
watch(() => dependency, (newVal, oldVal) => {
|
|
// 副作用逻辑
|
|
}, { immediate: true });
|
|
```
|
|
|
|
---
|
|
|
|
### 4. 计算属性
|
|
|
|
#### React useMemo
|
|
```typescript
|
|
const doubled = useMemo(() => {
|
|
return count * 2;
|
|
}, [count]);
|
|
```
|
|
|
|
#### Vue computed
|
|
```typescript
|
|
const doubled = computed(() => {
|
|
return count.value * 2;
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### 5. 条件渲染
|
|
|
|
#### React
|
|
```tsx
|
|
{isVisible && <Component />}
|
|
{condition ? <A /> : <B />}
|
|
{items.length > 0 && items.map(item => <Item key={item.id} />)}
|
|
```
|
|
|
|
#### Vue
|
|
```vue
|
|
<Component v-if="isVisible" />
|
|
<A v-if="condition" />
|
|
<B v-else />
|
|
<Item v-for="item in items" :key="item.id" v-if="items.length > 0" />
|
|
```
|
|
|
|
---
|
|
|
|
### 6. 事件处理
|
|
|
|
#### React
|
|
```tsx
|
|
<button onClick={handleClick}>Click</button>
|
|
<input onChange={(e) => setValue(e.target.value)} />
|
|
<form onSubmit={handleSubmit}>
|
|
```
|
|
|
|
#### Vue
|
|
```vue
|
|
<button @click="handleClick">Click</button>
|
|
<input @change="handleChange" v-model="value" />
|
|
<form @submit.prevent="handleSubmit">
|
|
```
|
|
|
|
---
|
|
|
|
### 7. 双向绑定
|
|
|
|
#### React
|
|
```tsx
|
|
<input
|
|
value={value}
|
|
onChange={(e) => setValue(e.target.value)}
|
|
/>
|
|
```
|
|
|
|
#### Vue
|
|
```vue
|
|
<input v-model="value" />
|
|
```
|
|
|
|
---
|
|
|
|
### 8. 样式绑定
|
|
|
|
#### React
|
|
```tsx
|
|
<div className={`base ${isActive ? 'active' : ''}`}>
|
|
<div className={cn('base', { active: isActive })}>
|
|
<div style={{ width: `${percent}%` }}>
|
|
```
|
|
|
|
#### Vue
|
|
```vue
|
|
<div :class="['base', { active: isActive }]">
|
|
<div :class="{ base: true, active: isActive }">
|
|
<div :style="{ width: `${percent}%` }">
|
|
```
|
|
|
|
---
|
|
|
|
### 9. 列表渲染
|
|
|
|
#### React
|
|
```tsx
|
|
{items.map(item => (
|
|
<Item key={item.id} data={item} />
|
|
))}
|
|
```
|
|
|
|
#### Vue
|
|
```vue
|
|
<Item
|
|
v-for="item in items"
|
|
:key="item.id"
|
|
:data="item"
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
### 10. 组件通信
|
|
|
|
#### React Props
|
|
```tsx
|
|
<ChildComponent
|
|
prop1={value1}
|
|
prop2={value2}
|
|
onClick={handleClick}
|
|
/>
|
|
```
|
|
|
|
#### Vue Props & Emits
|
|
```vue
|
|
<ChildComponent
|
|
:prop1="value1"
|
|
:prop2="value2"
|
|
@click="handleClick"
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
### 11. Context API
|
|
|
|
#### React Context
|
|
```typescript
|
|
const Context = createContext();
|
|
const value = useContext(Context);
|
|
```
|
|
|
|
#### Vue provide/inject
|
|
```typescript
|
|
// 提供
|
|
provide(key, value);
|
|
|
|
// 注入
|
|
const value = inject(key);
|
|
```
|
|
|
|
#### Pinia Store (推荐)
|
|
```typescript
|
|
// store
|
|
export const useStore = defineStore('store', () => {
|
|
const state = ref(0);
|
|
return { state };
|
|
});
|
|
|
|
// 使用
|
|
const store = useStore();
|
|
store.state;
|
|
```
|
|
|
|
---
|
|
|
|
### 12. 生命周期
|
|
|
|
#### React
|
|
```typescript
|
|
useEffect(() => {
|
|
// componentDidMount
|
|
return () => {
|
|
// componentWillUnmount
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
// componentDidUpdate
|
|
}, [dependency]);
|
|
```
|
|
|
|
#### Vue
|
|
```typescript
|
|
onMounted(() => {
|
|
// 组件挂载后
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
// 组件卸载前
|
|
});
|
|
|
|
onUpdated(() => {
|
|
// 组件更新后
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### 13. 表单处理
|
|
|
|
#### React
|
|
```tsx
|
|
const [form, setForm] = useState({ name: '', email: '' });
|
|
|
|
<input
|
|
value={form.name}
|
|
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
|
/>
|
|
```
|
|
|
|
#### Vue
|
|
```vue
|
|
<script setup>
|
|
const form = reactive({ name: '', email: '' });
|
|
</script>
|
|
|
|
<template>
|
|
<input v-model="form.name" />
|
|
</template>
|
|
```
|
|
|
|
---
|
|
|
|
### 14. 条件类名
|
|
|
|
#### React
|
|
```tsx
|
|
<div className={cn(
|
|
'base-class',
|
|
{ 'active': isActive, 'disabled': isDisabled }
|
|
)}>
|
|
```
|
|
|
|
#### Vue
|
|
```vue
|
|
<div :class="[
|
|
'base-class',
|
|
{ active: isActive, disabled: isDisabled }
|
|
]">
|
|
```
|
|
|
|
---
|
|
|
|
### 15. 动态属性
|
|
|
|
#### React
|
|
```tsx
|
|
<div {...props}>
|
|
<img src={imageSrc} alt={altText} />
|
|
```
|
|
|
|
#### Vue
|
|
```vue
|
|
<div v-bind="props">
|
|
<img :src="imageSrc" :alt="altText" />
|
|
```
|
|
|
|
---
|
|
|
|
### 16. 插槽 (Slots)
|
|
|
|
#### React
|
|
```tsx
|
|
function Layout({ children }) {
|
|
return <div>{children}</div>;
|
|
}
|
|
```
|
|
|
|
#### Vue
|
|
```vue
|
|
<template>
|
|
<div>
|
|
<slot />
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
#### 具名插槽
|
|
```vue
|
|
<!-- 父组件 -->
|
|
<Layout>
|
|
<template #header>Header</template>
|
|
<template #default>Content</template>
|
|
<template #footer>Footer</template>
|
|
</Layout>
|
|
|
|
<!-- 子组件 -->
|
|
<template>
|
|
<div>
|
|
<slot name="header" />
|
|
<slot />
|
|
<slot name="footer" />
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
---
|
|
|
|
### 17. 图标使用
|
|
|
|
#### React (lucide-react)
|
|
```tsx
|
|
import { FileJson, Terminal } from 'lucide-react';
|
|
|
|
<FileJson size={20} />
|
|
<Terminal size={20} className="text-blue-500" />
|
|
```
|
|
|
|
#### Vue (lucide-vue-next)
|
|
```vue
|
|
<script setup>
|
|
import { FileJson, Terminal } from 'lucide-vue-next';
|
|
</script>
|
|
|
|
<template>
|
|
<FileJson :size="20" />
|
|
<Terminal :size="20" class="text-blue-500" />
|
|
</template>
|
|
```
|
|
|
|
---
|
|
|
|
### 18. 异步数据获取
|
|
|
|
#### React
|
|
```typescript
|
|
const [data, setData] = useState(null);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setLoading(true);
|
|
fetchData().then(result => {
|
|
setData(result);
|
|
setLoading(false);
|
|
});
|
|
}, []);
|
|
```
|
|
|
|
#### Vue
|
|
```typescript
|
|
const data = ref(null);
|
|
const loading = ref(false);
|
|
|
|
onMounted(async () => {
|
|
loading.value = true;
|
|
data.value = await fetchData();
|
|
loading.value = false;
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### 19. 防抖/节流
|
|
|
|
#### React
|
|
```typescript
|
|
const debouncedSearch = useMemo(
|
|
() => debounce((value) => {
|
|
// 搜索逻辑
|
|
}, 300),
|
|
[]
|
|
);
|
|
```
|
|
|
|
#### Vue (使用 @vueuse/core)
|
|
```typescript
|
|
import { useDebounceFn } from '@vueuse/core';
|
|
|
|
const debouncedSearch = useDebounceFn((value) => {
|
|
// 搜索逻辑
|
|
}, 300);
|
|
```
|
|
|
|
---
|
|
|
|
### 20. 组件引用
|
|
|
|
#### React useRef
|
|
```tsx
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
<input ref={inputRef} />
|
|
inputRef.current?.focus();
|
|
```
|
|
|
|
#### Vue ref
|
|
```vue
|
|
<template>
|
|
<input ref="inputRef" />
|
|
</template>
|
|
|
|
<script setup>
|
|
const inputRef = ref<HTMLInputElement | null>(null);
|
|
|
|
onMounted(() => {
|
|
inputRef.value?.focus();
|
|
});
|
|
</script>
|
|
```
|
|
|
|
---
|
|
|
|
## 📝 常见陷阱
|
|
|
|
### 1. ref 值访问
|
|
```typescript
|
|
// ❌ 错误
|
|
const count = ref(0);
|
|
console.log(count); // RefImpl 对象
|
|
|
|
// ✅ 正确
|
|
const count = ref(0);
|
|
console.log(count.value); // 0
|
|
```
|
|
|
|
### 2. 响应式对象
|
|
```typescript
|
|
// ❌ 错误 - 解构会失去响应性
|
|
const { name } = user;
|
|
|
|
// ✅ 正确 - 使用 toRefs
|
|
const { name } = toRefs(user);
|
|
```
|
|
|
|
### 3. 数组更新
|
|
```typescript
|
|
// React
|
|
setItems([...items, newItem]);
|
|
|
|
// Vue
|
|
items.value.push(newItem);
|
|
// 或
|
|
items.value = [...items.value, newItem];
|
|
```
|
|
|
|
### 4. 对象更新
|
|
```typescript
|
|
// React
|
|
setUser({ ...user, name: 'John' });
|
|
|
|
// Vue (ref)
|
|
user.value = { ...user.value, name: 'John' };
|
|
|
|
// Vue (reactive)
|
|
user.name = 'John'; // 直接修改
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 最佳实践
|
|
|
|
1. **优先使用 `ref` 处理基本类型,`reactive` 处理对象**
|
|
2. **使用 `computed` 而不是 `watch` 处理派生状态**
|
|
3. **使用 `watchEffect` 处理副作用,`watch` 处理特定依赖**
|
|
4. **使用 Pinia 管理全局状态,而不是 provide/inject**
|
|
5. **使用 `<script setup>` 语法,更简洁**
|
|
6. **合理使用 `v-model` 处理表单双向绑定**
|
|
7. **使用 `defineProps` 和 `defineEmits` 定义组件接口**
|
|
|
|
---
|
|
|
|
## 📚 参考资源
|
|
|
|
- [Vue 3 官方文档](https://vuejs.org/)
|
|
- [Vue 3 Composition API](https://vuejs.org/guide/extras/composition-api-faq.html)
|
|
- [Pinia 文档](https://pinia.vuejs.org/)
|
|
- [VueUse 工具库](https://vueuse.org/)
|