npm package: @smart-portal/react-bridge
Description: Use Vue and React each other
Author: Layson
Latest version: 1.0.1

# SmartPortal react-bridge

门户vue-react bridge能力

---

## 重要功能

- 在react工程中可以直接使用 Vue3 组件
- 同项目中的vue以及react组件的context是共享的，可以实现数据无框架感阻碍通信
- 在react中可以使用vue的hooks，获取vue组件的上下文数据，比如vue-router、vuex，同时在vue组件也可以使用react的hooks，获取到react组件或者应用的上下文数据，比如react-router、provide、context等
- 支持高阶纯净模式使用，转换后的组件的子组件不再会有一个附加的dom元素容器

## 安装

```sh
$ npm i @smart-portal/react-bridge -S

# 一般搭配SmartPortal其他模块一起使用
$ npm i @smart-portal/components @smart-portal/hooks @smart-portal/utils @smart-portal/react-bridge -S
```

## 用法

### 在React组件中使用Vue组件 - 基本用法

```jsx
// SmartPortal组件
import { SmartPortalComponents } from "@smart-portal/utils/register/bridge";

export default function () {
  const notifyOptions = {
    schema: "General",
    type: "Notify",
    num: 1,
    props: {
      content:
        "关于进一步加强能力中台储备区举办科技成果日（第33、34、35期）的通知",
    },
  };

  // 组件所有事件回调
  const emitsEvent = ({ eventName, data }) => {
    console.log("事件回调：", eventName, data);
  };

  return (
    <>
      <SmartPortalComponents
        options={notifyOptions}
        onEmitsEvent={emitsEvent}
      />
    </>
  );
}
```

### 在React组件中使用Vue组件 - 使用业务项目系统手写的Vue组件

```jsx
// SmartPortal组件
import { ApplyVueInReact } from "@smart-portal/utils/register/bridge";
// 业务项目vue组件
import BasicVueComponent from "./Basic.vue";
const BasicWithNormal = ApplyVueInReact(BasicVueComponent);

export default function () {
  return (
    <>
      <Basic />
    </>
  );
}
```

### 在React组件中使用Vue组件 - 事件的用法

```jsx
// SmartPortal组件
import { ApplyVueInReact } from "@smart-portal/utils/register/bridge";
// 业务项目vue组件
import BasicVueComponent from "./Basic.vue";
import { useState } from "react";

const Basic = ApplyVueInReact(BasicVueComponent);
export default function () {
  function handleClickEvent() {
    console.log("clicked!")
  }

  function handleEmitsEvent() {
    console.log("事件回调")
  }

  return (
    <div>
      {/* 在Vue组件Basic中可以使用$emit('click')和$emit('emitsEvent')触发事件绑定的函数 */}
      <Basic onClick={handleClickEvent} onEmitsEvent={handleEmitsEvent} />
    </div>
  );
}
```

### 在React组件中使用Vue组件 - 插槽的用法

这个插槽的用法与Vue的jsx传递插槽的用法非常相似

```jsx
// SmartPortal组件
import { ApplyVueInReact } from "@smart-portal/utils/register/bridge";
// 业务项目vue组件
import BasicVueComponent from "./Basic.vue";

const Basic = ApplyVueInReact(BasicVueComponent);
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>
  );
}
```

### Context(上下文)

@smart-portal/react-bridge 会判断如果一个组件的外层有同一个框架的组件存在，那么就会使用 React 的 `Portal` 或者 Vue 的 `Teleport`创建被高阶组件包装的目标组件，而不是每次都创建一个新的应用实例。

#### React组件使用Vue组件 - Provider / useContext 的用法

```jsx
// SmartPortal组件
import { ApplyVueInReact } from "@smart-portal/utils/register/bridge";
// 业务项目vue组件
import BasicVueComponent from "./Basic.vue";
import { createContext, useContext } from "react";

const Basic = ApplyVueInReact(BasicVueComponent);
// 创建 React context 对象
const Context = createContext({});

// React子组件
function SubReactComponent() {
  // 获取 context 值
  const { name } = useContext(Context);
  return <div className="slot">bossName from Context: {name}</div>;
}

export default function () {
  // 设置 context 值
  return (
    <Context.Provider value={{ name: "God" }}>
      {/* Vue组件Basic */}
      <Basic>
        {/* 在Vue组件的children里, React子组件可以获得从外层Provider传入的context的值 */}
        <SubReactComponent />
      </Basic>
    </Context.Provider>
  );
}
```

### 在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']}`

```typescript
// types
type modelValue = any;
type modelSetter = (newValue) => void;
type argumentKey = string;
type argumentModifiers = string[];
```

```jsx
// SmartPortal组件
import { ApplyVueInReact } from "@smart-portal/utils/register/bridge";
// 业务项目vue组件
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>
  );
}
```

### 在React组件中使用Vue组件, 使用异步的Vue组件

```jsx
import { lazyVueInReact, lazyPureVueInReact } from "@smart-portal/react-bridge";

const AsyncBasicWithNormal = lazyVueInReact(() => import("./Basic"));
const AsyncBasicWithPure = lazyPureVueInReact(() => import("./Basic"));
export default function () {
  return (
    <>
      <AsyncBasicWithNormal />
      <AsyncBasicWithPure />
    </>
  );
}
```

```typescript
// types
type lazyReactInVue = (asyncImport: Promise<any>, options?: options) => any;
```

### 在React组件中使用Vue组件, 获取ref实例的用法

```jsx
// SmartPortal组件
import { ApplyVueInReact } from "@smart-portal/utils/register/bridge";
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 `__veauryVueRef__`
    console.log(basicInstance.current.__veauryVueRef__);
  }, []);
  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/react@18.0.14)

```diff
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/runtime-dom@3.2.37)

```diff
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: {}
     }

```