递归式生成前端代码:从原理到实践

原理思考

在设计递归式代码生成函数时,我们主要关注了几个核心原则。

第一点,明确我们的目标是自动化生成HTML代码,这需要我们深入理解代码的结构和语法。

第二点,考虑到代码的可读性和可维护性,我们尽量保持代码简洁,并使用字符串模板进行代码拼接。

第三点,我们根据属性前缀的不同格式化属性,并根据子节点的存在与否进行递归处理。

数据结构设计

可以设计一个组件节点ComponentNode的数据结构,包含name、props、children字段,其中children再嵌套子组件节点。

js 复制代码
class ComponentNode {
  constructor(name, props) {
    this.name = name
    this.props = props
    this.children = [] 
  }
}

递归算法设计

递归地处理节点和其子节点的属性及结构

js 复制代码
function generateCode(node) {

  // 拼接开始标签
  let code = `<${node.name}`
  
  // 拼接属性
Object.keys(node.props).forEach(key => {

  let value = node.props[key]

  if (key.startsWith('@')) {
    code += ` @${key.slice(1)}=${value}`
  } else if (key.startsWith(':')) {
    code += ` :${key.slice(1)}=${value}` 
  } else if (key.startsWith('v-')) {
    code += ` ${key}="${value}"`
  } else {
    code += ` ${key}="${value}"`
  }

})
  
  // 拼接子组件代码
  if (node.children.length) {
    code += '>'
    node.children.forEach(child => {
      code += generateCode(child) 
    })
    code += `</${node.name}>`
  } else {
    code += ' />'
  }

  return code
}

设计解析

  1. 开始标签的拼接

    • 首先,函数通过 node.name 获取节点名称,并将其与 < 符号拼接,从而得到开始标签。
  2. 属性拼接

    • 使用 Object.keys(node.props).forEach 遍历节点的属性对象 node.props

    • 根据属性的键前缀(例如 '@', ':', 'v-'),对属性进行不同的处理:

      • 如果属性键以 '@' 开头,则在代码中添加 @ 前缀和对应的属性值。
      • 如果属性键以 ':' 开头,则在代码中添加 : 前缀和对应的属性值。
      • 如果属性键以 'v-' 开头,则在代码中添加空格和属性键,以及等号和对应的属性值。
      • 对于其他属性键,直接添加空格、属性键和等号、属性值。
  3. 子组件代码的拼接

    • 检查节点是否有子节点。如果有,将 > 添加到代码中,然后对每个子节点递归调用 generateCode 函数,并将结果拼接到代码中。最后,添加 </${node.name}> 作为结束标签。
    • 如果没有子节点,则直接添加 /> 作为结束标签。
  4. 返回结果

    • 最后,返回生成的代码字符串。

入参示例

js 复制代码
// 组件节点
const node = new ComponentNode('el-table', {
  data: tableData, // 动态数据
  class: 'table-border', // 普通的 class 属性
  ':style': {color: 'red'}, // 动态样式
  '@click': onClick, // 点击事件
  'v-loading': loading, // v-指令
  'ref': 'tableRef', // ref 属性
  slot: 'slotContent', // 具名 slot
  is: 'component' // is 属性
})

node.children = [
  new ComponentNode('el-table-column', {
    prop: 'name',
    label: '姓名'
  }),
  new ComponentNode('el-table-column', { 
    prop: 'address',
    label: '地址' 
  }) 
]

生成结果

html 复制代码
<el-table data=tableData class="table-border" :style={color: 'red'} @click=onClick v-loading=loading ref="tableRef" slot="slotContent" is="component">
  <el-table-column prop="name" label="姓名" />
  <el-table-column prop="address" label="地址" /> 
</el-table>
相关推荐
小小愿望1 小时前
前端无法获取响应头(如 Content-Disposition)的原因与解决方案
前端·后端
小小愿望1 小时前
项目启功需要添加SKIP_PREFLIGHT_CHECK=true该怎么办?
前端
烛阴1 小时前
精简之道:TypeScript 参数属性 (Parameter Properties) 详解
前端·javascript·typescript
海上彼尚2 小时前
使用 npm-run-all2 简化你的 npm 脚本工作流
前端·npm·node.js
开发者小天2 小时前
为什么 /deep/ 现在不推荐使用?
前端·javascript·node.js
如白驹过隙3 小时前
cloudflare缓存配置
前端·缓存
excel3 小时前
JavaScript 异步编程全解析:Promise、Async/Await 与进阶技巧
前端
Jerry说前后端3 小时前
Android 组件封装实践:从解耦到架构演进
android·前端·架构
步行cgn4 小时前
在 HTML 表单中,name 和 value 属性在 GET 和 POST 请求中的对应关系如下:
前端·hive·html
hrrrrb4 小时前
【Java Web 快速入门】十一、Spring Boot 原理
java·前端·spring boot