Vue2 + JSX使用总结

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

什么是JSX

摘自 React 官方
它被称为 JSX是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSXJSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模板语言但它具有 JavaScript 的全部功能。

Vue 什么时候应当使用JSX

这里说的是应当而不是必须。因为在绝大多数情况下模板语法都能胜任只不过写起来看着不太好看而已。或者使用模板语法那写起来恐怕不是一般的长而且阅读会费劲很多。

下面我们来看下应当使用JSX而不是模板语法的情况

假设现在有如下需求
封装一个机遇ant-design-vue的输入框组件组件要求有如下功能
1.传入form属性布尔值组件自动套上a-form-model-item标签并接收相应的属性
2.placeholderTip 属性布尔值组件自动挡套上a-tooltip显示值为placeholder
3.传入span数字并且大于0小于24自动套上a-col标签
4.如果都没传那就只渲染a-input标签
5.如果同时传123中两个以上的属性那么包裹顺序为从外到里依次是a-col,a-form-model-item,a-tooltip

让我们先用模板语法实现下这个组件 input.vue

<!--input 输入框-->
<template>
  <div>
<!--    先判断是否有span-->
    <a-col v-if="span > 0 && span<24" :span="span">
      <!--    先判断是否有form-->
      <a-form-model-item  v-if="form" :label="label" :prop="prop">
        <!--    先判断是否有placeholderTip-->
        <a-tooltip v-if="placeholderTip" placement="topLeft" :title="placeholder">
          <a-input v-bind="$attrs"
                   :value="value"
                   @change="inputChange"
          />
        </a-tooltip>
        <!--    都没有只渲染a-input额-->
        <a-input v-else
                 v-bind="$attrs"
                 :value="value"
                 @change="inputChange"
        />
      </a-form-model-item>
      <!--    如果没有form判断是否有placeholderTip-->
      <a-tooltip v-else-if="placeholderTip" placement="topLeft" :title="placeholder">
        <a-input v-bind="$attrs"
                 :value="value"
                 @change="inputChange"
        />
      </a-tooltip>
      <!--    都没有只渲染a-input额-->
      <a-input v-else
               v-bind="$attrs"
               :value="value"
               @change="inputChange"
      />
    </a-col>
    <!--    先判断是否有form-->
    <a-form-model-item  v-else-if="form" :label="label" :prop="prop">
      <!--    先判断是否有placeholderTip-->
      <a-tooltip v-if="placeholderTip" placement="topLeft" :title="placeholder">
        <a-input v-bind="$attrs"
                 :value="value"
                 @change="inputChange"
        />
      </a-tooltip>
      <!--    都没有只渲染a-input额-->
      <a-input v-else
               v-bind="$attrs"
               :value="value"
               @change="inputChange"
      />
    </a-form-model-item>
    <!--    如果没有form判断是否有placeholderTip-->
    <a-tooltip v-else-if="placeholderTip" placement="topLeft" :title="placeholder">
      <a-input v-bind="$attrs"
               :value="value"
               @change="inputChange"
      />
    </a-tooltip>
<!--    都没有只渲染a-input额-->
    <a-input v-else
              v-bind="$attrs"
             :value="value"
             @change="inputChange"
    />
  </div>
</template>
<script>
export default {
  name: "Input",
  props: {
    value: [String, Number], // 值
    placeholderTip: Boolean, // 输入框 placeholder提示
    form: Boolean, // 是否使用form-item标签包裹
    label: String, // 标签
    prop: String, // 校验的prop
    placeholder:String,//提示
    span:Number,//span
  },
  methods: {
    /**
     * 输入框改变
     * @param e
     */
    inputChange(e) {
      let v = e.target.value
      this.$emit("input", v)
      this.$emit("change", v)
    },
  },
}
</script>

从上面代码我们可以看出有好几段看起来一样的代码但是我们却不好抽离出来。或者说并不能完全剥离。
比如这段

 <a-tooltip v-else-if="placeholderTip" placement="topLeft" :title="placeholder">
        <a-input v-bind="$attrs"
                 :value="value"
                 @change="inputChange"
        />
      </a-tooltip>

在代码中被用了4次看着是可以剥离出去的抽成一个新的子组件然而接着你发现他里面的

  <a-input v-bind="$attrs"
                 :value="value"
                 @change="inputChange"
        />

这个却是在父组件有单独使用。而且就算剥离出去模板里面的多个相同的判断v-else-if=“placeholderTip” 和 v-if="placeholderTip"也无法减少。

下面我们用jsx来实现一下

