json2html-react 核心代码源码解读

167 阅读6分钟

应广大读者的要求,源码来了。我会尽我所能的把细节扣出来,分享给大家,愿大家享读。如果还没读过之前文章《Json2Html 功成》的,可以先去读下之前的内容,里面有讲解大体思路,结合思路理解起来,可能会更方便些。

图片

以上是大致的思路以及代码实现,大家也可以直接在npm上搜索“json2html-react”下载源码看看效果,结合实际效果更方便理解。

core.js 核心逻辑源码解读

import React, { useState, useEffect } from 'react';

let JSON2HTML_ACTIONS = null// 存放action动作

let JSON2HTML_COMPONENTS = null// 存放component组件

// 注册action动作列表
const registerAction = (actions) => {  
    JSON2HTML_ACTIONS = { ...JSON2HTML_ACTIONS, ...actions };
};
 
// 注册components列表
const registerComponent = (components) => {  
    JSON2HTML_COMPONENTS = { ...JSON2HTML_COMPONENTS, ...components };
};

// 处理action动作列表
const handleAction = async (action, globalData) => { 
    // action判断,空则不做任何处理
    if (!action) {    return;  }
    
    // action类型判断,如果是数组则遍历执行里面的每个action  
    if (Array.isArray(action)) {    
        return action.forEach((i) => handleAction(i, globalData));
    }
    
    // action配置形式,固定为{type,data},type用于action映射,data为action传参。 
    
    const { type, data } = action;
    if (JSON2HTML_ACTIONS[type]) {   
        // 支持action异步动作,获取异步结果。
        const res = await JSON2HTML_ACTIONS[type](data, globalData);
        
        // 支持链式调用action。例如请求类型的action,返回结果还是action时,则继续执行。  
        if (res?.type && JSON2HTML_ACTIONS[res.type]) {     
            // 递归处理action。     
            handleAction(res, globalData);
        }
    } else {
        // 非action格式,则抛出错误。   
        console.error('不存在当前action:', type); 
        }
    };
    
// 兼容小写开头 | 字符串中包含"-"
const getWidget = (widgetStr) => widgetStr?.replace(widgetStr[0], widgetStr[0].toUpperCase()).replace(/(-.)/g, (v) => v[1].toUpperCase());

// 解析json,将json转化为页面html。只做页面初始化解析,后续动作组件内部控制。
function Json2Html({ jsonObj, globalData }) {  
    const [filterProps, setFilterProps] = useState(null); // 统一管理所有子组件的props,初始化为null,避免联动产生的无效渲染。  
    
    const [uniKey, setUniKey] = useState(''); // 用于Array jChildren渲染时提供key做优化。 
    
    // 组件渲染时,只执行一遍,生成该组件的唯一key值。  
    useEffect(() => {    setUniKey(Math.random());  }, []);
    
      // widget:组件名;action:组件点击事件;jProps:组件属性透传;jChildren:当前组件的子组件;linkage:组件联动;needFormItem:form组件标识;dataBind:form组件绑定值;rules:form组件规则;validateTrigger:form组件校验rules的时机。  
      const { widget, action, jProps, jChildren, linkage, needFormItem, dataBind, rules, validateTrigger } = jsonObj || {};
      
      const { form, FormItem } = globalData || {}; // form高阶函数注入变量,包含form的所有操作。
      const globalState = form ? form.getFieldsValue() : {}; // 用于管理页面所有状态。  
      const events = globalData?.events || {}; // 用于给表单组件绑定事件。
      
      //@TODO:处理表单组件默认值,其实理论上应该有form包处理,但是由于暂不支持默认值的注入,所以这边手动处理。如果有更合适的库,此代码可忽略。  
      useEffect(() => {    
          const { defaultValue } = filterProps || {};
          if (defaultValue && form) {   
              form.setFieldsValue({        [dataBind]: defaultValue,      });
          }
      }, [dataBind, JSON.stringify(filterProps)]);
      
      // 处理action,给子组件注入onClick属性  
      useEffect(() => {    
          if (action) {      
              setFilterProps((v) => ({ ...v, onClick: () => handleAction(action, globalData) }));   
          }
      }, [action, globalData]);
      
      // 处理表单事件events绑定  
      useEffect(() => {   
          if (needFormItem && Object.keys(events).length > 0) { 
          // 处理表单事件回传,带上dataBind      
              const tempV = {};    
              Object.keys(events).forEach((e) => {    
                  tempV[e] = (v) => events[e](dataBind, v); 
              });
              
              // 事件events注入到子组件属性内
              setFilterProps((v) => ({ ...v, ...tempV }));
          }
       }, [events, needFormItem, dataBind]);
       
      // 处理cascade联动,目前只支持new Function形式。
      useEffect(() => {    
          if (linkage) {  
              // eslint-disable-next-line 
              const fn = new Function('$globalState', linkage); 
              const newState = fn(globalState); 
              setFilterProps(newState ? (v) => ({ ...v, ...jProps, ...newState }) : null);   
          } else { 
          setFilterProps((v) => ({ ...v, ...jProps })); 
          } 
      }, [JSON.stringify(globalState), linkage, jProps]);
      
      // 最里层jsonObj为undefined时childrens为null; filterProps为空时,表示组件初始化或者联动隐藏时无需渲染子组件。  
      if (!jsonObj || !filterProps) {    return null;  }
      
      // 遍历childrens  
      if (Array.isArray(jsonObj)) {  
          return jsonObj.map((i, k) => { 
              const tProps = { jsonObj: i, globalData };
              return (<Json2Html key={`${uniKey}-${k}`} {...tProps} />); 
          });
      }
      
      //处理widget,用于匹配组件库。例如:div -> Div; custom-input -> CustomInput。
      const currentWidget = getWidget(widget);  
      
      //从组件库内获取当前组件。  
      const CurrentComponent = JSON2HTML_COMPONENTS[currentWidget];
      
      //组件不存在则报错。 
      if (!CurrentComponent) { 
          console.error('不存在当前组件:', widget); 
          return null;
      }
      
      //渲染子组件  
      const tempProps = { jsonObj: jChildren, globalData };
      
      //@TODO 如果是表单form组件,则需要特殊处理。如果有更合适的库,此代码可忽略。
      if (needFormItem && form && FormItem) { 
          const tProps = form.getFieldProps(dataBind, { rules, validateTrigger: validateTrigger || '' }); // validateTrigger设置为‘’,将校验时机由页面控制
          
          return (<FormItem key={uniKey}>  
              <div {...tProps}>  
                  <CurrentComponent {...filterProps}>  
                      {jChildren && (<Json2Html {...tempProps} />)}  
                  </CurrentComponent>  
              </div>
          </FormItem>);
      }  
      
      //渲染子组件  
      return (
          <CurrentComponent {...filterProps}> 
              {jChildren && (<Json2Html {...tempProps} />)}
          </CurrentComponent>
      );
}
   
