• 如何在Vue中使用React组件?
  • 发布于 2个月前
  • 589 热度
    0 评论
  • 梦清幽
  • 0 粉丝 33 篇博客
  •   
为什么要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 的定义在何处。
1.对于React,定义存放于@types/react
2.对于Vue,定义存放于@vue/runtime-dom
当同时存在这俩类型文件的时候,JSX的定义获取会优先获取@types/react的,从而当类型检测Vue项目自己的jsx文件时就会使用React的JSX定义,从而导致报错。
如何解决?

方案1
手动配置 tsconfig.json 的 types 选项, 从而避免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文件。

具体实现步骤
1.在Vue项目里,我们需要安装好React、ReactDom与我们需要的React组件
2.对于基于TS的项目,我们需要参考上面的解决JSX冲突方案处理
3.创建一个胶水层组件ReactInVue,提供一个div当做React组件的容器,当div挂载成功之后,利用react.createElement进行把我们的React组件创建为 React 元素, 再利用reactdom.render将创建的React元素渲染到实际的容器dom里
基于React 17 的基本实现Demo如下
// 堆代码 duidaima.com
// 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>
总结
1.核心实现原理其实就是例如react.createElement函数与ReactDom.render函数
2.主要需要避免JSX的命名空间冲突

用户评论