阅读 554

如何在Vue中书写JSX

简介

Vue中可以通过render函数代替template来获得完全的JavaScript编程能力。Vue官网上锚点标题的例子,说明了render函数在某些场景下可以有效地简化代码。我们可以通过createElement函数来编写render函数,但是createElement的写法过于繁琐,逻辑稍微复杂一点就会产生一堆代码,而且不易阅读。官方文档中指出可以通过Babel插件,在render函数中使用JSX语法,让代码更接近模板语法。Babel转化插件官方文档已经对JSX语法进行了说明,本文将结合实例说明如何在Vue中书写JSX

建立demo项目

通过Vue CLI快速创建demo项目

vue create learn-vue-jsx
复制代码

默认的@vue/babel-preset-app已经包含了转化JSX语法的插件

@babel/plugin-syntax-jsx
babel-helper-vue-jsx-merge-props
babel-plugin-transform-vue-jsx
复制代码

引入element-ui库,对在使用第三方库中涉及JSX的用法进行说明

vue add element
复制代码

书写JSX

从最简单的例子开始,template版本和render函数版本的Hello World

template版本

<template>
  <p
    id="helloWorld"
    :class="{'hello-world': true}"
    :style="{'color': 'red'}"
    @click="onClick">
    {{this.msg}}
  </p>
</template>
<script>
export default {
  data() {
    return {
      msg: 'Hello World'
    }
  },
  methods: {
    onClick() {
      alert('Hello World');
    }
  }
}
</script>
复制代码

render函数版本

<script>
export default {
  data() {
    return {
      msg: 'Hello World'
    }
  },
  methods: {
    onClick() {
      alert('Hello World');
    }
  },
  render() {
    return (
      <p 
        id="helloWorld"
        class={{'hello-world': true}}
        style={{'color': 'red'}}
        onClick={this.onClick}>
        {this.msg}
      </p>
    );
  }
}
</script>
复制代码

使用第三方库

  1. 使用element-uiel-button组件

template版本

<el-button
  size="medium"
  type="primary"
  round
  loading>
  按钮
</el-button>
复制代码

render函数版本

render() {
  return (
    <el-button
      type="primary"
      size="medium"
      round
      loading>
      按钮
    </el-button>
  );
}
复制代码

效果如下:

因为在全局加载了element-ui库,所以在JSX中可以识别el-button组件。如果是自己编写的控件,需要在components中引入该控件,或者像babel-plugin-transform-vue-jsx文档中介绍的,直接在render函数中使用import进来的控件,注意这里使用的控件首字母必须是大写的,插件才能识别。

import MyButton from './MyButton';
export default {
  render() {
    return (
      <MyButton>按钮</MyButton>
    );
  }
};
复制代码
  1. 使用element-uiel-input组件

template版本

<div>
  <el-input
    v-model="input"
    placeholder="请输入内容">
  </el-input>
  <p>{{input}}</p>
</div>
复制代码

render函数版本

methods: {
  onInput(value) {
    this.input = value;
  }
},
render() {
  return (
    <div>
      <el-input
        value={this.input}
        placeholder="请输入内容"
        onInput={this.onInput}>
      </el-input>
      <p>{this.input}</p>
    </div>
  );
}
复制代码

大部分的Vue的内置指令在JSX都是不支持的,所以需要用其他方式实现。像v-model指令其实是value属性和input事件的语法糖。v-if指令可以使用if语句实现,v-for指令可以使用array.map语句实现。比较例外的是v-show指令可以在JSX使用。具体例子如下:

template版本

<div>
  <p>v-if指令</p>
  <div v-if="isIf">v-if指令内容</div>
  <p>v-for指令</p>
  <div v-for="item in list">{{item}}</div>
  <p>v-show指令</p>
  <div v-show="isShow">v-show指令内容</div>
</div>
复制代码

render函数版本

render() {
  return (
    <div>
      <p>v-if指令</p>
      {
        this.isIf ? <div>v-if指令内容</div> : ''
      }
      <p>v-for指令</p>
      <div>
      {
        this.list.map((item) => {
          return item
        })
      }
      </div>
      <p>v-show指令</p>
      <div v-show="isShow">v-show指令内容</div>
    </div>
  );
}
复制代码
  1. 使用element-uiel-loading组件