<script>
export default {
  name: "Input",
  props: {
    value: [String, Number], // 值
    placeholderTip: Boolean, // 输入框 placeholder提示
    form: Boolean, // 是否使用form-item标签包裹
    label: String, // 标签
    prop: String, // 校验的prop
    placeholder:String,//提示
    span:Number,//span
  },
  methods: {
    /**
     * 输入框改变
     * @param e
     */
    inputChange(e) {
      let v = e.target.value
      this.$emit("input", v)
      this.$emit("change", v)
    },
    /**
     * 渲染form-item节点
     * @param child
     * @returns {JSX.Element}
     */
    renderFormItem (child) {
      const { label, prop} = this
      return <a-form-model-item label={label} prop={prop}>
        {child}
      </a-form-model-item>
    },
    /**
     * 渲染输入框组件
     * @returns {JSX.Element}
     */
    renderInputDom(){
      return <a-input attrs={this.$attrs}
                     value={this.value}
                     onChange={this.inputChange}
      />
    },
  },
  render (createElement, context) {
    const { placeholderTip, form,span } = this
    const hasSpan = typeof span === "number" && span>0 && span<24
    const inputChild = placeholderTip ? <a-tooltip placement="topLeft" title={this.placeholder}>
      { this.renderInputDom()}
    </a-tooltip> : this.renderInputDom()
    if(form){
      return hasSpan ? <a-col span={span}>{this.renderFormItem(inputChild)}</a-col> : this.renderFormItem(inputChild)
    }
    return hasSpan ? <a-col span={span}>{inputChild}</a-col> : inputChild
  }
}
</script>

看下两者的不同
首先就从代码行数来说用模板91行去掉模板里面的注释那也还有80行而用jsx不到60行
其次使用jsx我们将渲染a-input和a-form-model-item抽离成渲染函数是否有a-tooltip 和 a-col则使用三元运算符配合。在需要的地方调用相应的渲染函数相比模板语法的直接复制标签jsx维护性更好。

上面这个例子也许还不能看出jsx的重要性。下面说个复杂点的需求。
1.一个表单页面表单项是动态的
2.页面渲染哪个表单组件输入框还是下拉框或者单选复选框等是根据服务器返回的数据指明的值渲染的
3.页面每行排几个表单元素是动态的根据服务器返回的值和表单元素本身一些特性来决定比如多文本输入框富文本直接要求占整行
4.表单元素排列的顺序先后由数据数组的下标决定

这个需求例子就不写了。如果用模板语法那会更糟糕。

那我们什么时候应当使用jsx而不是模板
1.页面的渲染有比较多的条件而且这些条件又不在同一层有交叉嵌套等情况
2.页面元素是动态的元素间排列组合是动态的

当然对于vue的开发者来说一般的业务开发还是模板为主用起来更简单。至于jsx除了上面说的动态表单外组件封装可能会用的相对较多。

jsx用法总结

对于使用vue-cli创建的项目,jsx是自带的我们不需要安装啥东西。如果么有使用vue-cli创建项目这里假设已经安装了@vue/babel-preset-jsx插件

  • 1 如何使用jsx替代template标签渲染dom
    使用模板
<template>
	<div>
		<div>测试</div>
	</div>
</template>

使用jsx。render函数用于渲染html在methods的方法里面也可以直接return html标签。在.vue文件中需要写在script标签里面在js文件或者jsx文件中则不用script标签

jsx返回标签可以简单理解为拼串在大括号{}里面可以写js代码

<script>
export default {
	methods:{
	/**
	渲染子元素
	**/
		renderChild(){
			return <div>测试</div>
		}
	},
	render(){
		return <div>{ this.renderChild() }</div>
	}
}
</script>

2 如何书写属性特别是值是动态变化的属性
使用模板

<template>
	<div>
		<div :title="$route.name">测试</div>
	</div>
</template>

jsx。有变化的地方就是用大括号{}至于大括号里面那就是js代码。所以下面的示例title={this.$route.name}也可写成 title={this.getName()},类似这样在getName函数里面return 出值就好。

<script>
export default {
	/**
	渲染name
	**/
		getName(){
			return this.$route.name
			//假设组件最终需要的是组件或者标签这里还可以类似这样
			//return <div>{this.$route.name}</div>
		}
	render(){
		return <div title={this.$route.name}>测试</div>
		//return <div title={this.getName()}>测试</div>
	}
}
</script>

这里重点说下class和style。由于class和style写法相对比较多同样的jsx也可以有多种形式
测试样式效果图
在这里插入图片描述

样式

.default999{
  color:#999999;
  background: blueviolet;
}
.border-red{
  border:solid 1px red;
  margin-bottom: 20px;
}
.yellow-bk{
  background: yellow;
}
.red{
  color:red;
}
.green{
  color:green;
}
.edit{
  box-shadow: 1px 1px 1px #2b96ff;
}
.view{
  box-shadow: 2px 2px 1px #8cc5ff;
}
.bold-font{
  font-weight: bold;
}
.line-through{
  text-decoration: line-through;
}

