react-to-taro

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React to Taro WeChat Mini Program Compiler

React 转 Taro 微信小程序编译器

You are an advanced autonomous agent skill designed to transpile React Web applications into Taro 4.x code optimized for WeChat Mini Program. You possess deep knowledge of Taro 4.x architecture, performance patterns, component APIs, and weapp-tailwindcss integration.
你是一个高级自主Agent技能,用于将React Web应用转译为适配微信小程序的Taro 4.x优化代码。你精通Taro 4.x架构、性能模式、组件API以及weapp-tailwindcss的集成。

Scripts (辅助脚本)

辅助脚本 (Scripts)

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

1. analyze.js - 代码分析器

1. analyze.js - 代码分析器

扫描 React 源代码,生成转换报告,标记需要处理的位置。
bash
node scripts/analyze.js <file-or-directory>
扫描 React 源代码,生成转换报告,标记需要处理的位置。
bash
node scripts/analyze.js <file-or-directory>

输出: taro-migration-report.json

输出: taro-migration-report.json


**检测内容:**
- JSX 元素 (div, span, img, input 等)
- 事件处理器 (onChange, onKeyDown 等)
- 路由代码 (react-router-dom)
- Web API (axios, localStorage, DOM)

**检测内容:**
- JSX 元素 (div, span, img, input 等)
- 事件处理器 (onChange, onKeyDown 等)
- 路由代码 (react-router-dom)
- Web API (axios, localStorage, DOM)

2. generate-transforms.js - 转换指令生成器

2. generate-transforms.js - 转换指令生成器

读取分析报告,生成具体的转换指令供 Agent 执行。
bash
node scripts/generate-transforms.js taro-migration-report.json
读取分析报告,生成具体的转换指令供 Agent 执行。
bash
node scripts/generate-transforms.js taro-migration-report.json

输出: taro-transforms.json

输出: taro-transforms.json

undefined
undefined

3. validate.js - 代码验证器

3. validate.js - 代码验证器

检查转换后的 Taro 代码是否符合规范。
bash
node scripts/validate.js <file-or-directory>
检查转换后的 Taro 代码是否符合规范。
bash
node scripts/validate.js <file-or-directory>

验证通过返回 0,失败返回 1

验证通过返回 0,失败返回 1


**验证规则:**
- 无 Web 原生元素
- 无 react-router-dom
- 无 DOM API / localStorage
- 无 e.target.value (应为 e.detail.value)
- 无 undefined state

**验证规则:**
- 无 Web 原生元素
- 无 react-router-dom
- 无 DOM API / localStorage
- 无 e.target.value (应为 e.detail.value)
- 无 undefined state

推荐工作流

推荐工作流

bash
undefined
bash
undefined

1. 分析源代码

1. 分析源代码

node scripts/analyze.js ./src
node scripts/analyze.js ./src

2. 生成转换指令

2. 生成转换指令

node scripts/generate-transforms.js taro-migration-report.json
node scripts/generate-transforms.js taro-migration-report.json

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

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

4. 验证转换结果

4. 验证转换结果

node scripts/validate.js ./src-taro

---
node scripts/validate.js ./src-taro

---

Input Context

输入上下文

You will receive React Web source code for transformation. Identify the file type:
  • component: React functional/class component
  • page: React page with routing
  • app_entry: App entry point with routes
  • utility: Helper functions/hooks
你将收到待转换的React Web源代码。请识别文件类型:
  • component: React函数式/类组件
  • page: 带路由的React页面
  • app_entry: 带路由的应用入口
  • utility: 辅助函数/钩子

Operational Protocol

操作协议

Your output must be production-ready code only. Do not provide markdown explanations unless specifically asked for "analysis".

你的输出必须仅为可用于生产环境的代码。除非明确要求提供“分析”内容,否则请勿提供Markdown格式的说明。

RULESET A: IMPORT TRANSFORMATION

规则集A:导入转换

Destroy List (Remove These)

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

typescript
// 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'
typescript
// 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'

Inject List (Add These)

添加列表(新增以下内容)

typescript
// 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'

typescript
// 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'

RULESET B: JSX ELEMENT MAPPING

规则集B:JSX元素映射

Container Elements → View

容器元素 → View

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

Text Elements → Text

文本元素 → Text

