san-composition
Version:
San Composition API
633 lines (523 loc) • 15.8 kB
JavaScript
/**
* Copyright (c) Baidu Inc. All rights reserved.
*
* This source code is licensed under the MIT license.
* See LICENSE file in the project root for license information.
*
* @file San组合式API
*/
export const version = '__VERSION__';
/**
* 用于定义组件数据的临时对象
* @type {Object?}
*/
let context;
/**
* 用于存储多个context
* @type {Array}
*/
let contexts = [];
function componentInitData() {
return this.__scContext.initData;
}
/**
* 组件生命周期钩子
* @type {Array}
*/
const LIFECYCLE_HOOKS = [
'construct',
'compiled',
'inited',
'created',
'attached',
'detached',
'disposed',
'updated',
'error'
];
function componentInitLifeCycle() {
LIFECYCLE_HOOKS.forEach(lifeCycle => {
const hooks = this.__scContext[lifeCycle];
if (hooks) {
let len = hooks.length;
this[lifeCycle] = function (...args) {
for (let i = 0; i < len; i++) {
hooks[i].apply(this, args);
}
};
}
});
}
function componentInitComputed() {
let computed = this.__scContext.computed;
if (computed) {
let names = Object.keys(computed);
for (let i = 0; i < names.length; i++) {
let name = names[i];
let fn = computed[name];
let computedDatas = [];
this.__scContext.computedDatas = computedDatas;
let watcher = getComputedWatcher(name, fn);
watcher.call(this);
for (let j = 0; j < computedDatas.length; j++) {
this.watch(computedDatas[j], watcher);
}
this.__scContext.computedDatas = null;
}
}
}
function componentInitWatch() {
let watches = this.__scContext && this.__scContext.watches;
if (watches) {
let component = this.__scContext.component;
let names = Object.keys(watches);
for (let i = 0; i < names.length; i++) {
let name = names[i];
this.watch(name, watches[name].bind(component));
}
}
}
function componentCleanOnDisposed() {
if (this.__scContext) {
this.__scContext.publicContext.dispose();
this.__scContext = null;
}
}
function getComputedWatcher(name, fn) {
return function () {
let value = fn.call(this);
this.data.set(name, value);
};
}
/**
* 处理template方法
*
* @param {string} tpl 组件的模板,支持tagged template string
*/
export function template(tpl) {
if (context.creator) {
if (tpl instanceof Array) {
let realTpl = tpl[0];
for (let i = 1, l = tpl.length; i < l; i++) {
realTpl += arguments[i] + tpl[i];
}
context.template = realTpl;
}
else {
context.template = tpl;
}
}
};
/**
* template配置项
*
* @param {Object} 组件的模板配置项,包括:trimWhitespace、delimiters、autoFillStyleAndId
*/
export function templateOptions({trimWhitespace, delimiters, autoFillStyleAndId}) {
if (context.creator) {
context.trimWhitespace = trimWhitespace;
context.delimiters = delimiters;
context.autoFillStyleAndId = autoFillStyleAndId;
}
};
/**
* 组件数据的代理类
* @class DataProxy
*/
class DataProxy {
/**
* 组件数据的代理类
*
* @param {string|Array} name 数据的key,如果是通过键值对声明的数据,则name是一个数组
*/
constructor(name, component) {
this.name = name;
this.component = component;
}
/**
* get方法
*
* 1. const info = data('info', 'san composition api');
* info.get(); // 'san composition api'
*
* 2. 获取value为对象形式的数据
* const info = data('info', {name: 'jinz', company: 'baidu'})
* info.get() // {name: 'jinz', company: 'baidu'}
* info.get('name') // 'jinz',等价于: this.data.get('info.name')
*
* @param {string?} name 获取 data 方法设置的数据的 名称
*/
get(name) {
let fullName = name ? this._resolveName(name) : this.name;
let scContext = this.component.__scContext;
let computedDatas = scContext && scContext.computedDatas;
if (computedDatas) {
computedDatas.push(fullName);
}
return this.component.data.get(fullName);
}
/**
* set的用法
*
* 1. const info = data('info', 'san composition api');
* info.set('sca');
* info.get(); // 'sca'
*
* 2. 设置value为对象形式的数据
* const info = data('info', {name: 'jinz', company: 'baidu'})
* info.set('name', 'erik') // 'jinz',等价于: this.data.set('info.name', 'erik')
*
* 支持2种参数形式
*/
set(nameOrValue, value) {
if (typeof value === 'undefined') {
this.component.data.set(this.name, nameOrValue);
}
else {
this.component.data.set(this._resolveName(nameOrValue), value);
}
}
/**
* 将传入数据对象(source)与 data 合并,进行批量更新
* 作用类似于 JavaScript 中的 Object.assign
*
* @param {string|Object} nameOrSource
* @param {Object?} source
*/
merge(nameOrSource, source) {
if (source) {
this.component.data.merge(this._resolveName(nameOrSource), source);
}
else {
this.component.data.merge(this.name, nameOrSource);
}
}
apply(nameOrFn, fn) {
if (fn) {
this.component.data.apply(this._resolveName(nameOrFn), fn);
}
else {
this.component.data.apply(this.name, nameOrFn);
}
}
push(nameOrItem, item) {
if (typeof item === 'undefined') {
return this.component.data.push(this.name, nameOrItem);
}
return this.component.data.push(this._resolveName(nameOrItem), item);
}
pop(name) {
if (typeof name === 'string') {
return this.component.data.pop(this._resolveName(name));
}
return this.component.data.pop(this.name);
}
shift(name) {
if (typeof name === 'string') {
return this.component.data.shift(this._resolveName(name));
}
return this.component.data.shift(this.name);
}
unshift(nameOrItem, item) {
if (typeof item === 'undefined') {
return this.component.data.unshift(this.name, nameOrItem);
}
return this.component.data.unshift(this._resolveName(nameOrItem), item);
}
remove(nameOrItem, item) {
if (typeof item === 'undefined') {
return this.component.data.remove(this.name, nameOrItem);
}
return this.component.data.remove(this._resolveName(nameOrItem), item);
}
removeAt(nameOrIndex, index) {
if (typeof index === 'undefined') {
return this.component.data.removeAt(this.name, nameOrIndex);
}
return this.component.data.removeAt(this._resolveName(nameOrIndex), index);
}
splice(nameOrArgs, args) {
if (typeof args === 'undefined') {
return this.component.data.splice(this.name, nameOrArgs);
}
return this.component.data.splice(this._resolveName(nameOrArgs), args);
}
/**
* 将传入的数据项附加 this.name,转换成实际数据项
*
* @private
* @param {string} name
* @return {string}
*/
_resolveName(name) {
return this.name + (/^[\[.]/.test(name) ? name : '.' + name);
}
}
/**
* 操作数据的API,提供 get 和 set 方法
*
* @param {string} name 数据的名称
* @param {*} value 初始值
* @returns {Object} 返回一个带有包装有 this.data 相关数据操作API的对象
*/
export function data(name, value) {
if (context.creator) {
return;
}
if (typeof name !== 'string') {
return;
}
if (!context.initData) {
context.initData = {};
}
context.initData[name] = value;
let dataDefs = context.publicContext._dataDefs;
if (dataDefs[name]) {
return dataDefs[name];
}
return (dataDefs[name] = new DataProxy(name, context.component));
};
class ComputedProxy {
constructor(name, component) {
this.name = name;
this.component = component;
}
get() {
let scContext = this.component.__scContext;
let computedDatas = scContext && scContext.computedDatas;
if (computedDatas) {
computedDatas.push(this.name);
}
return this.component.data.get(this.name);
}
}
export function computed(name, fn) {
if (context.creator) {
return;
}
if (typeof name !== 'string') {
return;
}
if (!context.computed) {
context.computed = {};
}
context.computed[name] = fn;
return new ComputedProxy(name, context.component);
}
/**
* 创建组件类成员API的高阶函数,
* 负责:filters/components API创建
*
* @param {string} memberName 类成员名称
* @returns {Function}
*/
function classMemberCreator(memberName) {
/**
* 创建组件属性API方法
* 参数可以是key、val两个参数,也可以是对象的形式
*
* @param {string|Object} name 数据的key,或者键值对
* @param {Function} handler 添加的函数
*/
return function (name, value) {
if (context.creator) {
context[memberName] = context[memberName] || {};
switch (typeof name) {
case 'string':
context[memberName][name] = value;
break;
case 'object':
Object.assign(context[memberName], name);
}
}
};
}
export const filters = classMemberCreator('filters');
export const components = classMemberCreator('components');
/**
* 创建生命周期钩子方法的高阶函数
*
* @param {string} name 生命周期钩子名称,inited, attached...等
* @returns {Function}
*/
function hookMethodCreator(name) {
/**
* 创建生命周期钩子方法的函数
*
* @param {Function} handler 生命周期钩子,回调方法
*/
return function (handler) {
if (context.creator) {
return;
}
context[name] = context[name] || [];
context[name].push(handler);
};
}
export const onConstruct = hookMethodCreator('construct');
export const onCompiled = hookMethodCreator('compiled');
export const onInited = hookMethodCreator('inited');
export const onCreated = hookMethodCreator('created');
export const onAttached = hookMethodCreator('attached');
export const onDetached = hookMethodCreator('detached');
export const onDisposed = hookMethodCreator('disposed');
export const onUpdated = hookMethodCreator('updated');
export const onError = hookMethodCreator('error');
export function messages(name, value) {
if (context.creator) {
return;
}
let component = context.component;
switch (typeof name) {
case 'string':
if (!component.messages) {
component.messages = {};
}
component.messages[name] = value;
break;
case 'object':
if (!component.messages) {
component.messages = name;
}
else {
Object.assign(component.messages, name);
}
}
}
export function watch(name, value) {
if (context.creator) {
return;
}
if (!context.watches) {
context.watches = {};
}
switch (typeof name) {
case 'string':
context.watches[name] = value;
break;
case 'object':
Object.assign(context.watches, name);
}
}
/**
* 为组件添加方法
* 参数可以是key、val两个参数,也可以是对象的形式
*
* @param {string|Object} name 数据的key,或者键值对
* @param {Function} handler 添加的函数
* @return {Function|Object} 返回添加的方法或方法集
*/
export function method(name, value) {
if (!context.creator) {
switch (typeof name) {
case 'string':
context.component[name] = value;
break;
case 'object':
Object.assign(context.component, name);
}
}
return value || name;
}
class PublicComponentContext {
constructor(component) {
this.component = component;
this._dataDefs = {};
}
data(name) {
let dataProxy = this._dataDefs[name];
if (!dataProxy) {
dataProxy = this._dataDefs[name] = new DataProxy(name, this.component);
}
return dataProxy;
}
dispatch(name, value) {
if (this.component) {
this.component.dispatch(name, value);
}
}
fire(name, event) {
if (this.component) {
this.component.fire(name, event);
}
}
ref(name) {
if (this.component) {
return this.component.ref(name);
}
}
nextTick(fn, thisArg) {
if (this.component) {
this.component.nextTick(fn, thisArg);
}
}
dispose() {
this.component = null;
}
}
/**
* 通过组合式API定义San组件
*
* @param {Function} creator 通过调用组合式API的方法
* @param {Object} san
* @return {Function} 返回 san.defineComponent 定义的类
*/
export function defineComponent(creator, san) {
let defineContext = {
creator: creator
};
context = defineContext;
contexts.push(context);
// 执行san组合api
creator();
// 重置 context
contexts.pop();
context = contexts[contexts.length - 1];
const ComponentClass = function (options) {
this.__scContext = {
component: this,
publicContext: new PublicComponentContext(this),
inited: [componentInitComputed],
attached: [componentInitWatch]
};
context = this.__scContext;
contexts.push(context);
let creatorAsInstance = defineContext.creator;
creatorAsInstance(context.publicContext);
contexts.pop();
context = contexts[contexts.length - 1];
if (this.__scContext.disposed) {
this.__scContext.disposed.push(componentCleanOnDisposed);
}
else {
this.__scContext.disposed = [componentCleanOnDisposed];
}
this.__scInitLifeCycle();
san.Component.call(this, options);
};
function Empty() {}
Empty.prototype = san.Component.prototype;
ComponentClass.prototype = new Empty();
ComponentClass.prototype.constructor = ComponentClass;
ComponentClass.prototype.initData = componentInitData;
ComponentClass.prototype.__scInitLifeCycle = componentInitLifeCycle;
if (defineContext.template) {
ComponentClass.prototype.template = defineContext.template;
}
if (defineContext.filters) {
ComponentClass.prototype.filters = defineContext.filters;
}
if (defineContext.components) {
ComponentClass.prototype.components = defineContext.components;
}
if (defineContext.trimWhitespace != null) {
ComponentClass.prototype.trimWhitespace = defineContext.trimWhitespace;
}
if (defineContext.delimiters != null) {
ComponentClass.prototype.delimiters = defineContext.delimiters;
}
if (defineContext.autoFillStyleAndId != null) {
ComponentClass.prototype.autoFillStyleAndId = defineContext.autoFillStyleAndId;
}
return ComponentClass;
};