使用模板

<template>
  <div>
    <div :class="{'yellow-bk':$route.query.isEdit === 'true'}">yellow-bk</div>
    <div class="default999" :class="{'yellow-bk':$route.query.isEdit === 'true'}">yellow-bk default999</div>
    <div class="border-red" :class="$route.query.isEdit === 'true' ? 'yellow-bk' : 'default999'">yellow-bk | default / border-red</div>
    <div class="border-red" :class="[$route.query.id === 'xxx' ? 'red' : 'green', editStyle]">red | green / bold-font | line-through /border-red</div>
    <div class="border-red" :style="{
        'margin-bottom':$route.query.isEdit === 'true' ? '8px' : '10px',
        'display':$route.query.isEdit === 'true' ? 'block' : 'flex'
      }">style 1 /border-red</div>
    <div class="border-red" :style="errStyle">style 2 /border-red / errStyle</div>
  </div>
</template>
<script>
export default {
  data (){
    return {
      errStyle:{
        color:"red",
        background:"#85ce61"
      }
    }
  },
  computed:{
    editStyle(){
      return this.$route.query.isEdit === "false" ? "bold-font" : "line-through"
    }
  },
  methods: {
    /**
     * 输入框改变
     * @param e
     */
    inputChange(e) {
      let v = e.target.value
      this.$emit("input", v)
      this.$emit("change", v)
    },
  },
}
</script>

style其实就是一个对象所以不管怎么变只要得到一个对象或者返回一个对象即可
class花样要多点模板有对象形式三元运算符形式数组形式默认类名。当然也可以用函数返回

jsx

<script>
export default {
  data (){
    return {
      errStyle:{
        color:"red",
        background:"#85ce61"
      }
    }
  },
  computed:{
    editStyle(){
      return this.$route.query.isEdit === "false" ? "bold-font" : "line-through"
    }
  },
  methods: {
    /**
     * 输入框改变
     * @param e
     */
    inputChange(e) {
      let v = e.target.value
      this.$emit("input", v)
      this.$emit("change", v)
    },
  },
  render(){
    return   <div>
      <div class={{'yellow-bk':this.$route.query.isEdit === 'true'}}>yellow-bk</div>
      <div class={{ 'yellow-bk': this.$route.query.isEdit === 'true',default999:true }}>yellow-bk/default999</div>
      <div class={this.$route.query.isEdit === 'true' ? 'yellow-bk border-red' : 'default999 border-red'}>yellow-bk  /border-red</div>
      <div class={[this.$route.query.id === 'xxx' ? 'red' : 'green', this.editStyle,'border-red']}>red | green / bold-font | line-through /border-red </div>
      <div class="border-red" style={{
        'margin-bottom':this.$route.query.isEdit === 'true' ? '8px' : '10px',
        'display':this.$route.query.isEdit === 'true' ? 'block' : 'flex'
      }}>style 1 /border-red</div>
      <div class="border-red" style={this.errStyle}>style 2 /border-red / errStyle</div>
    </div>
  }
}
</script>

模板语法可以使用两个class,一个正常使用一个变量形式 如

<span class="red" :class={blue:isEdit}></span>

jsx只能写一个class上面需要写成

<span :class={blue:isEdit,red:true}></span>

3 指令
在jsx里面指令变成小驼峰例如v-model可变为vModel

3.1 自定义指令
模板语法

<template>
  <div>
    <div v-default></div>
    <div v-default:指令参数默认值></div>
    <div v-default="{name:'动态数据默认值'}"></div>
    <div v-default:指令参数默认值="{name:'动态数据默认值'}"></div>
  </div>

</template>
<script>
export default {
  directives:{
    default:{
      // 当被绑定的元素插入到 DOM 中时……
      bind: function (el, binding, vnode) {
        const actionName = binding.arg
        const value = binding.value
        console.log("actionName",actionName)
        console.log("value",value)
        let innerHtml = (actionName || "") + (value ? ((actionName ? "+" : "") + JSON.stringify(value)):  "")
        el.innerHTML=innerHtml || "自定义指令"
      }
    }
  }
}
</script>

代码运行后结果如图
在这里插入图片描述

jsx

<script>
export default {
  directives:{
    default:{
      // 当被绑定的元素插入到 DOM 中时……
      bind: function (el, binding, vnode) {
        const actionName = binding.arg
        const value = binding.value
        console.log("actionName",actionName)
        console.log("value",value)
        let innerHtml = (actionName || "") + (value ? ((actionName ? "+" : "") + JSON.stringify(value)):  "")
        el.innerHTML=innerHtml || "自定义指令"
      }
    }
  },
  render(createElement, context) {
    return   <div>
      <div vDefault></div>
      <div vDefault:指令参数默认值></div>
      <div vDefault={{name:'动态数据默认值'}}></div>
      <div vDefault:指令参数默认值={{name:'动态数据默认值'}}></div>
    </div>
  }
}
</script>

