支持復雜場景的vue和react集成引用的JS插件

先上一張牛逼的測試頁面截圖!疫衩!

image.png

測試頁面地址是 https://devilwjp.github.io/vuereact-for-cra-demo/build/index.html#/eleInReact
這個測試頁充分說明了這個插件強壯的地方帽芽,它把react antd和vue element-ui成功的混合在了一起互相套用!并且竟然還能在react里支持vue的v-model雙向綁定患膛!

Vue和React快捷集成的工具包,并且適合復雜的集成場景

如何在Vue中嵌入React組件?如何在React中嵌入Vue組件耻蛇?[下方高能]

image

可以在任何的Vue和React項目中使用另一個類型框架的組件踪蹬,并且解決了復雜的集成問題

安裝

npm i vuereact-combined -S

Why?

讓vue和react的同學們一起來完成同一個項目同一個頁面甚至同一個組件

  • 使項目的人員選擇性和機動性變得更強,vue和react的技術(shù)棧都可以加入項目
  • 使項目的第三方插件選擇性更強臣咖,vue和react的插件都可以通用
  • 使研發(fā)人員的技術(shù)交流性更強跃捣,研發(fā)人員不應(yīng)該被技術(shù)棧所限制
  • 使項目可以集成更多的業(yè)務(wù)代碼,其他vue和react項目的優(yōu)秀代碼可以快速引入
  • 使前端研發(fā)人員可以更好的學習vue和react夺蛇,了解兩者的精華疚漆,促進團隊在前端技術(shù)棧的廣度
  • 使用方式極其簡便

遇到的困難

眾所周知,React更純粹,Vue做的封裝更多娶聘,所以大多數(shù)的難度都是集中在react的組件引用vue組件的過程中

支持程度

在react組件中引入vue組件

功能 支持程度 說明
普通屬性 完全支持
html片段屬性 變向支持 通過$slots闻镶,在vue中使用具名插槽獲取
render props 變向支持 通過$scopedSlots,在vue中使用作用域插槽獲取
children(普通插槽) 完全支持
組件合成事件 完全支持 通過on屬性
組件原生事件(.native) 不支持 react沒有這種感念丸升,可以自己包囊div
v-model 變向支持 通過$model铆农,并且支持vue組件中隨意自定義model屬性
context傳入vue 暫不支持 未來會支持,當前只有在vue中使用redux做了polyfill
html片段中使用react或者vue組件 完全支持 react組件直接傳入发钝,vue組件繼續(xù)通過applyVueInReact轉(zhuǎn)換
懶加載vue組件 完全支持 通過lazyVueInReact
redux共享 完全支持 使用applyRedux
mobx共享 變向支持 mobx本身就有react和vue的連接方式
vuex共享 完全支持 使用applyVuex
sync裝飾 變向支持 使用$sync
事件修飾(key.enter顿涣、click.once) 不支持 自行處理
透傳 變向支持 使用data-passed-props

在vue組件中引入react組件

功能 支持程度 說明
普通屬性 完全支持
具名插槽 完全支持 在react中使用屬性獲取
作用域插槽 完全支持 在react中使用屬性獲取,類型是個函數(shù)
普通插槽 完全支持
組件合成事件 完全支持 在react中使用屬性獲取
組件原生事件(.native) 暫不支持
v-model 不支持 react組件沒有這個概念
provider/inject傳入react 暫不支持 未來會支持
sync裝飾 不支持 react組件沒有這個概念
redux共享 完全支持 使用applyRedux
mobx共享 變向支持 mobx本身就有react和vue的連接方式
vuex共享 完全支持 使用applyVuex
事件修飾(key.enter酝豪、click.once) 不支持 react組件沒有這個概念
懶加載react組件 完全支持 通過lazyReactInVue
透傳 變向支持 使用data-passed-props

使用前提

項目中要同時安裝react和vue的相關(guān)環(huán)境

如果是通過vue-cli3創(chuàng)建的項目

請參考 https://github.com/devilwjp/vuereact-for-vuecli3-demo

