从动态表单说起,在vue中使用jsx

2,192 阅读2分钟

1.动态表单

有时候我们的后台管理系统中会要求动态创建,更改表单。但是我们用template写表单的时候,如果不能通过json配置表单,这种需求就很难实现,即使封装成组件,我们不得不使用无数的v-if,来判断我们该渲染什么组件。

<el-input v-if="item.type === 'input'" />
<el-select v-else-if="item.type === 'select'" >...</el-select>

类似于这样的代码,当然,这的确能完成需求,但是,总有一种繁琐的感觉, 而且如果有什么变态需求, 可能也不太好写。 这时候, jsx就可以帮得上忙。

  init() {
    let arr = []
    this.items.forEach((element) => {
      arr.push(this.type[element.type]())
    })
  },
  ......
  render() {
    return <div>
    {this.init()}   
    </div>
  }

在vue-cli 3.0以上的版本中,已经可以直接使用jsx.

写起来也非常简单, 我们创建一个vue文件, 不用写template标签, 直接写一个script标签

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  data() {
    return {
      message: 'HelloWorld'
    }
  },
  render() {
    return <div>{this.message},{this.msg}</div>
  }
}
</script>

这就是这个文件的全部内容, 然后我们只要正常的当作组件引入其他文件里就可以了. 使用也是普通组件的使用方式.
当我们正常引入elementui后, 就可以使用ui中的组件来构建我们的界面了, 就像下边这样

// render.vue
<script>
export default {
  data() {
    return {
      msg: 'Hello World',
      initForm: {
        input: this.createInput,
        select: this.createSelect,
      },
      form: {},
    }
  },
  props: {
    items: {
      type: Array,
      default: () => [],
    },
  },
  methods: {
    onClick() {
      console.log(this.form)
    },
    createInput(options) {
      return <elInput v-model_trim={this.form[options.name]}></elInput>
    },
    createSelect(options) {
      const option = options.option.map((item) => (
        <el-option label={item.label} value={item.value}></el-option>
      ))
      return <el-select v-model={this.form[options.name]}>{option}</el-select>
    },
    init() {
      let arr = []
      this.items.forEach((element) => {
        arr.push(this.initForm[element.type](element.options))
      })
      return arr
    },
    returnForm() {
      return this.form
    },
  },
  render() {
    return (
      <div>
        {this.init()}
        <el-button onClick={this.onClick}>提交</el-button>
      </div>
    )
  },
}
</script>

// test.vue
<template>
  <div>
    <render :items="items"></render>
  </div>
</template>

<script>
import render from '../components/render.vue'
export default {
  components: {
    render,
  },
  data() {
    return {
      items: [
        {
          type: 'input',
          options: {
            name: 'name',
          },
        },
      ],
    }
  },
}
</script>

可以看到, 通过一个数组, 我们成功的创建了一个输入框, 并且绑定了v-model, 通过内置方法, 也可以将数据传给其他组件, 摆脱了繁重的template, 用判断替代了v-if, 用for替代了v-for。 获得了更灵活地创建页面的方法。但因为js限制, 要使用v-model.trim这种语法糖, 我们只能将它化为v-mode_trim。

在实际运用中,我们肯定会用到插槽, 而jsx的插槽也很简单

// test.vue
<render :items="items">
      <div>测试</div>
</render>
// render.vue
return (
<div>
  {this.init()}
  {this.$slots.default}
</div>
)

就像这样, 我们通过this.$slot中的信息(也就是VNode)渲染出了需要的组件, 我们甚至可以用forin来循环$slot,这样就算父组件使用了再多具名插槽, 我们也不用在子组件中一一写出。
同样的, 我们看到,vue组件的信息基本都能在this中找到, 当我们使用jsx时, 有需要的话就可以使用其中的信息。如this.$attr, this.$listeners等, 熟练使用可以写出扩展性极佳的组件。

2.动态表格

同样, 我们可以用jsx来封装表格, 就像封装表单一样, 值得注意的是, 我们对表格有着自定义单元格的需求, 但是有了this和render, 这点不难做到。
自定义单元格我们可以视为一个slot, 通过slot直接渲染, 我们也可以通过json(严格来说不是json了)直接渲染, 因为可以直接在数组中使用jsx, 我们只要把渲染所需要的数据传出去就好了。

// table.vue
initTable() {
      const columns = this.tableHeaders.map(i => {
        if (i.slot) {
          return this.$slots[i.slot][0]
        } else if (i.render) {
          const custom = scope => i.render(scope, scope.row)
          return  <el-table-column label={i.label}>{custom}</el-table-column>
        } else {
          return  <el-table-column prop={i.prop} label={i.label}></el-table-column>
        }
      })
      return columns
}

······

render() {
    const prop = {
      attrs: this.$attrs,
      on: this.$listeners
    }
    return (
      <el-table data={this.tableData} {...prop} style='width: 100%'>
        {this.initTable()}
      </el-table>
    )
  },
// test.vue
    <my-table :tableData="tableData" :tableHeaders="tableHeaders">
      <template v-slot:date>
        <el-table-column
          label="日期"
          width="180"
          >
          <template slot-scope="scope">
            <i class="el-icon-time"></i>
            <span style="margin-left: 10px">{{ scope.row.date }}</span>
          </template>
        </el-table-column>
      </template>
    </my-table>
<script>
 tableHeaders = [
        {label: '日期', prop: 'date', slot: 'date'},
        {label: '名字', prop: 'name'},
        {label: '地址', prop: 'address', render: (index, row) => {
            return  <span style="color: red">{row.address}</span>
          }
        },
  ]
</script>

通过jsx, 我们能很轻松的完成动态需求。