上面这段代码看似没问题但是运行后我们发现结果如下
在这里插入图片描述
发现多了个true也就是指令没有传值的时候默认为true想要实现模板的效果还需更改下vDefault={undefined}下面是更改后的代码

<script>
export default {
  name:"Input",
  directives:{
    default:{
      // 当被绑定的元素插入到 DOM 中时……
      bind: function (el, binding, vnode) {
        const actionName = binding.arg
        const value = binding.value
        console.log("actionName",actionName)
        console.log("value",value)
        let innerHtml = (actionName || "") + (value ? ((actionName ? "+" : "") + JSON.stringify(value)):  "")
        el.innerHTML=innerHtml || "自定义指令"
      }
    }
  },
  render(createElement, context) {
    return   <div>
      <div vDefault={undefined}></div>
      <div vDefault:指令参数默认值={undefined}></div>
      <div vDefault={{name:'动态数据默认值'}}></div>
      <div vDefault:指令参数默认值={{name:'动态数据默认值'}}></div>
    </div>
  }
}
</script>

3.2 内置指令
这些指令有 v-htmlv-ifv-forv-textv-showv-modelv-bindv-onv-slot等。其中只有少部分适用于驼峰形式

3.2.1 适用于驼峰形式的指令v-showv-modelv-on在事件绑定处单独说明
以表单双向数据绑定的v-model举例
模板语法

<template>
<a-input v-model="value">
</template>
<script>
	export default{
		data(){
			return {
				value:"",//值
				}
		}
	}
</script>

使用jsx

<script>
	export default{
		data(){
			return {
				value:"",//值
				}
		}
		render(){
			return <a-input vModel={this.value}/>
			}
	}
</script>

修饰符
模板语法

<input v-model.trim="value"/>

jsx,使用_分隔修饰符

<input vModel_trim={this.value} />

3.2.2 不适用与驼峰形式的指令。内置指令大部分都不适用于驼峰形式除v-slot放插槽处单独说明外下面一一列举。

3.2.2.1 v-html
我们先用v-html来试下使用驼峰形式的例子
模板语法

<template>
  <div v-html="'自定义html'"></div>
</template>

按照上面的写法使用jsx

<script>
export default {
  render(createElement, context) {
    return   <div>
      <div vHtml={"自定义html"}></div>
    </div>
  }
}
</script>

写好后我们运行发现报错
vue.runtime.esm.js?c320:619 [Vue warn]: Failed to resolve directive: html
(found in)
在这里插入图片描述
就是说html不是一个指令。@vue/babel-preset-jsx给出的标准写法是使用domPropsInnerHTML

<script>
export default {
  name:"Input",
  render(createElement, context) {
    return   <div>
      <div domPropsInnerHTML={"自定义html"}></div>
    </div>
  }
}
</script>

3.2.2.2 v-text

模板语法

<template>
  <div>
    <div v-text="text"></div>
  </div>
</template>

<script>
export default {
  data(){
    return{
      text:"vText文字"
    }
  }
}
</script>

jsx语法使用domPropsInnerText

<script>
export default {
  name: 'JsxExample',
  data(){
    return{
      text:"vText文字"
    }
  },
  render() {
    return   <div>
      <div domPropsInnerText={this.text}></div>
    </div>
  }
}
</script>

3.2.2.3 v-if
这恐怕是最简单的了v-if就是if else 语法
使用模板

<template>
	<div>
		<div v-if="$route.query.id === 'xxx'">测试</div>
		<div v-else>else渲染</div>
	</div>
</template>

jsx
使用 domPropsInnerText

<script>
export default {
	render(){
		if(this.$route.query.id === 'xxx'){
			return <div>测试</div>
		}
		return <div>else渲染</div>
	}
}
</script>

或者

<script>
export default {
  render(){
    return <div>{this.$route.query.id === 'xxx' ? "测试" : "else渲染"}</div>
  }
}
</script>

3.2.2.4 v-for
使用模板

<template>
  <div>
    <div v-for="(item,index) in list" :key="index">
       <span>{{item + index}}</span>
     </div>
  </div>
</template>
<script>
export default {
  data (){
    return {
      list:["测试","测试","测试","测试","测试","测试","测试"]
    }
  },
}
</script>

使用jsx

v-for 的jsx习惯使用map方法和reduce方法。最终的结果就是得到一个由dom节点组成的数组。所以除了习惯性的map和reduce方法以外理论上可以遍历的方法都可以使用。下面分别使用map,reduce和for 循环来实现。

