likes
comments
collection
share

牛头人竟是我自己?如何实现快速React 组件 in Vue

作者站长头像
站长
· 阅读数 10

前言

本文主要介绍如何实现快速实现在 Vue 项目中使用 React 组件 新人求大佬们轻喷🥲,欢迎提供改进意见等🫶!

为什么要React 组件 in Vue?

可能很多同学看到标题就会疑惑,什么场景才会出现将一个React组件放到Vue项目里使用。这难道不是技术选型没做好吗? 其实在公司实际开发过程中,你会需要维护许多老项目,而这些老项目是Vue开发的,但是在公司新项目都是基于React,并且最重要的是基建部分是基于React的。 你就面临一个问题:例如公司基建有一个非常不错的基于React的Form表单生成组件,使用非常方便且熟悉,而在Vue老项目里依旧还是只能非常繁琐的编写Form表单,并且如果引入Vue能用的类似库会导致一定的学习成本、维护成本,这样子自然而然你就会想到React in Vue

如何实现React in Vue?

这个问题看似复杂,其实也很简单。 因为其实我们完全不用考虑React 与 Vue的大部分差异性,例如生命周期限制等等。 我们只需要将一个组件高度封装,只需要提供传递参数入口、获取实例的ref绑定即可,然后将一切渲染该React组件的事情都丢给reactDom.render()处理即可,这样子就能避免Vue无法处理React的特性。 但是正如我说的,只是不需要考虑大部分差异性,但是有一个差异性仍然会影响实现:React 与 Vue 对于JSX namespace 定义冲突

JSX namespace 定义冲突解决方案

首先我们需要知道React与Vue的对于JSX namespace 的定义在何处。

  • 对于React,定义存放于@types/react
  • 对于Vue,定义存放于@vue/runtime-dom 当同时存在这俩类型文件的时候,JSX的定义获取会优先获取@types/react的,从而当类型检测Vue项目自己的jsx文件时就会使用React的JSX定义,从而导致报错 如何解决?

方案1

手动配置 tsconfig.jsontypes 选项, 从而避免TS 自动将所有类型从node_modules/@types 导入,进而避免@types/react的引入。但是这会导致维护成本过高,以后每引入一个库,都需要手动配置type定义来源

// tsconfig.json
{
  "compilerOptions": {
    // ...
    "types": [
      "vite/client", // 当你使用vite
      // ...
    ]
  }
}

方案2

既然@types/react是冲突来源,何必不直接将它“干掉”。 那么如何干掉?首先基于TS的项目,我们必须需要@types/react,否则会报无法找到React的类型定义,所以我们无法直接不安装@types/react,但是我们不希望类型这个库里的JSX定义存在,所以我们可以直接修改将这个库的index.d.ts 文件,毕竟我们基本无需在乎React的类型定义如何。

export = React
export as namespace React

declare namespace React {
  function createElement(...props: any[]): any
  ... // 你需要用到的React函数,这里只会用到createElement
}

注意你最好直接新建一个文件夹存放修改过的@types/react库 并将Package.json里的引用路径指向该文件,避免每次npm i 都需要重新修改dts文件

参考: 牛头人竟是我自己?如何实现快速React 组件 in Vue

具体实现步骤

  1. 在Vue项目里,我们需要安装好React、ReactDom与我们需要的React组件
  2. 对于基于TS的项目,我们需要参考上面的解决JSX冲突方案处理
  3. 创建一个胶水层组件ReactInVue,提供一个div当做React组件的容器,当div挂载成功之后,利用react.createElement进行把我们的React组件创建为 React 元素, 再利用reactdom.render将创建的React元素渲染到实际的容器dom里 基于React 17 的基本实现Demo如下
// ReactInVue胶水层组件
<template>
  <div ref="container"></div>
</template>

<script lang="ts">
import { defineComponent, onMounted, PropType, reactive, ref, watch } from 'vue'
import reactDom from 'react-dom'
import react from 'react'
import { ReactComponent } from 'xxx'

export default defineComponent({
  props: {
    dependence: Object as () => Record<string, any>,
  },
  setup(props) {
    const container = ref()
    onMounted(() => {
      reactDom.render(
        react.createElement(ReactComponent, {
          dependence: props?.dependence || {},
        }),
        container.value as Element
      )
    })
    return {
      container,
    }
  },
})
</script>

基于React 18 的基本实现Demo如下

// ReactInVue胶水层组件
<template>
  <div ref="container"></div>
</template>

<script lang="ts">
import { defineComponent, onMounted, PropType, reactive, ref, watch } from 'vue'
import reactDom from 'react-dom'
import react from 'react'
import { ReactComponent } from 'xxx'

export default defineComponent({
  props: {
    dependence: Object as () => Record<string, any>,
  },
  setup(props) {
    const container = ref()
    onMounted(() => {
      const root = reactDom.createRoot(container.value)
      root.render(React.createElement(ReactComponent, {
          dependence: props?.dependence || {},
        });
    })
    return {
      container,
    }
  },
})
</script>

总结

  • 核心实现原理其实就是例如react.createElement函数与ReactDom.render函数
  • 主要需要避免JSX的命名空间冲突

欢迎指点

本文的vue版本是vue3,使用vue-cli创建,至于wp5或者vite可能会涉及到一些其他问题 ,由于精力有限,本次就不作验证了,希望大家一起探索呀 本文可能有有些遗漏点与错误点,希望大家积极指出,也欢迎大家一起探讨如何更好实现远程组件呀