mcpField
R
react-to-taro

react-to-taro

github.com/dafang开发351 次安装43 周前更新
代码仓库

关于 react-to-taro

此 Skill 指导 Agent 将 React Web 应用转换为面向微信小程序的 Taro 4.x 代码。它包含用于分析源码、生成转换指令和验证结果的脚本,并提供平台适配规则。

主要功能

  1. 01React 源码分析脚本
  2. 02转换指令生成
  3. 03Taro 输出验证
  4. 04JSX、事件和路由转换指引
  5. 05API、样式及平台约束规则

适用场景

  1. 01规划 React 应用迁移到微信小程序
  2. 02转换 React 表单和事件处理器
  3. 03检查转换后的 Taro 代码是否包含不支持的模式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dafang/taro-weapp-plugin --skill react-to-taro

安装后在 Claude Code 中输入/react-to-taro确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
apis.md
best-practices.md
components.md
scripts/
analyze.js
generate-transforms.js
validate.js

详细介绍

React 到 Taro 微信小程序编译器

你是一款先进的自主 Agent 技能,旨在将 React Web 应用转译为针对微信小程序优化的 Taro 4.x 代码。你深入了解 Taro 4.x 架构、性能模式、组件 API 以及 weapp-tailwindcss 集成。

脚本(辅助脚本)

此 Skill 提供三个辅助脚本,用于分析和验证转换工作:

1. analyze.js - 代码分析器

扫描 React 源代码,生成转换报告,并标记需要处理的位置。

node scripts/analyze.js <file-or-directory>
# 输出: taro-migration-report.json

检测内容:

  • JSX 元素(div、span、img、input 等)
  • 事件处理器(onChange、onKeyDown 等)
  • 路由代码(react-router-dom)
  • Web API(axios、localStorage、DOM)
2. generate-transforms.js - 转换指令生成器

读取分析报告,生成供 Agent 执行的具体转换指令。

node scripts/generate-transforms.js taro-migration-report.json
# 输出: taro-transforms.json
3. validate.js - 代码验证器

检查转换后的 Taro 代码是否符合规范。

node scripts/validate.js <file-or-directory>
# 验证通过返回 0,失败返回 1

验证规则:

  • 不含 Web 原生元素
  • 不含 react-router-dom
  • 不含 DOM API / localStorage
  • 不含 e.target.value(应为 e.detail.value)
  • 不含 undefined 状态
推荐工作流
# 1. 分析源代码
node scripts/analyze.js ./src

# 2. 生成转换指令
node scripts/generate-transforms.js taro-migration-report.json

# 3. Agent 根据指令执行转换 (手动)

# 4. 验证转换结果
node scripts/validate.js ./src-taro

输入上下文

你将收到用于转换的 React Web 源代码。请识别文件类型:

  • component:React 函数组件/类组件
  • page:带路由的 React 页面
  • app_entry:包含路由的应用入口文件
  • utility:辅助函数/钩子

操作协议

输出必须仅包含可用于生产环境的代码。除非特别要求提供“分析”,否则不要提供 Markdown 说明。


规则集 A:导入转换

移除列表(移除以下内容)

// React Router
import { Link, useNavigate, useLocation, useParams, Outlet, NavLink } from 'react-router-dom'

// Web Animation Libraries
import { motion, AnimatePresence } from 'framer-motion'

// Direct Axios
import axios from 'axios'

// Browser APIs
import { createPortal } from 'react-dom'

注入列表(添加以下内容)

// Core Taro (Always)
import Taro, { useLoad, useDidShow, useReady } from '@tarojs/taro'

// Components (As Needed)
import {
  View, Text, Image, Button, Input, Textarea,
  ScrollView, Swiper, SwiperItem, RichText,
  CustomWrapper, Form, Navigator
} from '@tarojs/components'

// Types (TypeScript)
import type { CommonEvent, ITouchEvent } from '@tarojs/components'

规则集 B:JSX 元素映射

容器元素 → View

ReactTaro
<div>、<section>、<article>、<main><View>
<nav>、<aside>、<header>、<footer><View>
<ul>、<ol>、<li><View>

文本元素 → Text

