尝试在 react 项目中使用 vue2 组件且实现数据交互
项目背景
在使用 amis-editor
开发低代码平台时,因业务需要把在老项目中使用的组件移植到 amis-editor
中,以减少开发时长和bug风险。
因为amis-editor
是使用react
开发的,所以需要想办法在react
中使用vue
组件。本文以使用 create-react-app
初始化的项目为例展示如何在react
中使用vue
组件。以 element-ui
为例,展示如何引入第三方 vue
组件。
整体思路
- 对 Vue 组件使用基础 Vue 构造器,创建一个“子类”
- 新建一个 React 组件,在组件中 new 一个“子类”,接收 Vue 组件的 props 及监听 Vue 组件的事件
- 当 React 组件 mounted 后挂载 Vue 组件,当 React 组件销毁时调用 Vue 组件的
$destroy()
方法销毁 Vue 组件
初始化 React 项目
- 创建react项目:
npx create-react-app app
- 释放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/vue@2.7.8"></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 单文件组件
注意:使用单文件的方案时不可以通过桥接组件直接传递
slot
给vue
组件
配置 webpack
要在 React 项目中使用 Vue 的单文件组件,需要让 React 能改识别并编译.vue
文件,这就需要在项目中引入vue-loader
。具体操作如下:
-
安装依赖
npm i vue@2.6.14 -S
npm i vue-loader@15 vue-template-compiler@2.6.14 -D
注意:vue 版本需要与 vue-template-compiler 版本一致, vue2匹配的vue-loader版本不能高于15
-
配置
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;
附录
- github仓库地址:github.com/wtlsky/useV…
- 效果预览地址:wtlsky.github.io/useVueInRea…
转载自:https://juejin.cn/post/7128777642953670663