Loading...
Loading...
Transpile React Web applications (v18+) into Taro 4.x code optimized for WeChat Mini Program. Use this skill when converting React components, pages, or utilities to Taro-compatible code with proper JSX element mapping, event handler transformation, navigation/routing conversion, and API shim layer.
npx skill4agent add dafang/taro-weapp-plugin react-to-taronode scripts/analyze.js <file-or-directory>
# 输出: taro-migration-report.jsonnode scripts/generate-transforms.js taro-migration-report.json
# 输出: taro-transforms.jsonnode scripts/validate.js <file-or-directory>
# 验证通过返回 0,失败返回 1# 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 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'| React | Taro |
|---|---|
| |
| |
| |
| React | Taro | Constraint |
|---|---|---|
| | Pure text only |
| | No block elements inside |
// INVALID
<Text><View>Block</View></Text>
// VALID
<View><Text>Text</Text><View>Block</View></View>| React | Taro | Default |
|---|---|---|
| | |
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()}| React | Taro | Detail |
|---|---|---|
| | |
| | |
| | |
| | |
| | |
| | |
// 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} />// 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 Router | Taro |
|---|---|
| |
| |
| |
| TabBar route | |
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>// 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?' })// BEFORE
document.getElementById('el').getBoundingClientRect()
// AFTER
Taro.createSelectorQuery().select('#el').boundingClientRect().exec()npm install weapp-tailwindcss tailwindcss autoprefixer postcss -Dconst { 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 }]
}
}
})
}
}
}
})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[]
}
}prebundle: { enable: false }className<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">// Fixed bottom elements need safe area padding
<View style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}>// 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" />// 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'
})// INVALID: Only .map() allowed
{items.filter(x => x.active).map(...)}
// VALID: Pre-process
const activeItems = items.filter(x => x.active)
{activeItems.map(...)}onundefinednullidclassstyledefaultProps// 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' })const res = await Taro.request({ url, method: 'GET', data })
Taro.showToast({ title: 'Message', icon: 'none' })
Taro.setStorageSync('key', value)