在react里渲染vue组件

5,428 阅读2分钟

原文链接: github.com/yinxin630/b…
技术交流: fiora.suisuijiang.com/

前言

是不是看标题就感觉很蛋疼?

最近项目在基于 react 重构, 但是依赖的一个公共业务组件是 vue 的, 并且暂时没有使用 react 重构的计划, 但是总不能一直等下去, 遂尝试在 react 里渲染 vue 组件

vuera

需要借助 vuera(github.com/akxcv/vuera) 来做这个事情

同时, webpack里需要同时配置 react 和 vue 的 loader

首先, 写一个典型的 vue 组件

// Module.vue
<template>
    <div>
        <span>click times: {{count}}</span>
        <br />
        <button @click="handleClick">+1s</button>
    </div>
</template>
<script>
export default {
    name: "Module",
    data() {
        return {
            count: 0
        }
    },
    methods: {
        handleClick() {
            this.count++;
        }
    }
}
</script>

然后, 在 react 里面渲染 vue 组件

// index.tsx
import React from 'react';
import { render } from 'react-dom';
import { VueWrapper } from 'vuera';
import Module from './Module.vue';
render(
    <div>
        <h2>下面是vue组件</h2>
        <VueWrapper component={Module} />
    </div>,
    document.getElementById('app'),
);

渲染效果:

image

向 vue 组件传参

react 里就像正常 react 组件一样传参即可

// index.tsx
<VueWrapper component={Module} msg="message pass by props" />

vue 里也是和正常 vue 组件一样, 定义 props

// Module.vue
<template>
    <div>
        <h3>{{msg}}</h3>
        ...
    </div>
</template>
<script>
export default {
    ...
    props: {
        msg: {
            type: String,
        }
    },
    ...
}
</script>

接收 vue 组件抛出的事件

在 vue 里面可以调用 this.$emit('xxx') 抛出事件

但是, react 组件里, 不能直接用 @xxx 或者 v-on:xxx 绑定事件, 但是可以通过 props 传递 function, 在 vue 组件里调用 props function

vue 组件对外抛出事件

// Module.vue
<script>
export default {
    ...
    props: {
        ...
        onCountChange: {
            type: Function
        }
    },
    methods: {
        handleClick() {
            this.count++;
            this.onCountChange(this.count);
        }
    }
}
</script>

如果 vue 组件不受自己控制, 不能修改源码的话, 也可以自己封装一层, 将 props function 转为 @xxx

使用 this.$emit() 的 vue 组件

// Module.vue
<script>
export default {
    ...
    methods: {
        handleClick() {
            this.count++;
            this.$emit('count-change', this.count);
        }
    }
}
</script>

创建中间适配层组件 MoudleWrapper

// ModuleWrapper.vue
<template>
    <div>
        <Module :msg="msg" @count-change="onCountChange"></Module>
    </div>
</template>
<script>
import Module from './Module.vue';
export default {
    name: "ModuleWrapper",
    components: { Module },
    props: {
        msg: {
            type: String,
        },
        onCountChange: {
            type: Function
        }
    },
}
</script>

修改入口文件, 改为渲染 MoudleWrapper 组件

// index.tsx
import React from 'react';
import { render } from 'react-dom';
import { VueWrapper } from 'vuera';
import MoudleWrapper from './MoudleWrapper.vue';
render(
    <div>
        <h2>下面是vue组件</h2>
        <VueWrapper component={MoudleWrapper} msg="message pass by props" onCountChange={console.log} />
    </div>,
    document.getElementById('app'),
);

打包

使用 vue 组件, 会把完整的 vue.js 打包进去

如果该 vue 组件并不是初始状态就需要展示的话, 可以做按需加载打成一个单独的 chunk, 使其不影响页面初始渲染时间

下面是一个按需加载的例子, 新增 App 组件来做动态展示和按需加载的逻辑

// App.tsx
import React, { useState, useEffect } from 'react';
import { VueWrapper } from 'vuera';
let MoudleWrapper: any = null;
export default function App() {
    const [showVueModule, toggleVueModule] = useState(false);
    function showHideVueModule() {
        if (MoudleWrapper) {
            toggleVueModule(!showVueModule);
        } else {
            import(/* webpackChunkName: "vue-module" */ './MoudleWrapper.vue').then(module => {
                MoudleWrapper = module.default;
                toggleVueModule(!showVueModule);
            });
        }
    }
    return (
        <div>
            <h2>下面是vue组件</h2>
            {
                showVueModule && MoudleWrapper
                    ? <div><VueWrapper component={MoudleWrapper} msg="message pass by props" onCountChange={console.log} /></div>
                    : null
            }
            <button onClick={showHideVueModule}>展示/隐藏vue组件</button>
        </div>
    );
}

运行效果如下:

image