在开发小程序的时候,我们总是期望用以往的技术规范和语法特点来书写当前的小程序,所以才会有各色的小程序框架,例如 mpvue、taro 等这些编译型框架。当然这些框架本身对于新开发的项目是有所帮助。而对于老项目,我们又想要利用 vue 的语法特性进行维护,又该如何呢? 在此我研究了一下youzan的 vant-weapp。而发现该项目中的组件是如此编写的。 import { VantComponent } from '../common/component';VantComponent({ mixins: [], props: { name: String, size: String }, watch: { name(newVal) { ... }, size: 'changeSize' }, computed: { bigSize() { return this.data.size + 100 } }, data: { size: 0 }, methods: { onClick() { this.$emit('click'); }, changeSize(size) { this.set(size) } }, beforeCreate() {}, created() {}, mounted() {}, destroyed: {}});
居然发现该组件写法整体上类似于 Vue 语法。而本身却没有任何编译。看来问题是出在了导入的 VantComponet 这个方法上。下面我们开始详细介绍一下如何利用 VantComponet 来对老项目进行维护。 TLDR (不多废话,先说结论)小程序组件写法这里就不再介绍。这里我们给出利用 VantComponent 写 Page 的代码风格。 import { VantComponent } from '../common/component'; VantComponent({ mixins: [], props: { a: String, b: Number }, watch: {}, computed: { d() { return c++ } }, methods: { onLoad() {} }, created() {}, })
这里你可能感到疑惑,VantComponet 不是对组件 Component 生效的吗?怎么会对页面 Page 生效呢。事实上,我们是可以使用组件来构造小程序页面的。 在官方文档中,我们可以看到 使用 Component 构造器构造页面 事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用 Component 构造器构造,拥有与普通组件一样的定义段与实例方法。代码编写如下: Component({ behaviors: [myBehavior], properties: { paramA: Number, paramB: String, }, methods: { onLoad() { this.data.paramA this.data.paramB } }})
那么组件的生命周期和页面的生命周期又是怎么对应的呢。经过一番测试,得出结果为: (为了简便。只会列出 重要的的生命周期) component created -> component attched -> page onLoad -> component ready -> page OnUnload -> component detached
当然 我们重点不是在 onload 和 onunload 中间的状态,因为中间状态的时候,我们可以在页面中使用页面生命周期来操作更好。 某些时候我们的一些初始化代码不应该放在 onload 里面,我们可以考虑放在 component create 进行操作,甚至可以利用 behaviors 来复用初始化代码。 某种方面来说,如果不需要 Vue 风格,我们在老项目中直接利用 Component 代替 Page 也不失为一个不错的维护方案。毕竟官方标准,不用担心其他一系列后续问题。 VantComponent 源码解析VantComponent此时,我们对 VantComponent 开始进行解析 function mapKeys(source: object, target: object, map: object) { Object.keys(map).forEach(key => { if (source[key]) { target[map[key]] = source[key]; } });}function VantComponent<Data, Props, Watch, Methods, Computed>( vantOptions: VantComponentOptions< Data, Props, Watch, Methods, Computed, CombinedComponentInstance<Data, Props, Watch, Methods, Computed> > = {}): void { const options: any = {}; mapKeys(vantOptions, options, { data: 'data', props: 'properties', mixins: 'behaviors', methods: 'methods', beforeCreate: 'created', created: 'attached', mounted: 'ready', relations: 'relations', destroyed: 'detached', classes: 'externalClasses' }); const { relation } = vantOptions; if (relation) { options.relations = Object.assign(options.relations || {}, { [`../${relation.name}/index`]: relation }); } options.externalClasses = options.externalClasses || []; options.externalClasses.push('custom-class'); options.behaviors = options.behaviors || []; options.behaviors.push(basic); if (vantOptions.field) { options.behaviors.push('wx://form-field'); } options.options = { multipleSlots: true, addGlobalClass: true }; observe(vantOptions, options); Component(options);}
内置behaviors 组件样式隔离 basic behaviors刚刚我们谈到 basic behaviors,代码如下所示 export const basic = Behavior({ methods: { $emit() { this.triggerEvent.apply(this, arguments); }, getRect(selector: string, all: boolean) { return new Promise(resolve => { wx.createSelectorQuery() .in(this)[all ? 'selectAll' : 'select'](selector) .boundingClientRect(rect => { if (all && Array.isArray(rect) && rect.length) { resolve(rect); } if (!all && rect) { resolve(rect); } }) .exec(); }); } }});
observe小程序 watch 和 computed的 代码解析 export function observe(vantOptions, options) { const { watch, computed } = vantOptions; options.behaviors.push(behavior); if (watch) { const props = options.properties || {}; } Object.keys(watch).forEach(key => { if (key in props) { let prop = props[key]; if (prop === null || !('type' in prop)) { prop = { type: prop }; } prop.observer = watch[key]; props[key] = prop; } }); options.properties = props; } if (computed) { options.methods = options.methods || {}; options.methods.$options = () => vantOptions; if (options.properties) { observeProps(options.properties); } }}
observeProps现在剩下的也就是 observeProps 以及 behavior 两个文件了,这两个都是为了计算属性而生成的,这里我们先解释 observeProps 代码 export function observeProps(props) { if (!props) { return; } Object.keys(props).forEach(key => { let prop = props[key]; if (prop === null || !('type' in prop)) { prop = { type: prop }; } let { observer } = prop; prop.observer = function() { if (observer) { if (typeof observer === 'string') { observer = this[observer]; } observer.apply(this, arguments); } this.set(); }; props[key] = prop; });}
behavior最终 behavior,也就算 computed 实现机制 function setAsync(context: Weapp.Component, data: object) { return new Promise(resolve => { context.setData(data, resolve); });};export const behavior = Behavior({ created() { if (!this.$options) { return; } const cache = {}; const { computed } = this.$options(); const keys = Object.keys(computed); this.calcComputed = () => { const needUpdate = {}; keys.forEach(key => { const value = computed[key].call(this); if (cache[key] !== value) { cache[key] = needUpdate[key] = value; } }); return needUpdate; }; }, attached() { this.set(); }, methods: { set(data: object, callback: Function) { const stack = []; if (data) { stack.push(setAsync(this, data)); } if (this.calcComputed) { stack.push(setAsync(this, this.calcComputed())); } return Promise.all(stack).then(res => { if (callback && typeof callback === 'function') { callback.call(this); } return res; }); } }});
写在后面js 是一门灵活的语言(手动滑稽) 本身 小程序 Component 在 小程序 Page 之后,就要比Page 更加成熟好用,有时候新的方案往往藏在文档之中,每次多看几遍文档绝不是没有意义的。 小程序版本 版本2.6.1 Component 目前已经实现了 observers,可以监听 props data 数据监听器,目前 VantComponent没有实现,当然本身而言,Page 不需要对 prop 进行监听,因为进入页面压根不会变,而data变化本身就无需监听,直接调用函数即可,所以对page而言,observers 可有可无。 该方案也只是对 js 代码上有vue的风格,并没在 template 以及 style 做其他文章。 该方案性能一定是有所缺失的,因为computed是每次set都会进行计算,而并非根据set 的 data 来进行操作,在删减之后我认为本身是可以接受。如果本身对于vue的语法特性需求不高,可以直接利用 Component 来编写 Page,选择不同的解决方案实质上是需要权衡各种利弊。如果本身是有其他要求或者新的项目,仍旧推荐使用新技术,如果本身是已有项目并且需要维护的,同时又想拥有 Vue 特性。可以使用该方案,因为代码本身较少,而且本身也可以基于自身需求修改。 同时,vant-weapp是一个非常不错的项目,推荐各位可以去查看以及star。 |