小程序框架WePY 从入门到放弃踩坑合集

一点点介绍WePY

因为小程序的语法设计略迷,
所以x1 模块化起来并不方便,
所以x2 各厂就出了不少的框架用以方便小程序的开发,
腾讯看到别人家都出了框架了那看来我的小程序可能真的不太方便开发咯,那我也出一个吧🤔
所以x3 WePY 就这么横空出世了。

那小鹅鹅🐧 你早干嘛去了啊,为什么不把小程序设计的好用一点点!程序员好好玩是吧!

同样是仿原生应用你就不能学学人家Electron 把界面交给网页原生HTML 去处理么,搞一堆贼没用组件各种奇奇怪怪的问题。。

程序员何苦为难程序猿啊😂

不好意思激动了🙄

上面👆 都是我吹的其实WePY 是最早的小程序框架之一😂

WePY 是一个开发风格类似Vue.js, 支持类似Props, Mixin, Computed, Slot 等特性的风格开发,
支持组件化/模块化,NPM 依赖,Promise/Async, ESnext, Less/Sass/Styus, Babel/Typescript 的一个小程序开发框架。

语法上类似Vue.js 多一点,作为一个React 党我并没有很喜欢,但可能Vue 党也不一定就喜欢。
毕竟它只是像,并没有很一样,开发起来跟做连连看似的跟Vue.js 各种行为不一致。。

闲话就说到这了,开始说正事。

那些要注意的点

此节内容参考 <u>小色小魔</u> 于 <u>2018-03-20</u> 发布在 Segmentfault 的文章 Wepy-小程序踩坑记

ALL CREDIT GOES TO HIM THANKS ALOT

首先是文档类:

WePY首页

WePY官方文档

小程序官方文档

WePY Github Issues

然后是一些要点:

1. Data 及数据绑定

1.1 对于可能用到的所有数据要预先给值,即要先在data 中初始化;否则之后更新数据时将不会触发脏数据检查流程,也就不会触发页面重渲染。

1.2 更新数据时可以直接用 this.foo = bar, 不用微信原生的 this.setData 方法。

class Fn extends wepy.page {
  data = {
    foo: 1,
    // bar: 2, 此处不声明 bar
  }

  someFunction() {
    // 页面更新
    this.foo = 10;
    // 这不生效
    this.bar = 20;
  }
}

1.3 数据在模板中进行绑定时可以用 :foo=bar 的形式进行绑定。注意非字符类型的(比如说布尔值)及变量要加双大括号,否则当成字符串处理。其他的非绑定的,要直接显示的内容可直接用双大括号包住即可。双大括号内可进行简单的运算,但不支持JS 模板字符串或其他方法运算。

// Template
<component :name="{{myName}}">  // 绑定数据
  // 正常:渲染内容
  {{Greeting}}

  // 正常:渲染内容
  {{Greeting + 'World!'}}

  // 报错:不支持 JS模板字符串
  {{`${Greeting} World!`}}

  // 报错:不支持 JS 方法调用
  {{String(Greeting).trim()}}
</component>
// Script
class Foo extends wepy.page {
  data = {
    myName: 'WePY',
    greetings: 'Hello',
  }
}

1.4 操作方法用 @tap=function 的形式进行绑定。基本上所有微信原生的方法 bindFn=fn 都可以直接写成 @Fn=fn.

// Template
// 原生的 bindtap=fn
<view @tap="fn"></view>

// Script
class Foo extends wepy.page {
  methods = {
    fn: () => {},
  }
}

2. Methods & Computed

2.1 class 中的methods 中只包含模板用的事件方法,其他的自定义方法应该与methods 本身平级,处于顶层。

// Template
<view @tap="fn"></view>

// Script
class Foo extends wepy.page {
  methods = {
    fn: () => {
      this.doSomeThing();
    },
  }

  doSomeThing: () => {}
}

2.2 模板事件提供了“方法后缀”作为语法糖以代替原生写法,有.default, .stop.user 后缀,比较常用的是 .stop,相当于原生的 catch.

// Template
// .default 可不写
<view @tap="fn"></view>
// .stop 相当于 catch, 点击事件到此结束,不会向上层组件冒泡
<view @tap.stop="fn"></view>

2.3 此外还提供了computed 及watcher 基本与Vue.js 类似,就不介绍了~

3. Mixin & Components

3.1 Mixin 有后端编程的小伙伴应该会比较熟悉一点,可以将通用方法提供给不同地方使用。在这里它能直接读取共有的数据(比如说从Redux 里读取的内容等)但使用时要注意当Mixin 中用到了来自Redux 中的数据时,数据本身的connect 要在其具体引用到的页面里面定义,貌似Mixin 本身并不能将Redux 中的数据connect 进来。

3.2 Mixin 中的methods 中不能直接使用 this.foo = bar 去设置页面中的值,因为上下文不一致了不会生效。

3.3 Mixin 中的执行顺序与Vue.js 中的相反。Vue.js 中是先执行Mixin 中的函数, 再执行组件本身的函数。这里执行顺序相反。

补充:Vue.js 中对于钩子函数,会先执行Mixin 中的,再执行组件自身的;
Vue.js 中methods 如果和Mixin 同名,那么只会执行自身的方法。

3.4 组件在 import 时不要带后缀名:

// Good ;-)
import Xxx from '../xx';

// Damned :-(
import Xxx from '../xx.wpy';

