├── README.zh.md ├── README.ja.md ├── README.md └── minilogo.svg /README.zh.md: -------------------------------------------------------------------------------- 1 | # vuera 2 | 3 | [![explain](http://llever.com/explain.svg)](https://github.com/chinanf-boy/Source-Explain) 4 | 5 | 解释 6 | 7 | >"version": "0.1.3" 8 | 9 | [github source](https://github.com/topics/vuera) 10 | 11 | --- 12 | 13 | 同时在 react 与 vue 中 运行。 14 | 15 | - [react 在 vue](#ReactInVue) 16 | 17 | - [vue 在 react](#VueInReact) 18 | 19 | --- 20 | 21 | - 其他 22 | 23 | - [isReactComponent 函数 实现](#isreactcomponent) 24 | 25 | --- 26 | 27 | 两种情况 28 | 29 | 先说第一种: 30 | 31 | # ReactInVue 32 | 33 | ``` js 34 | import Vue from 'vue' 35 | import { VuePlugin } from 'vuera' 36 | 37 | Vue.use(VuePlugin) 38 | /* ... */ 39 | ``` 40 | 41 | Now, use your React components like you would normally use your Vue components! 42 | 43 | ``` vue 44 | 50 | 51 | 66 | ``` 67 | 68 | 以 Vue插件的形式 用 React 69 | 70 | vuera/src/VuePlugin.js 71 | ``` js 72 | // 判断是否 React的组件 73 | import isReactComponent from './utils/isReactComponent' 74 | // React Component -> Vue Component 75 | import VueResolver from './resolvers/Vue' 76 | 77 | /** 78 | * vue 插件 79 | */ 80 | export default { 81 | install (Vue, options) { 82 | /** 83 | * 自定义合并策略,这个策略真的只是 84 | * 包装所有React组件,同时保留Vue组件。 85 | */ 86 | const originalComponentsMergeStrategy = Vue.config.optionMergeStrategies.components 87 | 88 | Vue.config.optionMergeStrategies.components = function (parent, ...args) { 89 | // 之前设置的 值 <-- return Object.assign(parent, wrappedComponents) 90 | const mergedValue = originalComponentsMergeStrategy(parent, ...args) 91 | 92 | // 93 | const wrappedComponents = mergedValue 94 | ? Object.entries(mergedValue).reduce( 95 | (acc, [k, v]) => ({ 96 | ...acc, 97 | [k]: isReactComponent(v) ? VueResolver(v) : v, 98 | }), 99 | {} 100 | ) 101 | : mergedValue 102 | //合并 103 | return Object.assign(parent, wrappedComponents) 104 | } 105 | }, 106 | } 107 | 108 | ``` 109 | 110 | 其中出现 111 | 112 | - [install()](https://cn.vuejs.org/v2/guide/plugins.html#%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6) 113 | 114 | - [vue.config 是一个对象,包含 Vue 的全局配置。可以在启动应用之前修改下列属性:](https://cn.vuejs.org/v2/api/#optionMergeStrategies) 115 | 116 | - [Object.entries](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) 117 | 118 | ``` js 119 | const obj = { foo: 'bar', baz: 42 }; 120 | console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ] 121 | ``` 122 | - [Array.prototype.reduce](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) 123 | 124 | ``` js 125 | var flattened = [[0, 1], [2, 3], [4, 5]].reduce(function(a, b) { 126 | return a.concat(b); 127 | }, []); 128 | // flattened is [0, 1, 2, 3, 4, 5] 129 | ``` 130 | 131 | - VueResolver 132 | 133 | vuera/src/resolvers/Vue.js 134 | > React Component -> Vue Component 135 | 136 | 那么可以看出,使用遍历了一边 ``Vue``组件,如果发现 ``React``组件的样子 就改造成``Vue``。 137 | 138 | 再说第二种: 139 | 140 | # VueInReact 141 | 142 | 加 `vuera/babel` 到 你的`.babelrc` `plugins` 选项 143 | 144 | .babelrc 145 | ``` json 146 | { 147 | "presets": "react", 148 | "plugins": ["vuera/babel"] 149 | } 150 | ``` 151 | 152 | 使用 153 | 154 | ``` jsx 155 | import React from 'react' 156 | import MyVueComponent from './MyVueComponent.vue' 157 | 158 | export default () => ( 159 |
160 |

I'm a react component

