组件开发迁移
掌握 React 组件开发模式:Props/Events、插槽迁移、Hooks 完整指南
Props 和 Events 对比
Vue 的 props/emit → React 的 props/回调函数
<!-- 父组件 -->
<template>
<ChildComponent
:name="name"
@update="handleUpdate"
/>
</template>
<!-- 子组件 -->
<template>
<div>
<p>{{ name }}</p>
<button @click="handleChange">
更新
</button>
</div>
</template>
<script>
export default {
props: {
name: {
type: String,
required: true
}
},
methods: {
handleChange() {
this.$emit('update', newValue)
}
}
}
</script>
// 子组件类型定义
interface ChildComponentProps {
name: string
onUpdate: (value: string) => void
}
// 子组件
const ChildComponent: React.FC<ChildComponentProps> = ({
name,
onUpdate
}) => {
const handleChange = () => {
onUpdate(newValue)
}
return (
<div>
<p>{name}</p>
<button onClick={handleChange}>
更新
</button>
</div>
)
}
// 父组件
const ParentComponent: React.FC = () => {
const [name, setName] = useState('')
const handleUpdate = (value: string) => {
setName(value)
}
return (
<ChildComponent
name={name}
onUpdate={handleUpdate}
/>
)
}
关键差异
Vue 使用 $emit 发送事件,React 通过 props 传递回调函数。
React 的命名习惯是 on 前缀(如 onUpdate、onClick)。
插槽迁移
Vue 插槽 → React children / Render Props
| Vue 插槽类型 | React 实现方式 | 说明 |
|---|---|---|
默认插槽 |
children prop |
直接传递子元素 |
具名插槽 |
自定义 props | 如 header、footer |
作用域插槽 |
Render Props | 传递渲染函数 |
具名插槽示例
<!-- 布局组件 -->
<template>
<div class="layout">
<header>
<slot name="header" />
</header>
<main>
<slot>默认内容</slot>
</main>
<footer>
<slot name="footer" />
</footer>
</div>
</template>
<!-- 使用 -->
<BaseLayout>
<template #header>
<h1>页面标题</h1>
</template>
<template #default>
<p>页面内容</p>
</template>
<template #footer>
<p>页脚信息</p>
</template>
</BaseLayout>
// 布局组件类型定义
interface BaseLayoutProps {
header?: React.ReactNode
children: React.ReactNode
footer?: React.ReactNode
}
// 布局组件
const BaseLayout: React.FC<BaseLayoutProps> = ({
header,
children,
footer
}) => (
<div className="layout">
<header>{header}</header>
<main>{children}</main>
<footer>{footer}</footer>
</div>
)
// 使用
<BaseLayout
header={<h1>页面标题</h1>}
footer={<p>页脚信息</p>}
>
<p>页面内容</p>
</BaseLayout>
Hooks 完整指南
React Hooks 是函数式组件的核心,替代 Class 组件的状态和生命周期
useState
状态管理替代 Vue 的 data(),用于声明组件状态
useEffect
副作用处理替代 Vue 的 mounted、updated、watch
useMemo
性能优化替代 Vue 的 computed,缓存计算结果
useCallback
函数缓存缓存函数引用,避免不必要的子组件重渲染
useRef
DOM 引用替代 Vue 的 ref,获取 DOM 元素或保存可变值
useContext
跨组件通信替代 Vue 的 provide/inject
useReducer
复杂状态逻辑替代 Vuex,处理复杂的状态管理逻辑
自定义 Hooks
逻辑复用替代 Vue 的 mixin,实现逻辑复用
useState 基础用法
<template>
<div>
<p>计数: {{ count }}</p>
<button @click="increment">+1</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
import { useState } from 'react'
const Counter: React.FC = () => {
const [count, setCount] = useState<number>(0)
const increment = () => {
setCount(prev => prev + 1)
}
return (
<div>
<p>计数: {count}</p>
<button onClick={increment}>+1</button>
</div>
)
}
useEffect 副作用处理
<script>
export default {
data() {
return {
data: null,
loading: true
}
},
mounted() {
this.fetchData()
},
watch: {
id(newId) {
this.fetchData()
}
},
beforeDestroy() {
// 清理工作
},
methods: {
async fetchData() {
this.loading = true
this.data = await api.getData(this.id)
this.loading = false
}
}
}
</script>
import { useState, useEffect } from 'react'
const DataComponent: React.FC = () => {
const [data, setData] = useState<any>(null)
const [loading, setLoading] = useState<boolean>(true)
// 组件挂载时执行(类似 mounted)
useEffect(() => {
const fetchData = async () => {
setLoading(true)
const result = await api.getData(id)
setData(result)
setLoading(false)
}
fetchData()
// 返回清理函数(类似 beforeDestroy)
return () => {
// 清理工作
}
}, [id]) // 依赖数组(类似 watch)
if (loading) return <div>Loading...</div>
return <div>{data}</div>
}
自定义 Hooks
替代 Vue 的 mixin,实现逻辑复用
自定义 Hooks vs Mixin
Vue 的 mixin 存在命名冲突和隐式依赖的问题。 React 的自定义 Hooks 是显式的,返回值和参数都清晰可见,更容易理解和维护。
表单状态管理 Hook
import { useState, useCallback } from 'react'
interface UseFormOptions<T> {
initialValues: T
onSubmit: (values: T) => Promise<void>
validate?: (values: T) => Partial<Record<keyof T, string>>
}
export function useForm<T extends Record<string, any>>({
initialValues,
onSubmit,
validate
}: UseFormOptions<T>) {
const [values, setValues] = useState<T>(initialValues)
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({})
const [submitting, setSubmitting] = useState(false)
const handleChange = useCallback((
field: keyof T,
value: T[keyof T]
) => {
setValues(prev => ({ ...prev, [field]: value }))
// 清除该字段的错误
if (errors[field]) {
setErrors(prev => {
const newErrors = { ...prev }
delete newErrors[field]
return newErrors
})
}
}, [errors])
const handleSubmit = useCallback(async (e: React.FormEvent) => {
e.preventDefault()
// 验证
if (validate) {
const validationErrors = validate(values)
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors)
return
}
}
setSubmitting(true)
try {
await onSubmit(values)
} catch (error) {
console.error('提交失败:', error)
} finally {
setSubmitting(false)
}
}, [values, validate, onSubmit])
const reset = useCallback(() => {
setValues(initialValues)
setErrors({})
}, [initialValues])
return {
values,
errors,
submitting,
handleChange,
handleSubmit,
reset
}
}
使用示例
const LoginForm: React.FC = () => {
const {
values,
errors,
submitting,
handleChange,
handleSubmit
} = useForm({
initialValues: {
username: '',
password: ''
},
validate: (values) => {
const errors: Record<string, string> = {}
if (!values.username) errors.username = '请输入用户名'
if (!values.password) errors.password = '请输入密码'
return errors
},
onSubmit: async (values) => {
await api.login(values)
}
})
return (
<form onSubmit={handleSubmit}>
<input
value={values.username}
onChange={(e) => handleChange('username', e.target.value)}
/>
{errors.username && <span>{errors.username}</span>}
<input
type="password"
value={values.password}
onChange={(e) => handleChange('password', e.target.value)}
/>
{errors.password && <span>{errors.password}</span>}
<button disabled={submitting}>
{submitting ? '登录中...' : '登录'}
</button>
</form>
)
}
Class 组件(简要说明)
了解即可,推荐使用 Hooks
推荐使用 Hooks
Class 组件是 React 的旧写法,虽然仍然支持,但官方推荐使用函数式组件 + Hooks。 新项目应该优先使用 Hooks。
interface Props {
name: string
}
interface State {
count: number
}
class Counter extends React.Component<Props, State> {
state: State = { count: 0 }
handleClick = () => {
this.setState({ count: this.state.count + 1 })
}
componentDidMount() {
// 类似 Vue mounted
console.log('组件已挂载')
}
componentDidUpdate(prevProps: Props) {
// 类似 Vue updated
if (this.props.name !== prevProps.name) {
console.log('name 发生变化')
}
}
componentWillUnmount() {
// 类似 Vue beforeDestroy
console.log('组件将要卸载')
}
render() {
return (
<div>
<p>{this.props.name}: {this.state.count}</p>
<button onClick={this.handleClick}>+1</button>
</div>
)
}
}