如果通過react-create-app創(chuàng)建的項目(react版本需要>=16.3)

請參考 https://github.com/devilwjp/vuereact-for-cra-demo

安裝

npm i vuereact-combined

重要!

由于react hooks的取名規(guī)范是use開頭精堕,所以將use開頭的方法全部修改成了apply開頭孵淘,老的use開頭方法仍然有效

0.3.6新增

sync修飾(applyVueInReact)

在react組件中使用vue組件,如果要使用vue的sync修飾歹篓,使用sync屬性sync <Object>

  • 屬性名 <Object>
    ++ value <React State>
    ++ setter <Function> 純函數(shù)瘫证,接收一個值修改state
render () {
    return (
        <VueComInReact $sync={{
          test1: {
            value: this.state.test1,
            setter: (val) => {
              console.log(val)
              this.setState({
                test1: val
              })
            }
          }
        }}/>
    )
  }

applyVueInReact

在react組件中使用vue的組件

import React from 'react'
import VueComponent from '../views/test2.vue' // vue組件
import { applyVueInReact } from 'vuereact-combined'
let VueComponentInReact = applyVueInReact(VueComponent)
class demo1 extends React.Component{
  render(){
    return (
      <div>
        <VueComponentInReact prop1={'hello world'} prop2={'你好'}>
          <hr/>
          <h1>我是普通的插槽</h1>
        </VueComponentInReact>
      </div>
    )
  }
}
export default demo1

在react組件中,向vue組件傳遞具名插槽和作用域插槽庄撮,以及綁定自定義事件背捌,以及v-model應(yīng)用
react本身并不支持v-model,所以需要通過model的方式轉(zhuǎn)換成vue組件能接收的v-model洞斯,即便vue組件自定義了model屬性和事件毡庆,model的value和setter也不需要變化

import React from 'react'
import VueComponent from '../views/test2' // vue組件
import { applyVueInReact } from 'vuereact-combined'
let VueComponentInReact = applyVueInReact(VueComponent)
class demo1 extends React.Component{
  constructor (props) {
    super(props)
    this.event1 = this.event1.bind(this)
    this.state = {
      aaa: 1111
    }
  }
  event1 (...args) {
    console.log(args)
  }
  render(){
    return (
      <div>
        <VueComponentInReact prop1={'hello world'} prop2={'你好'} on={{
          event1: this.event1
        }} $slots={{
          slotA: <div>插槽A</div>,
          slotB: <div>插槽B</div>
        }} $scopedSlots={{
          slotC: (context) => <div>我是作用域插槽:{context.value}</div>
        }} $model={{
           value: this.state.aaa, // value必須是一個state
           setter: (value) => { this.setState({ aaa: value }) } // setter必須是直接修改state
         }}>
          <hr/>
          <h1>我是普通的插槽</h1>
        </VueComponentInReact>
      </div>
    )
  }
}
export default demo1

test2.vue

<template>
  <div>
    <h2>我是Vue組件</h2>
    <div>屬性1 {{prop1}}</div>
    <div>屬性2 {{prop2}}</div>
    <slot name="slotA"></slot>
    <slot></slot>
    <slot name="slotB"></slot>
    <slot name="slotC" :value="name"></slot>
  </div>
</template>

<script>
export default {
  name: 'demo1',
  data () {
    return {
      name: '本地作用域'
    }
  },
  props: ['prop1', 'prop2'],
  mounted () {
    this.$emit('event1', '11', '22')
  }
}
</script>

VueContainer

在react組件動態(tài)引用vue組件,類似vue的<component />

import React from 'react'
import VueComponent from '../views/test2' // vue組件
import { VueContainer } from 'vuereact-combined'
class demo1 extends React.Component{
  constructor (props) {
    super(props)
    this.event1 = this.event1.bind(this)
  }
  event1 (...args) {
    console.log(args)
  }
  render(){
    return (
      <div>
        <VueContainer component={VueComponent} prop1={'hello world'} prop2={'你好'} on={{
          event1: this.event1
        }} $slots={{
          slotA: <div>插槽A</div>,
          slotB: <div>插槽B</div>
        }} $scopedSlots={{
          slotC: (context) => <div>我是作用域插槽:{context.value}</div>
        }}>
          <hr/>
          <h1>我是普通的插槽</h1>
        </VueContainer>
      </div>
    )
  }
}
export default demo1