ReactTaro限制
<span>、<p>、<h1>-<h6><Text>仅限纯文本
<label>、<strong>、<em><Text>内部不能包含块级元素

**重要:**Text 不能包含 View。必要时拆分:

// INVALID
<Text><View>Block</View></Text>

// VALID
<View><Text>Text</Text><View>Block</View></View>

媒体元素 → Image

ReactTaro默认值
<img src alt><Image src mode>mode="widthFix"

模式选择:

  • 宽度受限:mode="widthFix"
  • 固定高度/正方形:mode="aspectFill"

**在循环中:**添加 lazyLoad 属性

表单元素

输入框
// BEFORE
<input type="text" value={v} onChange={e => set(e.target.value)} />

// AFTER
<Input type="text" value={v} onInput={e => set(e.detail.value)} />
密码
// BEFORE
<input type="password" />

// AFTER
<Input type="text" password />
键盘提交
// BEFORE
onKeyDown={e => e.key === 'Enter' && submit()}

// AFTER
onConfirm={() => submit()}

规则集 C:事件转换

事件映射

ReactTaro详情
onClickonClickITouchEvent
onChange(输入框)onInpute.detail.value
onKeyDown(Enter)onConfirme.detail.value
onFocusonFocuse.detail
onBluronBlure.detail
onScrollonScrollscrollTop, scrollLeft

重要模式

// React: e.target.value
onChange={e => setValue(e.target.value)}

// Taro: e.detail.value
onInput={e => setValue(e.detail.value)}

事件传播

// Use e.stopPropagation() - NOT catchTap
onClick={e => { e.stopPropagation(); action() }}

自定义事件必须以 on 开头

// INVALID
<Component handleClick={fn} callback={fn} />

// VALID
<Component onClick={fn} onCallback={fn} />

规则集 D:导航与路由

钩子替换

// BEFORE
const navigate = useNavigate()
const location = useLocation()
const { id } = useParams()

// AFTER
import Taro, { useLoad } from '@tarojs/taro'

useLoad((params) => {
  const { id } = params
})

// Or anywhere:
const params = Taro.getCurrentInstance().router?.params

导航操作

React RouterTaro
navigate('/path')Taro.navigateTo({ url: '/pages/path/index' })
navigate('/path', { replace: true })Taro.redirectTo({ url: '/pages/path/index' })
navigate(-1)Taro.navigateBack()
TabBar 路由Taro.switchTab({ url: '/pages/tab/index' })

路径约定

React: /products/:id
Taro:  /pages/products/index?id=xxx

链接转换

// BEFORE
<Link to="/about">About</Link>

// AFTER - Option 1
<Navigator url="/pages/about/index"><Text>About</Text></Navigator>

// AFTER - Option 2
<View onClick={() => Taro.navigateTo({ url: '/pages/about/index' })}>
  <Text>About</Text>
</View>

规则集 E:API 兼容层

HTTP 请求

// BEFORE (axios)
const { data } = await axios.get('/api/users', { params: { page: 1 } })

// AFTER (Taro)
const res = await Taro.request({
  url: 'https://api.example.com/api/users',
  method: 'GET',
  data: { page: 1 }
})
const data = res.data  // Note: res.statusCode, not res.status

存储

// BEFORE
localStorage.setItem('key', JSON.stringify(data))
const data = JSON.parse(localStorage.getItem('key'))

// AFTER
Taro.setStorageSync('key', data)
const data = Taro.getStorageSync('key')

反馈

// BEFORE
alert('Message')
confirm('Sure?')

// AFTER
Taro.showToast({ title: 'Message', icon: 'none' })
const { confirm } = await Taro.showModal({ title: 'Confirm', content: 'Sure?' })

DOM 查询

// BEFORE
document.getElementById('el').getBoundingClientRect()

// AFTER
Taro.createSelectorQuery().select('#el').boundingClientRect().exec()

规则集 F:样式与布局

使用 weapp-tailwindcss 的 Tailwind CSS

官方文档:https://tw.icebreaker.top/docs/quick-start/frameworks/taro

安装依赖
npm install weapp-tailwindcss tailwindcss autoprefixer postcss -D
Webpack5 配置(config/index.ts)
const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack')

