📦 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 前缀(如 onUpdateonClick)。

🎰 插槽迁移

Vue 插槽 → React children / Render Props

Vue 插槽类型 React 实现方式 说明
默认插槽 children prop 直接传递子元素
具名插槽 自定义 props headerfooter
作用域插槽 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 的 mountedupdatedwatch

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

useForm.ts
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
  }
}

使用示例

LoginForm.tsx
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。

Counter.tsx (Class 组件)
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>
    )
  }
}
← 核心概念对比 dva 数据流 →