在react中使用vue的全局注冊組件

與react不同烙如,vue有全局注冊組件的功能么抗,使每個組件不需要再單獨引入
將vue全局組件的id作為參數(shù)傳入applyVueInReact中,或者將id作為component屬性的值傳入VueContainer中
示例:在react中使用全局的vue版本element-ui的DatePicker

const ElDatePickerInReact = appluVueInReact('ElDatePicker') // 將el-date-picker轉(zhuǎn)換成ElDatePicker就是id
// 或者
<VueContainer component={'ElDatePicker'}/>

applyReactInVue

在Vue的組件中使用React組件

<template>
  <ReactCom :prop1="prop1Value" prop2="222">我是普通插槽</ReactCom>
</template>

<script>
import { applyReactInVue } from 'vuereact-combined'
import ReactComponents1 from '../reactComponents/cc.jsx' // React組件
export default {
  name: 'demo2',
  data () {
    return {
      prop1Value: 111
    }
  },
  components: {
    ReactCom: applyReactInVue(ReactComponents1)
  }
}
</script>

在Vue組件中亚铁,向React組件傳遞具名插槽和作用域插槽蝇刀,以及綁定自定義事件
由于React沒有插槽的概念,所有都是以屬性存在徘溢,Vue的具名插槽和作用域插槽會被轉(zhuǎn)化為React的屬性吞琐,其中作用域插槽會轉(zhuǎn)換成render props的方式
并且Vue組件的事件也會被轉(zhuǎn)化成React的屬性

<template>
  <ReactCom :prop1="prop1Value" prop2="222" @event1="callEvent1">
    我是普通插槽
    <template v-slot:slotA>
      我是插槽A
    </template>
    <template v-slot:slotB>
      我是插槽B
    </template>
    <template v-slot:slotC="context">
      我是作用域插槽:{{context.value}}
    </template>
  </ReactCom>
</template>

<script>
import { applyReactInVue } from 'vuereact-combined'
import ReactComponents1 from '../reactComponents/cc.jsx' // React組件
export default {
  name: 'demo2',
  data () {
    return {
      prop1Value: 111
    }
  },
  methods: {
    callEvent1 (...args) {
      console.log(args)
    }
  },
  components: {
    ReactCom: applyReactInVue(ReactComponents1)
  }
}
</script>

cc.jsx

import React from 'react'
class cc extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      slotValue: {
        value: '本地作用域'
      }
    }
  }
  componentDidMount () {
    this.props.event1(11,22)
  }
  render () {
    return (
      <div>
        <div>我是React組件</div>
        <div>屬性1:{this.props.prop1}</div>
        <div>屬性2:{this.props.prop2}</div>
        {this.props.slotA}
        {this.props.children}
        {this.props.slotB}
        {this.props.slotC(this.state.slotValue)}
      </div>
    )
  }
}
export default cc  

applyReactInVue的復雜案例

比如react版本的antd的Card組件,在react中的使用示例如下

render () {
    return (<Card title="Default size card" extra={<a href="#">More</a>}>
             <p>Card content</p>
             <p>Card content</p>
             <p>Card content</p>
           </Card>)
}

react版本的antd然爆,在vue組件中使用的示例如下

<CardInVue class="react-com" title="Default size card">
    <!--react antd的extra屬性是傳遞html片段的站粟,在vue中就使用具名插槽-->
    <template v-slot:extra>
        <a href="#">More</a>
    </template>
    <p>Card content</p>
    <p>Card content</p>
    <p>Card content</p>
</CardInVue>

applyRedux