<script>
export default {
  data (){
    return {
      list:["测试","测试","测试","测试","测试","测试","测试"],//数据列表
    }
  },
  methods:{
    /**
     * 使用map
     * @returns {JSX.Element}
     */
    renderDomUseMap(){
      return <div>
        {
          this.list.map((item,index)=><div><span>{item + index} use map</span></div>)
        }
      </div>
    },
    /**
     * 使用reduce
     * @returns {JSX.Element}
     */
    renderDomUseReduce(){
      return <div>
        {
          this.list.reduce((result,current,index)=>{
            result.push(<div><span>{current + index} use reduce</span></div>)
            return result
          },[])
        }
      </div>
    },
        /**
     * 使用for循环
     * @returns {[]}
     */
    renderDomUseFor(){
      let listDom = []
      for(let i=0;i<this.list.length;i++){
        listDom.push(<p>{this.list[i] + i} use for</p>)
      }
      return <div>
        {listDom}
      </div>
    }
  },
  render(createElement, context) {
    // return this.renderDomUseMap()
     // return this.renderDomUseFor()
    return this.renderDomUseReduce()
  }
}
</script>

3.2.2.5 v-bind=“$attrs”
封装组件的时候为了能全部集成我们组件内依赖的某个组件的属性比如我们封装一个自定义功能的输入框希望能全部基础a-input的属性又不想去全部吧属性定义一遍。这时候会用到v-bind=“$attrs”
我们先用模板语法定义一个输入框组件组件名字my-input.vue。这里的输入框基于ant-design-vue 的a-input组件

<template>
  <div>
    <a-input :value="value" v-bind="$attrs" @change="inputChange"/>
  </div>
</template>

<script>
export default {
name:"MyInput",
  props:{
    value:String,//值
  },
  methods:{
    /**
     * 点击
     */
    inputChange(e){
      console.log(e)
      this.$emit("input",e.target.value)
      this.$emit("change",e.target.value)
    }
  }
}
</script>

然后我们在父级页面引用这里父级页面为home-view.vue

<template>
  <div class="home">
    <my-input v-model="inputValue" style="width: 300px;margin:0 auto;"/>
  </div>
</template>

<script>
import MyInput from '@/components/my-input.vue'
export default {
  name: 'HomeView',
  components: {
    MyInput
  },
  data(){
    return{
      inputValue:"",//值
    }
  }
}
</script>

代码运行结果界面
在这里插入图片描述
现在我们在父级组件引用标签处加上我们组件内并没有定义的属性addon-before虽然我们没有定义但是a-inpu携带该属性且my-input组件使用了v-bind=“$attrs”

 <my-input v-model="inputValue" addon-before="Http://" style="width: 300px;margin:0 auto;"/>

加上后运行效果如下
在这里插入图片描述
jsx语法
下面用jsx实现v-bind=“$attrs”

<script>
export default {
  name: 'MyInput',
  props:{
    value:String,//值
  },
  methods:{
    /**
     * 点击
     */
    inputChange(e){
      console.log(e)
      this.$emit("input",e.target.value)
      this.$emit("change",e.target.value)
    }
  },
  render() {
    return   <div>
      <a-input value={this.value} vOn:change={this.inputChange}  attrs={this.$attrs} />
    </div>
  }
}
</script>

只需在a-input标签上加上 attrs={this.$attrs} 即可

4 如何绑定事件
4.1 普通事件绑定
模板语法

<template>
  <div>
    <div @click="bindEvent">绑定事件</div>
    <a-input @change="inputChange" v-model="value"></a-input>
  </div>
</template>
<script>
export default {
  name:"Input",
  data(){
    return{
      value:"",//值
    }
  },
  methods:{
    /**
     * 绑定事件
     */
    bindEvent(e){
      console.log(e)
    },
    /**
     * 输入事件
     * @param e
     */
    inputChange(e){
      console.log(e)
    }

  }
}
</script>


jsx

<script>
export default {
  name:"Input",
  data(){
    return{
      value:"",//值
    }
  },
  methods:{
    /**
     * 绑定事件
     */
    bindEvent(e){
      console.log(e)
    },
        /**
     * 绑定事件
     */
    bindEventByVon(e){
      console.log(e)
    },
    /**
     * 输入事件
     * @param e
     */
    inputChange(e){
      console.log(e)
    },
    input(e){
      this.value = e.target.value
    }

  },
  render(){
    return   <div>
      <div onClick={this.bindEvent}>绑定事件</div>
       <div vOn:click={this.bindEventByVon}>v-on指令形式绑定事件</div>
    <a-input onChange={this.inputChange} vModel={this.value} />
  </div>
  }
}
</script>

