React Modernization

将 React 类组件迁移至 Hooks,升级至 React 18/19 并引入 TypeScript。

已扫描
适合谁
前端开发工程师、技术负责人
不适合谁
无 React 开发经验者、无需维护旧项目者
国内可用性
需网络配置。可能需要网络配置或第三方服务可访问。
安装难度
中等(★★☆)。基于终端操作、依赖、API Key 和本地环境要求的初步判断。

安装与下载

openclaw skills install @wpank/react-modernization

Skill 说明

命令、参数、文件名以原文为准

React 现代化

将 React 应用从类组件升级为函数式组件,采用并发特性,并在主要版本间进行迁移。

什么是

系统化的 React 代码库现代化模式:

  • 类组件到 Hooks 的迁移,包含生命周期方法映射
  • React 18/19 并发特性的采用
  • React 组件的 TypeScript 迁移
  • 自动化 codemod 工具实现批量重构
  • 使用现代 API 进行性能优化

何时使用

  • 将类组件迁移到使用 Hooks 的函数组件
  • 将 React 16/17 项目升级至 React 18/19
  • 采用并发特性(Suspense、transition、use)
  • 将高阶组件(HOC)和渲染属性模式转换为自定义 Hook
  • 为 React 项目添加 TypeScript 支持

关键词

react 升级, 类组件转 Hooks, useEffect, useState, react 18, react 19, 并发, Suspense, transition, codemod, 迁移, 现代化, 函数式组件

安装

OpenClaw / Moltbot / Clawbot

npx clawhub@latest install react-modernization

版本升级路径

React 17 → 18 的破坏性变更

变更影响迁移方式
新的根 API必须修改ReactDOM.rendercreateRoot
自动批处理行为变化异步代码中的状态更新现在会被自动批处理
严格模式仅开发环境效果会执行两次(挂载/卸载/再挂载)
服务端 Suspense可选启用 SSR 流式传输

React 18 → 19 的破坏性变更

变更影响迁移方式
use() Hook新增 API在渲染中读取 Promise 或上下文
ref 作为属性简化语法不再需要 forwardRef
上下文作为提供者简化语法<Context> 而非 <Context.Provider>
异步动作新模式使用 useActionStateuseOptimistic

类组件到 Hooks 的迁移

生命周期方法映射

// componentDidMount → useEffect 且依赖为空数组
useEffect(() => {
  fetchData()
}, [])

// componentDidUpdate → useEffect 且包含依赖项
useEffect(() => {
  updateWhenIdChanges()
}, [id])

// componentWillUnmount → useEffect 的清理函数
useEffect(() => {
  const subscription = subscribe()
  return () => subscription.unsubscribe()
}, [])

// shouldComponentUpdate → React.memo
const Component = React.memo(({ data }) => <div>{data}</div>)

// getDerivedStateFromProps → useMemo
const derivedValue = useMemo(() => computeFrom(props), [props])

状态迁移模式

// 之前:类组件包含多个状态属性
class UserProfile extends React.Component {
  state = { user: null, loading: true, error: null }

  componentDidMount() {
    fetchUser(this.props.id)
      .then(user => this.setState({ user, loading: false }))
      .catch(error => this.setState({ error, loading: false }))
  }

  componentDidUpdate(prevProps) {
    if (prevProps.id !== this.props.id) {
      this.setState({ loading: true })
      fetchUser(this.props.id)
        .then(user => this.setState({ user, loading: false }))
    }
  }

  render() {
    const { user, loading, error } = this.state
    if (loading) return <Spinner />
    if (error) return <Error message={error.message} />
    return <Profile user={user} />
  }
}

// 之后:自定义 Hook + 函数式组件
function useUser(id: string) {
  const [user, setUser] = useState<User | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<Error | null>(null)

  useEffect(() => {
    let cancelled = false
    setLoading(true)

    fetchUser(id)
      .then(data => {
        if (!cancelled) {
          setUser(data)
          setLoading(false)
        }
      })
      .catch(err => {
        if (!cancelled) {
          setError(err)
          setLoading(false)
        }
      })

    return () => { cancelled = true }
  }, [id])

  return { user, loading, error }
}

function UserProfile({ id }: { id: string }) {
  const { user, loading, error } = useUser(id)

  if (loading) return <Spinner />
  if (error) return <Error message={error.message} />
  return <Profile user={user} />
}

高阶组件到 Hook 的迁移

// 之前:高阶组件
function withUser(Component) {
  return function WithUser(props) {
    const [user, setUser] = useState(null)
    useEffect(() => { fetchUser().then(setUser) }, [])
    return <Component {...props} user={user} />
  }
}

const ProfileWithUser = withUser(Profile)

