尝试在 react 项目中使用 vue2 组件且实现数据交互

项目背景

在使用 amis-editor 开发低代码平台时,因业务需要把在老项目中使用的组件移植到 amis-editor中,以减少开发时长和bug风险。

因为amis-editor是使用react开发的,所以需要想办法在react中使用vue组件。本文以使用 create-react-app 初始化的项目为例展示如何在react中使用vue组件。以 element-ui为例,展示如何引入第三方 vue组件。

整体思路

  1. 对 Vue 组件使用基础 Vue 构造器,创建一个“子类”
  2. 新建一个 React 组件,在组件中 new 一个“子类”,接收 Vue 组件的 props 及监听 Vue 组件的事件
  3. 当 React 组件 mounted 后挂载 Vue 组件,当 React 组件销毁时调用 Vue 组件的 $destroy()方法销毁 Vue 组件

初始化 React 项目

  1. 创建react项目: npx create-react-app app
  2. 释放webpack配置文件: npm run eject

释放webpack配置后可能会发现如下的eslint报错

Parsing error: [BABEL] F:\app\src\App.js: Using `babel-preset-react-app` requires that you specify `NODE_ENV` or `BABEL_ENV` environment variables. Valid values are "development", "test", and "production". Instead, received: undefined. (While processing: "F:\\app\\node_modules\\babel-preset-react-app\\index.js")
复制代码

package.json 文件的 eslintConfig 字段添加以下配置即可解决

"parserOptions": {
    "babelOptions": {
        "presets": [
            [
                "babel-preset-react-app",
                false
            ],
            "babel-preset-react-app/prod"
        ]
    }
}
复制代码

使用CDN方式引入组件

注意:使用CDN的方案不支持有 slot 的 vue 组件

引入 element-ui 组件

根据 element-ui 文档指引,在 public\index.html 文件 <head></head>标签中添加以下内容

<!-- 引入 vue  -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.9/theme-chalk/index.min.css">
<!-- 引入组件库 -->
<script src="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.9/index.min.js"></script>
复制代码

创建react桥接组件

import { useEffect, useRef } from "react"
const { Rate } = window.ELEMENT
const { Vue } = window

export default function ElButtonCDN (props) {
  const reference = useRef()
  const ElRate = Vue.extend(Rate)

  useEffect(() => {
    const rate = new ElRate({
      propsData: {
        ...props
      }
    })
    rate.$on('change', props.onChange)

    const el = document.createElement('div')
    reference.current.appendChild(el)
    rate.$mount(el)

    return () => {
      rate.$el.parentNode.removeChild(rate.$el)
      rate.$destroy()
    }
  })

  return (
    <div ref={reference}></div>
  )
}
复制代码

页面中使用桥接组件

import ElRateCDN from './components/ElRateCDN'
import { useState } from 'react';

function App () {
  const [rate, setRate] = useState(2)
  const onChange = (e) => {
    console.log('onChange', e)
    setRate(e)
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt=''></img>
        <ElRateCDN value={rate} onChange={onChange}></ElRateCDN>
      </header>
    </div>
  );
}
复制代码

使用 .vue 单文件组件

注意:使用单文件的方案时不可以通过桥接组件直接传递 slotvue 组件

配置 webpack

要在 React 项目中使用 Vue 的单文件组件,需要让 React 能改识别并编译.vue文件,这就需要在项目中引入vue-loader。具体操作如下:

  1. 安装依赖

    npm i [email protected] -S

    npm i vue-loader@15 [email protected] -D

注意:vue 版本需要与 vue-template-compiler 版本一致, vue2匹配的vue-loader版本不能高于15

  1. 配置 webpack

    3.1 修改 module.rules[1].oneOf 中的最后一项(type:'asset/resource'),在 exclude 中添加 /\.vue/

    // config/webpack.config.js
    
    module.epxorts = function (webpackEnv) {
        // ...
        return {
            module: {
                rules: [
                    // ...
                    {
                        oneOf: [
                            {
                              // Exclude `js` files to keep "css" loader working as it injects
                              // its runtime that would otherwise be processed through "file" loader.
                              // Also exclude `html` and `json` extensions so they get processed
                              // by webpacks internal loaders.
                              exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/, /\.vue/],
                              type: 'asset/resource',
                            },
                        ]
                    }
                ]
            }
        }
    }
    复制代码

    3.2 引入 vue-loadr

    // config/webpack.config.js
    
    const { VueLoaderPlugin } = require('vue-loader')
    
    module.epxorts = function(webpackEnv){
        // ...
       
        return {
            module: {
                rules: [
                    {
                      test: /\.vue$/,
                      loader: 'vue-loader'
                    },
                    // ...
                ]
            }
            plugins: [
                new VueLoaderPlugin(),
                // ...
            ]
        }
    }
    复制代码

创建vue组件

<template>
  <div class='rate-licker'>
    <rate v-model="score" @change="handleChange"></rate>
    <p>您选择了{{score}}分数</p>
    <x-button type="primary" @click="random">随机</x-button>
  </div>
</template>

<script>
import { Rate, Button } from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';

export default {
  components: {
    Rate,
    XButton: Button
  },
  props: {
    value: {
      type: Number,
      default: 0
    },
    onChange: {
      type: Function,
      default: () => { }
    }
  },
  data () {
    return {
      score: 0
    }
  },
  methods: {
    random () {
      const getSorce = () => Math.floor((Math.random() * 5))
      let val = getSorce()
      if (val === this.score) {
        val = getSorce()
      }
      this.score = val
      this.handleChange(val)
    },
    handleChange (val) {
      this.onChange(val)
    }
  },
  created () {
    this.score = this.value || 0
  }
}
</script>

<style scoped>
</style>
复制代码

创建react桥接组件

import { useEffect, useRef } from "react"
import Vue from 'vue'
import Rate from './Rate.vue'

const ElRate = Vue.extend(Rate)

export default function ElButton (props) {
  const reference = useRef()

  useEffect(() => {
    const rate = new ElRate({
      propsData: {
        ...props
      }
    })
    rate.$on('change', props.onChange)

    const el = document.createElement('div')
    reference.current.appendChild(el)
    rate.$mount(el)

    return () => {
      rate.$el.parentNode.removeChild(rate.$el)
      rate.$destroy()
    }
  })

  return (
    <div ref={reference}></div>
  )
}
复制代码

页面中使用桥接组件

import logo from './logo.svg';
import './App.css';
import ElRate from './components/Rate/index.js';
import { useState } from 'react';

function App () {
  const [rate, setRate] = useState(0)
  const onChange = (e) => {
    console.log('onChange', e)
    setRate(e)
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt=''></img>
        <ElRate value={rate} onChange={onChange}></ElRate>
      </header>
    </div>
  );
}

export default App;

复制代码

附录

猜你喜欢

转载自juejin.im/post/7128777642953670663