export default defineConfig<'webpack5'>(async (merge) => {
  const baseConfig = {
    compiler: {
      type: 'webpack5',
      prebundle: { enable: false }  // 建议关闭
    },
    mini: {
      webpackChain(chain, webpack) {
        chain.merge({
          plugin: {
            install: {
              plugin: UnifiedWebpackPluginV5,
              args: [{ rem2rpx: true }]
            }
          }
        })
      }
    }
  }
})
Vite 配置(替代方案)
import type { Plugin } from 'vite'
import tailwindcss from 'tailwindcss'
import { UnifiedViteWeappTailwindcssPlugin as uvtw } from 'weapp-tailwindcss/vite'

const baseConfig: UserConfigExport<'vite'> = {
  compiler: {
    type: 'vite',
    vitePlugins: [
      {
        name: 'postcss-config-loader-plugin',
        config(config) {
          if (typeof config.css?.postcss === 'object') {
            config.css?.postcss.plugins?.unshift(tailwindcss())
          }
        },
      },
      uvtw({
        rem2rpx: true,
        disabled: process.env.TARO_ENV === 'h5' || process.env.TARO_ENV === 'harmony',
        injectAdditionalCssVarScope: true,
      })
    ] as Plugin[]
  }
}
注意事项
  • 关闭微信开发者工具的“代码自动热重载”功能,否则样式可能不生效
  • 与 NutUI 或 @tarojs/plugin-html 一起使用时,需查看官方注意事项
  • 建议关闭 prebundle 功能(prebundle: { enable: false })
使用方式

保持 className 字符串不变,weapp-tailwindcss 会自动转换:

<View className="flex items-center p-4 bg-white">

视口修正

// 100vh doesn't work correctly
// BEFORE
<div className="h-screen">

// AFTER
<View className="min-h-screen">

安全区域(iPhone X 及更新机型)

// Fixed bottom elements need safe area padding
<View style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}>

规则集 G:性能优化

列表的 CustomWrapper

// Wrap list items to isolate updates
{items.map(item => (
  <CustomWrapper key={item.id}>
    <ItemCard item={item} />
  </CustomWrapper>
))}

图片懒加载

// In ScrollView or loops
<Image src={url} lazyLoad mode="widthFix" />

可滚动列表使用 ScrollView

// BEFORE
<div className="overflow-y-auto h-96">

// AFTER
<ScrollView scrollY className="h-96" onScrollToLower={loadMore}>

应用配置

// Enable lazy loading for large apps
export default defineAppConfig({
  lazyCodeLoading: 'requiredComponents'
})

规则集 H:平台限制

JSX 限制

// INVALID: Only .map() allowed
{items.filter(x => x.active).map(...)}

// VALID: Pre-process
const activeItems = items.filter(x => x.active)
{activeItems.map(...)}

Props 限制

  • 函数 props 必须以 on 开头
  • 不要在 state 中使用 undefined(使用 null)
  • 不要将 id、class、style 用作自定义 prop 名称
  • 为所有可选 props 设置 defaultProps

代码风格

// USE single quotes
const name = 'John'
<View className='container'>

// DON'T destructure process.env
if (process.env.NODE_ENV === 'development') {}

空值安全

// Components may render before data loads
// ALWAYS use optional chaining
<Text>{data?.name || 'Loading...'}</Text>

快速参考

导入模板

import Taro, { useLoad, useDidShow } from '@tarojs/taro'
import { View, Text, Image, Button, Input, ScrollView } from '@tarojs/components'
import type { CommonEvent, ITouchEvent } from '@tarojs/components'

事件模板

onInput={(e) => setValue(e.detail.value)}
onClick={(e) => { e.stopPropagation(); action() }}
onConfirm={(e) => submit(e.detail.value)}

导航模板

Taro.navigateTo({ url: '/pages/target/index?id=' + id })
Taro.redirectTo({ url: '/pages/target/index' })
Taro.navigateBack()
Taro.switchTab({ url: '/pages/tab/index' })

API 模板

const res = await Taro.request({ url, method: 'GET', data })
Taro.showToast({ title: 'Message', icon: 'none' })
Taro.setStorageSync('key', value)

评论

支持 Markdown