react-to-taro
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact 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
undefinedundefined3. 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
undefinedbash
undefined1. 分析源代码
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
| React | Taro |
|---|---|
| |
| |
| |
| React | Taro |
|---|---|
| |
| |
| |
Text Elements → Text
文本元素 → Text
| React | Taro | Constraint |
|---|---|---|
| | Pure text only |
| | 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>| React | Taro | 约束条件 |
|---|---|---|
| | 仅包含纯文本 |
| | 内部不能嵌套块级元素 |
重要提示: Text组件内部不能嵌套View组件。如有需要请拆分:
tsx
// INVALID
<Text><View>Block</View></Text>
// VALID
<View><Text>Text</Text><View>Block</View></View>Media Elements → Image
媒体元素 → Image
| React | Taro | Default |
|---|---|---|
| | |
Mode Selection:
- Width-constrained:
mode="widthFix" - Fixed height/square:
mode="aspectFill"
In loops: Add prop
lazyLoad| React | Taro | 默认值 |
|---|---|---|
| | |
模式选择:
- 宽度约束:
mode="widthFix" - 固定高度/正方形:
mode="aspectFill"
循环渲染时: 添加 属性
lazyLoadForm 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
事件映射
| React | Taro | Detail |
|---|---|---|
| | |
| | |
| | |
| | |
| | |
| | |
| React | Taro | 说明 |
|---|---|---|
| | |
| | |
| | |
| | |
| | |
| | 返回 |
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自定义事件必须以on
开头
ontsx
// 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?.paramstsx
// 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?.paramsNavigation Actions
导航操作
| React Router | Taro |
|---|---|
| |
| |
| |
| TabBar route | |
| React Router | Taro |
|---|---|
| |
| |
| |
| TabBar 路由 | |
Path Convention
路径规范
React: /products/:id
Taro: /pages/products/index?id=xxxReact: /products/:id
Taro: /pages/products/index?id=xxxLink 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.statustsx
// 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.statusStorage
本地存储
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 -Dbash
npm install weapp-tailwindcss tailwindcss autoprefixer postcss -DWebpack5 配置 (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 }
使用方式
使用方式
保持 字符串不变,weapp-tailwindcss 会自动转换:
classNametsx
<View className="flex items-center p-4 bg-white">保持 字符串不变,weapp-tailwindcss 会自动转换:
classNametsx
<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 in state (use
undefined)null - Don't use ,
id,classas custom prop namesstyle - Set for all optional props
defaultProps
- 函数类型的属性必须以开头
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)