TS与小程序的深度结合
前几篇都是讲如何用TS来写代码,那么如何将TS应用于小程序呢?或者让TS更好的应用的小程序?
小程序代码主要是组件
和页面
两个模块,那么接下来我们就从组件
和页面
开始封装。
前置知识
如何将官方的小程序代码写法,变个样?
官方的推荐的小程序代码的写法是这样的
// Component
Component({
properties: {},
data: {},
attached() {},
detached() {},
methods: {},
// more...
})
// Page
Page({
data: {},
onLoad() {},
onShow() {},
onHide() {},
onUnload() {},
// more...
})
他们的本质上是Page
或Component
接受一个对象作为参数。
那么我们可以得出他们的另一种ES6的写法
// Component
class Demo {
properties = {};
data = {};
attached() {}
detached() {}
methods = {};
}
Component(new Demo())
为什么可以这样写?
我们先看看编babel译成ES5的代码
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Demo = (function () {
function Demo() {
this.properties = {};
this.data = {};
Demo.prototype.attached = function() {};
Demo.prototype.detached = function() {};
this.methods = {};
}
return Demo;
}());
Component(new Demo());
我们用ES6写法最后传给Component
任然是一个满足官方要求有的属性方法的对象
。
用TS写小程序怎么写
官方的写法
type TSizeValue = 'large' | 'medium' | '' | 'small';
type TSizeProp = {
type: StringConstructor,
value: TSizeValue
}
Component({
properties: {
// 它规定了开发者 只能给size 传入 'large' | 'medium' | '' | 'small'
// 这四选一属性,这样在开发的时候就能起到了约束作用
size: <TSizeProp>{
type: String,
value: ''
}
},
data: {
value: ''
},
methods: {
handleClick(e: any):void {}
}
})
优点:能检测到官方定义的类型声明文件。
缺点:这样写,其实很难体现TS的语言特性,很憋足。我这么用接口?怎么体现class 修饰符?
期望写法是怎样的?
// component
class DemoComp implements Component {
properties = {};
data = {};
methods = {};
// more...
}
// page
class DemoPage extends BasePage {
public onLoad() {}
public onShow() {}
// more...
}
优点:像正常写TS文件一样去写小程序,能充分利用TS语言特性
缺点:...
那么实现这一效果,需要有三个条件
- 如何让
class XXX
具有官方定义的类型声明 - 如何去掉
Component(new Demo())
这种代码,让TS代码更纯粹、更优雅 - 如何兼容低版本SDK
如何让class XXX
具有官方定义的类型声明
这无疑是最核心的条件
要实现这个功能,我们需要先来看官方定义文件,这里用Page
封装距离,Component
原理封装类似
// lib.wx.page.d.ts
declare namespace WechatMiniprogram {
namespace Page {
type Instance<
TData extends DataOption,
TCustom extends CustomOption
> = OptionalInterface<ILifetime> &
InstanceProperties &
InstanceMethods<TData> &
Data<TData> &
TCustom
type Options<
TData extends DataOption,
TCustom extends CustomOption
> = (TCustom & Partial<Data<TData>> & Partial<ILifetime>) &
ThisType<Instance<TData, TCustom>>
type TrivialInstance = Instance<IAnyObject, IAnyObject>
interface Constructor {
<TData extends DataOption, TCustom extends CustomOption>(
options: Options<TData, TCustom>,
): void
}
....
}
declare const Page: WechatMiniprogram.Page.Constructor
我们的实际写TS
代码是Page({})
,那么不难发现,我们实际是实现的是
interface Constructor {
<TData extends DataOption, TCustom extends CustomOption>(
options: Options<TData, TCustom>,
): void
}
我们传入的对象实际的类型定义是options: Options<TData, TCustom>
那么我们只需要实现Options
这个声明了。先看其定义
type Options<
TData extends DataOption,
TCustom extends CustomOption
> = (TCustom & Partial<Data<TData>> & Partial<ILifetime>) &
ThisType<Instance<TData, TCustom>>
我们可以轻易写出让class 具有Options
的什么
// BasePage.ts
class BasePage implements WechatMiniprogram.Page.Options<
WechatMiniprogram.Page.DataOption,
WechatMiniprogram.Page.CustomOption
> {
}
这样BasePage
就具备了Options
定义的属性和方法声明。
但是当在业务里面使用的时候
// 引入 BasePage.ts
class DemoPage extends BasePage {
public onLoad() {
this.setData({}) // 类型“DemoPage”上不存在属性“setData”
}
}
如何让BasePage
具有setData
的方法声明?
// 看setData 在哪里定义的
type InstanceMethods<D extends DataOption> = Component.InstanceMethods<D>
那么我们只需要实现这个类型就行了,那么问题来了?
如何让class BasePage
既具有type Options
的类型声明又具有type InstanceMethods
的类型声明
答案就是:声明合并
// BasePage.ts
interface BasePage extends WechatMiniprogram.Page.InstanceMethods<WechatMiniprogram.Page.DataOption> {
}
class BasePage implements WechatMiniprogram.Page.Options<
WechatMiniprogram.Page.DataOption,
WechatMiniprogram.Page.CustomOption
> {
}
这样BasePage
达到刚刚的目的了。
如何去掉Component(new Demo())
这种代码,让TS代码更纯粹、更优雅
通过构建过程中,给TS文件插入类似Page(new DemoPage())
这样的代码,这里不再赘述了
如何兼容低版本SDK
为什么还有这个问题?
运行上面这段代码,在SDK 2.7.2
以下的版本会报如下错误。
// error 1
Uncaught TypeError: Cannot assign to read only property 'constructor' of object '#<V>'
at G (VM63 WAService.js:1)
at Object.t (VM63 WAService.js:1)
at mt (VM63 WAService.js:1)
at Rt (VM63 WAService.js:1)
at index.ts:8
at require (VM63 WAService.js:1)
at <anonymous>:11:7
at HTMLScriptElement.scriptLoaded (appservice?t=1569326156124:1147)
at HTMLScriptElement.script.onload (appservice?t=1569326156124:1159)
// error 2
Page is not constructed because it is not found.
怎么解决?
interface BasePage extends WechatMiniprogram.Page.InstanceMethods<WechatMiniprogram.Page.DataOption> {
}
class BasePage implements WechatMiniprogram.Page.Options<WechatMiniprogram.Page.DataOption, WechatMiniprogram.Page.CustomOption> {
constructor() {
// @ts-ignore
delete this.__proto__.constructor
}
}
最终效果
import BasePage from '@lib/Page';
export default class OrderConfirm extends BasePage {
private options: WashOrderConfirm.TPageOptions = {
orderId: ''
};
public data = {
test: ''
};
public onLoad(options: WashOrderConfirm.TPageOptions) {
this.options = options;
this.testMethods();
}
private testMethods () {
this.setData({ test: 'demo' })
}
}