Vue2 + JSX使用总结

什么是JSX

摘自 React 官方:
它被称为 JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 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.如果同时传1,2,3中两个以上的属性,那么包裹顺序为,从外到里依次是a-col,a-form-model-item,a-tooltip

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





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

 
        
      

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

  

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

下面我们用jsx来实现一下



看下两者的不同:
首先就从代码行数来说,用模板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?
    使用模板

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

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


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


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


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


image.png

样式

.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;
}

使用模板




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

jsx


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


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


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

3.1 自定义指令
模板语法



代码运行后结果如图:


image.png

jsx


上面这段代码看似没问题,但是运行后,我们发现结果如下:


image.png

发现多了个true,也就是指令没有传值的时候,默认为true,想要实现模板的效果,还需更改下,vDefault={undefined},下面是更改后的代码


3.2 内置指令
这些指令有 v-html,v-if,v-for,v-text,v-show,v-model,v-bind,v-on,v-slot等。其中只有少部分适用于驼峰形式

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



使用jsx


修饰符
模板语法


jsx,使用_分隔修饰符


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

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


按照上面的写法使用jsx


写好后,我们运行,发现报错
vue.runtime.esm.js?c320:619 [Vue warn]: Failed to resolve directive: html
(found in)


image.png

就是说html不是一个指令。@vue/babel-preset-jsx给出的标准写法是使用domPropsInnerHTML


3.2.2.2 v-text

模板语法





jsx语法,使用domPropsInnerText



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


jsx
使用 domPropsInnerText



或者


3.2.2.4 v-for
使用模板



使用jsx

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



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





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





代码运行结果界面


image.png

现在我们在父级组件引用标签处加上我们组件内并没有定义的属性,addon-before,虽然我们没有定义,但是a-inpu携带该属性,且my-input组件使用了v-bind="$attrs"

 

加上后运行效果如下


image.png

jsx语法
下面用jsx实现v-bind="$attrs"


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

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





jsx


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

 this.value=e.target.value}/>

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

按钮

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

按钮

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

this.input()}/> this.submit('form')}>按钮

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





jsx


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

模板语法




这里顺便讲下基于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可以如下:





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

父级组件HomeView.vue代码





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

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



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

  render() {
    return   
}

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

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


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





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




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

按jsx-vue2示例的写法



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

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



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



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





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





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



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

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



image.png

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

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



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


运行结果


image.png

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



然后相应的 AboutView.vue做更改



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

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

你可能感兴趣的:(Vue2 + JSX使用总结)