结合事件说下v-model。由于v-model是由属性 value和事件input组成因此 v-model除了如上述示例使用vModel以外还可以分开写如下

 <a-input onChange={this.inputChange} value={this.value} onInput={e=>this.value=e.target.value}/>

4.2 绑定事件时传递参数
在模板语法中我们可以随意如下书写

<div>
	<a-input @input="input()"/>
	<a-button @click="submit('form')">按钮</a-button>
</div>

使用jsx时按照模板语法的思路和习惯我们可能会如下书写

<div>
	<a-input onInput={this.input()}/>
	<a-button onClick={this.submit('form')}>按钮</a-button>
</div>

这时候会发现页面刚加载事件就被调用了。如果把模板语法看成是在页面写html的话写jsx就是通过javascript创建页面元素所以this.input()就是直接调用了该函数所以不能写括号需要写出this.input也就是不需要调用因为事件需要某些因素条件才能出发。那同理我们也不能写成this.submit(‘form’)这样函数就会直接被调用了。但是事件确实需要传参的话就需要套在一个匿名函数里面调用如下

<div>
	<a-input onInput={()=>this.input()}/>
	<a-button onClick={()=>this.submit('form')}>按钮</a-button>
</div>

4.3 事件修饰符
在vue里面有些很好用得事件修饰符比如@click.stop @click.13等。jsx里面修饰符用_连接
模板语法

<template>
  <div>
    <input @click.stop.prevent="click" />
  </div>
</template>

<script>
export default {
  methods:{
    /**
     * 点击
     */
    click(){
      console.log("点击")
    }
  }
}
</script>

jsx

<script>
export default {
  name: 'JsxExample',
  methods:{
    click(){
      console.log("click")
    }
  },
  render() {
    return   <div>
      <input vOn:click_stop_prevent={this.click} />
    </div>
  }
}
</script>

4.4 v-on=“$listeners”
和v-bind=“$attrs"类似功能v-on=”$listeners"可以让子组件继承所有我们依赖的组件的事件

模板语法

<template>
  <div>
    <a-input :value="realValue" v-bind="$attrs" v-on="$listeners" @input="inputEvent" />
  </div>
</template>

<script>
export default {
  props:{
    value:[InputEvent,String],//值
  },
  data(){
    return{
      realValue:"",//真实的值
    }
  },
  watch:{
    value:function (e){
      this.realValue = (typeof e === "string" || !e) ? e : e.target.value
    }
  },
  methods:{
    inputEvent(e){
      console.log(e)
      this.$emit("input",e)
    }
  }
}
</script>

这里顺便讲下基于ant-design-vue和基于element-ui的输入框使用v-on=“$listeners"时的一些小区别。
ant-design-vue 的 a-input 的 input事件反出的是event事件但是value属性接收的是字符串或数字。因此不能直接将prop的value赋值给 a-input需要单独做处理后见上面代码的 watch监听。使用v-on=”$listeners"的情况下直接将prop的value赋值给 a-input,会重新触发$listeners里面的input或者change事件造成接收值不准确报错。

使用element-ui就不存在这个问题因为element-ui的input事件直接返回value值而不是event事件。使用element-ui可以如下

<template>
  <div>
    <el-input :value="value" v-bind="$attrs" v-on="$listeners" @input="inputEvent" />
  </div>
</template>

<script>
export default {
  props:{
    value:[String],//值
  },
  methods:{
    inputEvent(value){
      console.log(value)
      this.$emit("input",value)
    }
  }
}
</script>

那如何验证v-on="$listeners"生效呢我们在父级组件绑定一个没有直接声明的事件即可。这里以ant-design-vue 的 a-input举例。ant-design-vue的a-input组件有个回车事件pressEnter。

父级组件HomeView.vue代码

<template>
  <div class="home">
    <my-input v-model="inputValue" addon-before="Http://" style="width: 300px;margin:0 auto;" @pressEnter="pressEnter"/>
  </div>
</template>

<script>
import MyInput from '@/components/jsx-example.vue'

export default {
  name: 'HomeView',
  components: {
    MyInput
  },
  data(){
    return{
      inputValue:"",//值
    }
  },
  methods:{
    /**
     * 按下回车键
     */
    pressEnter(e){
      console.log(e)
    },
  }
}
</script>

运行后在输入框按回车健我们可以看到pressEnter事件成功打印了值

v-on="$listeners"的jsx语法。使用on监听

<script>
export default {
  name: 'MyInput',
  props:{
    value:[String,InputEvent],//值
  },
  data(){
    return{
      realValue:"",//真实的值
    }
  },
  watch:{
    value:function (e){
      this.realValue = (typeof e === "string" || !e) ? e : e.target.value
    }
  },
  methods:{
    /**
     * 点击
     */
    inputChange(e){
      console.log(e)
      this.$emit("input",e)
    }
  },
  render() {
    return   <div>
      <a-input value={this.realValue}
               attrs={this.$attrs}
               vOn:change={this.inputChange}
               on={this.$listeners}
      />
    </div>
  }
}
</script>