JSX中使用自定义指令传递argumentmodifiers的写法比较繁琐,以el-loading组价的指令方式为例:

template版本

<el-button
  type="primary"
  @click="openFullScreen"
  v-loading.fullscreen.lock="fullscreenLoading">
  全屏Loading
</el-button>
复制代码

render函数版本

render() {
  const directives = [
    { 
      name: 'loading',
      value: this.fullscreenLoading,
      modifiers: { fullscreen: true, lock: true } 
    }
  ];
  return (
    <el-button
      type="primary"
      onClick={this.openFullScreen}
      {...{ directives}}>
      全屏Loading
    </el-button>
  );
}
复制代码

babel-plugin-transform-vue-jsx在官方文档中还介绍了另一种书写Vue指令的方法,但是尝试只有v-loading={this.fullscreenLoading}这种写法是生效的,不能设置argumentmodifiers等参数。

  1. 使用element-uiel-table组件

el-table组件提供了自定义表头和自定义列模板的能力,可以通过自定义插槽实现。具体例子如下:

template版本

<template>
  <el-table :data="tableData" style="width: 100%">
    <el-table-column label="日期" prop="date"></el-table-column>
    <el-table-column label="姓名" prop="name"></el-table-column>
    <el-table-column>
      <template slot="header">
        <el-input v-model="search" size="mini" placeholder="输入关键字搜索"/>
      </template>
      <template slot-scope="scope">
        <el-button size="mini" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
        <el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
      </template>
    </el-table-column>
  </el-table>
</template>

<script>
export default {
  data() {
    return {
      tableData: [
        {
          date: '2016-05-02',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1518 弄'
        },
        {
          date: '2016-05-04',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1517 弄'
        },
        {
          date: '2016-05-01',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1519 弄'
        },
        {
          date: '2016-05-03',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1516 弄'
        }
      ],
      search: ""
    };
  },
  methods: {
    handleEdit(index, row) {
      console.log(index, row);
    },
    handleDelete(index, row) {
      console.log(index, row);
    }
  }
};
</script>
复制代码

render函数版本

<script>
export default {
  data() {
    return {
      tableData: [
        {
          date: '2016-05-02',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1518 弄'
        },
        {
          date: '2016-05-04',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1517 弄'
        },
        {
          date: '2016-05-01',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1519 弄'
        },
        {
          date: '2016-05-03',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1516 弄'
        }
      ],
      search: ''
    };
  },
  methods: {
    updateSearch(value) {
      this.search = value;
    },
    handleEdit(index, row) {
      return () => {
        console.log(index, row);
      };
    },
    handleDelete(index, row) {
      return () => {
        console.log(index, row);
      };
    }
  },
  render() {
    let that = this;
    return (
      <el-table data={this.tableData} style={{ width: '100%' }}>
        <el-table-column label="日期" prop="date"></el-table-column>
        <el-table-column label="姓名" prop="name"></el-table-column>
        <el-table-column {...{
          scopedSlots: {
            header: scope => {
              return (
                <el-input size="mini" placeholder="输入关键字搜索" value={that.search} onInput={that.updateSearch}/>
              );
            },
            default: scope => {
              return [
                <el-button size="mini" onClick={that.handleEdit(scope.$index, scope.row)}>编辑</el-button>,
                <el-button size="mini" type="danger" onClick={that.handleDelete(scope.$index, scope.row)}>删除</el-button>
              ];
            }
          }
        }}>
        </el-table-column>
      </el-table>
    );
  }
};
</script>
复制代码

这个例子比较复杂,主要涉及了v-model指令、作用域插槽、事件内联处理等。v-model指令通过设置value属性和监听input事件来实现。作用域插槽的JSX写法可以参考官网渲染函数 & JSX插槽章节进行理解。网上一直没有找到对于事件内联处理的JSX写法介绍,如果按照template模板的方式编写事件内联处理,即类似这种格式onClick={that.handleEdit(scope.$index, scope.row)},会发现在第一次渲染时就触发了事件回调方法,并有click回调事件没有指定的报错。控制台输出结果如下:

通过简单改写事件回调方法,将事件处理的过程放入一个匿名函数并返回,就可以实现事件内联处理的效果。

关注下面的标签,发现更多相似文章
评论