关于 react-to-taro
此 Skill 指导 Agent 将 React Web 应用转换为面向微信小程序的 Taro 4.x 代码。它包含用于分析源码、生成转换指令和验证结果的脚本,并提供平台适配规则。
主要功能
- 01React 源码分析脚本
- 02转换指令生成
- 03Taro 输出验证
- 04JSX、事件和路由转换指引
- 05API、样式及平台约束规则
适用场景
- 01规划 React 应用迁移到微信小程序
- 02转换 React 表单和事件处理器
- 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.mdbest-practices.mdcomponents.mdscripts/analyze.jsgenerate-transforms.jsvalidate.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
| React | Taro |
|---|---|
<div>、<section>、<article>、<main> | <View> |
<nav>、<aside>、<header>、<footer> | <View> |
<ul>、<ol>、<li> | <View> |
文本元素 → Text
| React | Taro | 限制 |
|---|---|---|
<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
| React | Taro | 默认值 |
|---|---|---|
<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:事件转换
事件映射
| React | Taro | 详情 |
|---|---|---|
onClick | onClick | ITouchEvent |
onChange(输入框) | onInput | e.detail.value |
onKeyDown(Enter) | onConfirm | e.detail.value |
onFocus | onFocus | e.detail |
onBlur | onBlur | e.detail |
onScroll | onScroll | scrollTop, 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 Router | Taro |
|---|---|
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