mpregular

<p align="center"> <img src="https://haitao.nos.netease.com/2758f92e-c498-4768-add3-c15a88c90390.gif" alt="mpregular" width="400px" /> </p>

Usage no npm install needed!

<script type="module">
  import mpregular from 'https://cdn.skypack.dev/mpregular';
</script>

README

mpregular

基于 RegularJS 实现的小程序开发框架,可以直接借助 RegularJS 语法和生态进行小程序开发。保证开发体验一致性的同时,也提高了 web 应用小程序应用 之间代码互相转换的效率。

特性

  • 一致的开发体验
  • 更低的学习成本
  • 更强的语法表现力
  • 更强的性能表现

template

HTML 标签和小程序标签存在差异,比如 div 和 view 之间的关系,RegularJS 的模版语法和小程序模版语法也不一样,因此需要在编译时对模版进行转换。

小程序的原生组件可以在 RegularJS 模版中直接使用,对应的属性名和事件名都是一样的,但需要注意的是事件绑定的语法稍有不同,需要使用 RegularJs 的语法进行事件绑定。

例如:

小程序:

<scroll-view
  scroll-y
  bindscroll="onScroll"
></scroll-view>

mpregular:

<scroll-view
  scroll-y
  on-scroll="{ this.onScroll( $event ) }"
></scroll-view>

Page 生命周期

  1. config,regular
  2. onLoad
  3. init,regular 页面组件及各个子组件
  4. onReady
  5. onShow
  6. onHide
  7. onUnload

如何使用:

export default {
  mpType: 'page',
  config() {
    // this.$mp.options 与 onLoad 中的 options 相同
    console.log('config', this.$mp.options);
  },
  init() {
    console.log('init');
  },
  onLoad(options) {
    console.log('onLoad', options);
  },
  onReady() {
    console.log('onReady');
  },
  onShow() {
    console.log('onShow');
  },
  onHide() {
    console.log('onHide');
  },
  onPullDownRefresh() {
    console.log('PullDownRefresh');
  },
  onReachBottom() {
    console.log('ReachBottom');
  },
  onPageScroll(options) {
    console.log('PageScroll', options);
  }
}

建议只在最外层的 Regular 实例中使用小程序相关的钩子函数,这样在转回 web 代码的时候工作量会相对减少

App 生命周期

TODO

支持特性

事件绑定

事件绑定遵循 Regular 的事件绑定语法,利用 on- 指令,对于小程序的原生事件的绑定,也是一样,将 bind 替换成 on-

比如 click 事件对应小程序中 tap 事件。

<button on-click="{ this.onClick($event) }">click</button>

对于小程序中特有的事件绑定方式,在 Regular 中利用指令修饰符实现。

catch{event} -> on-{event}.catch

<div on-touchstart.catch="{ this.onTouchStart($event) }"></div>

capturebind{event} -> on-{event}.capture

<div on-touchstart.capture="{ this.onTouchStart($event) }"></div>

catch-capture{event} -> on-{event}.catch-capture

<div on-touchstart.catch-capture="{ this.onTouchStart($event) }"></div>

处理函数中所返回的事件对象与小程序原生事件对象一致,保持不变。

filter

<span>{ time | dateFormat: 'yyyy-MM-dd' }</span>
import Regular from 'regularjs';

cosnt App = Regular.extend({

}).filter('dateFormat', function() {
  // your filter code here
});

export default App;

这里需要注意下写法,需要用到 Regular.extend,下面的方式目前是不支持的(后面版本会加上)

export default {
  filters: {
    // ...
  }
}

{#inc this.$body}

<!-- <Modal> definition -->
<div class="modal">
  {#inc this.$body}
</div>

<!-- use -->
<Modal>
  <head>Tips</head>
  <section>It's a tip</section>
</Modal>

目前只支持静态的模版,不支持动态的字符串编译,例如 #{inc templateStr} 是不支持的

{#if}

{#if mode === 1}
  <div>1</div>
{#elseif mode === 2}
  <div>2</div>
{#else}
  <div>other</div>
{/if}

语法与 Regular 一致。

{#list}

{#list soure as item by item_index}
  <div on-click="{ this.onItemClick(item) }">{ item.name }</div>
{/list}

语法与 Regular 一致,注意不要对大型列表进行 list 操作,否则容易出现性能问题。

指令

r-model

<input r-model="{ title }">
<textarea r-model="{ article }">

r-hide

隐藏元素指令。

<div r-hide="{ !show }"></div>

r-html

利用第三方开源库 wxParse 将 html 转换成 wxml 进行渲染。

<div r-html="{ !htmlStr }"></div>

r-style

<div r-style="{{ height: '30px' }}"></div>

r-class

<button r-class="{{ rounded: isRounded }}"></button>

异常处理

全局异常处理,在 Regular 对象上定义异常处理方法 _errorHandler

import Regular from 'regularjs'

Regular._errorHandler = function(info, error, vm) {
  console.log('global handle ====== start')
  console.log(info, error, vm)
  console.log('global handle ====== end')
}

页面和组件中异常处理,在页面或组件中定义异常处理方法 _errorHanlder

export default {
  mpType: 'page',
  _errorHandler(info, error, vm) {
    console.log('handle ====== start')
    console.log(info, error, vm)
    console.log('handle ====== end')
  }
}

执行优先级,组件 -> 页面 -> 全局

会捕获生命周期钩子和事件处理函数的执行异常,如果没有定义任何 _errorHandler,默认行为是打印异常信息。

性能优化

小程序官方文档中特别强调 setData 传递大数据时会大量占用 WebView JS 线程,对此 mpregular 做了特别的优化。

收集 view 中使用的数据

仅仅收集 template 中用到的数据,过滤没有用到的数据,将插值表达式执行的结果计算完成后,再通过 setData 传递到 view 线程。这样做,可以极大减少传输的数据量。

例如:

<!-- RegularJS template -->
<span>{ largeData.info.countdown.time }</span>
<!-- 转换后的小程序 wxml -->
<text>{ __holders[0] }</text>

对于上面这个插值,通常的做法是将 largeData 传递给 view,然后由 view 去解析这个对象,得到目标值。但是,如果这个值挂在一个非常大的对象上,那么每次都需要传递这个大对象,性能将会非常差。

mpregular 的做法是,每次值更改时,先将这个表达式的值计算出来,在这个例子里就是从 largetData 上取到 time 这个值,再将值写到 __holders[0], 通过 setData 传递给 view。这样每次更新的数据从一个大对象缩减到一个字符串,性能上会有很大提升。

同时,对于很复杂的插值表达式,也可以通过这种方式将计算结果设置到对应的 __holders 上。模版中的 filter、复杂表达式等特性,就是通过这一个机制实现的。

缓存更新数据,定期更新

频繁调用 setData 传递数据也会造成性能问题,因此 mpregular 会设置一个 buffer 缓存更新数据,这段事件内重复的数据会进行 merge,并定时进行更新,目前这个时间是 50ms。这一机制的实现与 mpvue 时类似的。

仍可能存在性能问题的情况

虽然 mpregular 对数据更新进行了内部优化,但是存在一些情况,无法避免大数据的传递。

{#list largeList as item}
<span>{ item.name }</span>
{/list}

{#list} 表达式里面会直接把 largeList 传递到 view 去进行遍历。所以,如果要遍历一个数组,最好是将 view 中要遍历的列表精简成只包含 view 中要使用的属性,提高数据更新时的性能。当然,也不必要过度优化,当渲染大型列表出现性能问题时,你可以尝试从这个角度去处理问题。

暂不支持

  • r-animation

工具

demo

reference