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