作用:使得所有的Vue組件可以使用redux的狀態(tài)管理
對工具包開啟redux狀態(tài)管理,這個場景一般存在于以React為主的項目中施蜜,為了使Vue組件也可以共享到redux卒蘸,需要在項目的入口文件引入applyRedux方法(整個項目應(yīng)該只引一次),將redux的store以及redux的context作為參數(shù)傳入(或者至少在redux的Provider高階組件引入的地方使用applyRedux方法)

// 第二個參數(shù)是redux的context,之所以需要傳第二個參數(shù)缸沃,是因為有如下場景
// Provider -> ReactCom1 -> VueCom1 -> ReactCom2
// Provider無法直接透過Vue組件傳遞給之后的React組件恰起,所以applyRedux提供了第二個參數(shù),作用就是可以使通過Vue組件之后的React組件繼續(xù)可以獲取到redux的context
import { ReactReduxContext } from 'react-redux'
import store from '../reactComponents/reduxStore'
applyRedux({ store, ReactReduxContext })

store.js

// 原生的redux store的創(chuàng)建方式
import { createStore } from 'redux'
import someCombineReducer from './reducer' // 建議通過react-redux的combineReducer輸出
let store = createStore(someCombineReducer)
export default store

React組件連接redux的方式這里就不再做介紹了趾牧,應(yīng)該使用react-redux的connect方法
這里介紹Vue組件如何使用redux检盼,工具包盡可能的實現(xiàn)了vue組件使用vuex的方式去使用redux,通過vm.$redux可以在組件實例里獲取到redux狀態(tài)管理

<template>
  <div>
    redux狀態(tài)testState1: {{$redux.state.testState1}}
  </div>
</template>

<script>
export default {
  name: 'demo3',
  mounted () {
    // 打印redux的testState2狀態(tài)值
    console.log(this.$redux.state.testState2)
    // 五秒后將testState1修改成8888
    // 需要在reducer里存在一個action的type為test1可以修改testState1
    // 這里需要按照標準的redux的action標準(必須有type)觸發(fā)dispatch
    setTimeout(() => {
      this.$redux.dispatch({
        type: 'test1',
        value: 8888
      })
    }, 5000)
  }
}
</script>

applyVuex

作用:使得所有的Redux組件可以使用Vuex的狀態(tài)管理
對工具包開啟vuex狀態(tài)管理翘单,這個場景一般存在于以Vue為主的項目中吨枉,為了使React組件也可以共享到vuex,需要在項目的入口文件引入applyVuex方法(整個項目應(yīng)該只引一次)哄芜,將vuex的store作為參數(shù)傳入

import store from '../store' // vuex的store文件
applyVuex(store)

connectVuex

類似react-redux的connect方法貌亭,在React組件中使用,由于vuex的關(guān)鍵字比redux多认臊,所以將參數(shù)改成了對象圃庭,包含了mapStateToProps、mapCommitToProps失晴、mapGettersToProps剧腻、mapDispatchToProps,每個都是一個純函數(shù)涂屁,返回一個對象(和redux的connect使用方式完全一致)

export default connectVuex({
  mapStateToProps (state) {
    return {
      vuexState: state,
      state1: state.state1,
      moduleAstate: state.moduleA
    }
  },
  mapCommitToProps (commit) {
    return {
      vuexCommit: commit
    }
  },
  // mapGettersToProps = (getters) => {},
  // mapDispatchToProps = (dispatch) => {},
})(ReactComponent)

lazyVueInReact

在React的router里懶加載Vue組件

import React, { lazy, Suspense } from "react"
import { lazyVueInReact } from 'vuereact-combined'
const Hello = lazy(() => import("./react_app/hello"));
//懶加載vue組件
const TestVue = lazyVueInReact(() => import("./vue_app/test.vue"))


export default [
{
    path: "/reactHello",
    component: () => {
        return (
            <Suspense fallback={<div>Loading...</div>}>
                <Hello />
            </Suspense>
        );
    }
},
{
    path: "/vuetest1",
    component: () => {
        return (
            <Suspense fallback={<div>Loading...</div>}>
                <div>
                    <h1>我是一個vue組件</h1>
                    <TestVue />
                </div>
            </Suspense>
        );
    }
}]

