如何在Vue中書寫JSX

oliverbi發表於2019-01-27

簡介

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>
  );
}
複製程式碼

效果如下:

如何在Vue中書寫JSX
因為在全域性載入了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回撥事件沒有指定的報錯。控制檯輸出結果如下:

如何在Vue中書寫JSX
通過簡單改寫事件回撥方法,將事件處理的過程放入一個匿名函式並返回,就可以實現事件內聯處理的效果。

相關文章