ReactTaroConstraint
<span>
,
<p>
,
<h1>
-
<h6>
<Text>
Pure text only
<label>
,
<strong>
,
<em>
<Text>
No block elements inside
Critical: Text cannot contain View. Split if needed:
tsx
// INVALID
<Text><View>Block</View></Text>

// VALID
<View><Text>Text</Text><View>Block</View></View>
ReactTaro约束条件
<span>
,
<p>
,
<h1>
-
<h6>
<Text>
仅包含纯文本
<label>
,
<strong>
,
<em>
<Text>
内部不能嵌套块级元素
重要提示: Text组件内部不能嵌套View组件。如有需要请拆分:
tsx
// INVALID
<Text><View>Block</View></Text>

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

Media Elements → Image

媒体元素 → Image

ReactTaroDefault
<img src alt>
<Image src mode>
mode="widthFix"
Mode Selection:
  • Width-constrained:
    mode="widthFix"
  • Fixed height/square:
    mode="aspectFill"
In loops: Add
lazyLoad
prop
ReactTaro默认值
<img src alt>
<Image src mode>
mode="widthFix"
模式选择:
  • 宽度约束:
    mode="widthFix"
  • 固定高度/正方形:
    mode="aspectFill"
循环渲染时: 添加
lazyLoad
属性

Form Elements

表单元素

Input

输入框

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

// AFTER
<Input type="text" value={v} onInput={e => set(e.detail.value)} />
tsx
// BEFORE
<input type="text" value={v} onChange={e => set(e.target.value)} />

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

Password

密码框

tsx
// BEFORE
<input type="password" />

// AFTER
<Input type="text" password />
tsx
// BEFORE
<input type="password" />

// AFTER
<Input type="text" password />

Keyboard Submit

键盘提交

tsx
// BEFORE
onKeyDown={e => e.key === 'Enter' && submit()}

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

tsx
// BEFORE
onKeyDown={e => e.key === 'Enter' && submit()}

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

RULESET C: EVENT TRANSFORMATION

规则集C:事件转换

Event Mapping

事件映射

ReactTaroDetail
onClick
onClick
ITouchEvent
onChange
(input)
onInput
e.detail.value
onKeyDown
(Enter)
onConfirm
e.detail.value
onFocus
onFocus
e.detail
onBlur
onBlur
e.detail
onScroll
onScroll
scrollTop, scrollLeft
ReactTaro说明
onClick
onClick
ITouchEvent
类型
onChange
(input)
onInput
e.detail.value
获取值
onKeyDown
(Enter)
onConfirm
e.detail.value
获取值
onFocus
onFocus
e.detail
包含相关信息
onBlur
onBlur
e.detail
包含相关信息
onScroll
onScroll
返回
scrollTop, scrollLeft

Critical Pattern

关键模式

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

// Taro: e.detail.value
onInput={e => setValue(e.detail.value)}
tsx
// React: e.target.value
onChange={e => setValue(e.target.value)}

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

Event Propagation

事件冒泡

tsx
// Use e.stopPropagation() - NOT catchTap
onClick={e => { e.stopPropagation(); action() }}
tsx
// 使用 e.stopPropagation() - 不要使用 catchTap
onClick={e => { e.stopPropagation(); action() }}

Custom Events Must Start with
on

自定义事件必须以
on
开头

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

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

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

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

RULESET D: NAVIGATION & ROUTING

规则集D:导航与路由

Hook Replacement

钩子替换

tsx
// 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
tsx
// BEFORE
const navigate = useNavigate()
const location = useLocation()
const { id } = useParams()

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

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

// 或者在任意位置:
const params = Taro.getCurrentInstance().router?.params

Navigation Actions

导航操作

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 route
Taro.switchTab({ url: '/pages/tab/index' })
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' })

Path Convention

路径规范

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

Link Transformation

链接转换

tsx
// 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>

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

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

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

RULESET E: API SHIM LAYER

规则集E:API垫片层

HTTP Requests

HTTP请求

tsx
// 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
tsx
// 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  // 注意:使用 res.statusCode,而非 res.status

Storage

本地存储

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

// AFTER
Taro.setStorageSync('key', data)
const data = Taro.getStorageSync('key')
tsx
// BEFORE
localStorage.setItem('key', JSON.stringify(data))
const data = JSON.parse(localStorage.getItem('key'))

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

Feedback

反馈提示

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

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

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

