react-vue-like

0.2.0 • Public • Published

react-vue-like

NPM version NPM downloads

write react component like vue, implementation based on mbox@4.

Table of Contents:

Installation

npm install react-vue-like --save
# or 
yarn add react-vue-like

Support Vue feature

props

will transfrom to react's propTypes and defaultProps

see Vue props

example:

class Test extends ReactVueLike.Component {
  static props = {
    aa: {
      type: String,
      default: 'aa',
      required: true,
    },
    bb: Boolean,
  }
}

equals:

import PropTypes from 'prop-types';
class Test extends ReactVueLike.Component {
  static propTypes = {
    aa: PropTypes.string.isRequired,
    bb: PropTypes.bool
  }
  static defaultProps = {
    aa: 'aa'
  }
}

components

if tag name has - char will be treat as a component that find from self's components section or root's components section.

see Vue components

example:

import AComponent from './AComponent';
 
class Test extends ReactVueLike.Component {
  static components = {
    AComponent
  }
 
  render() {
    return (<a-component>dd</a-component>)
  }
}

filters

example:

class Test extends ReactVueLike.Component {
  static filters = {
    prefix(val, suffix = '') {
      return `test:${val}${suffix}`;
    }
  }
 
  render() {
    return (<div>
      {// output: test:hello }
      { 'hello' | 'test' }  
      {// output: test:helloa suffix }
      { 'hello' | 'test'('a suffix') }
      </div>);
  }
}

directives

see Vue directives

example:

class Test extends ReactVueLike.Component {
 static directives = {
   test: {
     bind(el, binding, vnode) {
 
     },
     insert(el, binding, vnode) {
 
     },
     update(el, binding, vnode) {
 
     },
     unbind(el, binding, vnode) {
 
     },
   }
 }
 
 render() {
   return (<div v-test_arg$aa$bb={1+1} {/* or */} v-test:arg$aa$bb={1+1} ></div>);
 }
}

v-test_arg$aa$bb={1+1}, the binding will be:

 { 
   name: 'test', 
   arg: 'arg',
   value: 2,
   modifiers: {
     aa: true,
     bb: true
   },
   expression: '1+1'
}

mixin

see Vue mixin example:

class Test extends ReactVueLike.Component {
  static mixins = [
    {
      data() {
        return {
          text: 'aa',
        };
      },
      methods: {
        test() {
          console.log('test');
        }
      }
    }
  ]
  
 
  render() {
    return (<div>
      <span onClick={this.test}>{this.text}</span>
    </div>);
  }
}

data

see Vue data example:

class Test extends ReactVueLike.Component {
  static data() {
    return {
      text: 'aa',
      formData: {
        name: 'dddd'
      }
    }
  }
 
  render() {
    return (<div>
       <span>{this.text}</span>
       <span>{this.formData.name}</span>
      </div>);
  }
}

methods

see Vue methods

example:

class Test extends ReactVueLike.Component {
  static methods = {
    test1() {
      console.log('test1');
    }
  }
 
  test2() {
     console.log('test2');
  }
 
  render() {
    return (<div>
       <span onClick={this.test1}>aa</span>
       <span onClick={this.test2}>dd</span>
      </div>);
  }
}

computed

see Vue computed

example:

class Test extends ReactVueLike.Component {
  static data() {
    return {
      text: 'aa',
    }
  }
 
  static computed = {
    test1() {
      return `test1:${this.text}`;
    },
    test2: {
      get() {
        return `test2:${this.text}`;
      },
      set(v) {
        this.text = v;
      }
    }
  }
 
  render() {
    return (<div>
      <span>{this.text}</span>
      <span>{this.test1}</span>
      <span onClick={() => this.test2 = 'bb'}>{this.test2}</span>
      </div>);
  }
}

watch

see Vue watch

example:

class Test extends ReactVueLike.Component {
  static data() {
    return {
      text: 'aa',
    }
  }
 
  static watch = {
    text(newVal, olVal) {
      console.log('text chagned', newVal, oldVal);
    },
  }
 
  render() {
    return (<div>
      <span>{this.text}</span>
      <button onClick={() => this.text = 'bb'}>change</button>
      </div>);
  }
}

lifecycle

see Options-Lifecycle-Hooks

example:

class Test extends ReactVueLike.Component {
 
  breforeCreate() { }
 
  created() { }
 
  beforeMount() { }
 
  mounted() { }
 
  beforeUpdate() {  }
 
  updated() { }
 
  beforeDestroy() { }
 
  render() {
    return (<div>haha</div>);
  }
 
}

scoped style

if import's style file name has ?scoped, then it will treat as scoped style

example:

.aa .bb {
  height: 100%;
}
 
.aa .bb >>> .cc {
  background-color: red;
}
 
.aa .bb:scope > .cc {
  background-color: red;
}
 
:global .aa .bb {
  background-color: red;
}
 
import React from 'react';
import ReactVueLike from 'react-vue-like';
 
import './test.scss?scoped';
 
class Test extends ReactVueLike.Component {
 
  render() {
    return (<div className="aa">
      haha
      <span>dd</span>
      <a-component className="bb" />
    </div>);
  }
 
}

will transform to like this:

.aa .bb.v-123dse43 {
  height: 100%;
}
 
.aa .bb.v-123dse43 .cc {
  background-color: red;
}
 
.aa .bb.v-123dse43 > .cc {
  background-color: red;
}
 
.aa .bb {
  background-color: red;
}
 
import React from 'react';
import ReactVueLike from 'react-vue-like';
 
import './test.scss?scoped';
 
class Test extends ReactVueLike.Component {
 
  render() {
    return (<div className="v-123dse43 aa">
      haha
      <span className="v-123dse43">dd</span>
      <a-component className="v-123dse43 bb" />
    </div>);
  }
 
}

slot

example:

import React from 'react';
import ReactVueLike from 'react-vue-like';
 
class ChildComponent extends ReactVueLike.Component {
 
  render() {
    return (<div>
      <slot name="header">
      haha1
      {
        [1, 2, 3].map(v => <slot value={v} user={user} />)
      }
      haha2
      <slot name="footer">
    </div>);
  }
 
}
import React from 'react';
import ReactVueLike from 'react-vue-like';
import ChildComponent from './ChildComponent';
 
class ParentComponent extends ReactVueLike.Component {
 
  render() {
    return (<ChildComponent>
      {/* if child-component is `ReactVueLike Component` then it will has `$slots: { header, default, footer }` */}
      {/* if child-component is `React Component` then it will has `header, footer` attributes, default slot will be it's 'children'  */}
      <span slot="header">this is header</span>
 
      {/* scoped slot */}
      <template>
        ({ value, user }) => <span>this is body: {user.name}: {value}</span>
      </template>
 
      <span slot="footer">this is footer</span>
    </ChildComponent>);
  }
 
}

Vue Internal Directives

v-if/v-else-if/v-else,v-show,v-model, v-html see Vue Directives

example:

import React from 'react';
import ReactVueLike from 'react-vue-like';
 
class Test extends ReactVueLike.Component {
 
  static data() {
    return {
      vif: true,
      vshow: true,
      text1: '',
      text2: 0,
      text3: ''
    }
  }
 
  render() {
    return (<div>
      <span v-if={this.vif}>v-if showing</span>
      <span v-else>else showing</span>
 
      <span v-show={this.vshow}>
        v-show showing
      </span>
 
      <input v-model$trim={this.text1}></input>
      <input type="number" v-model$number={this.text2}></input>
      <input v-model$lazy={this.text3}></input>
 
      {/* equals: dangerouslySetInnerHTML={{ __html: "<a href='#'>dd</a>" }} */}
      <span v-html="<a href='#'>dd</a>"></span>
    </div>);
  }
 
}

Vuelike Internal Directives

v-observer see Mobx Observer

example:

import React from 'react';
import ReactVueLike from 'react-vue-like';
 
class Test extends ReactVueLike.Component {
 
  render() {
    return (<div>
      {/* will transform <Observer>{() => <span v-observer>ddd</span>}</Observer> */}
      <span v-observer>ddd</span>
      {/* will transform <Observer render={() => <span v-observer>ddd</span>}</Observer>} /> */}
      <span v-observer$render>ddd</span>
    </div>);
  }
 
}

Event Mechanism

you can use $emit to send event message to bind Event that bind by $on or onXXXX event. see Instance-Methods-Events

import React from 'react';
import ReactVueLike from 'react-vue-like';
import ChildComponent from './ChildComponent';
 
class ParentComponent extends ReactVueLike.Component {
 
  mounted() {
    this.$on('change-user', (user) => {
      console.log('user changed', user);
    })
  }
 
  handleClick() {
    console.log('you click');
  }
 
  handleCusomEvent(message) {
    console.log('handing custom event...', message);
  }
 
  render() {
    return (<ChildComponent onClick={this.handleClick} onCustomEvent={this.handleCusomEvent}>
    </ChildComponent>);
  }
 
}
import React from 'react';
import ReactVueLike from 'react-vue-like';
 
class ChildComponent extends ReactVueLike.Component {
 
  render() {
    return (<div>
      <button onClick={this.$emit('click')}>click</button>
      <button onClick={this.$parent.$emit('change-user', { name: 'james' })}>change user</button>
      <button onClick={this.$emit('custom-event', 'something')}>cusom event</button>
    </div>);
  }
 
}

attribute transform

img src attribute string value transform to require expression example:

class Test extends ReactVueLike.Component {
 
  render() {
    return (<div>
      { /* src will transform to `require('./image/pic1.png')` */ }
      <img src="./image/pic1.png">
    </div>);
  }
 
}

ref

string ref transform to ref function and set ref to $refs. seevue ref

class Test extends ReactVueLike.Component {
 
  static data() {
    return {
      list: [
        { key: 'a', value: 1 },
        { key: 'b', value: 2 },
        { key: 'c', value: 3 },
      ]
    }
  }
  
  test() {
    this.$refs.some.doSomething();
  }
 
  render() {
    return (<div>
      { /* 
      if ref value is string, then ref value will transform to `ref=>this.$refs['some']=ref`, otherwise do nothing.
      */ }
      <SomeComponent ref="some" onClick={this.test}></SomeComponent>
 
      {
        this.list.map((v, i/*if not has second param, will auto inject `$index` */) => {
          {/* support Array.map/filter/sort/slice/reverse */}
          return [
            {/* 
              will transform to:
              ref=> {
                if (!this.$refs['item1']) this.$refs['item1'] = [];
                this.$refs['item1'][$index] = ref;
              }
            */}
            <span ref="item1" key={v.key}>{v.value}</span>,
 
            {/* 
              will transform to:
              ref=> {
                if (!this.$refs['item1']) this.$refs['item1'] = {};
                this.$refs['item1'][v.key] = ref;
              }
            */}
            <span ref$key="item2" key={v.key}>{v.value}</span>,
          ]
        })
      }
    </div>);
  }
 
}

Vue like props

like $el,$options,$parent,$root,$refs,$slots,$attrs. seeInstance-Properties

Vue like methods

like $nextTick,$set,$delete,$watch,$emit,$on,$off,$once,renderError, ReactVueLike.use, ReactVueLike.configure. see Instance-Methods-Data and Instance-Methods-Events

Attrs Inheirt

default ReactVueLike component will inherit className, style, id, disabled attributes that be defined in it`s parent component

Class Attribute Support And Enhance

class attribute in jsx will transfrom to className, and now class/className support String/Array/Object types. see Vue class

class Test extends ReactVueLike.Component {
  
  static data() {
    return {
      myClass: {
        cc: true,
        dd: false
      }
    }
  }
 
  render() {
    return (<div class="root">
      <span className={['aa', 'bb', this.myClass, ['ee', 'ff'] ]}></span>
    </div>);
  }
 
}

Prop And Event Modifiers

see: v-model v-bind v-no

import Child from './child';
 
class Test extends ReactVueLike.Component {
 
  render() {
    return (<div>
      {/* equals <div aa={this.aa} onChangeAa={v=>this.aa=v}></div> */}
      <Child aa$sync={this.aa}></Child>
      <div onClick$stop={this.test}></div>
      <div onClick$prevent={this.test}></div>
      <div onClick$capture={this.test}></div>
      <div onClick$self={this.test}></div>
      <div onClick$native={this.test}></div>
      <div onClick$once={this.test}></div>
      <div onClick$left={this.test}></div>
      <div onClick$right={this.test}></div>
      <div onClick$middle={this.test}></div>
      <div onClick$passive={this.test}></div>
      <div onClick$enter={this.test}></div>
      <div onClick$13={this.test}></div>
    </div>);
  }
 
}
class Child extends ReactVueLike.Component {
 
  changeAa() {
    this.$emit('change:aa', 1);
  }
 
  render() {
    return (<div>
      <button onClick={this.changeAa}></button>
    </div>);
  }
 
}

Provide Inject

see provide/inject

import React from 'react';
import ReactVueLike from 'react-vue-like';
import ChildComponent from './ChildComponent';
 
class ParentComponent extends ReactVueLike.Component {
 
  static provide() {
    return {
      text: this.formData
    }
  }
 
  static data() {
    return {
      formData: {
        text: '111'
      }
    }
  }
 
  render() {
    return (<ChildComponent>
    </ChildComponent>);
  }
 
}
import React from 'react';
import ReactVueLike from 'react-vue-like';
 
class ChildComponent extends ReactVueLike.Component {
 
  static inject = ['formData'];
 
  render() {
    return (<div>{this.formData.text}</div>);
  }
 
}

Vuex Store

support Vuex.Store and mapState,mapMutations,mapGetters,mapActions,createNamespacedHelpers. see Vuex

Note: Store was moved to react-vuex-like.

store like Vuex.Store:

import { Store } from 'react-vuex-like';
 
const store = new Store({
  modules: {
    child1: {
      state: {
        aa: true
      }
    },
    child2: {
      state: {
        bb: true
      }
    }
  },
  state: {
    user: {
      name: 'name1'
    },
  },
  getters: {
    aa(state) {
      return state.globalLoading;
    }
  }
  mutations: {
    'update-user'(state, v) {
      state.user = v;
    },
    'update-user-info'(state, v) {
      Object.keys(v).forEach(key => state.user[key] = v[key]);
    }
  },
  actions: {
    'update-user-info'({ commit }, v) {
      commit('update-user', v);
    }
  },
});
 
export default store;

Other feature

Const Var

support __filename, __dirname, __packagename, __packageversion, __now

Instance Methods

function $computed(target, expr, value) defined a computed in ReactVueLike Component instance.

function $runAction(nameOfFn, fn?) run fn in mobx action, equals runInAction in mobx.

Static Methods

ReactVueLike.runAction(nameOfFn, fn?) - equals $runAction in component instance.

ReactVueLike.observable - observable method in mobx, just re-export.

ReactVueLike.flow - flow method in mobx, just re-export.

ReactVueLike.action - action method in mobx, just re-export.

ReactVueLike.set - set method in mobx, just re-export.

ReactVueLike.delete - remove method in mobx, just re-export.

ReactVueLike.config - config something in ReactVueLike, example: ReactVueLike.config({ enforceActions: true });

toJS, isObservable, isObservableProp, isObservableObject, isObservableArray, isObservableMap, isBoxedObservable, isArrayLike, isAction, isComputed, isComputedProp, observable, extendObservable, observe, decorate, reaction, intercept, computed, action, autorun, when, runInAction, createAtom, set, get, remove, has, flow, configure, onBecomeObserved, onBecomeUnobserved mobx methods, just re-export.

Usage

webpack.config.js:

{
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'css-loader',
          'react-vue-like/loader',
          ...
        ]
      },
      {
        test: /\.scss$/,
        use: [
          'css-loader',
          'react-vue-like/loader',
          ...
        ]
      },
      {
        test: /\.less$/,
        use: [
          'css-loader',
          'react-vue-like/loader',
          ...
        ]
      }
    ]
  }
}

babel.config.js

module.exports = {
    presets: [
    '@babel/preset-env',
    'react-vue-like/preset',
    '@babel/preset-react'
  ],
}

routes file:

// routes.js
import Test from './test';
 
const routes = [
  {
    path: '/',
    component: Test
  }
]
 
export default routes;

router file:

// router.js
import ReactViewRouter from 'react-view-router';
import routes from './routes';
 
const router = new ReactViewRouter({ routes });
 
export default router;

global filters:

// filters.js
export default {
  myFilter(value) {
    return `myFilter:${value}`;
  }
 
  install(ReactVueLike, { App }) {
    App.filters = this;
  }
}

global directives:

// directives.js
export default {
  test: {
    bind(el, binding, vnode) {
 
    }
  }
 
  install(ReactVueLike, { App }) {
    App.directives = this;
  }
}

global components:

import * as antd from 'antd';
 
const PREFIX = 'Ad';
function normalizeComps(comps, parentKey = '') {
  const COMP_REGX = /^[A-Z][A-Za-z]+/;
  let ret = {};
  Object.keys(comps).forEach(key => {
    if (!COMP_REGX.test(key)) return;
    const comp = comps[key];
    ret[`${PREFIX}${parentKey}${key}`] = comp;
    if (!parentKey) Object.assign(ret, normalizeComps(comp, key));
  });
  return ret;
}
 
// components.js
export default function install(ReactVueLike, { App }) {
  if (!App.components) App.components = {};
  Object.assign(App.components, normalizeComps(antd));
}

entry file:

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import ReactVueLike from 'react-vue-like';
import store from './store';
import router from './router';
import filters from './filters';
import directives from './directives';
import components from './components';
import App from 'react-vue-like';
 
ReactVueLike.config({ enforceActions: true });
 
ReactVueLike.use(store, { App });
ReactVueLike.use(router, { App });
ReactVueLike.use(filters, { App });
ReactVueLike.use(directives, { App });
ReactVueLike.use(components, { App });
 
router.beforeEach((to, from, next) => {
  if (to) {
    console.log(
      '%croute changed',
      'background-color:#ccc;color:green;font-weight:bold;font-size:14px;',
      to.url, to.query, to.meta, to.redirectedFrom
    );
    return next();
  }
});
 
ReactDOM.render(<App />, document.getElementById('#root'));

root ReactVueLike component:

  // app.jsx
import React from 'react';
import ReactVueLike from 'react-vue-like';
import { RouterView } from 'react-view-router';
import router from './router';
import SomeComponent from './SomeComponent';
// scoped css
import './app.scss?scoped';
 
class App extends ReactVueLike.Component {
 
  static isRoot = true
 
  static data() {
    return {
      formData: {
        text: ''
      }
    };
  }
 
 
  static computed = {
    computedText() {
      return `haha:${this.formData.text}`;
    },
  }
 
  static methods = {
    func1(v) {
      console.log('dddd', v);
    }
  }
 
  render() {
    return (<div class="root">
      {/* root RouterView need `router` prop */}
      <RouterView router={router} />
    </div>);
  }
 
}
 
export default App;
  // some-component.jsx
import React from 'react';
import ReactVueLike from 'react-vue-like';
// scoped css
import './some-component.scss?scoped';
 
class SomeComponent extends ReactVueLike.Component {
 
  static computed = {
    user() {
      return this.$store.state.user;
    }
  }
 
  static methods = {
    doSomething() {
      console.log('doSomething');
    }
  }
 
  // when render throw error, then will call renderError
  renderError(error) {
    return `render has some error:${error.message}`;
  }
 
  render() {
    return (<div>
      <slot name="header">
      haha1
      {
        [1, 2, 3].map(v => <slot value={v} user={user} />)
      }
      haha2
      <slot name="footer">
 
      {/* src will be transformed: require('./images/pic1.png') */}
      <img src="./images/pic1.png" />
    </div>);
  }
}
  // test.jsx
import React from 'react';
import ReactVueLike from 'react-vue-like';
// scoped css
import './test.scss?scoped';
 
class Test extends ReactVueLike.Component {
 
  static computed = {
    user() {
      return this.$store.state.user;
    }
  }
 
  static methods = {
    test() {
      this.$refs.some.doSomething();
    }
  }
 
  render() {
    return (<div>
      {/* donot need import,  it will find from it's root component's components section */}
      <some-component ref="some" onClick={this.test} />
    </div>);
  }
}

store like Vuex.Store:

import { Store } from 'react-vue-like';
 
const store = new Store({
  modules: {
    child1: {
      state: {
        aa: true
      }
    },
    child2: {
      state: {
        bb: true
      }
    }
  },
  state: {
    user: {
      name: 'name1'
    },
  },
  getters: {
    aa(state) {
      return state.globalLoading;
    }
  }
  mutations: {
    'update-user'(state, v) {
      state.user = v;
    },
    'update-user-info'(state, v) {
      Object.keys(v).forEach(key => state.user[key] = v[key]);
    }
  },
  actions: {
    'update-user-info'({ commit }, v) {
      commit('update-user', v);
    }
  },
});
 
export default store;

Note

  1. In ReactVueLike Component, try not to use this.props, please use this.$attrs instead. and you can use this.$slots.default instead of this.props.children;

  2. the prop name that bind to ReactVueLike Component, do not begin with '_'or'$' chars, they are recognized as internal values of ReactVueLike.

License

MIT

Package Sidebar

Install

npm i react-vue-like

Weekly Downloads

1

Version

0.2.0

License

none

Unpacked Size

451 kB

Total Files

99

Last publish

Collaborators

  • gxlmyacc