3.5 最让人懊恼的东西来了—— WePY 中的组件。它是组件全是“单例”的,这意味着当你在同一个页面中去使同一个组件多次时,显示出来的那“多”个组件它绑定的内容都会被更新成最后一个,你不能给他们绑定到不同的内容上,你用组件时只能享受到样式上的优势,完全不知所想何物🤷‍♂️

// Template
<view wx:for="{{list}}">
  <comp :number.sync="{{item}}"></comp>  // 你也不能这样做,因为它不接受变量
</view>
// Script
import Comp from '../Comp';

class Foo extends wepy.page {
  data = {
    list: [1, 2, 3, 4],
  }

  components = {
    comp: Comp,
  }
}

难受 = =||

3.6 对于原生及非原生组件,它的模板逻辑也总是“看上去不那么固定”的,比如说:

// 正确 - 省略为 true
<scroll-view scroll-y></scroll-view>

// 错误 - 布尔值不能带括号
<scroll-view scroll-y="{{true}}"></scroll-view>

// 正确 - 估计这里实际上可能 "true" 是被忽略了的
<scroll-view scroll-y="true"></scroll-view>

// 正确 - 但此时这个方法接受到的第一个参数为当前Class的实例,坑爹啊
<view @tap="toggleSomeThing"></view>

// 错误 - 布尔值要带括号
<view @tap="toggleSomeThing(true)"></view>

// 正确
<view @tap="toggleSomeThing({{true}})"></view>

3.7 还有一个偶发的情况是 wx:if 时,class 等其他属性偶发会被“忽略掉”。

// 正常
<view wx:if="{{something === 'foo'}}" class="foo">

// 偶尔会不正常,class 被忽略掉了
<view class="bar" wx:if="{{something === 'bar'}}">

所以总体上比较建议将条件先写出来,后面再加其他属性,或者直接用 block 标签去作为条件判断。

3.8 Page 继承自Component, 但Page 扩展了页面所特有的 config 配置以及特有的页面生命周期函数等;所以Component 和Page 之间基本一样,但Component 没有 onLoad 等生命周期。

4. This 及内定方法

4.1 更新数据时可以直接用 this.foo = bar, 不用微信原生的 this.setData 方法。

4.2 所有可能发生异步的地方修改数据后要手动促发脏数据检查流程,否则不会促发页面重渲染。
常见的地方有请求后更新,setTimeOut, getStorageSync 等等~

5. canvas & base64

Canvas 的相关操作和微信原生一样,参照 这里 即可:

ArrayBuffer 和 Base64 互转:

v1.1.0 (2017.03.31) 版本就有这两个 API 了,可以直接用:

wx.arrayBufferToBase64
wx.base64ToArrayBuffer

6. 其他,一点开发建议

6.1 小程序内部定义的实例API 都以 $ 开头,所以我们命名时也不应该以 $ 开头,以便区分。

6.2 同样的,如果用了Redux, 那么connect 进来的方法也会是通过 this.methods.fn() 的形式访问,建议也得区分开比较好,比如说页面及组件中的方法加bind 前缀。像这样:

// Redux Action
this.methods.function();

// Page Methods
this.methods.bindFunction();

开发中写的一些有用的 Snippets

跨页面通讯

这是小程序原生的代码,用于更新前一个页面中的Data 值

应用场景比如说有一个表单类的页面,其中的某个字段可以点到另外一个全屏的界面去编辑

改完了保存后会回到当前的页面中继续编辑其他的字段

// 获取当前页面栈的实例 <Array>
let crtPage = getCurrentPages();
// 得到上一个页面实例 <Object>
let prevPage = crtPage[crtPage.length - 2];
// 设置前一个页面中的数据
prevPage.setData({note: this.value});
/*
 * 这个做法可以正常设置前一个页面中的Data 的值,
 * 但如果那个页面中的那个Data 是用作Props 传给组件的话
 * 组件中的值并不会跟着更新成新的值
 */

正则解析城市字符串,用于微信原生省市选择组件

因为有个项目的后端是保存的类似 "上海市上海市徐汇区" 的值

但微信的组件使用的value 却是类似 ['上海市', '上海市', '徐汇区'] 这样的数组

为了保证修改时能正确显示上次设置的内容所以就有了这么一个东西

/*
  * Parse String to Wechat City Array
  *
  * @param:
  *  city <String>: '上海市上海市徐汇区'
  *
  * @return:
  *  <Array> ['上海市', '上海市', '徐汇区']
  */
const parseRegion = (city) => {
  let str = city;
  let region = [];
  let reg = [
    /\S[^省|市|区]+[省|市|区]/,
    /\S[^市|区]+[市|区]/,
    /\S[^市|区|县]+[市|区|县]/,
  ];

  for (let r of reg) {
    const rs = r.exec(str);
    if (rs !== null) {
      region.push(rs[0]);
      str = str.replace(rs[0], '');
    }
  }

  return region;
};

URL 参数序列化

用途:当小程序需要栏截操作时,临时保存传入的参数

// onLoad(op) { this.op = op; }

// reLaunch 时 url = some-url + '?' + this.serializer(this.op)
serializer = (data = {}) => (
  Object.entries(data).map(
    ([key, value]) => (
      value !== undefined
        ? `${key}=${encodeURIComponent(value)}`
        : ''
    )
  )
  .filter((item) => (item))
  .join('&')
);

阿兵
7 声望2 粉丝