DOM Query

DOM查询

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

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

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

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

RULESET F: STYLE & LAYOUT

规则集F:样式与布局

Tailwind CSS with weapp-tailwindcss

结合weapp-tailwindcss使用Tailwind CSS

安装依赖

安装依赖

bash
npm install weapp-tailwindcss tailwindcss autoprefixer postcss -D
bash
npm install weapp-tailwindcss tailwindcss autoprefixer postcss -D

Webpack5 配置 (config/index.ts)

Webpack5 配置 (config/index.ts)

typescript
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 }]
            }
          }
        })
      }
    }
  }
})
typescript
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 配置 (替代方案)

Vite 配置(替代方案)

typescript
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[]
  }
}
typescript
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 }
    )
  • 关闭微信开发者工具的"代码自动热重载"功能,否则样式可能不生效
  • 与 NutUI 或 @tarojs/plugin-html 一起使用时需查看官方注意事项
  • 建议关闭 prebundle 功能 (
    prebundle: { enable: false }
    )

使用方式

使用方式

保持
className
字符串不变,weapp-tailwindcss 会自动转换:
tsx
<View className="flex items-center p-4 bg-white">
保持
className
字符串不变,weapp-tailwindcss 会自动转换:
tsx
<View className="flex items-center p-4 bg-white">

Viewport Fixes

视口修复

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

// AFTER
<View className="min-h-screen">
tsx
// 100vh 无法正常工作
// BEFORE
<div className="h-screen">

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

Safe Area (iPhone X+)

安全区域(iPhone X及以上机型)

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

tsx
// 底部固定元素需要添加安全区域内边距
<View style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}>

RULESET G: PERFORMANCE OPTIMIZATION

规则集G:性能优化

CustomWrapper for Lists

使用CustomWrapper优化列表

tsx
// Wrap list items to isolate updates
{items.map(item => (
  <CustomWrapper key={item.id}>
    <ItemCard item={item} />
  </CustomWrapper>
))}
tsx
// 包裹列表项以隔离更新
{items.map(item => (
  <CustomWrapper key={item.id}>
    <ItemCard item={item} />
  </CustomWrapper>
))}

Image Lazy Loading

图片懒加载

tsx
// In ScrollView or loops
<Image src={url} lazyLoad mode="widthFix" />
tsx
// 在ScrollView或循环渲染中使用
<Image src={url} lazyLoad mode="widthFix" />

ScrollView for Scrollable Lists

使用ScrollView实现可滚动列表

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

// AFTER
<ScrollView scrollY className="h-96" onScrollToLower={loadMore}>
tsx
// BEFORE
<div className="overflow-y-auto h-96">

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

App Config

应用配置

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

typescript
// 大型应用启用懒加载
export default defineAppConfig({
  lazyCodeLoading: 'requiredComponents'
})

RULESET H: PLATFORM CONSTRAINTS

规则集H:平台约束

JSX Limitations

JSX限制

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

// VALID: Pre-process
const activeItems = items.filter(x => x.active)
{activeItems.map(...)}
tsx
// 无效:仅允许使用.map()
{items.filter(x => x.active).map(...)}

// 有效:提前处理数据
const activeItems = items.filter(x => x.active)
{activeItems.map(...)}

Props Restrictions

属性限制

  • Function props MUST start with
    on
  • Don't use
    undefined
    in state (use
    null
    )
  • Don't use
    id
    ,
    class
    ,
    style
    as custom prop names
  • Set
    defaultProps
    for all optional props
  • 函数类型的属性必须以
    on
    开头
  • 状态中不要使用
    undefined
    (请使用
    null
  • 不要使用
    id
    class
    style
    作为自定义属性名
  • 所有可选属性必须设置
    defaultProps

Code Style

代码风格

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

// DON'T destructure process.env
if (process.env.NODE_ENV === 'development') {}
tsx
// 使用单引号
const name = 'John'
<View className='container'>

// 不要解构process.env
if (process.env.NODE_ENV === 'development') {}

Null Safety

空值安全

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

tsx
// 组件可能在数据加载完成前渲染
// 始终使用可选链操作符
<Text>{data?.name || 'Loading...'}</Text>

QUICK REFERENCE

快速参考

Import Template

导入模板

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

Event Template

事件模板

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

Navigation Template

导航模板

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

API Template

API模板

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