161 |
162 | 163 |
164 |
165 | ) 166 | ``` 167 | 168 | 从 添加 ``.babelrc`` 的方式来看,难道是用 babel 169 | 170 | vuera/babel.js 171 | ``` js 172 | /* eslint-env node */ 173 | 174 | function processCreateElement (maybeReactComponent, args, file, path, types) { 175 | // If the first argument is a string (built-in React component), return 176 | if (maybeReactComponent.type === 'StringLiteral') return 177 | 178 | if (!file.insertedVueraImport) { 179 | file.path.node.body.unshift( 180 | types.importDeclaration( 181 | [ 182 | types.importSpecifier( 183 | types.identifier('__vueraReactResolver'), 184 | types.identifier('__vueraReactResolver') 185 | ), 186 | ], 187 | types.stringLiteral('vuera') 188 | ) 189 | ) 190 | } 191 | // Prevent duplicate imports 192 | file.insertedVueraImport = true 193 | 194 | // Replace React.createElement(component, props) with our helper function 195 | path.replaceWith( 196 | types.callExpression(types.identifier('__vueraReactResolver'), [maybeReactComponent, ...args]) 197 | ) 198 | } 199 | 200 | // types 是 babel 插件 API 其中一个模块 201 | module.exports = function ({ types }) { 202 | return { 203 | visitor: { 204 | CallExpression (path, { file }) { 205 | const callee = path.node.callee 206 | const [maybeReactComponent, ...args] = path.node.arguments 207 | 208 | // 如果有 react 模块 ,reactImport 209 | const reactImport = file.path.node.body.find( 210 | x => x.type === 'ImportDeclaration' && x.source.value === 'react' 211 | ) 212 | if (!reactImport) return 213 | 214 | // 如果 CallExpression 是 react.createElement 215 | if (callee.type === 'MemberExpression') { 216 | /** 217 | * 获取默认导入名称. Examples: 218 | * import React from 'react' => "React" 219 | * import hahaLOL from 'react' => "hahaLOL" 220 | */ 221 | const defaultImport = reactImport.specifiers.find( 222 | x => x.type === 'ImportDefaultSpecifier' 223 | ) 224 | if (!defaultImport) return 225 | const reactName = defaultImport.local.name 226 | 227 | const { object, property } = callee 228 | if (!(object.name === reactName && property.name === 'createElement')) { 229 | return 230 | } 231 | 232 | processCreateElement(maybeReactComponent, args, file, path, types) 233 | } 234 | // 检查 CallExpression 是 react's 'createElement' 235 | if (callee.type === 'Identifier' && callee.name !== '__vueraReactResolver') { 236 | // Return unless createElement was imported 237 | const createElementImport = reactImport.specifiers.find( 238 | x => x.type === 'ImportSpecifier' && x.imported.name === 'createElement' 239 | ) 240 | if (!createElementImport) return 241 | 242 | processCreateElement(maybeReactComponent, args, file, path, types) 243 | } 244 | }, 245 | }, 246 | } 247 | } 248 | 249 | ``` 250 | 251 | - [babel-types](https://github.com/babel/babel/tree/master/packages/babel-types) 252 | 253 | - [babel-AST](http://web.jobbole.com/88236/) 254 | 255 | - [babel-语法插件中文](https://github.com/thejameskyle/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md#toc-api) 256 | 257 | - [在线AST语法树](http://astexplorer.net/) 258 | 259 | 这部分需要理解 AST语法树的 问题 , 上面的链接能帮助你简单明确 ``babel`` 的 ``plugins`` 插件中 ``ImportSpecifier`` ``MemberExpression`` 之类 问题 260 | 261 | >babel.js中总得来说,就是把 ``react.createElement`` 变成 ``__vueraReactResolver`` 内置函数 262 | 263 | ```js 264 | export function babelReactResolver (component, props, children) { 265 | return isReactComponent(component) 266 | ? React.createElement(component, props, children) 267 | : React.createElement(VueWrapper, Object.assign({ component }, props), children) 268 | } 269 | 270 | // babelReactResolver as __vueraReactResolver 271 | ``` 272 | 273 | 上面的只是再 ``babel`` 将 ``js`` 降级时所作的事情 274 | 275 | --- 276 | 277 | VueWrapper 上面代码中 👄最重要的 278 | 279 | vuera/src/wrapper/vue.js 280 | ``` js 281 | import React from 'react' 282 | import Vue from 'vue' 283 | import ReactWrapper from './React' 284 | 285 | const VUE_COMPONENT_NAME = 'vuera-internal-component-name' 286 | 287 | const wrapReactChildren = (createElement, children) => 288 | createElement('vuera-internal-react-wrapper', { 289 | props: { 290 | component: () =>
{children}
, 291 | }, 292 | }) 293 | 294 | export default class VueContainer extends React.Component { 295 | constructor (props) { 296 | super(props) 297 | 298 | /** 299 | * 传入并重新定义真正的 组件 300 | * `component` prop. 301 | */ 302 | this.currentVueComponent = props.component 303 | 304 | /** 305 | * 修改createVueInstance函数以正确传递此绑定。 在做这个 306 | * 构造函数避免在渲染中实例化函数。 307 | * //我觉得有点难理解 :译者曰 308 | */ 309 | const createVueInstance = this.createVueInstance 310 | const self = this 311 | this.createVueInstance = function (element, component, prevComponent) { 312 | createVueInstance(element, self, component, prevComponent) 313 | } 314 | } 315 | 316 | componentWillReceiveProps (nextProps) { 317 | const { component, ...props } = nextProps 318 | 319 | if (this.currentVueComponent !== component) { 320 | this.updateVueComponent(this.props.component, component) 321 | } 322 | /** 323 | * NOTE: 没有去比较 props 和 nextprops, because I didn't want to write a 324 | * function for deep object comparison. I don't know if this hurts performance a lot, maybe 325 | * we do need to compare those objects. 326 | */ 327 | Object.assign(this.vueInstance.$data, props) 328 | } 329 | 330 | componentWillUnmount () { 331 | this.vueInstance.$destroy() 332 | } 333 | 334 | /** 335 | * 创建和加载 VueInstance 接口 336 | * NOTE: VueInstance inside VueContainer 337 | * 我们不能绑定 createVueInstance 到 这个 VueContainer 对象, 需要明确 338 | * 传递 绑定对象 339 | * @param {HTMLElement} targetElement - element to attact the Vue instance to 340 | * @param {ReactInstance} reactThisBinding - current instance of VueContainer 341 | */ 342 | createVueInstance (targetElement, reactThisBinding) { 343 | const { component, ...props } = reactThisBinding.props 344 | 345 | // `this` refers to Vue instance in the constructor 346 | reactThisBinding.vueInstance = new Vue({ 347 | // targetElement 就是 VueContainer render() element 348 | el: targetElement, 349 | data: props, 350 | render (createElement) { 351 | return createElement( 352 | VUE_COMPONENT_NAME, 353 | { 354 | props: this.$data, 355 | }, 356 | [wrapReactChildren(createElement, this.children)] 357 | ) 358 | }, 359 | components: { 360 | [VUE_COMPONENT_NAME]: component, 361 | 'vuera-internal-react-wrapper': ReactWrapper, 362 | }, 363 | }) 364 | } 365 | 366 | updateVueComponent (prevComponent, nextComponent) { 367 | this.currentVueComponent = nextComponent 368 | 369 | /** 370 | * Replace the component in the Vue instance and update it. 371 | */ 372 | this.vueInstance.$options.components[VUE_COMPONENT_NAME] = nextComponent 373 | this.vueInstance.$forceUpdate() 374 | } 375 | 376 | render () { 377 | return
378 | } 379 | } 380 | ``` 381 | 382 | 用 ``React.Component`` 包裹传入的 ``props.component`` 组件,然后内部新建 ``Vue实例 reactThisBinding.vueInstance``, 383 | 384 | - 相当于说 这部分 ``
`` 交给 Vue 处理, 然后把 Vue的部分事件 给予 react.component 组件事件处理调用。 385 | 386 | --- 387 | 388 | ## 其他 389 | 390 | ### isReactComponent 391 | 392 | ``` js 393 | export default function isReactComponent (component) { 394 | if (typeof component === 'object') { 395 | return false // no object == no react 396 | } else if ( 397 | typeof component === 'function' && 398 | component.prototype.constructor.super && 399 | component.prototype.constructor.super.name.startsWith('Vue') 400 | ) { 401 | return false // is vue 402 | } else { 403 | return true // is react 404 | } 405 | } 406 | 407 | ``` -------------------------------------------------------------------------------- /README.ja.md: -------------------------------------------------------------------------------- 1 | # ヴェラ 2 | 3 | [![explain](http://llever.com/explain.svg)](https://github.com/chinanf-boy/Source-Explain) 4 | 5 | 説明 6 | 7 | > "version": "0.1.3" 8 | 9 | [ギブスソース](https://github.com/topics/vuera) 10 | 11 | [中文版](./README.zh.md) 12 | 13 | * * * 14 | 15 | 反応して同時に動いている. 16 | 17 | - [vueに反応する](#ReactInVue) 18 | 19 | - [反応時の意見](#VueInReact) 20 | 21 | * * * 22 | 23 | - 続きを見る 24 | 25 | - [isreactcomponent](#isreactcomponent) 26 | 27 | * * * 28 | 29 | 2つの状況 30 | 31 | 最初に言った: 32 | 33 | # 反応する 34 | 35 | ```js 36 | import Vue from 'vue' 37 | import {VuePlugin} from 'vuera' 38 | 39 | Vue.use (VuePlugin) 40 | /* ... */ 41 | ``` 42 | 43 | 通常はあなたのvueコンポーネントを使用するようにㄡあなたの反応コンポーネントを使用してください! 44 | 45 | ```vue 46 | 52 | 53 | 68 | ``` 69 | 70 | * * * 71 | 72 | vueプラグインとして反応する 73 | 74 | vuera / src / vueplugin.js 75 | 76 | ```js 77 | // Determine if React's component 78 | import isReactComponent from './utils/isReactComponent' 79 | // React Component -> Vue Component 80 | import VueResolver from './resolvers/Vue' 81 | 82 | /** 83 | * vue plugin 84 | */ 85 | export default { 86 | install (Vue, options) { 87 | /** 88 | * Custom merge strategy, this strategy is really just 89 | * Wraps all React components, while retaining the Vue components. 90 | */ 91 | const originalComponentsMergeStrategy = Vue.config.optionMergeStrategies.components 92 | 93 | Vue.config.optionMergeStrategies.components = function (parent, ... args) { 94 | // value set before <- return Object.assign (parent, wrappedComponents) 95 | const mergedValue = originalComponentsMergeStrategy (parent, ... args) 96 | 97 | // 98 | const wrappedComponents = mergedValue 99 | Object.entries (mergedValue) .reduce ( 100 | (acc, [k, v]) => ({ 101 | ... acc, 102 | [k]: isReactComponent (v)? VueResolver (v): v, 103 | }), 104 | {} 105 | ) 106 | : mergedValue 107 | //merge 108 | return Object.assign (parent, wrappedComponents) 109 | } 110 | }, 111 | } 112 | ``` 113 | 114 | 登場した 115 | 116 | - [install()](https://cn.vuejs.org/v2/guide/plugins.html#%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6) 117 | 118 | - [vue.configはㄡvueのグローバルコンフィグレーションを含むオブジェクトです. ](https://cn.vuejs.org/v2/api/#optionMergeStrategies) 119 | 120 | - [アプリケーションを起動する前に以下のプロパティを変更することができます: ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) 121 | 122 | ```js 123 | const obj = {foo: 'bar', baz: 42}; 124 | console.log (Object.entries (obj)); // [['foo', 'bar'], ['baz', 42]] 125 | ``` 126 | 127 | - [object.entries](https://developer.mozilla.org/wiki/Reduce) 128 | 129 | ```js 130 | var flattened = [[0, 1], [2, 3], [4, 5]]. reduce (function (a, b) { 131 | return a.concat (b); 132 | }, []); 133 | // flattened is [0, 1, 2, 3, 4, 5] 134 | ``` 135 | 136 | - array.prototype.reduce 137 | 138 | vueresolver 139 | 140 | > vuera / src / resolvers / vue.js 141 | 142 | 反応コンポーネント→vueコンポーネント`あなたはㄡあなたの側を横切ることの使用が`ビュー`コンポーネントが見つかった場合ㄡ`反応する`コンポーネントが変わったように見える`ビュー 143 | 144 | . 145 | 146 | # 第二に言う: 147 | 148 | vueinreact`追加する`ヴェラ/ベルベル`あなたの` `.babelrc`プラグイン 149 | 150 | オプション 151 | 152 | ```json 153 | { 154 | "presets": "react", 155 | "plugins": ["vuera/babel"] 156 | } 157 | ``` 158 | 159 | .babelrc 160 | 161 | ```jsx 162 | import React from 'react' 163 | import MyVueComponent from './MyVueComponent.vue' 164 | 165 | export default () => ( 166 |
167 |

I'm a react component

168 |
169 | 170 |
171 |
172 | ) 173 | ``` 174 | 175 | つかいます`追加の方法から`.babelrc 176 | 177 | * * * 178 | 179 | ㄡそれはバベルを使用することは可能ですか? 180 | 181 | ```js 182 | /* eslint-env node */ 183 | 184 | function processCreateElement (maybeReactComponent, args, file, path, types) { 185 | // If the first argument is a string (built-in React component), return 186 | if (maybeReactComponent.type === 'StringLiteral') return 187 | 188 | if (! file.insertedVueraImport) { 189 | file.path.node.body.unshift ( 190 | types.importDeclaration ( 191 | [ 192 | types.importSpecifier ( 193 | types.identifier ('__ vueraReactResolver'), 194 | types.identifier ('__ vueraReactResolver') 195 | ), 196 | ], 197 | types.stringLiteral ('vuera') 198 | ) 199 | ) 200 | } 201 | // Prevent duplicate imports 202 | file.insertedVueraImport = true 203 | 204 | // Replace React.createElement (component, props) with our helper function 205 | path.replaceWith ( 206 | types.callExpression (types.identifier ('__ vueraReactResolver'), [maybeReactComponent, ... args]) 207 | ) 208 | } 209 | 210 | // types is the babel plugin API? One of the modules 211 | module.exports = function ({types}) { 212 | return { 213 | visitor: { 214 | CallExpression (path, {file}) { 215 | const callee = path.node.callee 216 | const [maybeReactComponent, ... args] = path.node.arguments 217 | 218 | // If there is a react module, reactImport 219 | const reactImport = file.path.node.body.find ( 220 | x => x.type === 'ImportDeclaration' && x.source.value === 'react' 221 | ) 222 | if (! reactImport) return 223 | 224 | // if CallExpression is react.createElement 225 | if (callee.type === 'MemberExpression') { 226 | /** 227 | * Get the default import name Examples: 228 | * import React from 'react' => "React" 229 | * import hahaLOL from 'react' => "hahaLOL" 230 | */ 231 | const defaultImport = reactImport.specifiers.find ( 232 | x => x.type === 'ImportDefaultSpecifier' 233 | ) 234 | if (! defaultImport) return 235 | const reactName = defaultImport.local.name 236 | 237 | const {object, property} = callee 238 | if (! (object.name === reactName && property.name === 'createElement')) { 239 | return 240 | } 241 | 242 | processCreateElement (maybeReactComponent, args, file, path, types) 243 | } 244 | // Check CallExpression is react's 'createElement' 245 | if (callee.type === 'Identifier' && callee.name! == '__vueraReactResolver') { 246 | // Return unless createElement was imported 247 | const createElementImport = reactImport.specifiers.find ( 248 | x => x.type === 'ImportSpecifier' && x.imported.name === 'createElement' 249 | ) 250 | if (! createElementImport) return 251 | 252 | processCreateElement (maybeReactComponent, args, file, path, types) 253 | } 254 | }, 255 | }, 256 | } 257 | } 258 | ``` 259 | 260 | - [vuera / babel.js](https://github.com/babel/babel/tree/master/packages/babel-types) 261 | 262 | - [バベルタイプ](http://web.jobbole.com/88236/) 263 | 264 | - [バベル](https://github.com/thejameskyle/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md#toc-api) 265 | 266 | - [バーベル構文プラグイン中国語](http://astexplorer.net/) 267 | 268 | オンラインast構文ツリー`上記のリンクはあなたがシンプルで明確になるのを助けることができます` `バベル`プラグイン`プラグイン`importspecifier 269 | 270 | > メンバーの問題?`一般的にbabel.jsはㄡ`react.createelement``\`\_\_vuerareactresolver 271 | 272 | ```js 273 | export function babelReactResolver (component, props, children) { 274 | return isReactComponent (component) 275 | ? React.createElement (component, props, children) 276 | : React.createElement (VueWrapper, Object.assign ({component}, props), children) 277 | } 278 | 279 | // babelReactResolver as __vueraReactResolver 280 | ``` 281 | 282 | 組み込み関数 283 | 284 | * * * 285 | 286 | 上記はバベルがjsをダウングレードするために行う最後のものです 287 | 288 | vuewrapper上記のコードが最も重要です 289 | 290 | ```js 291 | import React from 'react' 292 | import Vue from 'vue' 293 | import ReactWrapper from './React' 294 | 295 | const VUE_COMPONENT_NAME = 'vuera-internal-component-name' 296 | 297 | const wrapReactChildren = (createElement, children) => 298 | createElement ('vuera-internal-react-wrapper', { 299 | props: { 300 | component: () =>
{children}
, 301 | }, 302 | }) 303 | 304 | export default class VueContainer extends React.Component { 305 | constructor (props) { 306 | super (props) 307 | 308 | /** 309 | * Incoming and redefinition of real components 310 | * `component` prop. 311 | */ 312 | this.currentVueComponent = props.component 313 | 314 | /** 315 | * Modify the createVueInstance function to pass this binding correctly. Doing this 316 | * Constructor Avoid rendering functions in the rendering. 317 | *// I feel a bit difficult to understand: translator said 318 | */ 319 | const createVueInstance = this.createVueInstance 320 | const self = this 321 | this.createVueInstance = function (element, component, prevComponent) { 322 | createVueInstance (element, self, component, prevComponent) 323 | } 324 | } 325 | 326 | componentWillReceiveProps (nextProps) { 327 | const {component, ... props} = nextProps 328 | 329 | if (this.currentVueComponent! == component) { 330 | this.updateVueComponent (this.props.component, component) 331 | } 332 | /** 333 | * NOTE: Did not compare props and nextprops, because I did not want to write a 334 | function for deep object comparison. I do not know if this hurts performance a lot, maybe 335 | * we do need to compare those objects. 336 | */ 337 | Object.assign (this.vueInstance. $ Data, props) 338 | } 339 | 340 | componentWillUnmount () { 341 | this.vueInstance. $ destroy () 342 | } 343 | 344 | /** 345 | * Create and load VueInstance interface 346 | * NOTE: VueInstance inside VueContainer 347 | * We can not bind createVueInstance to this VueContainer object and need to be explicit 348 | Pass the binding object 349 | * @param {HTMLElement} targetElement - element to attact the Vue instance to 350 | * @param {ReactInstance} reactThisBinding - current instance of VueContainer 351 | */ 352 | createVueInstance (targetElement, reactThisBinding) { 353 | const {component, ... props} = reactThisBinding.props 354 | 355 | // `this` refers to Vue instance in the constructor 356 | reactThisBinding.vueInstance = new Vue ({ 357 | // targetElement is VueContainer render () element 358 | el: targetElement, 359 | data: props, 360 | render (createElement) { 361 | return createElement ( 362 | VUE_COMPONENT_NAME, 363 | { 364 | props: this. $ data, 365 | }, 366 | [wrapReactChildren (createElement, this.children)] 367 | ) 368 | }, 369 | components: { 370 | [VUE_COMPONENT_NAME]: component, 371 | 'vuera-internal-react-wrapper': ReactWrapper, 372 | }, 373 | }) 374 | } 375 | 376 | updateVueComponent (prevComponent, nextComponent) { 377 | this.currentVueComponent = nextComponent 378 | 379 | /** 380 | * Replace the component in the Vue instance and update it. 381 | */ 382 | this.vueInstance. $ options.components [VUE_COMPONENT_NAME] = nextComponent 383 | this.vueInstance. $ forceUpdate () 384 | } 385 | 386 | render () { 387 | return
388 | } 389 | } 390 | ``` 391 | 392 | vuera / src / wrapper / vue.js`その`props.component`コンポーネントが`react.component`ㄡ次に`vue instance reactthisbinding.vueinstance 393 | 394 | - 内部で作成されます. `これと同じことはㄡ`<div ref = {this.createvueinstance} /> 395 | 396 | * * * 397 | 398 | ## vue react.componentコンポーネントのイベントハンドラ呼び出しの一部に渡します. 399 | 400 | ### 続きを見るisreactcomponent 401 | 402 | ```js 403 | export default function isReactComponent (component) { 404 | if (typeof component === 'object') { 405 | return false // no object == no react 406 | } else if ( 407 | typeof component === 'function' && 408 | component.prototype.constructor.super && 409 | component.prototype.constructor.super.name.startsWith('Vue') 410 | ) { 411 | return false // is vue 412 | } else { 413 | return true // is react 414 | } 415 | } 416 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vuera 2 | 3 | [![explain](http://llever.com/explain.svg)](https://github.com/chinanf-boy/Source-Explain) 4 | 5 | Explanation 6 | > "version": "0.1.3" 7 | 8 | [github source](https://github.com/topics/vuera) 9 | 10 | [中文版](./README.zh.md) 11 | 12 | [日文版](./README.ja.md) 13 | 14 | --- 15 | 16 | Run in react and vue at the same time. 17 | 18 | - [react on vue](#ReactInVue) 19 | 20 | - [vue at react](#VueInReact) 21 | 22 | --- 23 | 24 | - see more 25 | 26 | - [isReactComponent](#isreactcomponent) 27 | 28 | --- 29 | 30 | Two situations 31 | 32 | First said first: 33 | 34 | # ReactInVue 35 | 36 | ```js 37 | import Vue from 'vue' 38 | import {VuePlugin} from 'vuera' 39 | 40 | Vue.use (VuePlugin) 41 | /* ... */ 42 | ``` 43 | 44 | Now, use your React components like you will normally use your Vue components! 45 | 46 | ```vue 47 | 53 | 54 | 69 | ``` 70 | 71 | --- 72 | 73 | Use React as a Vue plugin 74 | 75 | vuera/src/VuePlugin.js 76 | ```js 77 | // Determine if React's component 78 | import isReactComponent from './utils/isReactComponent' 79 | // React Component -> Vue Component 80 | import VueResolver from './resolvers/Vue' 81 | 82 | /** 83 | * vue plugin 84 | */ 85 | export default { 86 | install (Vue, options) { 87 | /** 88 | * Custom merge strategy, this strategy is really just 89 | * Wraps all React components, while retaining the Vue components. 90 | */ 91 | const originalComponentsMergeStrategy = Vue.config.optionMergeStrategies.components 92 | 93 | Vue.config.optionMergeStrategies.components = function (parent, ... args) { 94 | // value set before <- return Object.assign (parent, wrappedComponents) 95 | const mergedValue = originalComponentsMergeStrategy (parent, ... args) 96 | 97 | // 98 | const wrappedComponents = mergedValue 99 | Object.entries (mergedValue) .reduce ( 100 | (acc, [k, v]) => ({ 101 | ... acc, 102 | [k]: isReactComponent (v)? VueResolver (v): v, 103 | }), 104 | {} 105 | ) 106 | : mergedValue 107 | //merge 108 | return Object.assign (parent, wrappedComponents) 109 | } 110 | }, 111 | } 112 | 113 | ``` 114 | 115 | Which appeared 116 | 117 | - [install ()](https://cn.vuejs.org/v2/guide/plugins.html#%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6) 118 | 119 | - [vue.config is an object that contains the global configuration of Vue. You can modify the following properties before starting the application:](https://cn.vuejs.org/v2/api/#optionMergeStrategies) 120 | 121 | - [Object.entries](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) 122 | 123 | ```js 124 | const obj = {foo: 'bar', baz: 42}; 125 | console.log (Object.entries (obj)); // [['foo', 'bar'], ['baz', 42]] 126 | ``` 127 | - [Array.prototype.reduce](https://developer.mozilla.org/wiki/Reduce) 128 | 129 | ```js 130 | var flattened = [[0, 1], [2, 3], [4, 5]]. reduce (function (a, b) { 131 | return a.concat (b); 132 | }, []); 133 | // flattened is [0, 1, 2, 3, 4, 5] 134 | ``` 135 | 136 | - VueResolver 137 | 138 | vuera/src/resolvers/Vue.js 139 | > React Component -> Vue Component 140 | 141 | So you can see that the use of traversing the side of the `` Vue`` component, if found, `` React`` component looks like it is transformed into `` Vue``. 142 | 143 | Say the second: 144 | 145 | # VueInReact 146 | 147 | Add `vuera/babel` to your` .babelrc` `plugins` option 148 | 149 | .babelrc 150 | ```json 151 | { 152 | "presets": "react", 153 | "plugins": ["vuera/babel"] 154 | } 155 | ``` 156 | 157 | use 158 | 159 | ```jsx 160 | import React from 'react' 161 | import MyVueComponent from './MyVueComponent.vue' 162 | 163 | export default () => ( 164 |
165 |

I'm a react component

166 |
167 | 168 |
169 |
170 | ) 171 | ``` 172 | 173 | From the way of adding `` .babelrc``, is it possible to use babel? 174 | 175 | --- 176 | 177 | vuera/babel.js 178 | ```js 179 | /* eslint-env node */ 180 | 181 | function processCreateElement (maybeReactComponent, args, file, path, types) { 182 | // If the first argument is a string (built-in React component), return 183 | if (maybeReactComponent.type === 'StringLiteral') return 184 | 185 | if (! file.insertedVueraImport) { 186 | file.path.node.body.unshift ( 187 | types.importDeclaration ( 188 | [ 189 | types.importSpecifier ( 190 | types.identifier ('__ vueraReactResolver'), 191 | types.identifier ('__ vueraReactResolver') 192 | ), 193 | ], 194 | types.stringLiteral ('vuera') 195 | ) 196 | ) 197 | } 198 | // Prevent duplicate imports 199 | file.insertedVueraImport = true 200 | 201 | // Replace React.createElement (component, props) with our helper function 202 | path.replaceWith ( 203 | types.callExpression (types.identifier ('__ vueraReactResolver'), [maybeReactComponent, ... args]) 204 | ) 205 | } 206 | 207 | // types is the babel plugin API? One of the modules 208 | module.exports = function ({types}) { 209 | return { 210 | visitor: { 211 | CallExpression (path, {file}) { 212 | const callee = path.node.callee 213 | const [maybeReactComponent, ... args] = path.node.arguments 214 | 215 | // If there is a react module, reactImport 216 | const reactImport = file.path.node.body.find ( 217 | x => x.type === 'ImportDeclaration' && x.source.value === 'react' 218 | ) 219 | if (! reactImport) return 220 | 221 | // if CallExpression is react.createElement 222 | if (callee.type === 'MemberExpression') { 223 | /** 224 | * Get the default import name Examples: 225 | * import React from 'react' => "React" 226 | * import hahaLOL from 'react' => "hahaLOL" 227 | */ 228 | const defaultImport = reactImport.specifiers.find ( 229 | x => x.type === 'ImportDefaultSpecifier' 230 | ) 231 | if (! defaultImport) return 232 | const reactName = defaultImport.local.name 233 | 234 | const {object, property} = callee 235 | if (! (object.name === reactName && property.name === 'createElement')) { 236 | return 237 | } 238 | 239 | processCreateElement (maybeReactComponent, args, file, path, types) 240 | } 241 | // Check CallExpression is react's 'createElement' 242 | if (callee.type === 'Identifier' && callee.name! == '__vueraReactResolver') { 243 | // Return unless createElement was imported 244 | const createElementImport = reactImport.specifiers.find ( 245 | x => x.type === 'ImportSpecifier' && x.imported.name === 'createElement' 246 | ) 247 | if (! createElementImport) return 248 | 249 | processCreateElement (maybeReactComponent, args, file, path, types) 250 | } 251 | }, 252 | }, 253 | } 254 | } 255 | 256 | ``` 257 | 258 | - [babel-types](https://github.com/babel/babel/tree/master/packages/babel-types) 259 | 260 | - [babel-AST](http://web.jobbole.com/88236/) 261 | 262 | - [babel-syntax plug-in Chinese](https://github.com/thejameskyle/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md#toc-api) 263 | 264 | - [Online AST Syntax Tree](http://astexplorer.net/) 265 | 266 | This part of the need to understand the AST syntax tree, the above link can help you to be simple and clear `babel` `` plugins`` plug-in ` ImportSpecifier`member problems? 267 | 268 | > babel.js in general, is to turn `` react.createElement`` into a `__vueraReactResolver`` built-in function 269 | 270 | ```js 271 | export function babelReactResolver (component, props, children) { 272 | return isReactComponent (component) 273 | ? React.createElement (component, props, children) 274 | : React.createElement (VueWrapper, Object.assign ({component}, props), children) 275 | } 276 | 277 | // babelReactResolver as __vueraReactResolver 278 | ``` 279 | 280 | The above is only the last thing babel does to downgrade js 281 | 282 | --- 283 | 284 | VueWrapper The above code is the most important 285 | 286 | vuera/src/wrapper/vue.js 287 | ```js 288 | import React from 'react' 289 | import Vue from 'vue' 290 | import ReactWrapper from './React' 291 | 292 | const VUE_COMPONENT_NAME = 'vuera-internal-component-name' 293 | 294 | const wrapReactChildren = (createElement, children) => 295 | createElement ('vuera-internal-react-wrapper', { 296 | props: { 297 | component: () =>
{children}
, 298 | }, 299 | }) 300 | 301 | export default class VueContainer extends React.Component { 302 | constructor (props) { 303 | super (props) 304 | 305 | /** 306 | * Incoming and redefinition of real components 307 | * `component` prop. 308 | */ 309 | this.currentVueComponent = props.component 310 | 311 | /** 312 | * Modify the createVueInstance function to pass this binding correctly. Doing this 313 | * Constructor Avoid rendering functions in the rendering. 314 | *// I feel a bit difficult to understand: translator said 315 | */ 316 | const createVueInstance = this.createVueInstance 317 | const self = this 318 | this.createVueInstance = function (element, component, prevComponent) { 319 | createVueInstance (element, self, component, prevComponent) 320 | } 321 | } 322 | 323 | componentWillReceiveProps (nextProps) { 324 | const {component, ... props} = nextProps 325 | 326 | if (this.currentVueComponent! == component) { 327 | this.updateVueComponent (this.props.component, component) 328 | } 329 | /** 330 | * NOTE: Did not compare props and nextprops, because I did not want to write a 331 | function for deep object comparison. I do not know if this hurts performance a lot, maybe 332 | * we do need to compare those objects. 333 | */ 334 | Object.assign (this.vueInstance. $ Data, props) 335 | } 336 | 337 | componentWillUnmount () { 338 | this.vueInstance. $ destroy () 339 | } 340 | 341 | /** 342 | * Create and load VueInstance interface 343 | * NOTE: VueInstance inside VueContainer 344 | * We can not bind createVueInstance to this VueContainer object and need to be explicit 345 | Pass the binding object 346 | * @param {HTMLElement} targetElement - element to attact the Vue instance to 347 | * @param {ReactInstance} reactThisBinding - current instance of VueContainer 348 | */ 349 | createVueInstance (targetElement, reactThisBinding) { 350 | const {component, ... props} = reactThisBinding.props 351 | 352 | // `this` refers to Vue instance in the constructor 353 | reactThisBinding.vueInstance = new Vue ({ 354 | // targetElement is VueContainer render () element 355 | el: targetElement, 356 | data: props, 357 | render (createElement) { 358 | return createElement ( 359 | VUE_COMPONENT_NAME, 360 | { 361 | props: this. $ data, 362 | }, 363 | [wrapReactChildren (createElement, this.children)] 364 | ) 365 | }, 366 | components: { 367 | [VUE_COMPONENT_NAME]: component, 368 | 'vuera-internal-react-wrapper': ReactWrapper, 369 | }, 370 | }) 371 | } 372 | 373 | updateVueComponent (prevComponent, nextComponent) { 374 | this.currentVueComponent = nextComponent 375 | 376 | /** 377 | * Replace the component in the Vue instance and update it. 378 | */ 379 | this.vueInstance. $ options.components [VUE_COMPONENT_NAME] = nextComponent 380 | this.vueInstance. $ forceUpdate () 381 | } 382 | 383 | render () { 384 | return
385 | } 386 | } 387 | ``` 388 | 389 | The `` props.component`` component is wrapped with `` React.Component``, and then the `` Vue instance reactThisBinding.vueInstance`` is created internally. 390 | 391 | - Equivalent to say that this part of the ``
`` to the Vue processing, and then part of Vue react.component component event handler call. 392 | 393 | --- 394 | 395 | ## see more 396 | 397 | ### isReactComponent 398 | 399 | ``` js 400 | export default function isReactComponent (component) { 401 | if (typeof component === 'object') { 402 | return false // no object == no react 403 | } else if ( 404 | typeof component === 'function' && 405 | component.prototype.constructor.super && 406 | component.prototype.constructor.super.name.startsWith('Vue') 407 | ) { 408 | return false // is vue 409 | } else { 410 | return true // is react 411 | } 412 | } 413 | 414 | ``` -------------------------------------------------------------------------------- /minilogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | background 27 | 28 | 29 | 30 | Layer 1 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | explain 40 | 41 | explain 42 | 43 | 44 | --------------------------------------------------------------------------------