React 渲染到小程序的原理浅析(运行时转换)
一、核心问题
React 和小程序使用不同的渲染机制:
- React:使用虚拟 DOM(Virtual DOM),在浏览器中渲染为真实 DOM
- 小程序:使用自己的组件系统(如微信小程序的
<view>、<text>等),不能直接使用 DOM
问题:如何让 React 的虚拟 DOM 在小程序中渲染?
二、解决方案:基于 React Reconciler 的自定义渲染器
核心思想
React 提供了 Reconciler(协调器) 机制,允许开发者创建自定义渲染器。通过实现一个针对小程序的渲染器,可以将 React 的虚拟 DOM 直接渲染到小程序环境中。
React Reconciler 简介
React Reconciler 是 React 的核心机制,负责:
- 管理组件的生命周期
- 协调虚拟 DOM 的更新
- 调用渲染器进行实际渲染
React 本身使用 Reconciler + DOM 渲染器(react-dom)来渲染到浏览器。我们可以实现一个 小程序渲染器 来替代 DOM 渲染器。
工作流程
React 代码 → React Reconciler → 自定义小程序渲染器 → 小程序数据结构 → 小程序渲染
关键:不是转换虚拟 DOM,而是直接实现一个渲染器,让 React Reconciler 调用我们的渲染器来渲染到小程序。
三、详细实现原理
3.1 React Reconciler 的工作机制
React Reconciler 会调用渲染器的方法来执行实际的渲染操作。渲染器需要实现以下接口:
// 渲染器需要实现的接口(简化版)
const renderer = {
// 创建节点
createInstance(type, props) {},
// 创建文本节点
createTextInstance(text) {},
// 追加子节点
appendChild(parent, child) {},
// 插入子节点
insertBefore(parent, child, before) {},
// 移除子节点
removeChild(parent, child) {},
// 更新属性
commitUpdate(instance, updatePayload) {},
// 提交更新
commitTextUpdate(textInstance, oldText, newText) {}
}
3.2 实现小程序渲染器
基于 React Reconciler 实现小程序渲染器:
import { Reconciler } from 'react-reconciler'
// 创建小程序渲染器配置
const hostConfig = {
// 创建组件实例(对应小程序的组件)
createInstance(type, props) {
// 组件映射
const componentMap = {
View: 'view',
Text: 'text',
Button: 'button',
Image: 'image'
}
const miniprogramType = componentMap[type] || type.toLowerCase()
// 转换属性
const miniprogramProps = {}
if (props.className) {
miniprogramProps.class = props.className
}
if (props.onClick) {
miniprogramProps.bindtap = props.onClick
}
// 返回小程序组件实例
return {
type: miniprogramType,
props: miniprogramProps,
children: []
}
},
// 创建文本节点
createTextInstance(text) {
return {
type: 'text',
text: String(text)
}
},
// 追加子节点
appendChild(parent, child) {
if (!parent.children) {
parent.children = []
}
parent.children.push(child)
},
// 插入子节点
insertBefore(parent, child, before) {
if (!parent.children) {
parent.children = []
}
const index = parent.children.indexOf(before)
parent.children.splice(index, 0, child)
},
// 移除子节点
removeChild(parent, child) {
if (parent.children) {
const index = parent.children.indexOf(child)
parent.children.splice(index, 1)
}
},
// 更新属性
commitUpdate(instance, updatePayload) {
Object.assign(instance.props, updatePayload)
},
// 提交更新(批量更新完成后调用)
commitTextUpdate(textInstance, oldText, newText) {
textInstance.text = newText
},
// 获取父节点
getParentInstance(instance) {
return instance.parent
},
// 获取根容器
getRootHostContext() {
return {}
},
// 获取子节点上下文
getChildHostContext() {
return {}
}
}
// 创建 Reconciler 实例
const reconciler = Reconciler(hostConfig)
// 渲染函数
export function render(element, container, callback) {
const root = reconciler.createContainer(container, false, false)
reconciler.updateContainer(element, root, null, callback)
// 将渲染结果转换为小程序数据结构
const nodes = convertToMiniProgramData(container)
// 通过 setData 更新小程序
container.page.setData({ nodes })
}
3.3 使用自定义渲染器
// 在小程序页面中
import { render } from './miniprogram-renderer'
Page({
data: {
nodes: []
},
onLoad() {
// 使用自定义渲染器渲染 React 组件
render(<App />, {
page: this,
children: []
})
}
})
小程序使用 template 渲染:
<!-- 小程序 WXML -->
<template name="react-node">
<block wx:for="{{nodes}}" wx:key="index">
<!-- view 组件 -->
<view wx:if="{{item.type === 'view'}}" class="{{item.props.class}}">
<template is="react-node" data="{{nodes: item.children}}" />
</view>
<!-- text 组件 -->
<text wx:elif="{{item.type === 'text'}}">{{item.text}}</text>
<!-- button 组件 -->
<button wx:elif="{{item.type === 'button'}}" bindtap="{{item.props.bindtap}}">
<template is="react-node" data="{{nodes: item.children}}" />
</button>
</block>
</template>
<!-- 使用 template 渲染 -->
<template is="react-node" data="{{nodes}}" />
四、关键技术点详解
4.1 组件映射
问题:React 组件需要映射到小程序组件
实现:
const componentMap = {
View: 'view', // React View → 小程序 view
Text: 'text', // React Text → 小程序 text
Image: 'image', // React Image → 小程序 image
Button: 'button', // React Button → 小程序 button
ScrollView: 'scroll-view',
Swiper: 'swiper'
// ... 更多映射
}
4.2 属性转换
问题:React 属性名和小程序属性名不同
转换规则:
const propMap = {
className: 'class', // className → class
onClick: 'bindtap', // onClick → bindtap
onChange: 'bindchange', // onChange → bindchange
onInput: 'bindinput', // onInput → bindinput
style: 'style' // style 需要特殊处理
}
4.3 状态管理
原理:React Reconciler 会自动处理状态更新
当组件调用 setState 时:
- React Reconciler 检测到状态变化
- 重新协调虚拟 DOM(diff 算法)
- 调用渲染器的更新方法(
commitUpdate、commitTextUpdate等) - 在
resetAfterCommit中统一调用setData更新小程序
实现:
// React Reconciler 自动处理状态更新
// 当组件 setState 时,Reconciler 会:
// 1. 重新协调组件树
// 2. 调用 hostConfig.commitUpdate 更新实例
// 3. 调用 hostConfig.resetAfterCommit 提交更新
// 4. 在 resetAfterCommit 中调用 setData
4.4 事件处理
问题:React 事件需要转换为小程序事件
实现:
// 事件处理映射
function convertEvent(eventName) {
const eventMap = {
onClick: 'bindtap',
onChange: 'bindchange',
onInput: 'bindinput',
onSubmit: 'bindsubmit'
}
return eventMap[eventName] || eventName
}
// 事件处理函数需要绑定到小程序页面
function bindEvent(handler) {
// 将 React 事件处理函数绑定到小程序页面
return function (event) {
// 转换小程序事件对象为 React 事件对象
const reactEvent = convertMiniProgramEvent(event)
handler(reactEvent)
}
}
4.5 生命周期映射
问题:React 生命周期需要映射到小程序生命周期
映射关系:
// React 生命周期 → 小程序生命周期
const lifecycleMap = {
componentDidMount: 'onLoad', // 组件挂载 → 页面加载
componentDidUpdate: 'onShow', // 组件更新 → 页面显示
componentWillUnmount: 'onUnload' // 组件卸载 → 页面卸载
}
五、完整实现示例
5.1 基于 React Reconciler 的实现
import { Reconciler } from 'react-reconciler'
// 小程序渲染器配置
const hostConfig = {
// 组件映射表
componentMap: {
View: 'view',
Text: 'text',
Button: 'button',
Image: 'image'
},
// 创建组件实例
createInstance(type, props, rootContainerInstance) {
const miniprogramType = this.componentMap[type] || type.toLowerCase()
const instance = {
type: miniprogramType,
props: this.convertProps(props),
children: [],
parent: null
}
return instance
},
// 创建文本节点
createTextInstance(text, rootContainerInstance) {
return {
type: 'text',
text: String(text),
parent: null
}
},
// 转换属性
convertProps(props) {
const miniprogramProps = {}
// className → class
if (props.className) {
miniprogramProps.class = props.className
}
// 事件转换
Object.keys(props).forEach(key => {
if (key.startsWith('on')) {
const eventMap = {
onClick: 'bindtap',
onChange: 'bindchange',
onInput: 'bindinput'
}
const eventName = eventMap[key] || key.toLowerCase()
miniprogramProps[eventName] = props[key]
}
})
return miniprogramProps
},
// 追加子节点
appendChild(parentInstance, child) {
child.parent = parentInstance
parentInstance.children.push(child)
},
// 插入子节点
insertBefore(parentInstance, child, beforeChild) {
child.parent = parentInstance
const index = parentInstance.children.indexOf(beforeChild)
parentInstance.children.splice(index, 0, child)
},
// 移除子节点
removeChild(parentInstance, child) {
const index = parentInstance.children.indexOf(child)
parentInstance.children.splice(index, 1)
child.parent = null
},
// 更新属性
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
const newMiniprogramProps = this.convertProps(newProps)
Object.assign(instance.props, newMiniprogramProps)
},
// 提交文本更新
commitTextUpdate(textInstance, oldText, newText) {
textInstance.text = newText
},
// 获取父节点
getParentInstance(instance) {
return instance.parent
},
// 获取根容器上下文
getRootHostContext() {
return {}
},
// 获取子节点上下文
getChildHostContext(parentHostContext, type) {
return {}
},
// 准备更新(提交阶段前)
prepareForCommit(containerInfo) {
// 可以在这里做一些准备工作
},
// 重置提交后的状态
resetAfterCommit(containerInfo) {
// 渲染完成后,将结果转换为小程序数据
const nodes = this.convertToMiniProgramData(containerInfo)
containerInfo.page.setData({ nodes })
},
// 转换为小程序数据结构
convertToMiniProgramData(container) {
function convertNode(node) {
if (node.type === 'text') {
return { type: 'text', text: node.text }
}
return {
type: node.type,
props: node.props,
children: node.children ? node.children.map(convertNode) : []
}
}
return container.children.map(convertNode)
}
}
// 创建 Reconciler 实例
const reconciler = Reconciler(hostConfig)
// 渲染函数
export function render(element, container, callback) {
const root = reconciler.createContainer(container, false, false)
reconciler.updateContainer(element, root, null, callback)
}
5.2 使用示例
// 小程序页面
import { render } from './miniprogram-renderer'
import App from './App'
Page({
data: {
nodes: []
},
onLoad() {
// 使用自定义渲染器渲染 React 组件
render(<App />, {
page: this,
children: []
})
}
})
六、完整实现流程详解(从 React 到小程序显示)
6.1 核心关联机制:数据流
关键理解:WXML 和 React 的关联是通过数据绑定实现的,而不是直接关联。
React 组件
↓ (React Reconciler 渲染)
渲染器转换
↓ (生成小程序数据结构)
setData 更新
↓ (小程序数据更新)
WXML 数据绑定
↓ ({{nodes}} 渲染)
小程序显示
6.2 详细流程步骤
步骤 1:编写 React 组件
// src/pages/index/App.jsx
import React from 'react'
export default function App() {
return (
<View className="container">
<Text>Hello World</Text>
</View>
)
}
步骤 2:在小程序页面中调用渲染器
// miniprogram/pages/index/index.js
import { render } from '../../src/renderer/miniprogram-renderer'
import App from '../../src/pages/index/App'
Page({
data: {
nodes: [] // 这个 nodes 就是连接 React 和 WXML 的桥梁
},
onLoad() {
// 调用渲染器,将 React 组件转换为数据结构
render(App, {
page: this, // 传入小程序页面实例,用于调用 setData
children: []
})
}
})
步骤 3:渲染器内部处理流程
// src/renderer/miniprogram-renderer.js
export function render(element, container, callback) {
// 1. React Reconciler 开始工作
const root = reconciler.createContainer(container, false, false)
// 2. 更新容器,触发渲染流程
reconciler.updateContainer(element, root, null, callback)
// 注意:实际的转换和 setData 在 resetAfterCommit 中完成
}
// 在 hostConfig.resetAfterCommit 中:
resetAfterCommit(containerInfo) {
// 3. 将渲染结果转换为小程序数据结构
const nodes = this.convertToMiniProgramData(containerInfo)
// 4. 通过 setData 更新小程序页面数据
// 这一步是关键!将数据写入小程序的 data.nodes
containerInfo.page.setData({ nodes })
}
转换后的数据结构示例:
// 对于 <View><Text>Hello World</Text></View>
// 转换后的 nodes 数据结构:
;[
{
type: 'view',
props: { class: 'container' },
children: [
{
type: 'text',
text: 'Hello World'
}
]
}
]
步骤 4:WXML 通过数据绑定渲染
<!-- miniprogram/pages/index/index.wxml -->
<template name="react-node">
<block wx:for="{{nodes}}" wx:key="index">
<!-- 当 item.type === 'view' 时,渲染 view 组件 -->
<view wx:if="{{item.type === 'view'}}" class="{{item.props.class}}">
<!-- 递归渲染子节点 -->
<template is="react-node" data="{{nodes: item.children}}" />
</view>
<!-- 当 item.type === 'text' 时,渲染 text 组件 -->
<text wx:elif="{{item.type === 'text'}}">{{item.text}}</text>
</block>
</template>
<!-- 使用 template 渲染,nodes 来自 Page 的 data -->
<template is="react-node" data="{{nodes}}" />
关键点:
{{nodes}}绑定的是小程序 Page 的data.nodes- 当
setData({ nodes })执行后,WXML 会自动重新渲染 - WXML 通过
wx:if、wx:elif判断节点类型,递归渲染子节点
6.3 完整数据流示例
假设 React 组件是:
<View className="container">
<Text>Hello World</Text>
</View>
流程 1:React Reconciler 渲染
React.createElement('View', { className: 'container' },
React.createElement('Text', null, 'Hello World')
)
流程 2:渲染器转换
createInstance('View', { className: 'container' })
→ { type: 'view', props: { class: 'container' }, children: [] }
appendChild(viewInstance, textInstance)
→ viewInstance.children = [{ type: 'text', text: 'Hello World' }]
流程 3:转换为小程序数据
nodes = [
{
type: 'view',
props: { class: 'container' },
children: [{ type: 'text', text: 'Hello World' }]
}
]
流程 4:setData 更新
this.setData({ nodes })
// 小程序 data.nodes 被更新
流程 5:WXML 渲染
<!-- WXML 读取 data.nodes -->
<template is="react-node" data="{{nodes}}" />
<!-- 渲染结果 -->
<view class="container">
<text>Hello World</text>
</view>
6.4 为什么需要构建工具?
问题:小程序不支持直接运行 JSX 和 ES6+ 语法
解决方案:使用构建工具(如 Webpack、Rollup)将 React 代码编译为小程序可用的代码
src/pages/index/App.jsx (JSX + ES6)
↓ (Babel 编译)
src/pages/index/App.js (ES5)
↓ (打包)
miniprogram/pages/index/index.js (小程序可运行)
6.5 完整 Demo 项目
我已经创建了一个完整的可执行 demo,位于 docs/react-to-miniprogram-demo/ 目录。
项目结构:
react-to-miniprogram-demo/
├── src/ # React 源码
│ ├── components/ # React 组件
│ ├── pages/index/App.js # Hello World 组件
│ └── renderer/ # 渲染器
├── miniprogram/ # 小程序目录
│ ├── pages/index/
│ │ ├── index.js # 调用渲染器
│ │ ├── index.wxml # 数据绑定渲染
│ │ └── index.wxss # 样式
│ └── app.js
└── package.json
关键文件说明:
src/pages/index/App.js:React 组件,返回<View><Text>Hello World</Text></View>src/renderer/miniprogram-renderer.js:渲染器,将 React 转换为小程序数据miniprogram/pages/index/index.js:小程序页面,调用render(App, container),触发setData({ nodes })miniprogram/pages/index/index.wxml:通过{{nodes}}数据绑定渲染
执行流程:
1. 小程序页面加载 (onLoad)
↓
2. 调用 render(App, container)
↓
3. React Reconciler 渲染组件
↓
4. 调用 hostConfig.createInstance/createTextInstance
↓
5. 调用 hostConfig.appendChild 构建节点树
↓
6. 调用 hostConfig.resetAfterCommit
↓
7. convertToMiniProgramData 转换为小程序数据
↓
8. container.page.setData({ nodes }) 更新数据
↓
9. WXML 通过 {{nodes}} 自动重新渲染
↓
10. 显示 "Hello World"
数据流示例:
// React 组件
<View className="container">
<Text>Hello World</Text>
</View>
// 渲染器转换后的数据结构
nodes = [
{
type: 'view',
props: { class: 'container' },
children: [
{ type: 'text', text: 'Hello World' }
]
}
]
// setData 更新
this.setData({ nodes })
// WXML 渲染
<view class="container">
<text>Hello World</text>
</view>
七、实际使用指南
6.1 安装依赖
首先需要安装 React 和 React Reconciler:
npm install react react-reconciler
# 或
yarn add react react-reconciler
6.2 项目结构
推荐的项目结构:
miniprogram-project/
├── src/ # React 源码目录
│ ├── components/ # React 组件
│ │ ├── View.jsx
│ │ ├── Text.jsx
│ │ └── Button.jsx
│ ├── pages/ # React 页面组件
│ │ └── index/
│ │ └── App.jsx
│ └── renderer/ # 渲染器代码
│ └── miniprogram-renderer.js
├── miniprogram/ # 小程序目录
│ ├── pages/
│ │ └── index/
│ │ ├── index.js # 小程序页面逻辑
│ │ ├── index.wxml # 小程序模板
│ │ ├── index.wxss # 小程序样式
│ │ └── index.json # 小程序配置
│ └── app.js
└── package.json
6.3 创建渲染器
创建 src/renderer/miniprogram-renderer.js:
import React from 'react'
import Reconciler from 'react-reconciler'
// 小程序渲染器配置
const hostConfig = {
// 组件映射表
componentMap: {
View: 'view',
Text: 'text',
Button: 'button',
Image: 'image',
ScrollView: 'scroll-view',
Swiper: 'swiper'
},
// 创建组件实例
createInstance(type, props, rootContainerInstance) {
const miniprogramType = this.componentMap[type] || type.toLowerCase()
const instance = {
type: miniprogramType,
props: this.convertProps(props),
children: [],
parent: null
}
return instance
},
// 创建文本节点
createTextInstance(text, rootContainerInstance) {
return {
type: 'text',
text: String(text),
parent: null
}
},
// 转换属性
convertProps(props) {
const miniprogramProps = {}
// className → class
if (props.className) {
miniprogramProps.class = props.className
}
// style 处理
if (props.style) {
if (typeof props.style === 'string') {
miniprogramProps.style = props.style
} else {
// 对象转字符串
miniprogramProps.style = Object.keys(props.style)
.map(key => `${key}: ${props.style[key]}`)
.join('; ')
}
}
// 事件转换
Object.keys(props).forEach(key => {
if (key.startsWith('on')) {
const eventMap = {
onClick: 'bindtap',
onChange: 'bindchange',
onInput: 'bindinput',
onSubmit: 'bindsubmit',
onScroll: 'bindscroll'
}
const eventName = eventMap[key] || key.replace('on', 'bind').toLowerCase()
miniprogramProps[eventName] = props[key]
} else if (key !== 'className' && key !== 'style' && key !== 'children') {
// 其他属性直接传递
miniprogramProps[key] = props[key]
}
})
return miniprogramProps
},
// 追加子节点
appendChild(parentInstance, child) {
child.parent = parentInstance
if (!parentInstance.children) {
parentInstance.children = []
}
parentInstance.children.push(child)
},
// 插入子节点
insertBefore(parentInstance, child, beforeChild) {
child.parent = parentInstance
if (!parentInstance.children) {
parentInstance.children = []
}
const index = parentInstance.children.indexOf(beforeChild)
parentInstance.children.splice(index, 0, child)
},
// 移除子节点
removeChild(parentInstance, child) {
if (parentInstance.children) {
const index = parentInstance.children.indexOf(child)
parentInstance.children.splice(index, 1)
}
child.parent = null
},
// 更新属性
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
const newMiniprogramProps = this.convertProps(newProps)
Object.assign(instance.props, newMiniprogramProps)
},
// 提交文本更新
commitTextUpdate(textInstance, oldText, newText) {
textInstance.text = newText
},
// 获取父节点
getParentInstance(instance) {
return instance.parent
},
// 获取根容器上下文
getRootHostContext() {
return {}
},
// 获取子节点上下文
getChildHostContext(parentHostContext, type) {
return {}
},
// 准备更新(提交阶段前)
prepareForCommit(containerInfo) {
// 可以在这里做一些准备工作
},
// 重置提交后的状态
resetAfterCommit(containerInfo) {
// 渲染完成后,将结果转换为小程序数据
const nodes = this.convertToMiniProgramData(containerInfo)
containerInfo.page.setData({ nodes })
},
// 转换为小程序数据结构
convertToMiniProgramData(container) {
function convertNode(node) {
if (node.type === 'text') {
return { type: 'text', text: node.text }
}
return {
type: node.type,
props: node.props || {},
children: node.children ? node.children.map(convertNode) : []
}
}
return container.children ? container.children.map(convertNode) : []
},
// 其他必需的接口
shouldSetTextContent() {
return false
},
finalizeInitialChildren() {
return false
},
getPublicInstance(instance) {
return instance
},
prepareUpdate() {
return true
},
clearContainer() {
// 清空容器
}
}
// 创建 Reconciler 实例
const reconciler = Reconciler(hostConfig)
// 渲染函数
export function render(element, container, callback) {
const root = reconciler.createContainer(container, false, false)
reconciler.updateContainer(element, root, null, callback)
}
// 卸载函数
export function unmount(container) {
const root = reconciler.getContainerForHostContainer(container)
if (root) {
reconciler.updateContainer(null, root, null)
}
}
6.4 创建 React 组件
创建 src/components/View.jsx:
import React from 'react'
export function View({ children, className, style, onClick, ...props }) {
return React.createElement(
'View',
{
className,
style,
onClick,
...props
},
children
)
}
创建 src/components/Text.jsx:
import React from 'react'
export function Text({ children, className, style, ...props }) {
return React.createElement(
'Text',
{
className,
style,
...props
},
children
)
}
创建 src/components/Button.jsx:
import React from 'react'
export function Button({ children, onClick, className, ...props }) {
return React.createElement(
'Button',
{
onClick,
className,
...props
},
children
)
}
6.5 创建 React 页面组件
创建 src/pages/index/App.jsx:
import React, { useState } from 'react'
import { View } from '../../components/View'
import { Text } from '../../components/Text'
import { Button } from '../../components/Button'
export default function App() {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count + 1)
}
return (
<View className="container">
<Text className="title">Hello React MiniProgram!</Text>
<Text className="count">计数: {count}</Text>
<Button onClick={handleClick} className="btn">
点击增加
</Button>
</View>
)
}
6.6 在小程序页面中使用
创建 miniprogram/pages/index/index.js:
import { render } from '../../src/renderer/miniprogram-renderer'
import App from '../../src/pages/index/App'
Page({
data: {
nodes: []
},
onLoad() {
// 使用自定义渲染器渲染 React 组件
render(
App,
{
page: this,
children: []
},
() => {
console.log('渲染完成')
}
)
},
onUnload() {
// 页面卸载时可以清理资源
}
})
6.7 小程序模板文件
创建 miniprogram/pages/index/index.wxml:
<!-- 小程序 WXML -->
<template name="react-node">
<block wx:for="{{nodes}}" wx:key="index">
<!-- view 组件 -->
<view wx:if="{{item.type === 'view'}}" class="{{item.props.class}}" style="{{item.props.style}}">
<template is="react-node" data="{{nodes: item.children}}" />
</view>
<!-- text 组件 -->
<text wx:elif="{{item.type === 'text'}}" class="{{item.props.class}}">{{item.text}}</text>
<!-- button 组件 -->
<button wx:elif="{{item.type === 'button'}}"
class="{{item.props.class}}"
bindtap="{{item.props.bindtap}}">
<template is="react-node" data="{{nodes: item.children}}" />
</button>
<!-- image 组件 -->
<image wx:elif="{{item.type === 'image'}}"
src="{{item.props.src}}"
class="{{item.props.class}}"
mode="{{item.props.mode}}" />
<!-- scroll-view 组件 -->
<scroll-view wx:elif="{{item.type === 'scroll-view'}}"
class="{{item.props.class}}"
scroll-y="{{item.props.scrollY}}"
bindscroll="{{item.props.bindscroll}}">
<template is="react-node" data="{{nodes: item.children}}" />
</scroll-view>
</block>
</template>
<!-- 使用 template 渲染 -->
<view class="react-root">
<template is="react-node" data="{{nodes}}" />
</view>
6.8 小程序样式文件
创建 miniprogram/pages/index/index.wxss:
.react-root {
width: 100%;
min-height: 100vh;
}
.container {
padding: 20px;
}
.title {
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
}
.count {
font-size: 18px;
margin-bottom: 20px;
}
.btn {
background-color: #007aff;
color: white;
padding: 10px 20px;
border-radius: 4px;
}
6.9 构建配置
如果需要使用 JSX,需要配置 Babel:
创建 babel.config.js:
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current'
}
}
],
[
'@babel/preset-react',
{
pragma: 'React.createElement',
pragmaFrag: 'React.Fragment'
}
]
]
}
6.10 实际开发注意事项
- 事件处理:小程序的事件对象和 React 的事件对象不同,需要在渲染器中转换
- 样式处理:小程序的样式支持有限,某些 CSS 特性可能不支持
- 性能优化:避免频繁的 setData,可以使用防抖或节流
- 组件库:可以使用现有的 React 组件库,但需要确保组件映射正确
- 状态管理:可以使用 Redux、MobX 等状态管理库
- 路由:需要自己实现路由系统,或使用小程序的路由 API
6.11 使用现有框架
实际上,不建议自己实现,推荐使用成熟的框架:
- Remax:基于 React Reconciler 的小程序框架
- Taro:支持 React 的多端框架(编译时转换)
这些框架已经处理了大部分兼容性问题,可以直接使用。
七、核心原理总结
7.1 工作流程
- 创建自定义渲染器:基于 React Reconciler 实现小程序渲染器
- React Reconciler 协调:React 使用 Reconciler 管理组件生命周期和更新
- 渲染器方法调用:Reconciler 调用渲染器的方法(createInstance、appendChild 等)
- 转换为小程序数据:在
resetAfterCommit中将渲染结果转换为小程序数据结构 - 小程序渲染:通过
setData更新小程序,使用 template 渲染
7.2 关键技术
- React Reconciler:React 提供的协调器机制,允许创建自定义渲染器
- 渲染器接口实现:实现 hostConfig 中的各种方法(createInstance、appendChild 等)
- 组件映射:React 组件 → 小程序组件(在 createInstance 中实现)
- 属性转换:React 属性 → 小程序属性(className → class,在 convertProps 中实现)
- 事件转换:React 事件 → 小程序事件(onClick → bindtap)
- 状态同步:Reconciler 自动处理 setState,在 resetAfterCommit 中调用 setData
7.3 优缺点
优点:
- ✅ 真正的 React:可以使用所有 React 特性
- ✅ 灵活性高:完全兼容 React 生态
- ✅ 开发体验好:纯 React 开发体验
缺点:
- ❌ 性能较差:需要运行时转换,性能不如原生
- ❌ 包体积大:需要包含 React 和适配层代码
- ❌ 兼容性问题:部分 React 特性可能无法完美适配