在开辟小顺序的时刻,我们老是希冀用以往的技术规范和语法特征来誊写当前的小顺序,所以才会有各色的小顺序框架,比方 mpvue、taro 等这些编译型框架。固然这些框架本身关于新开辟的项目是有所协助。而关于老项目,我们又想要应用 vue 的语法特征举行保护,又该怎样呢?
在此我研讨了一下youzan的 vant-weapp。而发明该项目中的组件是云云编写的。
import { VantComponent } from '../common/component';
VantComponent({
mixins: [],
props: {
name: String,
size: String
},
// 能够运用 watch 来监控 props 变化
// 实在就是把properties中的observer提取出来
watch: {
name(newVal) {
...
},
// 能够直接运用字符串 替代函数挪用
size: 'changeSize'
},
// 运用盘算属性 来 猎取数据,能够在 wxml直接运用
computed: {
bigSize() {
return this.data.size + 100
}
},
data: {
size: 0
},
methods: {
onClick() {
this.$emit('click');
},
changeSize(size) {
// 运用set
this.set(size)
}
},
// 对应小顺序组件 created 周期
beforeCreate() {},
// 对应小顺序组件 attached 周期
created() {},
// 对应小顺序组件 ready 周期
mounted() {},
// 对应小顺序组件 detached 周期
destroyed: {}
});
竟然发明该组件写法团体上类似于 Vue 语法。而本身却没有任何编译。看来题目是出在了导入的 VantComponet 这个要领上。下面我们最先细致引见一下怎样应用 VantComponet 来对老项目举行保护。
TLDR (不多空话,先说结论)
小顺序组件写法这里就不再引见。这里我们给出应用 VantComponent 写 Page 的代码作风。
import { VantComponent } from '../common/component';
VantComponent({
mixins: [],
props: {
a: String,
b: Number
},
// 在页面这里 watch 基本上是没有作用了,由于只做了props 变化的watch,page不会涌现 props 变化
// 背面会细致申明为什么
watch: {},
// 盘算属性依旧可用
computed: {
d() {
return c++
}
},
methods: {
onLoad() {}
},
created() {},
// 其他组件生命周期
})
这里你能够以为迷惑,VantComponet 不是对组件 Component 见效的吗?怎样会对页面 Page 见效呢。事实上,我们是能够运用组件来组织小顺序页面的。
在官方文档中,我们能够看到 运用 Component 组织器组织页面
事实上,小顺序的页面也能够视为自定义组件。因此,页面也能够运用 Component 组织器组织,具有与一般组件一样的定义段与实例要领。代码编写以下:
Component({
// 能够运用组件的 behaviors 机制,虽然 React 以为 mixins 并不是一个很好的计划
// 然则在某种程度该计划确实能够复用雷同的逻辑代码
behaviors: [myBehavior],
// 对应于page的options,与此本身是有范例的,而从options 获得数据均为 string范例
// 接见 页面 /pages/index/index?paramA=123¶mB=xyz
// 假如声明有属性 paramA 或 paramB ,则它们会被赋值为 123 或 xyz,而不是 string范例
properties: {
paramA: Number,
paramB: String,
},
methods: {
// onLoad 不须要 option
// 然则页面级别的生命周期却只能写道 methods中来
onLoad() {
this.data.paramA // 页面参数 paramA 的值 123
this.data.paramB // 页面参数 paramB 的值 ’xyz’
}
}
})
那末组件的生命周期和页面的生命周期又是怎样对应的呢。经由一番测试,得出效果为: (为了轻便。只会列出 主要的的生命周期)
// 组件实例被建立 到 组件实例进入页面节点树
component created -> component attched ->
// 页面页面加载 到 组件在视图层规划完成
page onLoad -> component ready ->
// 页面卸载 到 组件实例被从页面节点树移除
page OnUnload -> component detached
固然 我们重点不是在 onload 和 onunload 中心的状况,由于中心状况的时刻,我们能够在页面中运用页面生命周期来操纵更好。
某些时刻我们的一些初始化代码不应该放在 onload 内里,我们能够斟酌放在 component create 举行操纵,以至能够应用 behaviors 来复用初始化代码。
某种方面来讲,假如不须要 Vue 作风,我们在老项目中直接应用 Component 替代 Page 也不失为一个不错的保护计划。毕竟官方规范,不必忧郁其他一系列后续题目。
VantComponent 源码剖析
VantComponent
此时,我们对 VantComponent 最先举行剖析
// 赋值,依据 map 的 key 和 value 来举行操纵
function mapKeys(source: object, target: object, map: object) {
Object.keys(map).forEach(key => {
if (source[key]) {
// 目的对象 的 map[key] 对应 源数据对象的 key
target[map[key]] = source[key];
}
});
}
// ts代码,也就是 泛型
function VantComponent<Data, Props, Watch, Methods, Computed>(
vantOptions: VantComponentOptions<
Data,
Props,
Watch,
Methods,
Computed,
CombinedComponentInstance<Data, Props, Watch, Methods, Computed>
> = {}
): void {
const options: any = {};
// 用function 来拷贝 新的数据,也就是我们能够用的 Vue 作风
mapKeys(vantOptions, options, {
data: 'data',
props: 'properties',
mixins: 'behaviors',
methods: 'methods',
beforeCreate: 'created',
created: 'attached',
mounted: 'ready',
relations: 'relations',
destroyed: 'detached',
classes: 'externalClasses'
});
// 对组件间关联举行编辑,然则page不须要,能够删除
const { relation } = vantOptions;
if (relation) {
options.relations = Object.assign(options.relations || {}, {
[`../${relation.name}/index`]: relation
});
}
// 对组件默许增加 externalClasses,然则page不须要,能够删除
// add default externalClasses
options.externalClasses = options.externalClasses || [];
options.externalClasses.push('custom-class');
// 对组件默许增加 basic,封装了 $emit 和小顺序节点查询要领,能够删除
// add default behaviors
options.behaviors = options.behaviors || [];
options.behaviors.push(basic);
// map field to form-field behavior
// 默许增加 内置 behavior wx://form-field
// 它使得这个自定义组件有类似于表单控件的行动。
// 能够研讨下文给出的 内置behaviors
if (vantOptions.field) {
options.behaviors.push('wx://form-field');
}
// add default options
// 增加组件默许设置,多slot
options.options = {
multipleSlots: true,// 在组件定义时的选项中启用多slot支撑
// 假如这个 Component 组织器用于组织页面 ,则默许值为 shared
// 组件的apply-shared,能够研讨下文给出的 组件款式断绝
addGlobalClass: true
};
// 监控 vantOptions
observe(vantOptions, options);
// 把当前重新设置的options 放入Component
Component(options);
}
basic behaviors
方才我们谈到 basic behaviors,代码以下所示
export const basic = Behavior({
methods: {
// 挪用 $emit组件 实际上是运用了 triggerEvent
$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) {
// 从传入的 option中获得 watch computed
const { watch, computed } = vantOptions;
// 增加 behavior
options.behaviors.push(behavior);
/// 假如有 watch 对象
if (watch) {
const props = options.properties || {};
// 比方:
// props: {
// a: String
// },
// watch: {
// a(val) {
// // 每次val变化时刻打印
// consol.log(val)
// }
}
Object.keys(watch).forEach(key => {
// watch只会对prop中的数据举行 看管
if (key in props) {
let prop = props[key];
if (prop === null || !('type' in prop)) {
prop = { type: prop };
}
// prop的observer被watch赋值,也就是小顺序组件本身的功用。
prop.observer = watch[key];
// 把当前的key 放入prop
props[key] = prop;
}
});
// 经由此要领
// props: {
// a: {
// type: String,
// observer: (val) {
// console.log(val)
// }
// }
// }
options.properties = props;
}
// 对盘算属性举行封装
if (computed) {
options.methods = options.methods || {};
options.methods.$options = () => vantOptions;
if (options.properties) {
// 看管props,假如props发作转变,盘算属性本身也要变
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 };
}
// 保留之前的 observer,也就是上一个代码天生的prop
let { observer } = prop;
prop.observer = function() {
if (observer) {
if (typeof observer === 'string') {
observer = this[observer];
}
// 挪用之前保留的 observer
observer.apply(this, arguments);
}
// 在发作转变的时刻挪用一次 set 来重置盘算属性
this.set();
};
// 把修正的props 赋值归去
props[key] = prop;
});
}
behavior
终究 behavior,也就算 computed 完成机制
// 异步挪用 setData
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;
}
});
// 返回须要的更新的 computed
return needUpdate;
};
},
attached() {
// 在 attached 周期 挪用一次,算出当前的computed数值
this.set();
},
methods: {
// set data and set computed data
// set能够运用callback 和 then
set(data: object, callback: Function) {
const stack = [];
// set时刻放入数据
if (data) {
stack.push(setAsync(this, data));
}
if (this.calcComputed) {
// 有盘算属性,一样也放入 stack中,然则每次set都邑挪用一次,props转变也会挪用
stack.push(setAsync(this, this.calcComputed()));
}
return Promise.all(stack).then(res => {
// 一切 data以及盘算属性都完成后挪用callback
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。