// 之后:自定义 Hook(更简洁,可组合)
function useCurrentUser() {
  const [user, setUser] = useState(null)
  useEffect(() => { fetchUser().then(setUser) }, [])
  return user
}

function Profile() {
  const user = useCurrentUser()
  return user ? <div>{user.name}</div> : null
}

React 18+ 并发特性

新的根 API(必需)

// 之前:React 17
import ReactDOM from 'react-dom'
ReactDOM.render(<App />, document.getElementById('root'))

// 之后:React 18+
import { createRoot } from 'react-dom/client'
const root = createRoot(document.getElementById('root')!)
root.render(<App />)

useTransition 用于非紧急更新

function SearchResults() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])
  const [isPending, startTransition] = useTransition()

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    // 紧急:立即更新输入框
    setQuery(e.target.value)

    // 非紧急:可以被中断
    startTransition(() => {
      setResults(searchDatabase(e.target.value))
    })
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending ? <Spinner /> : <ResultsList data={results} />}
    </>
  )
}

Suspense 用于数据获取

// React 19 的 use() 钩子
function ProfilePage({ userId }: { userId: string }) {
  return (
    <Suspense fallback={<ProfileSkeleton />}>
      <ProfileDetails userId={userId} />
    </Suspense>
  )
}

function ProfileDetails({ userId }: { userId: string }) {
  // use() 会暂停,直到 Promise 解析完成
  const user = use(fetchUser(userId))
  return <h1>{user.name}</h1>
}

React 19: use() 钩子

// 在渲染中直接读取 Promise
function Comments({ commentsPromise }) {
  const comments = use(commentsPromise)
  return comments.map(c => <Comment key={c.id} {...c} />)
}

// 读取上下文(比 useContext 更简单)
function ThemeButton() {
  const theme = use(ThemeContext)
  return <button className={theme}>点击</button>
}

React 19: Actions

// useActionState 用于表单提交
function UpdateName() {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) => {
      const error = await updateName(formData.get('name'))
      if (error) return error
      redirect('/profile')
    },
    null
  )

  return (
    <form action={submitAction}>
      <input name="name" />
      <button disabled={isPending}>更新</button>
      {error && <p>{error}</p>}
    </form>
  )
}

自动化代码转换

运行官方 React 代码转换工具

# 更新到新的 JSX 转换(无需 React 导入)
npx codemod@latest react/19/replace-reactdom-render

# 更新已弃用的 API
npx codemod@latest react/19/replace-string-ref

# 类组件转函数组件
npx codemod@latest react/19/replace-use-form-state

手动搜索模式

# 查找类组件
rg "class \w+ extends (React\.)?Component" --type tsx

# 查找已弃用的生命周期方法
rg "componentWillMount|componentWillReceiveProps|componentWillUpdate" --type tsx

# 查找 ReactDOM.render(需要迁移到 createRoot)
rg "ReactDOM\.render" --type tsx

TypeScript 迁移

// 为函数式组件添加类型
interface ButtonProps {
  onClick: () => void
  children: React.ReactNode
  variant?: 'primary' | 'secondary'
}

function Button({ onClick, children, variant = 'primary' }: ButtonProps) {
  return (
    <button onClick={onClick} className={variant}>
      {children}
    </button>
  )
}

// 类型化事件处理器
function Form() {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
  }

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} />
    </form>
  )
}

// 泛型组件
interface ListProps<T> {
  items: T[]
  renderItem: (item: T) => React.ReactNode
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return <>{items.map(renderItem)}</>
}

迁移检查清单

迁移前

  • [ ] 逐步升级依赖项
  • [ ] 查看发布说明中的破坏性变更
  • [ ] 建立全面的测试覆盖率
  • [ ] 创建功能分支

类 → Hooks

  • [ ] 从叶子组件开始(无子组件)
  • [ ] 将状态转换为 useState
  • [ ] 将生命周期转换为 useEffect
  • [ ] 将共享逻辑提取为自定义钩子
  • [ ] 尽可能将高阶组件转换为钩子

React 18+ 升级

  • [ ] 更新至 createRoot API
  • [ ] 使用 StrictMode 进行双调用测试
  • [ ] 处理水合不匹配问题
  • [ ] 在合适位置使用 Suspense 边界
  • [ ] 对于耗时更新使用 transitions

迁移后

  • [ ] 运行完整的测试套件
  • [ ] 检查控制台警告
  • [ ] 在迁移前后进行性能分析
  • [ ] 为团队记录变更内容

绝对不要

  • 迁移后跳过测试
  • 一次提交中迁移多个组件
  • 忽略 StrictMode 警告(它们会暴露潜在问题)
  • 在未理解原因的情况下使用 // eslint-disable-next-line react-hooks/exhaustive-deps
  • 在同一组件中混合使用类和 hooks
W
@wpank

已收录 3 个 Skill

相关推荐