alibaba / formily

📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
https://formilyjs.org/
MIT License
11.36k stars 1.48k forks source link

[RFC] x-linkages 联动规则描述提案 #478

Closed janryWang closed 4 years ago

janryWang commented 4 years ago

为什么会考虑支持一个独立的联动规则描述?

主要是之前总是想着把effects强大的能力用JSON来描述(#105 ),结果后面越想越觉得会带来很多很多问题,因为只要想要描述逻辑,就必须要满足图灵完备性,你需要考虑表达式解析编译,环境上下文等等很多方方面面的东西,更甚者,会导致整个JSON Schema变成一个类似Vue的JSON DSL,表达式还会支持指令,过滤器等等,想想就觉得复杂,而且对用户而言,学习成本,理解成本也非常高,反而会丧失JSON Schema的核心理念。

这也是为什么UForm一直没有动身开始做JSON层面的逻辑描述的事情的主要原因。

最近,我一直在思考,我们的目标,我们要解决的问题到底是什么,业务上的痛点到底是什么?

我们可以从根源来思考,为什么我们需要JSON维度去描述逻辑?

其实,我们并不是需要用JSON去描述逻辑,我们只是需要将业务变动最大的部分扔出去,不管是给后端控制,还是给配置界面做配置,只要扔出去了,前端就可以释放大部分工作量,而不需要后续每次类似的需求,都需要改代码。所以,我们可以分析一下,业务变动最大的到底是什么?

  1. 数据结构,业务需要增加字段,减少字段,这种场景非常常见,至少我本人的业务现状就是这样的,基本上只要页面开发好了之后,后续的业务需求,基本上80%都是字段的增删。

  2. UI配置,有些字段文案可能会调整,有些字段的校验规则可能会调整,有些字段的展示形态可能会调整,有可能今天一个单选下拉框,明天需求又希望变成多选,等等。

  3. 联动关系,某些字段可能会随着字段的增加,删除,会出现一些新的联动关系,但是这些联动关系,大多都是控制其他字段的显示隐藏,至少我这边最复杂的表单页面,80%都是控制显示隐藏 所以,总结下来,其实1,2两点,现在UForm已经能很好的解决了,只是针对第三点并没有比较好的办法来解决,至少是,每次都必须得写effects代码来应对新业务逻辑调整。

然后,我们可以再看看变化最小的部分是啥?

  1. 表单初始数据加载逻辑
  2. 表单提交数据处理逻辑
  3. 表单页面的各种操作按钮逻辑
  4. 针对数据的各种异步副作用处理逻辑

这些逻辑,如果想让一个JSON来描述,想想都感觉像天方夜谭,因为这部分逻辑是最脏,最复杂,最难维护的逻辑,但是,其实他们却是变动最小的逻辑,一次开发,可能后面几个月都不需要动,只要后端接口没变化,前端完全不需要做任何升级处理。

所以,整个分析下来,其实我们仅仅只是需要一个可以描述联动关系的协议。 那么,这个协议我们要做到什么度呢?是既描述关系,又描述关系发生过程中的数据交换处理过程实现? 还是只描述关系,不描述过程实现?

如果是前者,我们需要在JSON维度借助大量的表达式能力来实现最终完成的版本,在某些场景下会使用比较方便,但是,只要遇到一些edge case。那处理起来就会非常复杂,很有可能用户还会吐槽你这个能力不完备。 后者,一句话就能说清楚目的,只描述关系,不描述过程实现,实现全部下沉至js处理。

可以看看下面这个例子:

{
  type:"object",
  properties:{
    aa:{
      type:"string",
      "x-linkages":[
        //数组结构可以实现1对多联动关系
        {
          target:"bb"
          type:"value:visible",
          condition:{
           //主要描述对值的条件化判断,这个用户可以自己定义规则
           $eq:"123",
           // $range:["123"],
           // $gt:"321",
           // $lt:"321",
           // $gte:"321",
           // $lte:"321"
          }
        },
        {
           target:"cc",
           type:"value:schema",
           condition:{
             //主要描述对值的条件化判断,这个用户可以自己定义规则
             $eq:"123",
             // $range:["123"],
             // $gt:"321",
             // $lt:"321",
             // $gte:"321",
             // $lte:"321"
           },
           schema:{
              title:"修改标题"
              description:"修改描述",
              required:true,//修改必填
           }
        },
        {
           target:"dd",
           type:"value:state",
           condition:{
             //主要描述对值的条件化判断,这个用户可以自己定义规则
             $eq:"123",
             // $range:["123"],
             // $gt:"321",
             // $lt:"321",
             // $gte:"321",
             // $lte:"321"
           },
           state:{
             display:false
           }
        }
      ]
    },
    bb:{
      type:"string"
    },
    cc:{
      type:"string"
    }
  }
}

registerLinkage('value:visible',({name,target,condition})=>{
  return ($,{ setFieldState })=>{
    $('onFieldValueChange',name).subscribe(()=>{
      setFieldState(target,(state)=>{
        state.visible = calcualateConditions(condition,state.value)
      })
    })
  }
})

registerLinkage('value:schema',({name,target,condition,schema})=>{
  return ($,{ setFieldState })=>{
    $('onFieldValueChange',name).subscribe(()=>{
      if(calcualateConditions(condition,state.value)){
        setFieldState(target,(state)=>{
           Object.assign(state.props,schema)
        })
      }
    })
  }
})

registerLinkage('value:state',({name,target,condition,state})=>{
  return ($,{ setFieldState })=>{
    $('onFieldValueChange',name).subscribe(()=>{
      if(calcualateConditions(condition,state.value)){
        setFieldState(target,(fieldState)=>{
           Object.assign(fieldState,state)
        })
      }
    })
  }
})

//同理可以实现value:schema、value:state、value:asyncEnum等等

通过一个注册联动规则的行为,可以将大部分的联动行为给沉淀在js层。

整个方案的核心目标不是为了让前端彻底不需要写代码,而是为了让产品run起来更高效。

当然,大家也可以投票选择一下: 如果是支持JSON描述逻辑的,回复1; 如果是支持JSON只描述联动关系,但不描述实现逻辑的,回复2;

foolyoghurt commented 4 years ago

2

定义联动的标准协议,开发者可基于协议自定义联动类型以及实现方式。这样通过 UForm 最终产出的还是一份 JSON Schema,对于 UForm 的复杂应用场景- 搭建,保存的就是这份 schema,灵活性和通用性都能满足

使用 1 的问题在于 UForm 的联动逻辑一定无法满足所有的 case,这时候还是会回到 2,但是 1 的好处是开箱即用。所以 UForm 在采用 2 的情况下也可以内置几个最常见的基础的联动规则,比如上面例子提到的「visible」,当然这个只是一个产品层面上优化的可选项

yujiangshui commented 4 years ago

逻辑用 JSON 描述我个人认为只是费力不讨好,又长又难维护,万一能力覆盖不全就坏了。

然后联动关系也是一样,我觉得这个东西挺难设计的,之前也有同事研究过,不知道成果怎么样。相比之下我个人选 2,但是如果没有对联动配置化强诉求的化,我个人倾向于优化 effect 的联动 API 等,再收集收集实际需求再看。

xufei commented 4 years ago

2

因为联动关系不一定是个简单的逻辑,可能是有一段复杂语句,并且涉及跟其他字段当前值的联合判断,并发起请求重新设置另外一些东西,这部分东西的描述,未必是uform暂时所能覆盖的,因为这里面到底会干什么,短期穷举不完。

atzcl commented 4 years ago

2

JSON Schema 纯粹做好表单数据结构这一项就好了,是没必跟业务逻辑强耦合的,分离职责也方便业务的后续维护,还可以沉淀一些通用的业务逻辑处理

zheNeng commented 4 years ago

2 文档应该把2的暴露出去,并且把规范定义清楚。。 1应该当做底层的扩展功能,不暴露在文档中,但是可以给开发者使用。。 2更适合后期的可视化构建工具,进行统一抽象。

quirkyshop commented 4 years ago

2

1 至少目前的里程碑,甚至给再多的时间都没法做好,投入大,收益小。

考虑到终局很可能是用可视化甚至是智能开发来生成表单的话,如果JSON Schema能够描述80%的联动(不是手写,而是拖拽和或者代码生成的话)已经非常好了。剩下的20%也能通过上帝模式搞定(前端手写代码)

// god mode
registerLinkage(()=>{
  return ($,{ setFieldState })=>{
    $('onFieldValueChange', 'name').subscribe(()=>{
      setFieldState('target',(state)=>{
          // do something
      })
    })
  }
})
janryWang commented 4 years ago

2 咱们可以思考一下,其实这种方式还有个好处,就是可以借助社区的力量无限枚举出各种联动解决方案,社区可以提供很多linkage小插件可以帮助UForm解决更多问题

ascoders commented 4 years ago
  1. nocode 方案
  2. lowcode 方案

哪怕是中后台应用渲染引擎,现在 nocode 都因为其协议复杂性处于探索阶段,更别说一个表单引擎了。。再说如果真把 nocode 方案做出来了,这还是表单引擎吗?

综上,选 2.

sanwei-alibaba commented 4 years ago

2

该问题需要考虑实现场景,简单场景第一种方式会更简单一些, 在复杂场景比如多字段联动情况下需要大量脚本,如果强行用JSON的方式首先考虑能否满足需求,如果不能的话,又需要实现全局的effects方法支持,封装性会比较差,所以我选2

这个提案我的理解是提供了可封装部分组件之间联动逻辑的能力,也是对之前只能封装单一展示区域的补充方案

anyuxuan commented 4 years ago

2 方案1的话,实现起来会比较复杂,得有一套完备的DSL,而且如果没有可视化搭建的配套能力,后端同学可能也不太想去写JSON来实现联动逻辑,毕竟JSON对机器比较友好,对人不是那么友好,同时使用JSON实现联动逻辑,中间如果遇到报错,可能会比较难调试。 对比下来,倾向于2

kpaxqin commented 4 years ago

2

其实我们目前对uform在业务中的应用,已经是这么做的了。这样的另一个好处是配置可以完全面向业务,和具体的表单方案解耦。对于跨端的表单来说,这个解耦的好处是目前没有一个表单方案是跨端的,表单核心必然有差别的情况下,让端根据配置自行实现逻辑至少能保证配置的稳定。坏处是同样的effect业务逻辑在不同端上可能需要重复实现。但在现状下其实是没有其它办法的

Airkro commented 4 years ago

2 短期内好实现,又不违背长期利益,可行的情况下又能为 1 打下基础

jschyz commented 4 years ago

业界对JSON Schema封装我觉得最好的是 amis,给后端开发者赋能;但局限性比较大,特定场景特定UI才能使用。

我觉得 uform 目标应该灵活,轻巧;完备的 DSL 会增加很多的维护成本及学习成本。况且实际场景中 UI weiget ,新手不一定能很好的掌握,促使 uform 门槛变高。

方案2 级联方案能很好弱化组件直接通信 effect 逻辑,业务组件逻辑单独封装即可。

threescales commented 4 years ago

2222 1.json并不能穷举所有业务上用到的联动规则。 2.同时随着支持的规则变多,json将会变得无比复杂,维护json将会比维护js痛苦。 3.通用逻辑的话,可以提供一个插件实现一个标准的联动实现。简单场景可以直接使用插件,复杂场景可以参考标准实现自定义一个组件。 所以只需要提供描述联动关系的协议,由开发自定义定制,及灵活又简洁。

henryybai commented 4 years ago

选 2, 1 的话,DSL 会过于复杂

kingzs70 commented 4 years ago

2

这就不是个选择题吧,如果方案不完备,就不要动

baoyuzhang commented 4 years ago

2

1最大的问题是现阶段无法保证覆盖全部场景,那么不如先从2入手,将常见的联动逻辑抽离成插件,并在使用2的过程中不断完善插件库,逐渐向1过渡。用户角度而言,只需要将要使用的插件写入json schema即可,最终产物还是一份schema。

tao1991123 commented 4 years ago

2

1的话 看过太多样例最后会变成 复杂dsl 可能还如不 直接写js来得快

ChenQiao-happy commented 4 years ago

2 从源码开发角度来说,逻辑独立编写肯定更好;相应的成本就是底层api的学习与使用;

但是存在两个疑问: 1、联动部分使用这个部分来实现, form-engine 这一层是不是显得有点尴尬? 2、form-editor 制作工具这一层,联动的设计也得重新考虑?

jincdream commented 4 years ago

这其实是两个问题。

  1. 是否增加 "x-linkages" 字段,来针对联动相关的处理
  2. "x-linkages" 的解析处理是怎样的

我觉得"x-linkages"字段是必要的,如果只用"x-component-props"同时描述联动和值,是存在问题的。

然后到第二个问题,才是大家正在讨论的。

对于联动描述的处理来看,目前有两种方式:

  1. "支持JSON描述逻辑的",用表达式描述完整逻辑,框架层中心化处理
  2. "支持JSON只描述联动关系,但不描述实现逻辑的", 只描述关系,不在乎过程实现,框架层非中心化处理,通过注入插件的方式

在我看来。1 其实是 2 的一个子集,从框架的角度来看,必然是通过非中心化的设计,注入解析器的方式来解析对应描述的。

在 2 的基础上,用一个 expression 的 type,就能实现 1 的场景了。

接下来应该讨论 "x-linkages" 里的字段是否需要标准化,如何标准化了吧·~

janryWang commented 4 years ago

@jincdream 说的挺有道理的,不过对于expression的type的话,其实对于搭建配置场景来说可能并不太友好,因为不是结构化的形态,让用户直接写表达式,对于完全不知道写程序的产品或者运营而言,阻碍就比较大了,所以推荐的方式还是在插件参数维度提供结构化的配置吧

zhangjv commented 4 years ago

2

jincdream commented 4 years ago

@jincdream 说的挺有道理的,不过对于expression的type的话,其实对于搭建配置场景来说可能并不太友好,因为不是结构化的形态,让用户直接写表达式,对于完全不知道写程序的产品或者运营而言,阻碍就比较大了,所以推荐的方式还是在插件参数维度提供结构化的配置吧

搭建场景应该是要把对应的一些逻辑给抽象出来的,给用户写表达式或者代码,其实是将生产成本转嫁给了用户。但我们的上层用户肯定是开发者或者机器,这个定位应该是清晰的,支撑我们的上层用户再二次开发出其他的生产工具来,这个应该才是核心目的。

所以,对于完全不知道写程序的产品或者运营而言,他们应该还有符合他们使用的其他生产工具,但底层是依赖于我们的。

xufei commented 4 years ago

@jincdream 说的挺有道理的,不过对于expression的type的话,其实对于搭建配置场景来说可能并不太友好,因为不是结构化的形态,让用户直接写表达式,对于完全不知道写程序的产品或者运营而言,阻碍就比较大了,所以推荐的方式还是在插件参数维度提供结构化的配置吧

搭建的逻辑 dsl 抽象方式可以跟 uform 本身一点关系都没有。可以考虑支持一个注入的 dsl 解析器,只要这个东西的输出是个函数就完事了,uform 可以帮他管一下注册

andyforever commented 4 years ago

@janryWang 描述了两个问题:

  1. 初始化、提交、异步逻辑或副作用是否在协议里描述
  2. 联动逻辑的实现

第1个问题: 赞同协议不处理复杂业务逻辑(比如初始化、提交、异步等),而是通过组件的对外暴露API或对内注入函数实现。 第2个问题: 这个问题内部讨论过,通过模板表达式的形式去描述联动逻辑用户是非常容易理解的,比如某个字段是否禁用取决于a字段的值是否大于1,通过模板表达式可以这样写"diabled":"{{root.value.a > 1}}",用户只需要关心当前字段是哪个字段影响的以及影响它的逻辑是什么。如果更加复杂的逻辑,可以通过注入函数扩展实现。添加x-linkages也好还是watch也好,本质是从开发者的角度去解决实现层面的问题,并且使得协议变得复杂,没有带来太大的收益。

举两个例子

联动逻辑:

{
  "type": "object",
  "title": "我是表单",
  "properties": {
    "fieldA": {
      type: "number",
      title: "字段A"
    },
    "fieldB": {
      type: "number",
      title: "字段B"
    },
    "name": {
      "type": "string",
      "title": "姓名",
      "default": "xxx",
      "x-component": "Input",
      "x-component-props": {
        "value": "{{root.value.fieldA === 'xxx' ? 0 : 1}}",
        "disabled": "{{root.value.fieldA > root.value.fieldB}}", //支持嵌套字段值获取,支持JS原生方法、逻辑表达式
      },
      "x-rules": [{
        "required": true
      }]
    }
   }
}

异步描述:

{
  "type": "object",
  "title": "我是表单",
  "properties": {
    "status": {
      "type": "number",
      "title": "状态",
      "default": "xxx",
      "desciption": "我是字段描述",
      "x-component": "Select",
      "x-props" : {
      },
      "x-component": "Select",
      "x-component-props": {
        "dataSource": "{{$async.fetchData}}", //异步数据取值
        "$async": "fetchData",  
      },
      "x-rules": []
    }
   }
}
janryWang commented 4 years ago

@andyforever 为什么我提议schema维度在底层不引入表达式的概念,核心是表达式带来的优点很明显,其实缺点也很明显,我们可以举个例子:

{
  type:"object",
  properties:{
    aa:{
      type:"string",
      "x-props":{
         visible:"{{root.values.bb === 123}}"
      }
    },
    bb:{
      type:"string"
    }
  }
}

这个应该就是 @andyforever 所提倡的写法,看着的确是非常简单的,从代码量来看是很少,这一点,用linkages方案已经是极限了,因为它是有一个配置结构的,所以无法做到最简写法。然后,我们说下这样写会存在哪些问题:

  1. 容错问题,如果我们取值判断是root.values.aa.bb.cc === 123,如果一开始aa.bb是不存在的,就会导致页面挂掉,为什么会出现这个问题,核心原因是我们不知道值发生变化的时机,只知道哪里要依赖这个变化,要处理的话,就需要复杂的表达式root.values.aa && root.values.aa.bb && root.values.aa.bb.cc === 123

  2. 异步逻辑会导致复杂度直线提升,基于上一个例子,如果在联动过程中存在异步情况,必须得引入表达式函数来描述,只要引入了表达式匿名函数,那么表达式解析复杂度会提升一个维度,我们将不再是解析Expression,还得解析Statement,比如

    {
    type:"object",
    properties:{
    aa:{
      type:"string",
      "x-props":{
         visible:"{{root.values.cc === 123}}"
      }
    },
    bb:{
      type:"string",
      "x-component-props":{
         "onChange":"{{()=>{fetch('url').then(()=>{root.values.cc === 123})}}}"
      }
    }
    }
    }

    这样一看,都支持写函数表达式了,为啥不直接点?

    {
    type:"object",
    "x-component-props":{
     "effects":"{{($,{setFieldState})=>{.....}}}"
    },
    properties:{
    aa:{
      type:"string"
    },
    bb:{
      type:"string"
    }
    }
    }

    所以说,用了表达式写法,看似很简单,但是对于复杂场景,一下子就会陷入泥沼。

  3. 调试问题,因为有了表达式,如果线上出现问题,调试成本将会非常高,因为这个内部需要编译解析的,想要调试可能需要进入DSL解析流程去断点调试

  4. 联动复用性问题,因为所有联动都是使用表达式来实现,那其实很容易会出现一个需求,用户需要支持联动的复用,按照现在这种方式,基本上每次都得写重复的表达式逻辑,当然,你可能说,我们可以将联动抽象成一个函数来复用,比如

    {
    type:"object",
    properties:{
    aa:{
      type:"string",
      "x-props":{
         visible:"{{root.methods.valueEqual('bb','123')}}"
      }
    },
    bb:{
      type:"string"
    }
    }
    }

    用这种方式解决,貌似还能解决前面的容错问题,但是,它又引入新问题了,我们如何观察bb的变化?因为bb完全可以是一个变量了,静态解析是根本做不到观察bb的变化的,那么,用mobx的autorun思路?即便用了,貌似可以,但是这个方案又强依赖Proxy了,浏览器兼容性很难解决

  5. 实现复杂度很高 前面说了那么多,都没怎么提实现复杂度,现在详细讲下复杂在哪里:

    • 是否考虑xss注入问题,如果考虑,就必须完整的对表达式做解析,编译
    • 表达式是否考虑支持写匿名函数,如果支持,就必须对表达式做Statement维度的解析编译
    • 是否要解决联动复用问题,如果要解决,就得又引入mobx的autorun思路,内部强耦合Proxy。

以上所说的不止是复杂度,它带来的还有很多边界问题,风险问题,还有就是,我想反驳的一点,用x-linkages会导致协议变得复杂,这个其实恰好没有使得协议复杂,反而减少了协议带来的边界问题,投入-收益比是最高的(实现成本低,能快速解决业务80%问题)

kotot commented 4 years ago

1 就没可能可以实现。。。 只能选2 2 ’只描述联动关系‘ 这个有些微妙取舍

cnt1992 commented 4 years ago

2 + 部分1

andyforever commented 4 years ago

@andyforever 为什么我提议schema维度在底层不引入表达式的概念,核心是表达式带来的优点很明显,其实缺点也很明显,我们可以举个例子:

{
  type:"object",
  properties:{
    aa:{
      type:"string",
      "x-props":{
         visible:"{{root.values.bb === 123}}"
      }
    },
    bb:{
      type:"string"
    }
  }
}

这个应该就是 @andyforever 所提倡的写法,看着的确是非常简单的,从代码量来看是很少,这一点,用linkages方案已经是极限了,因为它是有一个配置结构的,所以无法做到最简写法。然后,我们说下这样写会存在哪些问题:

  1. 容错问题,如果我们取值判断是root.values.aa.bb.cc === 123,如果一开始aa.bb是不存在的,就会导致页面挂掉,为什么会出现这个问题,核心原因是我们不知道值发生变化的时机,只知道哪里要依赖这个变化,要处理的话,就需要复杂的表达式root.values.aa && root.values.aa.bb && root.values.aa.bb.cc === 123
  2. 异步逻辑会导致复杂度直线提升,基于上一个例子,如果在联动过程中存在异步情况,必须得引入表达式函数来描述,只要引入了表达式匿名函数,那么表达式解析复杂度会提升一个维度,我们将不再是解析Expression,还得解析Statement,比如
{
  type:"object",
  properties:{
    aa:{
      type:"string",
      "x-props":{
         visible:"{{root.values.cc === 123}}"
      }
    },
    bb:{
      type:"string",
      "x-component-props":{
         "onChange":"{{()=>{fetch('url').then(()=>{root.values.cc === 123})}}}"
      }
    }
  }
}

这样一看,都支持写函数表达式了,为啥不直接点?

{
  type:"object",
  "x-component-props":{
     "effects":"{{($,{setFieldState})=>{.....}}}"
  },
  properties:{
    aa:{
      type:"string"
    },
    bb:{
      type:"string"
    }
  }
}

所以说,用了表达式写法,看似很简单,但是对于复杂场景,一下子就会陷入泥沼。

  1. 调试问题,因为有了表达式,如果线上出现问题,调试成本将会非常高,因为这个内部需要编译解析的,想要调试可能需要进入DSL解析流程去断点调试
  2. 联动复用性问题,因为所有联动都是使用表达式来实现,那其实很容易会出现一个需求,用户需要支持联动的复用,按照现在这种方式,基本上每次都得写重复的表达式逻辑,当然,你可能说,我们可以将联动抽象成一个函数来复用,比如
{
  type:"object",
  properties:{
    aa:{
      type:"string",
      "x-props":{
         visible:"{{root.methods.valueEqual('bb','123')}}"
      }
    },
    bb:{
      type:"string"
    }
  }
}

用这种方式解决,貌似还能解决前面的容错问题,但是,它又引入新问题了,我们如何观察bb的变化?因为bb完全可以是一个变量了,静态解析是根本做不到观察bb的变化的,那么,用mobx的autorun思路?即便用了,貌似可以,但是这个方案又强依赖Proxy了,浏览器兼容性很难解决

  1. 实现复杂度很高 前面说了那么多,都没怎么提实现复杂度,现在详细讲下复杂在哪里:
  • 是否考虑xss注入问题,如果考虑,就必须完整的对表达式做解析,编译
  • 表达式是否考虑支持写匿名函数,如果支持,就必须对表达式做Statement维度的解析编译
  • 是否要解决联动复用问题,如果要解决,就得又引入mobx的autorun思路,内部强耦合Proxy。

以上所说的不止是复杂度,它带来的还有很多边界问题,风险问题,还有就是,我想反驳的一点,用x-linkages会导致协议变得复杂,这个其实恰好没有使得协议复杂,反而减少了协议带来的边界问题,投入-收益比是最高的(实现成本低,能快速解决业务80%问题)

我赞同这个方案实现成本相对会高些,但是协议足够简洁优雅,用户不需要再去了解更多的协议概念。

  1. 容错可以由渲染层去做异常处理,给出用户必要的提示。x-linkages 注册内部处理也避不开这个问题;
  2. 80%的业务问题x-linkage解决的,模板变量表达式也能解决,并且更简单。不建议用户写复杂逻辑,给出最佳实践。但用户到底写的多复杂,是否有异步逻辑,由业务决定也由用户自己决定;
  3. 调试是可以直接定位到new Function 内部的逻辑(即变量表达式)的,并且会生成独立的debugger 匿名函数文件;
  4. 跟第2点一样,如果联动逻辑还要考虑抽象,那就交由用户自己决定吧,渲染层提供方案就可以;
  5. 安全是需要解决的问题,实现上对用户黑盒,是开发者要考虑的事情。
s0ngyee commented 4 years ago

选2 1 schema中包含涉及具体逻辑代码不可控因素较多 2中的联动关系除了80%的场景是隐藏显示 另外20%的场景能否也一起列举一下,异步数据加载等联动如何方式表示。纯展示隐藏的联动

lcygithub commented 4 years ago

2 + 1 我以为终局是业务50%纯配置,剩下的50%中80%业务配置+源码(配置转源码,源码可以不转为配置),其余源码。 然后楼主说”细细品味这个核心理念,只描述关系,不描述逻辑“,看来我得再琢磨琢磨😄。

DarK-AleX-alibaba commented 4 years ago

2, 在目前的人力与uform所处阶段,1不具备可实施性,很可能1投入大量人力做出来了,口碑砸了,uform1.0还是应该以现阶段简单好用的表单为目标去实现。按28法则来看,2的做法能达到百分之80的收益,所以选2。

janryWang commented 4 years ago

@andyforever

  1. 实现成本相对高 回复:目前我的判断,实现成本不是相对高,是很高很高,即便用new Function来执行表达式,那要解决自动监听数据变化的能力也得做解析,不做解析也得使用Proxy,最重要的是,这部分很有可能会影响到底层架构,为了一个表达式能力去重构底层,感觉投入成本太高了。
  2. 容错可以由渲染层去做异常处理,给出用户必要的提示。x-linkages 注册内部处理也避不开这个问题; 回复:x-linkages的注册是从设计层面就不需要考虑异常问题的,可以看看这个例子
    
    registerLinkage('visible',(name,target,conditions)=>{
    return ($,{ setFieldState })=>{
    $('onFieldValueChange',name).subscribe(()=>{
     //传递的target,是标准的FormPath匹配语法,这个只是一个匹配逻辑,不存在js变量获取不到的问题
      setFieldState(target,(state)=>{
        state.visible = conditions.value === state.value
      })
    })
    }
    })

3. 80%的业务问题x-linkage解决的,模板变量表达式也能解决,并且更简单。不建议用户写复杂逻辑,给出最佳实践。但用户到底写的多复杂,是否有异步逻辑,由业务决定也由用户自己决定;
回复:80%的业务问题,代表还有各种异步逻辑场景,这个非常常见,我不认为表达式能优雅解决异步问题,你也并没有给出一个优雅的解决方案,单纯扔给用户,不现实

4. 调试是可以直接定位到new Function 内部的逻辑(即变量表达式)的,并且会生成独立的debugger 匿名函数文件;
回复:如果要考虑XSS风险,就不能用new Function,必须走最原始的表达式AST解析到编译的过程,所以debugger问题还是存在的。

5. 跟第2点一样,如果联动逻辑还要考虑抽象,那就交由用户自己决定吧,渲染层提供方案就可以;
回复:不是什么都能用户决定的,仔细思考一下,如果在表达式维度处理联动抽象,基本上只能借助外部传进去的工具函数,但是这个工具函数怎么跟自动监听数据变化的那套机制通讯,也是个问题,这部分还有个API设计上的考虑,不是用户想怎样做就怎样做的。

6. 安全是需要解决的问题,实现上对用户黑盒,是开发者要考虑的事情。
回复:XSS是用了表达式,而且内部使用new Function带来的问题,这个不是用户决定的了的。
javahuang commented 4 years ago

2 实际上我目前也是这么做的,这对可视化工具非常友好。 我现在做的由可视化工具生成 json schema,比如计算规则,显示隐藏控制等,对每个 Field 都是独立配置的,然后利用 uform 强大的 effect 能力来实现具体的逻辑。

janryWang commented 4 years ago

选2 1 schema中包含涉及具体逻辑代码不可控因素较多 2中的联动关系除了80%的场景是隐藏显示 另外20%的场景能否也一起列举一下,异步数据加载等联动如何方式表示。纯展示隐藏的联动 @s0ngyee 异步联动也是属于80%场景中的,只是我没列举,看下这个写法

registerLinkage('asyncVisible',(name,target,conditions)=>{
  return ($,{ setFieldState })=>{
    $('onFieldValueChange',name).subscribe(()=>{
     fetch().then(()=>{
       setFieldState(target,(state)=>{
          state.visible = conditions.value === state.value
       })
      })
   })
  }
})

其实说白了就是把异步逻辑内化到注册的实现里去,仔细品味:只描述关系,不描述逻辑实现 这句话

andyforever commented 4 years ago

@janryWang 交给用户决定不是让用户自己实现,是我们提供相应机制(比如插件机制在渲染时自动注入函数)提供给用户更简单的表达方法。

janryWang commented 4 years ago

@andyforever 现在问题就来了,使用了表达式,遇到问题又得借助插件来解决了,这样就有两套方案了,插件还会有插件的一套API,表达式也有表达式的一套API,看似很简单的表达式方案,其实要真正解决80%的问题,还是得借助插件解决,如果任何插件都不用,最多只能解决40%-50%的问题,相反,x-linkages就很简单,一切都是注册进去使用的,用户不需要理解太多概念

imsobear commented 4 years ago

RFC 最好能根据一些常用场景给出两种方案的示例,给讨论参与者更加清晰的输入。看目前的描述我倾向于 2 的方向(协议不要描述过于复杂的逻辑),但是例子太少,不够直观。

janryWang commented 4 years ago

@imsobear 主要是x-linkages现在已经简单到,所有逻辑能力都可以通过注册来实现,在schema维度仅仅只是配置,没有其他额外的东西了,所以,按理说,官网上提到的所有联动场景,只要是用effects能实现的,基本上在注册函数内都能实现,因为注册函数就是个effects,只不过它是个局部钩子

Icemic commented 4 years ago

最新的 JSON-Schema v7 规范中实际上已经有了可以用于联动逻辑描述的关键字:if else not allof oneof...等,FormRender 自己扩展的 when 也是个不错的方案(比用allOf描述更简洁直观)。 X-* 具体解决了他们的什么缺陷呢?

janryWang commented 4 years ago

最新的 JSON-Schema v7 规范中实际上已经有了可以用于联动逻辑描述的关键字:if else not allof oneof...等,FormRender 自己扩展的 when 也是个不错的方案(比用allOf描述更简洁直观)。 X-* 具体解决了他们的什么缺陷呢?

x-是扩展属性,uform的JSON-Schena,在UI维度的属性不会有独立的UISchema,所以统一用x-来描述扩展属性,然后,JSON Schema的描述联动的协议,其实更偏数据层的联动协议,不能完全解决UI层面的问题,很难描述一些带副作用逻辑的UI元素间的联动关系

Icemic commented 4 years ago

带副作用逻辑的UI元素间的联动

有例子吗?

一个问题是,我们到底需要一种专门的字段「描述字段间的连接」还是应该「描述数据之间的存在关系」?Schema v7 选择了后者,毕竟 JSON-Schema 的目标是描述数据格式,而不是定义表单。

说句题外话,基于 Schema v6 的高度复杂多级复合联动表单我们有过实践,需要 test case 可以找我ww

janryWang commented 4 years ago

@Icemic https://github.com/alibaba/uform/issues/478#issuecomment-563225586 这个就是带异步副作用的联动

janryWang commented 4 years ago

@Icemic 你说到这个,突然提醒我了,其实你说的Schema v7的联动特性也是可以实现的,只是将它转换成x-linkages就行了,因为x-linkages是足够灵活的方案

Icemic commented 4 years ago

@Icemic #478 (comment) 这个就是带异步副作用的联动

所以 uForm 的方向更倾向于让用户使用 Javascript 去实现而不是基于 json 描述自动生成? 我这边有一个场景,会让我的用户定义表单,这种情况下,JS 的方案在学习门槛和安全性上都会出现问题。

Icemic commented 4 years ago

@Icemic 你说到这个,突然提醒我了,其实你说的Schema v7的联动特性也是可以实现的,只是将它转换成x-linkages就行了,因为x-linkages是足够灵活的方案

hmmm 我觉得有标准应该尽量跟随标准吧

janryWang commented 4 years ago

@Icemic 你说到这个,突然提醒我了,其实你说的Schema v7的联动特性也是可以实现的,只是将它转换成x-linkages就行了,因为x-linkages是足够灵活的方案

hmmm 我觉得有标准应该尽量跟随标准吧

标准主要是针对数据的,但x-linkages是主要针对UI的,两者有关联,但不等价,底层提供一个灵活的方式这个对上层用户并不影响,就像现在uform有x-rules属性,可以配很复杂的校验规则,但是也支持了JSON Schema的各种校验属性一样,内部还是将这些校验属性转换成x-rules

janryWang commented 4 years ago

@Icemic #478 (comment) 这个就是带异步副作用的联动

所以 uForm 的方向更倾向于让用户使用 Javascript 去实现而不是基于 json 描述自动生成? 我这边有一个场景,会让我的用户定义表单,这种情况下,JS 的方案在学习门槛和安全性上都会出现问题。

不是说所有都是通过用户写js来实现,更多的是借助它的可复用能力,用户可以根据自己的业务定制一批业务通用的联动规则,未来的业务需求,基本上都是配置的事情,而不是每次都需要写js,还有,你说的让用户定义表单,其实就是一个选择low code还是nocode的方案,就目前而言,nocode肯定做不到,更多的是low code吧

soulwu commented 4 years ago

2

只描述联动关系,提供开放的可注册的机制进行关系类型的行为定义,这样会很清晰,而且也比较利于同类关系操作的复用

czbczb commented 4 years ago

联动确实是很纠结的问题,我花了两天的时间处理了我们用到的联动,现在都是从后台配置,前台引入一个associate feild组件就可以实现,相关代码请看我的知乎文章: https://zhuanlan.zhihu.com/p/88115449