其他 · 2025年9月17日 0

UniApp技术栈支持React体系(一)

UniApp是一套非常完整的跨平台开发框架,有丰富的社区资源和生态,但是唯独不支持React生态,并且在开发小程序的时候由于平台限制无法使用Vue的全部特性,比如说JSX能力,这种能力可以极大地降低开发难度,一些通过JSX可以非常容易实现的特性在缺失这种能力的时候是极其难以做到的或者说根本无法做到!!!

基于这个原因我产生了一个想法,把React开发体系嵌入到UniApp中,有了想法就需要验证可行性,所以才有了这个项目:uni-app-react

核心能力

这个方案主要解决了以下几个技术问题:

  • 包体积控制:运行时核心库仅30+KB,配合preact(20KB)替代react,总计50KB即可运行完整的React体系。微信小程序额外付出150KB左右的模版空间,支付宝小程序额外付出30KB左右
  • 跨平台一致性:同一份React代码可以在微信/支付宝/钉钉/H5/APP平台运行,无需针对不同平台做适配
  • 框架互通:Vue组件可以在React中直接使用,解决了团队技术栈迁移或混合开发的问题
  • 渐进式接入:不需要重构整个项目,可以在现有UniApp项目中按需引入React,逐步迁移

快速上手

1. 安装依赖

# 安装插件包
npm i @js-css/uni-app-react
# 安装依赖包
npm i preact @types/react

2. 配置 vite.config.ts

import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import { UniAppReact } from '@js-css/uni-app-react/dist/plugins/jsx'
import * as path from 'node:path'

export default defineConfig({
  plugins: [
    // 添加插件
    UniAppReact(),
    uni(),
  ],
  resolve: {
    alias: {
      '@': '/src',
      // 关键:把 react 指向 preact/compat
      react: path.resolve(__dirname, './node_modules/preact/compat'),
      'react-is': path.resolve(__dirname, './node_modules/preact/compat'),
      'react-dom': path.resolve(__dirname, './node_modules/preact/compat'),
      '@js-css/uni-app-react': path.resolve(
        __dirname,
        './node_modules/@js-css/uni-app-react'
      ),
    },
  },
})

3. 配置 pages.json

添加一个全局组件,该组件由插件自动注入:

{
  "pages": [...],
  "globalStyle": {
    "navigationBarTextStyle": "black",
    "usingComponents": {
      "document": "/document"
    }
  }
}

基本使用

配置完成后,就可以在Vue页面中渲染React组件了:

<template>
  <button @click="handleRender">渲染React组件</button>
  <!-- React渲染入口 -->
  <ReactRender @mounted="handleMounted"></ReactRender>
</template>

<script setup lang="tsx">
import { Button, View } from '@js-css/uni-app-react'
import ReactRender from '@js-css/uni-app-react/react.vue'
import { useState } from 'react'
import { ref } from 'vue'

const renderRef = ref<any>()

const handleMounted = (event: any) => {
  renderRef.value = event
  console.log('React环境已就绪', event)
}

const handleRender = () => {
  // 一个简单的React组件
  const Counter = (props: { unmount: () => void }) => {
    const [count, setCount] = useState(0)
    return (
      <View>
        <View>计数: {count}</View>
        <Button onClick={() => setCount(c => c + 1)}>+1</Button>
        <Button onClick={props.unmount}>关闭</Button>
      </View>
    )
  }

  // 渲染React组件,返回组件ID
  const id = renderRef.value?.render(
    <Counter unmount={() => renderRef.value?.unmount(id)} />
  )
}
</script>

API 说明

react.vue 组件对外提供 Render 对象,接口如下:

type Render = {
  // 内置document组件实例
  $documentInstance: DocumentComponentInstance
  // react.vue 组件实例
  $vm: any
  // 渲染React组件,返回组件ID
  render(element: ReactElement): number
  // 更新已渲染的组件
  update(id: number, element: ReactElement): number
  // 卸载组件
  unmount(id: number): void
}

useRender Hook

在React组件内部,可以通过 useRender() 获取渲染对象:

import { useRender, View, Button } from '@js-css/uni-app-react'

const Comp = () => {
  const render = useRender()

  const handleClick = () => {
    const id = render.render(<View>Hello React</View>)
    setTimeout(() => {
      render.update(id, <View>Updated!</View>)
      setTimeout(() => render.unmount(id), 2000)
    }, 2000)
  }

  return <Button onClick={handleClick}>Click Me</Button>
}

跨平台效果

同一份React代码在不同平台的运行效果:

微信小程序 支付宝小程序 H5
微信小程序 支付宝小程序 H5

下一篇预告

本篇介绍了基础的安装配置和使用方法。下一篇会深入讲解:

  • Vue in React:如何在React组件中无缝使用Vue组件(包括第三方UI库)
  • connectVueObserver:让React组件响应Vue的ref和reactive
  • 组件体系:Host组件、内置组件、Vue组件的区别和使用场景

项目地址:github.com/nap-liu/uni-app-react

欢迎Star和提Issue!