牛头人竟是我自己?如何实现快速React 组件 in Vue
前言
本文主要介绍如何实现快速实现在 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.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文件
参考:
具体实现步骤
- 在Vue项目里,我们需要安装好React、ReactDom与我们需要的React组件
- 对于基于TS的项目,我们需要参考上面的解决JSX冲突方案处理
- 创建一个胶水层组件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可能会涉及到一些其他问题 ,由于精力有限,本次就不作验证了,希望大家一起探索呀 本文可能有有些遗漏点与错误点,希望大家积极指出,也欢迎大家一起探讨如何更好实现远程组件呀
转载自:https://juejin.cn/post/7252921345314799653