vaexy
v1.0.4
Published
Use React in Vue3 and Vue3 in React, And as perfect as possible!
Downloads
14
Maintainers
Readme
目录引导
- Vaexy
- Vue3应用可以使用React组件,React应用可以使用Vue3组件,并且非常完美!
- 什么是Vaexy?
- 重要功能
- 支持Vue2?
- 使用场景
- 安装
- 项目的预配置
- 用法
- 在React组件中使用Vue组件 - 基本用法
- 在Vue组件中使用React组件 - 基本用法
- 在React组件中使用Vue组件 - 事件的用法
- 在Vue组件中使用React组件 - 事件的用法
- 在React组件中使用Vue组件 - 插槽的用法
- 在Vue组件中使用React组件 - 传递render props 和 React node 的用法
- Context(上下文)
- 在React组件中使用 VueContainer 组件的用法
- Usage of getVNode
- 通过VNode获取ReactNode - getReactNode 的用法
- 在Vue组件中直接渲染ReactNode - RenderReactNode 的用法
- 在React组件中使用Vue组件, v-model / v-models 的用法
- Option useInjectPropsFromWrapper
- 跨框架的Provider
- ReactMissVue
- lazyReactInVue的用法(在Vue组件中使用异步的React组件)
- lazyVueInReact的用法(在React组件中使用异步的Vue组件)
- 获取ref实例的用法
- Vue 和 React共存时会引发JSX的TS类型错误.
- 开发指引
什么是Vaexy?
Vaexy 是基于React和Vue3的工具库,主要用于React和Vue在一个项目中公共使用的场景,主要运用在项目迁移、技术栈融合的开发模式、跨技术栈使用第三方组件的场景。
重要功能
- 🌞 支持 Vue3
- 🌈 支持 Context - 同一个应用中出现的vue组件和react组件的context是共享的.
- 💗 支持跨框架的hooks调用 - 可以在react组件中使用vue的hooks,获取到vue组件或者应用的上下文数据,比如vue-router、vuex,也可以在vue组件中使用react的hooks,获取到react组件或者应用的上下文数据,比如react-router、provide、context等
支持Vue2?
使用场景
- 👨👩👧 在一个应用中同时开发React和Vue
- 🏃 从Vue项目迁移到React项目,或者从React项目迁移到Vue项目
- 📲 在一个应用中可以随意使用React或者Vue的第三方组件, 比如
antd
,element-ui
,vuetify
安装
# Install with yarn:
$ yarn add vaexy
# or with npm:
$ npm i vaexy -S
项目的预配置
理论上,不需要在 React 项目中做额外的配置来支持 Vue,也不需要在 Vue 项目中做额外的配置来支持 React。
如果要转换的 React 或 Vue 组件来自 npm 包,或者已经经过构建(不是直接的vue文件以及不含有jsx),则可以直接使用 applyPureReactInVue
或 applyVueInReact
。
如果需要在一个项目中同时开发 Vue 和 React(一个项目源码中同时存在.vue文件和react jsx文件),而不是仅仅使用现有的 npm 组件,那么应该做一些配置。
Webpack
如何配置由'@vue/cli'创建的vue项目支持开发react
如何配置由'create-react-app'创建的react项目支持开发vue
Vite
如果项目是通过vite
构建的,那么需要对vite.config.js
做如下配置
首先安装 @vitejs/plugin-react
, @vitejs/plugin-vue
和 @vitejs/plugin-vue-jsx
。
- 主项目是vue:
import { defineConfig } from 'vite'
// >= [email protected]
import vaexyVitePlugins from 'vaexy/vite/index.js'
// 如果是vite 6, 应该使用 `vaexy/vite/esm`
// import vaexyVitePlugins from 'vaexy/vite/esm'
export default defineConfig({
plugins: [
// 关闭 vue 和 vuejsx 插件
// vue(),
// vueJsx(),
// type设为vue时, 所有名为react_app目录中的文件的jsx将被react jsx编译,其他文件里的jsx将以vue jsx编译
vaexyVitePlugins({
type: 'vue'
})
]
})
- The main project is React:
import { defineConfig } from 'vite'
// >= [email protected]
import vaexyVitePlugins from 'vaexy/vite/index.js'
export default defineConfig({
plugins: [
// 关闭 react 插件
// react(),
// type设为react时,所有.vue文件里的jsx将以vue jsx进行编译,其他文件的jsx都是以react jsx编译
vaexyVitePlugins({
type: 'react'
})
]
})
如果想自定义vue jsx编译的范围, 可以将type设置为custom
,然后通过设置vueJsxInclude
和 vueJsxExclude
来自定义编译范围
import { defineConfig } from 'vite'
// >= [email protected]
import vaexyVitePlugins from 'vaexy/vite/index.js'
export default defineConfig({
plugins: [
vaexyVitePlugins({
type: 'custom',
// 所有.vue文件中的jsx以及在名为vue_app目录里的jsx文件都将以vue jsx编译
vueJsxInclude: [/vue&type=script&lang\.[tj]sx$/i, /vue&type=script&setup=true&lang\.[tj]sx$/i, /[/\\]vue_app[\\/][\w\W]+\.[tj]sx$/],
// vueJsxExclude: []
})
]
})
用法
在React组件中使用Vue组件 - 基本用法
import {applyVueInReact, applyPureVueInReact} from 'vaexy'
// This is a Vue component
import BasicVueComponent from './Basic.vue'
import {useState} from 'react'
// Use HOC 'applyVueInReact'
const BasicWithNormal = applyVueInReact(BasicVueComponent)
// Use HOC 'applyPureVueInReact'
const BasicWithPure = applyPureVueInReact(BasicVueComponent)
export default function () {
const [foo] = useState('Hello!')
return <>
<BasicWithNormal foo={foo}>
<div>
the default slot
</div>
</BasicWithNormal>
<BasicWithPure foo={foo}>
<div>
the default slot
</div>
</BasicWithPure>
</>
}
在Vue组件中使用React组件 - 基本用法
现在推荐使用applyPureReactInVue
代替applyReactInvue
.
了解 applyPureReactInVue
和 applyReactInVue
的区别
<template>
<BasicPure :foo="foo">
<div>
children内容
</div>
</BasicPure>
</template>
<script>
import {applyReactInVue, applyPureReactInVue} from 'vaexy'
// 这是一个React组件
import BasicReactComponent from './react_app/Basic.jsx'
import {ref} from 'vue'
export default {
components: {
// 使用高阶组件 'applyReactInVue'
Basic: applyReactInVue(BasicReactComponent),
// 现在推荐使用纯净模式的 'applyPureReactInVue'
BasicPure: applyPureReactInVue(BasicReactComponent)
},
setup() {
return {
foo: ref('Hello!')
}
}
}
</script>
在React组件中使用Vue组件 - 事件的用法
import {applyVueInReact} from 'vaexy'
import BasicVue from './Basic.vue'
import {useState} from 'react'
const Basic = applyVueInReact(BasicVue)
export default function () {
function onClickForVue() {
console.log('clicked!')
}
return <div>
{/*在Vue组件Basic中可以使用$emit('click')触发这个事件绑定的函数*/}
<Basic onClick={onClickForVue}/>
</div>
}
在Vue组件中使用React组件 - 事件的用法
<template>
<!-- 在React组件ReactButton中可以使用props.onClick()触发这个事件绑定的函数 -->
<ReactButton @click="onClickForReact"/>
</template>
<script>
import {applyPureReactInVue} from 'vaexy'
// React组件ReactButton
import ReactButton from "./react_app/Button.jsx"
export default {
components: {
ReactButton: applyPureReactInVue(ReactButton)
},
setup() {
function onClickForReact() {
console.log('clicked!')
}
return {
onClickForReact,
}
}
}
</script>
在React组件中使用Vue组件 - 插槽的用法
这个插槽的用法与Vue的jsx传递插槽的用法非常相似
import {applyVueInReact} from 'vaexy'
import BasicVue from './Basic.vue'
const Basic = applyVueInReact(BasicVue)
export default function () {
const vSlots = {
// 在Vue组件的template内使用'<slot name="slot1" />'进行渲染
slot1: <div>this is slot1(namedSlot)</div>,
// 在Vue组件的template内使用'<slot name="slot2" value="xxxxxx"/>'进行渲染
slot2: ({value}) => <div>this is slot2(scopedSlot), and receive value: {value}</div>,
// 在Vue组件的template内使用'<slot/>'进行渲染
default: <div>this is children</div>
}
return <div>
{/*只传递children*/}
<Basic>
{/* 在Vue组件的template内使用'<slot/>'进行渲染 */}
<div>this is children</div>
</Basic>
{/*传递 v-slots*/}
<Basic v-slots={vSlots}/>
{/*另一种用法*/}
<Basic>
{vSlots}
</Basic>
</div>
}
在Vue组件中使用React组件 - 传递render props 和 React node 的用法
Vue3的具名插槽和作用域插槽 = React render props.
Vue3的默认插槽和children = React props.children.
一个带有node:
前缀的具名插槽 = React Node
<template>
<Basic>
<!-- 在React组件里使用'props.slot1()'进行渲染 -->
<template v-slot:slot1>
<div>
插槽1 (render props)
</div>
</template>
<!-- 在React组件里使用'props.slot2("xxxxx")'进行渲染 -->
<template v-slot:slot2="bar">
<div>
插槽2 (render props)<br/>
从React组件传递的内容: {{bar}}
</div>
</template>
<!-- 在React组件里使用'props.slot3'进行渲染 -->
<template v-slot:node:slot3>
<div>
插槽3 (react node)
</div>
</template>
<!-- 在React组件里使用'props.children'进行渲染 -->
<div>
默认插槽children (react node)
</div>
</Basic>
</template>
<script>
import {applyPureReactInVue} from 'vaexy'
// 这是一个React组件
import ReactBasic from "./react_app/Slots.jsx"
export default {
components: {
Basic: applyPureReactInVue(ReactBasic)
}
}
</script>
Context(上下文)
Vaexy 会判断如果一个组件的外层有同一个框架的组件存在,那么Vaexy 就会使用 React 的 Portal
或者 Vue 的 Teleport
创建被高阶组件包装的目标组件,而不是每次都创建一个新的应用实例。
这是非常牛逼的做法! Vaexy 可以很好地将根节点的上下文跨过不同的框架组件传递给内部与根节点相同框架的组件.
这意味着一个 Vue 组件使用了一个 React 组件,然后这个 React 组件使用了另一个 Vue 子组件。 这个 Vue 子组件可以获取外部 Vue 组件的上下文。
React组件使用Vue组件 - Provider / useContext 的用法
import {applyVueInReact} from 'vaexy'
import BasicVue from './Basic.vue'
import {createContext, useContext} from 'react'
const Basic = applyVueInReact(BasicVue)
// 创建 React context 对象
const Context = createContext({})
// React子组件
function SubReactComponent() {
// 获取 context 值
const {bossName} = useContext(Context)
return <div className="slot">bossName from Context: {bossName}</div>
}
export default function () {
// 设置 context 值
return <Context.Provider value={{bossName: 'God'}}>
{/* Vue组件Basic */}
<Basic>
{/* 在Vue组件的children里, React子组件可以获得从外层Provider传入的context的值 */}
<SubReactComponent/>
</Basic>
</Context.Provider>
}
Vue组件使用React组件 - Provide / Inject 的用法
<template>
<Basic>
<!-- 在这个Vue组件里可以通过inject获得从外层provide传入的值 -->
<SubVueComponent/>
</Basic>
</template>
<script>
import {provide, inject, h} from 'vue'
import {applyPureReactInVue} from 'vaexy'
// 这是一个React组件
import ReactBasic from "./react_app/Basic"
// 这是一个Vue组件
const SubVueComponent = {
setup() {
// get bossName from injection
const bossName = inject('bossName')
return h('div', () => bossName)
}
}
export default {
components: {
Basic: applyPureReactInVue(ReactBasic),
SubVueComponent
},
setup() {
// Use 'provide' to set the value of bossName
provide('bossName', 'God')
}
}
</script>
在React组件中使用 VueContainer 组件的用法
可以在React组件中直接使用 VueContainer
组件动态展示一个Vue组件
当这个React组件存在于某个Vue组件中时, 此时React组件中使用VueContainer
可以显示在上层 Vue 应用中注册的全局 Vue 组件。
import {VueContainer} from "vaexy"
import BasicVue from './Basic.vue'
export default function () {
const passedProps = {
name: 'Mike'
}
// 如果 'vue-router' 存在,则渲染 '<router-view>' 可以使用 '<VueContainer component="RouterView"/>'
return <VueContainer component={BasicVue} {...passedProps}/>
}
VueContainer
也可以渲染 VNode。
import {VueContainer} from "vaexy"
import {h} from 'vue'
const VNode = h('div', null, () => 'This is a VNode')
export default function ReactComponent() {
return <VueContainer node={VNode}/>
}
Usage of getVNode
VNode = getVNode
(ReactNode)
大多数情况下,vue 组件遵循 SFC 规范,但也可以通过其他方式创建 vue 组件,例如 h
或 jsx,可能通过属性获取 VNode。
在 react 中将 VNode 类型的属性传递给 vue 组件时,可以使用 getVNode
。
import { applyVueInReact, getVNode } from 'vaexy'
import AAVue from './AA.vue'
const AA = applyVueInReact(AAVue)
const VNodeBar = getVNode(
<div style={{background: '#105a31', marginTop: '5px', color: 'white'}}>
<div>rendered with a property</div>
<div>This is Bar's VNode</div>
</div>
)
export default function ReactComponent () {
// `VNodeBar` is a property of type VNode, so use getVNode to convert reactNode to VNode.
return <AA VNodeBar={VNodeBar}/>
}
通过VNode获取ReactNode - getReactNode 的用法
ReactNode = getReactNode
(VNode)
有时候react组件的属性是一个复杂的数据结构,里面包含了ReactNode,而在vue文件中,jsx的定义会被编译成vue的jsx(也就是VNode),如果直接将这个属性传递给react组件,react组件是不能识别VNode的。
<template>
<AA :prop1="propForReact"/>
</template>
<script setup>
import { getReactNode, applyPureReactInVue } from 'vaexy'
import AAReact from './react_app/AA.jsx'
const AA = applyPureReactInVue(AAReact)
const propForReact = {
foo: 'Mike',
bar: [{
body: getReactNode(<div>John</div>)
}],
render: (name) => getReactNode(<div>{name}</div>)
}
</script>
在Vue组件中直接渲染ReactNode - RenderReactNode 的用法
有时react组件的render props的输入参数是ReactNode,而vue组件中如果直接使用作用域插槽去展示这个ReactNode是错误的。RenderReactNode
是一个 vue 组件,它接受 node
参数,可以在 Vue 组件中渲染 ReactNode。
<template>
<AA>
<template v-slot:prop2="itemReactNode">
<RenderReactNode :node="itemReactNode"/>
</template>
</AA>
</template>
<script setup>
import { RenderReactNode, applyPureReactInVue } from 'vaexy'
import AAReact from './react_app/AA.jsx'
const AA = applyPureReactInVue(AAReact)
</script>
在React组件中使用Vue组件, v-model / v-models 的用法
'v-model' 的用法与Vue的jsx中的'v-model'用法相似
在React jsx中使用 v-model
属性, 可以有如下格式:
[ modelValue, modelSetter, argumentKey, argumentModifiers ]
[ modelValue, modelSetter, argumentModifiers ]
[ modelValue, modelSetter ]
'argumentKey'代表了v-model的自定义参数名, 默认情况下, v-model的参数名时modelValue, 也可以将'argumentKey'设置在v-model属性之后的附加后缀上, 比如 v-model-god={[godValue, setGodValue]}
= v-model={[godValue, setGodValue, 'god']}
// types
type modelValue = any
type modelSetter = (newValue) => void
type argumentKey = string
type argumentModifiers = string[]
import {applyVueInReact} from 'vaexy'
import BasicVue from './Basic.vue'
import Basic1Vue from './Basic1.vue'
import {useState} from 'react'
const Basic = applyVueInReact(BasicVue)
const Basic1 = applyVueInReact(Basic1Vue)
export default function () {
const [foo, setFoo] = useState(Math.random())
const [bar, setBar] = useState(Math.random())
const [zoo, setZoo] = useState(Math.random())
return <div>
<Basic v-model={[foo, setFoo]} v-model-bar={[bar, setBar]} />
{/*<Basic1 v-model={[zoo, setZoo, 'zoo']}/>*/}
{/*<Basic1 v-model={[zoo, setZoo, 'zoo', ['number']]}/>*/}
{/*<Basic1 v-model-zoo={[zoo, setZoo, ['number']]}/>*/}
<Basic1 v-models={{
// v-models对象中的key设置为'modelValue'时, 等同于默认的v-model属性
modelValue: [zoo, setZoo],
//...可以设置其他的自定义v-model的key
}} />
</div>
}
Option useInjectPropsFromWrapper
useInjectPropsFromWrapper
是 applyReactInVue
和 applyVueInReact
的一个选项。
在同时开发 Vue 和 React 应用时,有时需要在 Vue 组件内部获取 React 应用的上下文,反之亦然。
例如,在Vue组件中使用react-router
,或者在React组件中使用vuex
。
在 Vue 组件中注入 React hooks的用法
React 应用使用 Vue 组件,以下例子是在这个Vue组件中使用react-router
。
<template>
<div class="vue-component">
<h3>This is the Vue Component.</h3>
the path info from 'react-router': <span style="font-weight: bold">{{fullPath}}</span><br/><br/>
<button @click="changeQuery">change query</button>
</div>
</template>
<script>
import { computed } from 'vue'
export default {
props: ['reactRouter'],
// 不要对props进行解构
setup(props) {
function changeQuery() {
props.reactRouter?.navigate(`?a=${Math.random()}`, {replace: true})
}
const fullPath = computed(() => {
const { location } = props.reactRouter || {}
return location?.pathname + location?.search
})
return {
fullPath,
changeQuery
}
}
}
</script>
使用applyVueInReact
将上面的Vue组件包装成一个React组件,并传入react-router
。
import { applyVueInReact } from 'vaexy'
import { useLocation, useNavigate } from 'react-router-dom'
import AboveVueComponent from './AboveVueComponent'
export default applyVueInReact(AboveVueComponent, {
useInjectPropsFromWrapper(reactProps) {
// 在这个函数中可以使用 React hooks
// 使用 react-router-dom's hooks
const location = useLocation()
const navigate = useNavigate()
// 返回的对象会作为 props 传递给 Vue 组件
return {
reactRouter: {
navigate,
location
}
}
}
})
在 React 组件中注入 Vue hooks 的用法
Vue 应用使用 React 组件,示例在 React 组件中获取 vue-router
和 vuex
。
注入函数有两种模式,“设置”和“计算”模式
import React from 'react'
import {toRef} from 'vue'
import {useStore} from 'vuex'
import {useRoute, useRouter} from 'vue-router'
import {applyPureReactInVue} from 'vaexy'
// 这个 React 组件将在 Vue 应用程序中使用,需要使用 vue-router 和 vuex 钩子
// setup函数模式
function VueInjectionHookInSetupMode(vueProps) {
// 可以在这个函数中使用 Vue hooks
// 这个函数将在 Vue 包装器组件的 'setup' 函数中调用
const store = useStore()
const route = useRoute()
const router = useRouter()
// 返回的对象将作为 props 传递给 React 组件
return {
fullPath: toRef(route, 'fullPath'),
count: toRef(store.state, 'count'),
changeQuery: () => router.replace({
query: {
a: Math.random()
}
}),
incrementCount: () => store.dispatch('increment')
}
}
// computed函数模式
function VueInjectionHookInComputedMode(vueProps) {
// 该函数的上下文与来自getCurrentInstance().proxy
// 返回一个函数表示使用compute模式
// 所有逻辑代码都应该写在这个计算函数中。
return function computedFunction() {
return {
fullPath: this.$route.fullPath,
count: this.$store.state.count,
changeQuery: () => this.$router.replace({
query: {
a: Math.random()
}
}),
incrementCount: () => this.$store.dispatch('increment')
}
}
}
function ReactComponent (props) {
return (<div>
This is the React Component
<span>
the path info from 'vue-router': <span style={{fontWeight: 'bold'}}>{props.fullPath}</span><br/>
the count from 'vuex': <span style={{fontWeight: 'bold'}}>{props.count}</span>
</span><br/>
<button onClick={props.changeQuery}>change query</button> <button onClick={props.incrementCount}>increment count</button>
</div>)
}
// Vue 的注入函数有两种模式:'setup' 和 'computed'
// 请参考以上两种注入函数类型的案例
// 可以尝试用 'VueInjectionHookInComputedMode'
export default applyPureReactInVue(ReactComponent, {
useInjectPropsFromWrapper: VueInjectionHookInSetupMode
})
跨框架的Provider
虽然可以通过useInjectPropsFromWrapper
使用其他框架的hook,然后通过属性在组件中获取hook的状态,但大多数情况下都是为了获取上下文类型数据,比如vue-router,react-router,redux,vuex,或者其他框架的自定义上下文。
使用 createCrossingProviderForPureReactInVue
和 createCrossingProviderForVueInReact
创建跨框架的Provider,在这个Provider内,另一个框架的组件可以获取到这个框架的上下文状态。
createCrossingProviderForVueInReact 的用法
通过createCrossingProviderForVueInReact
创建一个Vue的hook和一个React的Provider,React Provider会将自定义的上下文传递给所有vue的子组件,例子中创建(reactRouterCrossingProvider.js)来定义一个crossing provider,provider包含了react-router。
// Create a Provider that can get react hooks
// This Provider will be exported as a react component,
// and all of the vue components in this Provider can get the status of react hooks
import { useLocation, useNavigate } from 'react-router-dom'
import { createCrossingProviderForVueInReact } from 'vaexy'
// Execute 'useReactRouterForVue' in the setup function of the vue component to get the object returned by the incoming function
const [useReactRouterForVue, ReactRouterProviderForVue] = createCrossingProviderForVueInReact(
// This incoming function can execute react hooks
function() {
return {
location: useLocation(),
navigate: useNavigate()
}
}
)
export {
useReactRouterForVue,
ReactRouterProviderForVue
}
然后vue组件(Basic.vue)可以通过上面的js创建的vue hook获取到react-router的上下文。
<template>
<div class="vue-component">
<h3>This is the Vue Component.</h3>
the path info from 'react-router': <span style="font-weight: bold">{{pathname + search}}</span><br/><br/>
<button @click="changeQuery">change query</button>
</div>
</template>
<script>
import { useReactRouterForVue } from './reactRouterCrossingProvider'
import React from 'react'
export default {
setup() {
const { location, navigate } = useReactRouterForVue()
function changeQuery() {
navigate(`?a=${Math.random()}`, {replace: true})
}
return {
pathname: location.pathname,
search: location.search,
changeQuery
}
}
}
</script>
在react项目或者组件中,将之前创建的provider包囊在外层即可
import {applyVueInReact} from 'vaexy'
// Basic is a Vue component
import BasicVue from './Basic.vue'
import { ReactRouterProviderForVue } from './reactRouterCrossingProvider'
const Basic = applyVueInReact(BasicVue)
export default function () {
return <ReactRouterProviderForVue>
<Basic/>
</ReactRouterProviderForVue>
}
createCrossingProviderForReactInVue的用法
It is now recommended to use createCrossingProviderForPureReactInVue
instead of createCrossingProviderForReactInVue
.
Create a provider including vue-router and vuex and a React hooks that can be executed in the React function component and get the vue-router and vuex. (vueRouterAndVuexCrossingProvider.js)
import {useStore} from 'vuex'
import {useRouter, useRoute} from 'vue-router'
import {createCrossingProviderForPureReactInVue} from 'vaexy'
const [useVueHooksInReact, VueProviderForReact] = createCrossingProviderForPureReactInVue(function() {
return {
vuex: useStore(),
vueRoute: useRoute(),
vueRouter: useRouter()
}
})
export {
useVueHooksInReact,
VueProviderForReact
}
The React component(Basic.js) can get the context from the provider through the custom hook.
import React from 'react'
import { useVueHooksInReact } from '../vueRouterAndVuexCrossingProvider'
export default function (props) {
const { vuex, vueRoute, vueRouter } = useVueHooksInReact()
function changeQuery() {
vueRouter.replace({
query: {
a: Math.random()
}
})
}
function incrementCount() {
vuex.dispatch('increment')
}
return (<div>
This is the React Component<br/>
<span>
the path info from 'vue-router': <span style={{fontWeight: 'bold'}}>{vueRoute.fullPath}</span><br/>
the count from 'vuex': <span style={{fontWeight: 'bold'}}>{vuex.state.count}</span>
</span><br/>
<button onClick={changeQuery}>change query</button> <button onClick={incrementCount}>increment count</button>
</div>)
}
Vue components use the provider, so that all React components (including internal components) in the provider can get the context of this provider through custom hooks.
<template>
<VueProviderForReact>
<Basic/>
</VueProviderForReact>
</template>
<script>
import { applyPureReactInVue } from 'vaexy'
// This is a React Component
import ReactBasic from "./react_app/Basic"
import {VueProviderForReact} from "./vueRouterAndVuexCrossingProvider";
export default {
components: {
VueProviderForReact,
Basic: applyPureReactInVue(ReactBasic),
}
}
</script>
ReactMissVue
Sometimes some features and plugins of Vue are really more useful than React, such as beforeEach
of vue-router
, and pinia
.
So I implemented a factory function called createReactMissVue
that returns a React provider component and a React hook.
With ReactMissVue, you can use Vue's plugins directly in React applications.
Enjoy it!
Usage of createReactMissVue
For detailed use cases, please refer to dev-project-react/src/components/reactMissVue
import { defineStore, createPinia } from 'pinia'
import { createRouter, createWebHashHistory, useRouter, useRoute } from 'vue-router'
import { createReactMissVue, applyReactInVue, VueContainer } from 'vaexy'
// create vue-router instance
const router = createRouter({
// Using vue-router inside route 'ReactMissVue'
history: createWebHashHistory('/#/ReactMissVue'),
routes: [
{
name: '',
path: '/aaa',
component: applyReactInVue(() => <div className="react-component">
react use vue-router<br/>
path: /aaa
</div>)
},
{
name: 'empty',
path: '/:default(.*)',
component: applyReactInVue(() => <div className="react-component">
react use vue-router<br/>
empty
</div>)
},
],
})
// create a pinia store
const useFooStore = defineStore({
id: 'foo',
state() {
return {
name: 'Eduardo'
}
},
actions: {
changeName(name) {
this.$patch({
name
})
}
}
})
// create a ReactMissVue instance
let [useReactMissVue, ReactMissVue, ReactMissVueContext] = createReactMissVue({
useVueInjection() {
// This object can be obtained by using useReactMissVue in the react component
return {
fooStore: useFooStore(),
vueRouter: useRouter(),
vueRoute: useRoute()
}
},
// beforeVueAppMount can only be used in the outermost ReactMissVue
// Because vaexy will only create a vue application in the outermost layer
beforeVueAppMount(app) {
// register pinia
app.use(createPinia())
// register vue-router
app.use(router)
}
})
function Demo() {
const { fooStore } = useReactMissVue()
return <div>
<div>
Foo's name: {fooStore?.name}
</div>
{/* Use the global component router-view */}
<VueContainer component="RouterView"/>
</div>
}
export default function () {
return <ReactMissVue>
<Demo/>
</ReactMissVue>
}
lazyReactInVue的用法(在Vue组件中使用异步的React组件)
It is now recommended to use lazyPureReactInVue
instead of lazyReactInVue
.
<template>
<Basic/>
</template>
<script>
import { lazyPureReactInVue } from 'vaexy'
export default {
components: {
// import an async React component
// It is also possible to use the full parameter of the Vue3 API 'defineAsyncComponent'
// for example: lazyPureReactInVue({ loader: () => import('./react_app/Basic'), timeout: 3000 })
Basic: lazyPureReactInVue(() => import('./react_app/Basic'))
},
}
</script>
// types
type lazyPureReactInVue = (asyncImport: Promise<any> | defineAsyncComponentOptions, options?: options) => any;
lazyVueInReact的用法(在React组件中使用异步的Vue组件)
import { lazyVueInReact, lazyPureVueInReact } from 'vaexy'
const AsyncBasicWithNormal = lazyVueInReact(() => import('./Basic'))
const AsyncBasicWithPure = lazyPureVueInReact(() => import('./Basic'))
export default function () {
return <>
<AsyncBasicWithNormal/>
<AsyncBasicWithPure/>
</>
}
// types
type lazyReactInVue = (asyncImport: Promise<any>, options?: options) => any
获取ref实例的用法
Get the React component's instance in the Vue Component.
<template>
<Basic ref="Basic">
<div ref="div">hello</div>
<AA ref="AA"/>
</Basic>
</template>
<script>
import { applyPureReactInVue } from 'vaexy'
import BasicReact from './Basic.jsx'
import AAReact from './AA.jsx'
export default {
components: {
Basic: applyPureReactInVue(BasicReact),
AA: applyPureReactInVue(AAReact)
},
mounted() {
// Get the real react instance through `__vaexyReactRef__`
console.log(this.$refs.Basic.__vaexyReactRef__)
// If the converted react component or dom element is placed as a child node in a pure mode react component,
// the ref can be obtained directly
console.log(this.$refs.div)
console.log(this.$refs.AA)
}
}
</script>
Get the Vue component's instance in the React Component.
import {applyVueInReact} from 'vaexy'
import BasicVue from './Basic.vue'
import React, { createRef, useEffect } from "react"
const Basic = applyVueInReact(BasicVue)
export default function () {
const basicInstance = createRef(null)
useEffect(() => {
// Get the real vue instance through `__vaexyVueRef__`
console.log(basicInstance.current.__vaexyVueRef__)
}, [])
return <Basic ref={basicInstance}/>
}
Vue 和 React共存时会引发JSX的TS类型错误
如果您可以忽略 IDE 中的 TS 错误警告,则可以跳过本章。
Vue(@vue/runtime-dom) 和 React(@types/react) 都在全局命名空间 JSX 中扩展了类型接口,这会导致类型冲突。
例如,JSX.Element 不能同时扩展 ReactElement 和 VNode。
所以如果项目中同时安装了Vue和React,会在IDE(如vscode或webstorm)的JSX中引起TS错误警告,但这不会影响开发环境和生产环境的编译。
一个可行的解决方案是使用 patch-package
来修改 @vue/runtime-dom/dist/runtime-dom.d.ts
和 @types/react/index.d.ts
,并且确保在tsconfig.json中设置compilerOptions.jsx
为preserve
。
比如这两个文件的改动如下。
node_modules/@types/react/index.d.ts(@types/[email protected])
diff --git a/node_modules/@types/react/index.d.ts b/node_modules/@types/react/index.d.ts
index 5c5d343..a850f38 100644
--- a/node_modules/@types/react/index.d.ts
+++ b/node_modules/@types/react/index.d.ts
@@ -3118,7 +3118,9 @@ type ReactManagedAttributes<C, P> = C extends { propTypes: infer T; defaultProps
declare global {
namespace JSX {
- interface Element extends React.ReactElement<any, any> { }
+ interface Element extends React.ReactElement<any, any> {
+ [k: string]: any
+ }
interface ElementClass extends React.Component<any> {
render(): React.ReactNode;
}
@@ -3133,8 +3135,12 @@ declare global {
: ReactManagedAttributes<T, P>
: ReactManagedAttributes<C, P>;
- interface IntrinsicAttributes extends React.Attributes { }
- interface IntrinsicClassAttributes<T> extends React.ClassAttributes<T> { }
+ interface IntrinsicAttributes extends React.Attributes {
+ [k: string]: any
+ }
+ interface IntrinsicClassAttributes<T> extends React.ClassAttributes<T> {
+ [k: string]: any
+ }
interface IntrinsicElements {
// HTML
node_modules/@vue/runtime-dom/dist/runtime-dom.d.ts(@vue/[email protected])
diff --git a/node_modules/@vue/runtime-dom/dist/runtime-dom.d.ts b/node_modules/@vue/runtime-dom/dist/runtime-dom.d.ts
index 3366f5a..b9eacc6 100644
--- a/node_modules/@vue/runtime-dom/dist/runtime-dom.d.ts
+++ b/node_modules/@vue/runtime-dom/dist/runtime-dom.d.ts
@@ -1493,7 +1493,7 @@ type NativeElements = {
declare global {
namespace JSX {
- interface Element extends VNode {}
+ // interface Element extends VNode {}
interface ElementClass {
$props: {}
}
开发指引
本项目中的dev-project-react
和dev-project-vue3
目录是vaexy
开发环境的基础项目,分别由create-react-app
和@vue/cli
创建的两个初始项目。
Note: 在react项目中的
config/webpack.config.js
以及vue项目中的vue.config.js
里,可以找到webpack的alias别名配置,将vaexy
的别名注释解开,就可以对根项目中src
目录里的vaexy
源代码进行开发调试了Setup: 在主项目的根目录下运行命令行
npm run setup:yarn
或者npm run setup:npm
,可以整体安装主项目和两个调试用的子项目Develop: 在主项目的根目录下运行命令行
npm run dev:vue
以及npm run setup:npm
就可以对子项目进行开发调试