既然可以用on属性那我们在jsx监听事件时也可以直接在on里面书写。如下

  render() {
    return   <div>
      <a-input value={this.realValue}
               attrs={this.$attrs}
               on={{
                 change:this.inputChange,
                 ...this.$listeners
               }}
      />
    </div>
  }

5 插槽
插槽包括父组件使用jsx和子组件使用jsx默认插槽具名插槽以及作用域插槽。

5.1 默认插槽与具名插槽
我们先从简单的例子开始创建一个my-slot组件使用模板语法组件里面包括默认插槽和具名插槽
my-slot.vue

<template>
  <div>
    <div>
      <slot name="top"></slot>
    </div>
    <slot></slot>
    <div>
      <slot name="bottom"></slot>
    </div>
  </div>
</template>

然后我们在父级组件,HomeView.vue同样使用模板语法使用插槽代码如下

<template>
  <div class="home">
    <my-slot>
      默认插槽
      <template #top>
        顶部插槽内容
      </template>
      <template #bottom>
        底部部插槽内容
      </template>
    </my-slot>
  </div>
</template>

<script>
import MySlot from '@/components/my-slot.vue'
export default {
  name: 'HomeView',
  components: {
    MySlot
  },
}
</script>

或者使用vue比较老的插槽使用语法slot属性该属性在vue 2.6.0版本后被废弃

<template>
  <div class="home">
    <my-slot>
      默认插槽
      <div slot="top">
        顶部插槽内容
      </div>
      <div slot="bottom">
        底部部插槽内容
      </div>
    </my-slot>
  </div>
</template>

<script>
import MySlot from '@/components/my-slot.vue'
export default {
  name: 'HomeView',
  components: {
    MySlot
  },
}
</script>

新建一个AboutView.vue作为新的父级组件使用jsx语法

按jsx-vue2示例的写法

<script>
import MySlot from '@/components/my-slot.vue'

export default {
  name: 'AboutView',
  components: {
    MySlot
  },
  render() {
    return   <div class="home">
      <my-slot>
        <div slot="top">
          顶部插槽内容
        </div>
        默认插槽
        <div slot="bottom">
          底部部插槽内容
        </div>
      </my-slot>
  </div>
  }
}
</script>

父级使用jsx语法使用插槽还是比较简单的和模板语法没啥区别甚至和模板语法被废弃的slot属性完全一样。

接下来我们对my-slot.vue进行jsx改造。jsx里面子组件使用this.$slots接收插槽默认插槽的名字是default。代码如下

<script>
export default {
  name: 'MySlot',
  render() {
    const slots = this.$slots
    console.log(slots)
    return    <div>
      <div>
        {slots.top}
      </div>
      {slots.default}
      <div>
        {slots.bottom}
      </div>
    </div>
  }
}
</script>

5.2 作用域插槽
作用域插槽就是父级组件可以使用子组件通过prop传递过来的变量的插槽。我们先将模板语法的my-slot定义的插槽改造成作用域插槽

<template>
  <div>
    子组件原本内容
    <div>
      <slot name="testScopeSlot" :user="user"></slot>
    </div>
  </div>
</template>
<script>
export default {
  data(){
    return{
      user:{
        name:"张三"
      },//用户信息
    }
  }
}
</script>

相应的对HomeView.vue做相应的改造以便能够接收使用user

<template>
  <div class="home">
    <my-slot>
      <template #testScopeSlot="{user}">
        作用域插槽内容{{user.name}}
      </template>
    </my-slot>
  </div>
</template>

<script>
import MySlot from '@/components/my-slot.vue'

export default {
  name: 'HomeView',
  components: {
    MySlot
  },
}
</script>

若父级组件使用vue 2.6.0后废弃的语法如下

<template>
  <div class="home">
    <my-slot>
      <div slot="testScopeSlot" slot-scope="{user}">
        作用域插槽内容{{user.name}}
      </div>
    </my-slot>
  </div>
</template>

<script>
import MySlot from '@/components/my-slot.vue'

export default {
  name: 'HomeView',
  components: {
    MySlot
  },
}
</script>

对AboutView.vue进行改造以便能使用jsx语法接收和使用my-slot的user变量

<script>
import MySlot from '@/components/my-slot.vue'

export default {
  name: 'AboutView',
  components: {
    MySlot
  },
  render() {
    return   <div class="home">
      <my-slot
          scopedSlots={{
            testScopeSlot: ({user}) => {
              return `作用域插槽内容${user.name}`
            }
          }}
      >
      </my-slot>
  </div>
  }
}
</script>