lazyReactInVue

在Vue的router里懶加載React組件

import Vue from 'vue'
import VueRouter from 'vue-router'
import { lazyReactInVue } from 'vuereact-combined'
Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import('../views/Home')
  },
  {
    path: '/reactInVueDemo',
    name: 'reactInVueDemo',
    component: lazyReactInVue(() => import('../reactComponents/cc.jsx'))
  }
]

const router = new VueRouter({
  routes
})

export default router

v0.3.3新增

data-passed-props(透傳)

每個通過applyVueInReact的的vue組件书在,以及通過applyReactInVue的react組件,都可以收到一個data-passed-props的屬性拆又,這個屬性可以幫助你不做任何包裝的儒旬,被之后再次使用applyVueInReact或applyReactInVue的組件收到全部的屬性(由于是跨框架透傳,原生的透傳方式并不會自動做相應(yīng)的封裝和轉(zhuǎn)換)

// react組件透傳給vue組件
const VueComponent = applyVueInReact(require('./anyVueComponent'))
class theReactComponentFromVue extends React.Component{
    render () {
        return <VueComponent data-passed-props={this.props['data-passed-props']}/>
    }
}
<template>
    <!--vue組件透傳給react組件-->
    <!--通過$attrs['data-passed-props']或者$props.dataPassedProps-->
    <ReactComponent :data-passed-props="$attrs['data-passed-props']"></ReactComponent>
</template>
<script>
const ReactComponent = applyReactInVue(require('./anyReactComponent'))
export default {
    name: 'theVueComponentFromReact'
    // 如果通過props獲取data-passed-props遏乔,需要轉(zhuǎn)成駝峰
    // props: ['dataPassedProps']
}
</script>

需要注意的包囊性問題

由于在每一次跨越一個框架進行組件引用時义矛,都會出現(xiàn)一層包囊,這個包囊是以div呈現(xiàn)盟萨,并且會被特殊屬性標注
React->Vue凉翻,會在vue組件的dom元素外包囊一層標識data-use-vue-component-wrap的div
Vue->React,會在react組件的dom元素外包囊一層標識__use_react_component_wrap的div
如果引發(fā)樣式問題捻激,可以全局對這些標識進行樣式修正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末制轰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子胞谭,更是在濱河造成了極大的恐慌垃杖,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丈屹,死亡現(xiàn)場離奇詭異调俘,居然都是意外死亡伶棒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門彩库,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肤无,“玉大人,你說我怎么就攤上這事骇钦⊥鸾ィ” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵眯搭,是天一觀的道長窥翩。 經(jīng)常有香客問我,道長鳞仙,這世上最難降的妖魔是什么寇蚊? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮繁扎,結(jié)果婚禮上幔荒,老公的妹妹穿的比我還像新娘。我一直安慰自己梳玫,他們只是感情好,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布右犹。 她就那樣靜靜地躺著提澎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪念链。 梳的紋絲不亂的頭發(fā)上盼忌,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機與錄音掂墓,去河邊找鬼谦纱。 笑死,一個胖子當著我的面吹牛君编,可吹牛的內(nèi)容都是我干的跨嘉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼吃嘿,長吁一口氣:“原來是場噩夢啊……” “哼祠乃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起兑燥,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤亮瓷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后降瞳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘱支,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了除师。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沛膳。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖馍盟,靈堂內(nèi)的尸體忽然破棺而出于置,到底是詐尸還是另有隱情,我是刑警寧澤贞岭,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布八毯,位于F島的核電站,受9級特大地震影響瞄桨,放射性物質(zhì)發(fā)生泄漏话速。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一芯侥、第九天 我趴在偏房一處隱蔽的房頂上張望泊交。 院中可真熱鬧,春花似錦柱查、人聲如沸廓俭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽研乒。三九已至,卻和暖如春淋硝,著一層夾襖步出監(jiān)牢的瞬間雹熬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工谣膳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留竿报,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓继谚,卻偏偏與公主長得像烈菌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子犬庇,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359