exportJson2Html//解析器
    handleAction, //处理action动作
    registerAction, //注册action动作
    registerComponent, //注册component组件
};

f51285f22821305e3fd2ca7134dc6bcb.gif

可能的疑问?

  1. 觉得当前的component组件库管理和action库管理有些粗糙,可以优化下,单独分割出来吗?

    可以的,目前我是利用变量块级作用域的技术实现,大家也可以更换为全局状态管理。像vue的vuex,或者react的redux。

  2. 为啥action处理,默认不是链式处理呢?

    我理解如果某个时间节点的action动作确定,存在多个,那应该是同步处理。如果一定要有异步的action动作,需要有先后之分,理应放到上一个action结果中处理。

  3. 这种形式的铺页面,较传统形式,会不会不太灵活?维护成本会不会很高?

    首先可以确定,是很灵活,有多灵活?比我们传统的开发模式还要灵活!因为这套逻辑设计的初衷就已经把动态的模块进行分割。json2html解析可以理解为一个“纯函数”(虽然里面有些副作用处理),它只做解析处理,理论上不应该有组件上的各种业务交互;你也可以当它是个中转站,将传入的json数据按照一定的规则处理渲染成子组件。关键它还处理了很多共性问题,相信我,用上一段时间,你会爱上它的。
    维护成本?json2html有一定的维护成本,很少,少得可怜。只有当你需要把业务公共的处理数据的逻辑抽离时,才可能会考虑到迭代它。而默认的action和linkage基本涵盖了90%的场景。而与json2html关联的自定义组件开发成本,也会降低很多。因为它默认就是用的颗粒度最小的组件来实现的;当然如果你用的第三方组件库有业务类的大组件,那也可以直接拿来用。真正需要你自定义的组件会很少,例如:需要开发防刷+验证码组件【需要注意,这个自定义组件,跟你用不用json2html无关,无论是否用它,有需求,你都需要开发它】。

  4. 还有更多问题?

    可以进群交流,有时间的情况,绝对知无不言。如果嫌烦,也可以直接公众号留言,看到也会第一时间回复。
    图片

这就完了?
这只是刚刚开始,虽然目前json2html已成形,结合之前分享的预览器《【一抹骚】预览器》基本可以实现很顺畅的开发,基本跟传统开发模式没有两样,甚至效率会更高点。但这只是刚刚开始,真的只是个开始。后续阶段依然有很多功能待解决。例如:低代码html2json(是真正的低代码,只做解析,组件库/action库依然是自由选择),页面性能优化(既然已经实现了数据即页面,那不妨设想下,如果数据有version,进行数据缓存后的效果)等等。

愿我们奔赴山海 不负热爱

也可以关注公众号《小火球烧屁股

项目地址:github.com/alan1111/js…