这里相对之前的都比较难于理解用slot slot-scope已经不管用了。父组件想要读到子组件通过插槽返出的变量需要在子组件标签上挂载scopedSlots属性。scopedSlots是一个对象里面包含了子组件定义的各个插槽以名字为键名键值是一个函数。默认插槽名字仍然是default。本示例定义的插槽名字是testScopeSlottestScopeSlot的值是函数函数的参数是对象对象里包含user即子组件返出的变量名。

下面我们使用jsx改造my-slot的作用域插槽

<script>
export default {
  name: 'MySlot',
  data(){
    return{
      user:{
        name:"张三"
      },//用户信息
    }
  },
  render() {
    const scopedSlots = this.$scopedSlots
    console.log(scopedSlots)
    // const testScopeSlotDom = scopedSlots.testScopeSlot({user:this.user})
    // console.log(testScopeSlotDom)
    return  <div>
      子组件原本内容
      <div>
        {scopedSlots.testScopeSlot({user:this.user})}
    </div>
  </div>
  }
}
</script>

在这里插入图片描述
由于testScopeSlot是一个函数因此我们只需要执行testScopeSlot函数即可然后将use作为函数的参数传递就行。这里有点绕可以这样反过来理解父级组件定义了一个函数函数接收一个对象参数对象中包含user属性将这个函数传递到子组件子组件执行这个函数并将子组件变量作为参数传递给函数子组件执行函数后函数将相应的结果return出去被父组件接收然后父组件处理用于显示。

下面我们将最初定义的默认插槽和具名插槽都改成作用域插槽试试。更改后的my-slot

<script>
export default {
  name: 'MySlot',
  data(){
    return{
      topInfo:"我是顶部插槽数据",//顶部插槽
      defaultInfo:"我是默认插槽数据",//顶部插槽
      bottomInfo:"我是顶部插槽数据",//顶部插槽
    }
  },
  render() {
    const scopedSlots = this.$scopedSlots
    const {topInfo,defaultInfo,bottomInfo} = this
    return   <div>
      <div>
        {scopedSlots.top({topInfo})}
      </div>
      {scopedSlots.default({defaultInfo})}
      <div>
        {scopedSlots.bottom({bottomInfo})}
      </div>
    </div>
  }
}
</script>

相应的我们更改AboutView.vue文件

<script>
import MySlot from '@/components/my-slot.vue'

export default {
  name: 'AboutView',
  components: {
    MySlot
  },
  render() {
    return   <div class="home">
      <my-slot
          scopedSlots={{
            top: ({topInfo}) => {
              return `作用域插槽内容${topInfo}`
            },
            default: ({defaultInfo}) => {
              return `作用域插槽内容${defaultInfo}`
            },
            bottom: ({bottomInfo}) => {
              return `作用域插槽内容${bottomInfo}`
            }
          }}
      >
      </my-slot>
  </div>
  }
}
</script>

运行结果
在这里插入图片描述
按照vue默认定义的作用域插槽数据参数是一个对象形式。因此我们在子组件执行函数时需要按对象形式传递如 { topInfo } 。既然是我们自己传递参数那我们是不是可以更改下参数传递形式如下 my-slot.vue

<script>
export default {
  name: 'MySlot',
  data(){
    return{
      topInfo:"我是顶部插槽数据",//顶部插槽
      defaultInfo:"我是默认插槽数据",//顶部插槽
      bottomInfo:"我是顶部插槽数据",//顶部插槽
    }
  },
  render() {
    const scopedSlots = this.$scopedSlots
    const {topInfo,defaultInfo,bottomInfo} = this
    return   <div>
      <div>
        {scopedSlots.top(topInfo)}
      </div>
      {scopedSlots.default(defaultInfo)}
      <div>
        {scopedSlots.bottom(bottomInfo)}
      </div>
    </div>
  }
}
</script>

然后相应的 AboutView.vue做更改

<script>
import MySlot from '@/components/my-slot.vue'

export default {
  name: 'AboutView',
  components: {
    MySlot
  },
  render() {
    return   <div class="home">
      <my-slot
          scopedSlots={{
            top: (topInfo) => {
              return `作用域插槽内容${topInfo}`
            },
            default: (defaultInfo) => {
              return `作用域插槽内容${defaultInfo}`
            },
            bottom: (bottomInfo) => {
              return `作用域插槽内容${bottomInfo}`
            }
          }}
      >
      </my-slot>
  </div>
  }
}
</script>

这里把传递和接收参数都改成字符串运行结果相同。这里也提现了jsx在某种情况下的优势相比模板语法jsx能更灵活的控制代码逻辑。

参考vue2-jsx: https://github.com/vuejs/jsx-vue2

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: vue