https://juejin.cn/post/6994568856807145479
Vuera 可以使 Vue 和 React 开发的组件相互混用。
当已经有成熟的 React 组件或某个开源组件不是用你使用的技术栈 Vue 开发的时候,你可以使用 Vuera 来导入这个 React 组件,从而低成本的复用 React 组件。
既然我们已经知道 Vuera 是用来混用 Vue、React 组件的,那么接下来我们来看看是怎么使用的。
举个例子
最简单的 demo :
javascript复制代码
<template>
<div>
<my-react-component :message="message" />
</div>
</template>
<script>
import { ReactInVue } from 'vuera'
import MyReactComponent from './MyReactComponent'
export default {
data () {
message: 'Hello from React!',
},
components: {
'my-react-component': ReactInVue(MyReactComponent)
}
}
</script>
可以看到,这段代码引入了一个 React 组件,并且通过 ReactInVue 包装了一下,然后这个组件就可以在 Vue 环境中跑起来了。
从这里可以看出来,Vuera 的使用方式还是很简单的,在 Vue 中使用 React 组件只需要使用 ReactInVue 包装一下。那么现在我们来看看 ReactInVue都做了什么。
前置知识
在 Vue 中,有两种⽅式可以创建⼀个组件。
javascript复制代码// way 1
Vue.component('com-1', {
template: '<div class="constainer">{{msg}}</div>',
data() {
return {
msg: 'com-1'
}
},
})
// way 2
Vue.component('com-1', {
data() {
return {
msg: 'com-1'
}
},
render(h) {
return h('div', { class: "constainer" }, this.msg)
}
})
关键点在于 Vue支持配置 template 或者 手写 render 函数。
template:html + vue 语法糖的模板字符串
render 函数:render 的第一个参数是渲染函数,即 createElement。可以接收三个参数,分别是 标签名,标签的属性,子标签或文本。
在 Vue 中,支持 ref 属性获取元素渲染后的真实 DOM。
javascript复制代码
Vue.component('com-1', {
template: '<div class="container" ref="container">{{msg}}</div>',
data() {
return {
msg: 'com-1'
}
},
mounted() {
console.log(this.$refs.container)
},
})
可以看到,在 container 上添加了一个 ref 属性,它的值是 container。这样在 mounted(页面渲染完毕后),就可以通过 this.$refs 获取到 container 的真实 DOM 节点。
在 Vue 中,可以通过 new Vue({ el: xx }) 渲染 Vue组件到 el 节点上
React 中,支持书写 JSX 节点。看下 JSX 编译前后的对比:
javascript复制代码
const App = (props) => (
<div className="container"> {props.msg} </div>
)
// ->
const h = React.createElement
const App = () => h('div', {
className: 'container'
}, props.msg)
这时可以看到,App 编译后也是 render 函数,这里的 h 也就是 createElement,这里和 Vue 几乎一致。
React 也支持 ref
javascript复制代码
class App extends React.Component {
onRefMounted(element) {
console.log(element)
}
render() {
return (
{/* <div className="container" ref={this.containerRef}> */}
<div className="container" ref={this.onRefMounted}>
{props.msg}
</div>
)
}
}
使用方式和 Vue 相同,ref 属性可以是一个函数或一个变量。如果是一个函数,那么在页面渲染完毕后,这个函数的形参上会有 ref 对应的 DOM 真实节点。
React 可以通过 ReactDOM.render 渲染 React 组件到节点上
javascript复制代码ReactDOM.render(
ReactInVue 的核心原理
我们先以上面的例子来解释,即在 Vue 中使用 React 组件:
javascript复制代码
<template>
<div>
<ReactComponent />
</div>
</template>
<script>
export default {
components: {
'my-react-component': ReactInVue(MyReactComponent)
}
}
</script>
new Vue({
el: '#app',
render(h) {
return h('div', {}, h('ReactComponent'))
}
})
我们先把上面的代码转化为 render 函数(babel 编译后):
javascript复制代码
Vue.component('ReactComponent', {
render(h) {
return h('react-wrapper', {
props: {
component: MyReactComponent,
}
})
}
})
new Vue({
el: '#app',
render(h) {
return h('div', {}, h('ReactComponent'))
}
})
可以看到这里的关键在于 react-wrapper。它接收一个 component ,值是我们传入的 React 组件。
这里的 react-wrapper 很明显是个 Vue 组件:
javascript复制代码{
render(h) {
return h('div', { ref: 'react' }),
},
methods: {
mountReactComponent() {
// 渲染 React 组件在 ref="react" 的 DOM 节点上
}
},
mounted() {
this.mountReactComponent()
}
}
上面代码就是 react-wrapper 最精简的版本。可以看到:这个 Vue 组件 绑定了一个 ref 叫 react。在这个div 渲染完毕后,会执行 mountReactComponent。
这里可以看到,Vue 内嵌套 React 组件 首先通过 将 React组件作为参数传入一个 react-wrapper`
组件,然后 render 时渲染一个空的 div,这个div的作用是作为父亲,传入的react组件最终会渲染到这个 div上。
我们接着说 mountReactComponent 做了哪些事情。
javascript复制代码
{
methods: {
mountReactComponent (component) {
const Component = makeReactContainer(component)
const children = this.$slots.default !== undefined
? { children: this.$slots.default }
: {}
ReactDOM.render(
<Component
{...this.$props.passedProps}
{...this.$attrs}
{...this.$listeners}
{...children}
ref={ref => (this.reactComponentRef = ref)}
/>,
this.$refs.react
)
}
}
}
这里的 makeReactContainer 是用来处理 children 相关的逻辑,我们先暂时不看。
我们知道 this.$slots.default 就相当于 React 中的 children,然后这里 会把 this.$slots.default 包装成对象 赋给 children 。
然后会通过 ReactDOM.render 来将传入的 React 组件,渲染在 this.$refs.react,这就是刚刚 render 中渲染的 空 div 节点。
看到这里其实就可以总结出,先创建一个空的 div 节点,然后通过 ref 拿到真实 DOM,再通过 ReactDOM.render 来将React组件渲染到这个 DOM 上,至此就实现了 在 Vue 中使用 React 组件。
如果引入的 React 组件没有子节点,那么代码其实就到此为止了。但是实际上大多数的 React 组件都是层层嵌套的,它的子节点也是需要处理的。
上述代码 render 的 Component 是经过了 makeReactContainer 。所以这里我们接着再看一下 makeReactComponent 做了什么。
从它的用法可以看出,makeReactContainer 返回的肯定是一个 react 组件。
javascript复制代码
const makeReactContainer = Component => {
return class ReactInVue extends React.Component {
constructor(props) {
super(props)
this.state = { ...props }
}
render() {
const { children, ...rest } = this.state
const wrappedChildren = this.wrapVueChildren(children)
return (
<Component {...rest}>
{children && <VueWrapper component={wrappedChildren} />}
</Component>
)
}
}
}
我们可以看到 传给 Component 组件的 props 会被 ReactInVue 组件接收到,然后会将props绑定到 state上。
然后在 render 中,将 children子节点解构出来,然后传入了 wrapVueChildren 并作为 component 参数 传入到了 VueWrapper 组件中。
也就是说 React组件的子节点会包装一下 并传入 VueWrapper 中。
那么 VueWrapper 中又做了什么呢,我们展开看一下:
javascript复制代码
class VueWrapper extends React.Component {
constructor(props) {
super(props)
this.currentVueComponent = props.component
}
createVueInstance (targetElement, reactThisBinding) {
const { component, on, ...props } = reactThisBinding.props
// `this` refers to Vue instance in the constructor
reactThisBinding.vueInstance = new Vue({
el: targetElement,
data: props,
...config.vueInstanceOptions,
render (createElement) {
return createElement(
VUE_COMPONENT_NAME,
{
props: this.$data,
on,
},
[wrapReactChildren(createElement, this.children)]
)
},
components: {
[VUE_COMPONENT_NAME]: component,
'vuera-internal-react-wrapper': ReactWrapper,
},
})
}
render () {
return <div ref={this.createVueInstance} />
}
}
首先将传入的 component(嵌入Vue中 React组件的 子节点)保存到了 currentVueComponent 中。
然后在 render 函数中,将创建一个空的 div,然后绑定 ref 用来获取真实的 div DOM节点。
当div渲染完毕后,在 createVueInstance 可以拿到div 的 真实DOM 节点 targetElement。这里 reactThisBinding 就是当前的 React类的 this 实例(在 constructor中绑定,绑定过程省略)
然后可以看到 首先将传入的 props 中 component、on 解构出来。
显然 component 就是嵌入Vue中 React组件的 子节点,而 on 是在 Vue 中组件绑定的一些事件(onClick ….),然后会通过 new Vue 创建一个 Vue 节点。然后渲染到 targetElement 上。这里和 刚才的 ReactDOM.render() 渲染到一个 ref 节点上是完全一致的。
然后在 render 的部分,可以看到 会渲染传入的这个 component 组件,然后 render 函数的第二个参数是 { props, on }, 最后一个参数是关于子节点的部分,传入了
wrapReactChildren,来看看 wrapReactChildren 的代码:
javascript复制代码
const wrapReactChildren = (createElement, children) =>
createElement('vuera-internal-react-wrapper', {
props: {
component: () => <div>{children}</div>,
},
})
这里会将 children 作为 component 的参数传入 vuera-internal-react-wrapper 组件中,而在上一段代码 new Vue 中可以看到:
javascript复制代码components:
{
[VUE_COMPONENT_NAME]: component,
'vuera-internal-react-wrapper': ReactWrapper,
}
vuera-internal-react-wrapper 其实就是 ReactWrapper。这这就我们刚才说的 react-wrapper.
所以到此为止可以看到,React 的所有子节点都会循环在 react-wrapper 和 vue-wrapper 中转换。
到这,所有的代码和原理已经讲解完毕。
作者:bib1du
链接:https://juejin.cn/post/6994568856807145479
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。