Reactive

// @namespace Reactive
import { Reactive } from 'rcfm'

Custom Types

@type {ContextInstance} ReactiveInstance
@description - Reactive 实例

@prop {DependencyInstance} dependency - 依赖关系实例
@prop {DispatcherInstance} dispatcher - 订阅器实例
@prop {ExecutorInstance} executor - 任务执行器实例
@prop {FamilyInstance} family - 家族关系实例
@prop {HookInstance} hooks - 钩子函数管理器实例
@prop {CalculatorInstance} calculator - 计算器实例
@prop {ValidatorInstance} validator - 校验器实例
@prop {SchemaInstance} schema - DSL 解析器实例
@prop {Object} members - 环境成员状态存储空间
@typedef {Object} ReactiveMemberType
@description - Reactive 成员类型

@prop {string} type - 类型名称
@prop {ContextHook[]} [hooks] - 钩子定义数组
@prop {Function} [collect = reactive#collect] - 自定义搜集该类型成员状态函数
@prop {Function} [seed = reactive#seed] - 自定义填充该类型成员属性设定值函数
@prop {Object} [schema = {}]
@prop {ReactiveMemberPropSchema[]} [schema.props] - 成员属性定义
@prop {ReactiveMemberPropSchema} [schema.fallback = ReactiveMemberFallbackPropSchema] - 成员属性缺省定义
@prop {Object} [reference = {}]
@prop {Function} [reference.parse = reactive#schema.reference.parse] - 自定义成员路径转唯一标识及属性名称方法
@prop {Function} [reference.stringify = reactive#schema.reference.stringify] - 自定义成员唯一标识及属性名称转路径方法
@typedef {Object} ReactiveMemberPropSchema
@description - Reactive 成员属性定义

@prop {string} prop - 属性名称
@prop {boolean} [schemable = true] - 是否启用 DSL 解析
@prop {boolean} [calculable = true] - 是否启用计算表达式解析 schemable  true 时有效
@prop {*} [<prop>] - 任意自定义属性配置通常需要钩子函数实现做支持
@typedef {Object} ReactiveMemberFallbackPropSchema
@description - Reactive 成员属性缺省定义

@prop {string}  prop = <a_random_unique_id>
@prop {boolean} schemable = true
@prop {boolean} calculable = true
@prop {null}  default = null - 属性初始设定值
@prop {null}  rawValidation = null - 属性当前值校验规则
@prop {null}  validation = null - 属性设定值校验规则
@type {ContextMember} ReactiveDefaultMember
@description - Reactive 默认成员

@prop {Object} props - 成员属性设定值构成的对象
@prop {string} [props.type = "default"] - 成员类型
@prop {string} [props.name = <a_random_unique_id>] - 成员名称
@prop {integer} [props.order = Infinity] - 成员在同辈中的顺序设定值
@prop {Object[]} props.children = [] - ContextInstance 自动维护的成员子级设定值
@prop {string} props.children[i].id - 子成员唯一标识
@prop {*} props.<custom> - 其它任意自定义属性设定值

Exception Types

@type {ExceptionInstance}
@description - 成员类型冲突

@prop {string} type = "REACTIVE_CONFLICTED_MEMBER_TYPE"
@prop {string} [name = type]
@prop {Object} [data = {}]
@prop {string} [message]
@type {ExceptionInstance}
@description - 成员类型未注册

@prop {string} type = "REACTIVE_UNINSTALLED_MEMBER_TYPE"
@prop {string} [name = type]
@prop {Object} [data = {}]
@prop {string} [message]
@type {ExceptionInstance}
@description - 成员存在依赖约束

@prop {string} type = "REACTIVE_CONSTRAINTS_EXIST"
@prop {string} [name = type]
@prop {Object} [data = {}]
@prop {string} [message]
@type {ExceptionInstance}
@description - 成员名称冲突

@prop {string} type = "REACTIVE_CONFLICTED_MEMBER_NAME"
@prop {string} [name = type]
@prop {Object} [data = {}]
@prop {string} [message]
@type {ExceptionInstance}
@description - 被依赖者不存在

@prop {string} type = "REACTIVE_REFERENCED_PROVIDER_NOT_FOUND"
@prop {string} [name = type]
@prop {Object} [data = {}]
@prop {string} [message]
@type {ExceptionInstance}
@description - 不合规的父成员

@prop {string} type = "REACTIVE_INVALID_PARENT"
@prop {string} [name = type]
@prop {Object} [data = {}]
@prop {string} [message]
@type {ExceptionInstance}
@description - 钩子函数不存在

@prop {string} type = "REACTIVE_HOOK_NOT_EXIST"
@prop {string} [name = type]
@prop {Object} [data = {}]
@prop {string} [message]
@type {ExceptionInstance}
@description - 条件语句不合规

@prop {string} type = "CONDITION_INVALID_CONDITION"
@prop {Object} data = {dueto: "whens"}
@prop {string} [name = type]
@prop {string} [message = <global_i18n_defination>]
@type {ExceptionInstance}
@description - 条件语句布尔表达式不合规

@prop {string} type = "CONDITION_INVALID_CONDITION"
@prop {Object} data = {branchIndex: <invalid_branch_index>, dueto: "rules"}
@prop {string} [name = type]
@prop {string} [message = <global_i18n_defination>]
@type {ExceptionInstance}
@description - 条件语句布尔表达式关键字不合规

@prop {string} type = "CONDITION_INVALID_CONDITION"
@prop {Object} data = {branchIndex: <invalid_branch_index>, ruleIndex: <invalid_rule_index>, dueto: "logic"}
@prop {string} [name = type]
@prop {string} [message = <global_i18n_defination>]
@type {ExceptionInstance}
@description - 条件语句布尔表达式中的被依赖者不合规

@prop {string} type = "CONDITION_INVALID_CONDITION"
@prop {Object} data = {branchIndex: <invalid_branch_index>, ruleIndex: <invalid_rule_index>, dueto: "provider"}
@prop {string} [name = type]
@prop {string} [message = <global_i18n_defination>]
@type {ExceptionInstance}
@description - 条件语句布尔表达式中的校验方法名或校验规则不合规

@prop {string} type = "CONDITION_INVALID_CONDITION"
@prop {Object} data = {branchIndex: <invalid_branch_index>, ruleIndex: <invalid_rule_index>, dueto: "validation"}
@prop {string} [name = type]
@prop {string} [message = <global_i18n_defination>]
@type {ExceptionInstance}
@description - 条件语句分支结果(成员属性设定值)校验未通过

@prop {string} type = "VALIDATOR_VALIDATION_FAILED_RAW"
@prop {Object} data = {branchIndex: <invalid_branch_index>, dueto: "result"}
@prop {string} [name = type]
@prop {string} [message = <global_i18n_defination>]
@type {ExceptionInstance}
@description - 成员属性当前值校验未通过

@prop {string} type = "VALIDATOR_VALIDATION_FAILED"
@prop {Object} [data = {}]
@prop {string} [name = type]
@prop {string} [message = <global_i18n_defination>]

Builtin Hooks

  • 预设并冻结成员 type

    当成员被创建后,优先设定其 type 属性,如果创建时,未定义成员 type,默认为 default

  • 预设并校验成员 name

    当成员被创建后,优先设定其 name 属性,如果创建时,未定义成员 name,默认为其 id, 当 name 被更新时,执行同辈成员间 name 唯一性校验。

  • 解析成员属性 DSL

    根据成员属性的设定值是否包含 DSL,自动建立/解除成员间的关联关系,动态解析 DSL 并更新该属性的当前值。

  • 阻止创建未知 type 成员

    当创建成员时,如果成员的类型未注册,抛出异常。

  • 阻止删除被依赖的成员

    当删除成员时候,如果该成员正在被引用,抛出异常。

Shared Hooks

  • BLOCK_INVALID_PARENT

    子成员只能添加到特定类型范围内的父级成员。

    // @example
    reactive.install({
      type: 'MyTextInput',
      // 限制 MyTextInput 类型的成员
      // 只能作为 default 类型成员的子级
      hooks: [
        Reactive.hooks.use('BLOCK_INVALID_PARENT', {accept: ['default']})
      ]
    })
    
  • ENABLE_SCHEMA_ALWAYS

    如果在成员属性 schema 中定义了 always,成员属性的设定值将始终等于 always 的值 。

    // @example
    reactive.install({
      type: 'MyTextInput',
      hooks: [
        Reactive.hooks.use('ENABLE_SCHEMA_ALWAYS')
      ],
      schema: {
        props: [{
          prop: 'value',
          always: '被冻结了'
        }]
      }
    })
    
  • ENABLE_SCHEMA_DEFAULT

    如果在成员属性 schema 中定义了 default,那么在创建成员时,如果成员属性设定值未定义, 则使用 default 相同的值来作为其设定值。

    // @example
    reactive.install({
      type: 'MyTextInput',
      hooks: [
        Reactive.hooks.use('ENABLE_SCHEMA_DEFAULT')
      ],
      schema: {
        props: [{
          prop: 'fakeId',
          // schema 中定义的属性也可以是函数
          default: () => Math.random()
        }]
      }
    })
    
  • ENABLE_SCHEMA_VALIDATION

    如果在成员属性 schema 中定义了 validation,每当属性的当前值变更后,会按照 validation 规则对该值进行校验,成员的错误信息将根据校验结果被赋值为 null 或一个包含 VALIDATOR_VALIDATION_FAILED 类型错误的数组。

    如果在成员属性 schema 中定义了 rawValidation,每当属性的设定值变更后,会按照 rawValidation 规则对该值进行校验,成员的错误信息将根据校验结果被赋值为 null 或一个包含 VALIDATOR_VALIDATION_FAILED_RAW 类型错误的数组。

    校验规则可以被定义为 null,或一个数组,数组中可包含受支持的 DSL 校验表达式,或返回错误信息的函数。

    // @example
    reactive.install({
      type: 'MyTextInput',
      // 使用 Reactive 提供的钩子函数
      hooks: [
        Reactive.hooks.use('ENABLE_SCHEMA_VALIDATION')
      ],
      // 根据钩子函数的实现定义 schema
      schema: {
        props: [{
          prop: 'label',
          validation: [
            // DSL 校验表达式
            {dataType: 'String'},
            // 自定义校验函数
            function({id, value}){
              if (/\s/.test(value)) {
                return 'Cannot contain spaces.'
              }
            }
          ]
        }]
      }
    })
    

Class Methods

Reactive()

创建一个 Reactive 实例。

// Reactive()
// @returns {ReactiveInstance}

// @example
const reactive = Reactive()

.guid()

获取一个全局唯一 id。

// Reactive.guid()
// @returns {string}

// @example
const id = Reactive.guid()

.hooks.use()

获取 Reactive 共用的某个钩子函数定义。

// Reactive.hooks.use(defName)
// @param {string} defName - 钩子定义名称
// @returns {ContextHook} - 钩子函数定义

// @examples
Reactive.hooks.use('ENABLE_SCHEMA_ALWAYS')
Reactive.hooks.use('ENABLE_SCHEMA_DEFAULT')
Reactive.hooks.use('ENABLE_SCHEMA_VALIDATION')
Reactive.hooks.use('BLOCK_INVALID_PARENT', {accept: ['Fieldset']})

Instance Methods

#hooks.mount()

挂载钩子函数定义。

// reactive#hooks.mount(def, options)
// @param {ContextHook | ContextHook[]} - 钩子函数定义
// @param {Object} [options = {}]
// @prop {string} [options.only] - 当设定时,钩子函数只在特定 type 成员上触发
// @returns {undefined}

// @example - 添加全局钩子函数
reactive.hooks.mount({
  hook: 'before-add',
  handler(mutation) {
    console.log('This is a global hook.')
  }
})

// @example - 添加作用于特定 type 成员的钩子函数
reactive.hooks.mount([{
  hook: ['before-add', 'before-del'],
  handler: [function(mutation) {
    console.log('This is a type specific hook function.')
  }, function(mutation) {
    console.log('This is another type specific hook function.')
  }]
}, {
  hook: 'after-add',
  handler(mutation, result) {
    console.log('This is still a type specific hook function.')
  }
}], {only: 'MyTextInput'})

#schema.for()

获取成员所有属性或某个属性的定义。

// reactive#schema.for(params, options)
// @param {Object} params
// @prop {string} params.id - 成员唯一标识
// @prop {string} [params.prop] - 属性名称,如设定之返回该属性定义,否则返回所有属性定义。
// @prop {Object} [options = {}]
// @prop {string | Array} [options.only] - 当 params.prop 未定义时可使用属性名称正向过滤
// @prop {string | Array} [options.except] - 当 params.prop 未定义时可使用属性名称反向过滤
// @returns {ReactiveMemberPropSchema | ReactiveMemberPropSchema[]}

// @examples
reactive.schema.for({id: myMember, prop: 'value'})
reactive.schema.for({id: myMember})
reactive.schema.for({id: myMember}, {except: 'name'})
reactive.schema.for({id: myMember}, {only: ['name', 'value']})

#schema.reference.parse()

将以 . 分割的由成员 name 和属性名构成的引用路径字符串,转换为由成员 id 和属性名称构成的 DependencyProvider 对象。

// reactive#schema.reference.parse(path, options)
// @param {string} path
// @param {Object} [options]
// @prop {FamilyMember} [options.under = <root_member>] - 在家族树中查找被依赖者的起始节点(引用路径不包含起始节点)
// @prop {DependencyRelier} [options.relier = null] - 属性设定值中含有 path 的依赖者,当被依赖者类型定义的 parse 方法需要时传入
// @returns {DependencyProvider | null}

// @example
const JerryId = reactive.add({props: {name: 'Jerry'}})
const JimId = reactive.add({parentId: JerryId, props: {name: 'Jim'}})

// returns {id: JimId, prop: 'age'}
reactive.schema.reference.parse('Jerry.Jim.age')
// returns null
reactive.schema.reference.parse('NonExistedName.Jacky.age')

#schema.reference.stringify()

将由成员 id 和属性名称构成的 DependencyProvider 对象,转换为以 . 分割的由成员 name 和属性名构成的引用路径字符串。

// reactive#schema.reference.stringify(provider, options)
// @param {DependencyProvider} provider
// @param {Object} [options]
// @prop {FamilyMember} [options.under = <root_member>] - 在家族树中查找 provider 的起始节点(引用路径不包含起始节点)
// @prop {DependencyRelier} [options.relier = null] - 属性设定值依赖 provider 的依赖者,当 proider 类型定义的 stringify 方法需要时传入
// @returns {string}

// @example
const JerryId = reactive.add({props: {name: 'Jerry'}})
const JimId = reactive.add({parentId: JerryId, props: {name: 'Jim'}})

// returns "Jerry.Jim.age"
reactive.schema.reference.stringify({id: JimId, prop: 'age'})
// returns ''
reactive.schema.reference.stringify({id: 'NonExistedId', prop: 'age'})

#collect()

获取某成员及其后代成员的状态。

// reactive.collect(from, options)
// @param {FamilyMember} from
// @param {Object} [options]
// @prop {string | Array} [options.only] - 设定时,只搜集范围内的属性状态
// @prop {string | Array} [options.except] - 设定时,不搜集范围内的属性状态
// @prop {boolean} [errors = true] - 当为 false 时,返回结果不包含成员属性的错误信息
// options.only 和 options.except 不过滤错误信息
// @returns {null | Object}

// @example
const fields = reactive.add()
reactive.seed({id: fields}, {
  name: 'fields',
  children: [{
    name: 'name',
    value: 'Jerry'
  }, {
    name: 'nickname',
    value: 'Little << fields.name.value >>'
  }]
})

// returns -
// {
//   errors: null
//   values: {
//     name: 'fields',
//     children: [{
//       name: 'name', value: 'Jerry', children: []
//     }, {
//       name: 'nickname', value: 'Little Jerry', children: []
//     }]
//   },
//   raw: {
//     errors: null
//     values: {
//       name: 'fields',
//       children: [{
//         name: 'name', value: 'Jerry', children: []
//       }, {
//         name: 'nickname', value: 'Little << fields.name.value >>', children: []
//       }]
//     }
//   }
// }
reactive.collect({id: fields}, {only: ['name', 'value', 'children']})

#install()

注册一个成员类型。

// reactive#install(typeDef)
// @param {ReactiveMemberType} typeDef
// @returns {undefined}

// @example
reactive.install({
  type: 'ACustomType',
  hooks: [
    Reactive.hooks.use('ENABLE_SCHEMA_DEFAULT'),
    Reactive.hooks.use('ENABLE_SCHEMA_VALIDATION')
  ],
  schema: {
    fallback: {validation: [{presence: true}]},
    props: [
      {prop: 'lastName', default: 'Stone', validation: [{dataType: 'String'}]},
      {prop: 'firstName', default: 'Shara', validation: [{dataType: 'String'}]}
    ],
    reference: {
      // @param {Object} provider -
      // @prop {string} provider.id - 被引用的 ACustomType 成员唯一标识
      // @prop {string} provider.downPath - 待解析的被引用路径
      // @prop {string} provider.target - 触发解析的引用插值符内的原始路径
      // @param {Object} [relier = null] -
      // @prop {string} [relier.id] - 引用 provider 的成员唯一标识
      // @prop {string} [relier.prop] - 引用 provider 的成员属性
      // @returns {Object|null} - 如 provider.downPath 可被引用应返回由目标成员 id 和 prop 构成的对象,否则返回 null
      parse(provider, relier) {
        console.log(this === reactive) // true
        const {id, downPath, target} = provider
        // For example, ensures only the firstName property can be referenced.
        return {id, prop: 'firstName'}
      },
      // @param {Object} provider -
      // @prop {string} provider.id - 被引用的 ACustomType 成员唯一标识
      // @prop {string} provider.downPath - 待解析的被引用路径
      // @prop {string} provider.upPath - 由被引用成员的祖先及其自身的 name 当前值,以 . 号连接形成的字符串
      // @prop {Object} provider.target - 触发解析的由成员 id 和 prop 构成的原始对象
      // @param {Object} [relier = null] -
      // @prop {string} [relier.id] - 引用 provider 的成员唯一标识
      // @prop {string} [relier.prop] - 引用 provider 的成员属性
      // @returns {string} - 如 provider.downPath 可被引用应返回完整的引用路径字符串,否则返回 null
      stringify(provider, relier) {
        console.log(this === reactive) // true
        const {id, upPath, downPath, target} = provider
        // For example, Ensures only the firstName property can be referenced.
        return `${upPath}.firstName`
      }
    }
  },
  // 应返回用于创建成员、填充成员属性的 mutation 对象数组
  seed(member, props) {
    console.log(this === reactive) // true
    let {id} = member
    if ('firstName' in props) { return }
    // For example, ensures only the firstName property can be setted during seed processes.
    return [{action: 'set', id, prop: 'firstName', value: props.firstName}]
  },
  // 应返回成员属性当前值和设定值构成的对象
  collect(member) {
    console.log(this === reactive) // true
    let {id} = member
    let {value, raw} = this.get({id, prop: 'firstName'}, {include: 'raw'})
    // For example, ensures only the firstName property can be collected.
    return {values: {firstName: value}, raw: {values: {firstName: raw.value}}}
  }
})

#seed()

从某成员开始,按照 plain object 属性填充该成员属性设定值, 并按 plain object 嵌套的 children 层级,创建其后代成员、初始化后代成员属性设定值。

// reactive#seed(from, props, options)
// @param {FamilyMember} from
// @param {Object} props - 成员属性名称和设定值构成的对象,可用 children 字段嵌套后代
// @param {Object} [options]
// @prop {boolean} [options.execute = true] - 如设定为 false,返回该操作对应的 mutation 对象数组,而不是直接执行操作。
// @returns {ContextMutation[] | undefined}

// @example
const fields = reactive.add()
reactive.seed({id: fields}, {
  name: 'fields',
  children: [{
    name: 'name',
    value: 'Jerry'
  }, {
    name: 'age',
    value: 100
  }]
})