当前位置: 首页 > Web前端 > JavaScript

画布和组件元数据数据流

时间:2023-03-27 17:04:54 JavaScript

接下来需要解决两个问题:如何与视觉构建的其他业务元素交互画布。例如展开属性配置面板、图层列表、拖拽添加组件、定位锚点、主题等。runtimeProps如何访问当前组件实例的props。这两个问题非常重要,可以通过良好的数据流设计一次性解决。下面我们分别分析讨论。问题1:视觉构建的其他业务元素如何与画布交互。比如需要设计一个HooksAPI来访问canvas提供的方法和数据,比如展开属性配置面板、图层列表、拖拽组件、定位锚点、主题等。在React设计中,访问HooksAPI需要在一定的上下文中,所以可以拆分为,其中提供Hooks上下文,负责渲染画布。这样一来,开发者的使用方式就变成了这样:{()=>addComponent(/**...*/)}>创建组件}constApp=()=>{}for支持多个Designer实例隔离,通过createDesigner创建一套context-independent的API,使得canvas和configurationpanel可以用Designer同时实现,canvas和configurationform可以同时实现有了一套技术方案,让学习上下文和组件规范可以统一成一套,表单和画布能力也可以共享。中的组件可以通过useDesigner直接访问数据和方法。比如上面的例子直接访问内置方法addComponent时,不需要额外参与,addComponent方法会一直保持引用不变。此时useDesigner不会导致EditPanelRe-render。如果需要访问当前组件树,当组件树发生变化时重新渲染,可以通过以下方式访问:))}这种写法的效果是当state.componentTree发生变化时,会触发EditPanel重新渲染,得到最新的值。同时也可以传入第二个参数compare来自定义比较方式。默认是shallowEqual:useDesigner((state)=>({componentTree:state.componentTree,}),isEqual);这样,无论再多的UI元素扩展到canvas上都可以,而且UI元素可以自由访问canvas的方法和数据。问题二:runtimeProps如何访问当前组件实例的props?在componentMeta.runtimeProps中,我们构造了一个selector函数来访问当前组件的props:.name)return{fullName:`full-${name}`}}element:/**...*/};首先,支持从runtimeProps回调选择器中获取,选择器支持传入回调函数。回调函数参数中的props指向当前组件实例的props,可以通过该方法访问组件props。selector只有在props.name改变时才会重新执行,同样遵循compare比较规则,即props.name改变时,selector回调函数的返回值通过compare与之前的值进行比较,如果没有变化,它返回以前的值旧值,如果改变,返回新值。默认的比较函数是shallowEqual,类似useDesigner,也可以在第二个参数位置重写compare方法。组件元信息如何访问内置静态方法?由于静态方法引用不变,所以在selector层可以直接传入:constdivMeta={componentName:"div",runtimeProps:({addComponent})=>{return{add:()=>{/**addComponent(...)*/}}}元素:/**...*/};这样我们就打通了数据流和组件的元信息,即UI可以通过useDesigner访问和操作数据流和组件的元信息,也可以直接通过方法获取,也可以通过选择器获取数据,相应地,可以访问和操作数据流。以后扩展更多组件元信息功能时,可以继承这种设计。开发者只需学习一次语法,即可获得非常强大的可扩展性。展开应用状态和静态方法刚才介绍了一些内置状态(componentTree)和方法(addComponent),下一章我会系统的介绍作者整理了哪些内置状态和方法。首先,无论内置状态和方法如何,应用程序都必须定义自己的状态和方法。我们可以为用户提供两种模式。首先是应用程序的状态和方法是在外部定义的,对应于受控模式。假设你的应用在连接Designer之前使用过Redux、Dva、Zustand等状态管理库,那么你可以使用controlled方式直接访问:constApp=()=>{//伪代码,不管是useState还是其他数据流管理的状态,这里是数据和方法const{getAppInfo}=useSomeLib();const{userName}=useSomeLib("userName");return;};将方法传递给动作,将状态传递给状态。二是应用的状态和方法由定义,采用非受控模式。假设你的应用之前没有使用过任何数据流,你也可以直接使用Designer的数据流作为项目数据流:"},actions:{getAppInfo:()=>{}},});const{Designer}=createDesigner(middleware1);constApp=()=>{return;};通过createMiddleware组件定义状态和函数创建中间件,传递给createDesigner生效。也可以通过createMiddleware中的第二个参数定义自定义hooks,或者获取改变State的方法:React.useCallback((newName:string)=>{setState((state)=>({...state,userName:newName,}));});return{setUserName};});设计师用的最多的simpleRedux管理状态提供了最基本的getState和setState获取和修改状态,并基于它们封装业务功能。无论是受控模式还是非受控模式(或者两种模式同时使用),定义的状态和方法可以在下面两个地方访问到,第一个地方是useDesigner:const{/**customfunction*/,setUserName,/**自定义函数*/getAppInfo,/**内置函数*/addComponent,//内置变量componentTree,//自定义变量userNamee}=useDesigner(state=>({componentTree:state.componentTree,userName:state.userName}))第二个位置是组件元信息上的回调函数,比如runtimeProps:constdivMeta={componentName:"div",runtimeProps:({selector,/**自定义函数*/,setUserName,/**自定义函数*/getAppInfo,/**内置函数*/addComponent})=>{const{/**内置变量*/componentTree,/**自定义变量*/userName}=selector(({state})=>({componentTree:state.componentTree,userName:state.userName}))return{componentTree,userName}}element:/**...*/};至此,我们实现了一套完整的Dataflow定义,包括:不同Designer之间的上下文隔离。可以与项目数据流无缝对接,也可以作为独立的数据流解决方案提供。内置变量和函数与自定义变量和函数混合在一起。这些变量和函数可以通过UI中的useDesigner或通过组件元信息中的选择器访问。本章总结了一个基本可用的可视化构建框架的设计。但这只是视觉构建问题的冰山一角。在以后的章节中,作者会逐步为大家介绍更多可视化的建筑设计。但无论以后框架如何发展,它始终会以前三章的基本设定为基础。总结一下,这三章的基本设定是:设计一个可视化的构建协议,将逻辑与UI、数据流、组件元信息、组件实例是一个永恒的铁三角分离。数据流可以连接到任何现有的实现,或基于Designer规范。组件元信息和组件实例只存储最基本的信息,受益于数据流的定制能力,无论在何处都具备完整的数据流访问能力,让业务框架不仅可以遵循规则,还要千变万化。抛开具体的API设计或命名,简洁抽象,提供极少量的API,但能满足所有业务定制需求,才是可视化建设的永恒目标。只要熟悉了这套规范,几乎只根据业务性能,就可以一眼猜出是基于哪些API包,那么维护成本和理解成本就会大大降低,规范的意义就在于反映在这里。有的同学可能觉得现在各大厂的可视化构建的实现不计其数,可视化构建的概念已经烂掉了。为什么我们需要重新设计一个?因为也许数量并不代表质量。维护时间越长,涉及的学生越多,越容易使设计冗余,概念越复杂。要对抗这些不断增加的熵,唯一的办法就是从头开始不断地重新设计和反思计划。下一讲理论思考会比较少,会介绍可视化框架中会考虑到的内置变量和方法。讨论地址为:Jingdu《画布与组件元信息数据流》·Issue#466·dt-fe/weekly想参与讨论的请戳这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号版权声明:免费转载-非商业-非衍生保留属性(CreativeCommons3.0License)