Vue中jsx不完全应用的示例分析

这篇文章主要介绍Vue中jsx不完全应用的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

为榆树等地区用户提供了全套网页设计制作服务,及榆树网站建设行业解决方案。主营业务为网站建设、成都网站设计、榆树网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!

在使用Vue开发项目时绝大多数情况下都是使用模板来写HTML,但是有些时候页面复杂又存在各种条件判断来显示/隐藏和拼凑页面内容,或者页面中很多部分存在部分DOM结构一样的时候就略显捉襟见肘,会写大量重复的代码,会出现单个.vue文件过长的情况,这个时候我们就需要更多的代码控制,这时候可以使用渲染函数。

渲染函数想必平时几乎没有人去写,因为写起来很痛苦(本人也没有写过)。更多的是在Vue中使用JSX语法。写法上和在React中差不多,但是功能上还是没有React中那么完善。

在写JSX的过程中不得考虑一个样式的问题,虽然可以直接在.vue文件中不写部分,只写

在父组件中使用:


 
  
{{ injectedProps.user.firstName }}
  Log Full Name  

在上面的代码中我们实际上使用解构的方式来取得injectedProps,基于解构的特性还可以重命名属性名,在prop为undefined的时候指定初始值。


 {{ user.firstName }}

如果组件只有一个默认的插槽还可以使用缩写语法,将v-slot:default="slotProps"写成v-slot="slotProps",命名插槽写成v-slot:user="slotProps",如果想要动态插槽名还可以写成v-slot:[dynamicSlotName],此外具名插槽同样也有缩写语法,例如 v-slot:header可以被重写为#header

上面介绍了很多插槽相关的知识点足已说明其在开发过程中的重要性。说了很多在模板中如何定义和使用作用域插槽,现在进入正题如何在jsx中同样使用呢?

// current-user components
{
 data() {
 return {
  user: {
  firstName: 'snow',
  lastName: 'wolf'
  }
 }
 },
 
 computed: {
 slotProps() {
  return {
  user: this.user,
  logFullName: this.logFullName
  }
 }
 },
 
 methods: {
 logFullName() {
  console.log(`${this.firstName} ${this.lastName}`)
 }
 },
 
 render() {
 return (
  
  {this.$scopedSlots.subTitle({    injectedProps: this.slotProps   })}   
 )  } }

然后在父组件中以jsx使用:

 (
  
   

injectedProps.user

  Log Full Name   
 )  } }}>

指令

这里需要注意的是在jsx中所有Vue内置的指令除了v-show以外都不支持,需要使用一些等价方式来实现,比如v-if使用三目运算表达式、v-for使用array.map()等。

对于自定义的指令可以使用v-name={value}的语法来写,需要注意的是指令的参数、修饰符此种方式并不支持。以官方文档指令部分给出的示例v-focus使用为例,介绍二种解决办法:

1 直接使用对象传递所有指令属性

2 使用原始的vnode指令数据格式

{
 directives:{
 focus: {
  inserted: function(el) {
  el.focus()
  }
 }
 },
 
 render() {
 const directives = [
  { name: 'focus', value: true }
 ]
  
 return (
  
      
 )  } }

过滤器

过滤器其实在开发过程中用得倒是不多,因为更多时候可以通过计算属性来对数据做一些转换和筛选。这里只是简单提及一下并没有什么可以深究的知识点。

在模板中的用法如下:


{{ message | capitalize }}


在jsx中使用方法为:

{this.$options.filters('formatDate')('2019-07-01')}

注意:由于Vue全局的过滤器只用于模板中,如果需要用于组件的方法中,可以把过滤器方法单独抽离出一个公共Js文件,然后引入组件中,然后用于方法中。

一些简单经验分享

并不是说我们在开发Vue项目的时候一定要使用jsx的方式来写,但是多掌握一种方式来灵活变通,提高工作效率,扩展思路何尝不值得一试。而且,在有些场景下释放js的完全编程能力会让你更加能够得心应手。其实在使用模板方式的时候我们并没有完全采用组件的思维方式来做,或者说是做得不彻底,不纯粹,拆分的粒度不够。更多 的时候并没有考虑到组件怎么切分和抽象,多人协作的时候如何处理依赖并明确自己的功能点。

关于DOM属性、HTML属性和组件属性

在React中所有数据均挂载在props下,Vue则不然,仅属性就有三种:组件属性props,普通html属性attrs和DOM属性domProps。在Angular的文档中关于插值绑定部分是重点说明了DOM属性和HTML属性的区别,在大多数情况下两者都有对应的同名属性,也就是1:1映射关系,但是也有例外的情况,比如HTML中colspan,DOM中的textContent。HTML属性的值指定了初始值,并且不能改变,而DOM属性的值表示当前值,是可以改变的。

然后在Vue的模板语法中是不区分DOM属性和HTML属性的,例如:



运行示例可以看到input的初始值被设置为了“我是DOM属性值",当我们在输入框中添加或者删除文字时,HTML属性始终没有变化,而绑定的DOM值一值在变动。然后再看一下在jsx中的实现:

输入值:{ this.title }

同样运行后会发现在jsx写法中并没有直接将HTML属性初始化为DOM属性值,即输入框中当前值为空字符串,这符合预期的行为。

此外在模板语法中是无法区分HTML属性和DOM属性命名一样的场景,但是在jsx中可以很好的区分:

结果会就是在HMTL中显示title="我是DOM属性,而"我是组件属性”传递给了组件。

在React中CSS的样式写义在jsx中的语法是以className="xx"的形式,而在Vue的jsx中可以直接写成class="xx"。实际上由于class是Js的保留字,因此在DOM中其属性名为className而在HTML属性中为class,我们可以在Vue中这样写,经过Babel转译后得到正确的样式类名:

注意:如果同时写了class="xx" domPropsClassName="yy"那么后者的优先级较高,和位置无关。所以尽量还是采用class的写法。

有使用过Bootstrap经验的可能会注意到它里面包含了很多ARIA属性,这些属性并不属于DOM,在jsx中可以通过attrsXX或者直接aria-xx的方式来添加:


但是上面的换成domPropsAria-label就没有任何效果。

注意:在jsx中所有DOM属性(Property)语法为domPropsXx, HTML特性(Attribute)语法为attrsXx。更多的时候建议还是少使用,或者说合理使用。

在jsx中还可以使用混用的写法,例如在组件中写了,还可以定义一个属性对象,然后使用{...props}的方式写在一起,这个时候就会出现属性合并的问题,同样的事件多个地方声明事件处理函数,都会触发。

最后需要提及一点的是,在Vue中当给一个组件传了很多props,但是有的并不是组件声明的,也有可能是一些通用的HTML或者DOM属性,但是在最终编译后的HTML中会直接显示这些props,如果不希望这些属性显示在最终的HTML中,可以在组件中设inheritAttrs: false。虽然不显示了,但是我们依然可以通过vm.$attrs获取所有(除class和style)绑定的属性,包括不在props中定义的。

关于事件

前面已经把事件相关的知识点都介绍了,这里主要是提及一下关于jsx事件绑定语法onXx和组件属性(主要是函数prop)以on开头的情况如何处理。

虽然在写组件的时候可以避开命名以on开头,但是在使用第三库的时候,如果遇到了该如何处理呢?比如Element组件Upload很多钩子都是以on开头。 下面提供两种解决办法:

1.使用展开

使用propsXx

推荐使用第二种方式,写起来要简单些。

复杂逻辑条件判断

在模板语法中可以使用v-if、v-else-if和v-else来做条件判断。在jsx中可以通过?:三元运算符(Ternary operator)运算符来做if-else判断:

const Demo = () => isTrue ? 

True!

 : null

然后可以利用&&运算符的特性简写为:

const Demo = () => isTrue && 

True!

对于复杂的条件判断,例如:

const Demo = () => (
 
  {flag && flag2 && !flag3   ? flag4    ? 

Blash

  : flag5    ? 

Meh

  : 

hErp

  : 

Derp

 }  
)

可以采用两种方式来降低判断识别的复杂度

  • 最好的办法:将判断逻辑转移到子组件

  • 可选的hacky方法:使用IIFE(立即执行表达式)

  • 使用第三方库解决:jsx-control-statements

下面是使用IIFE通过内部使用if-else返回值来优化上述问题:

const Demo = () => (
 
 {   (() => {   if (flag && flag2 &&!flag3) {    if (flag4) {    return 

Blah

   } else if (flag5) {    return 

Meh

   } else {    return 

Herp

   }   } else {    return 

Derp

  }   })()  }  
)

还可以使用do表达式,但是需要插件@babel/plugin-proposal-do-expressions的转译来支持,

const Demo = () => (
 
 {   do {   if (flag1 && flag2 && !flag3) {    if (flag4) {    

Blah

   } else if (flag5) {    

Meh

   } else {    

Herp

   }   } else {    

Derp

  }   }  }  
)

再就是一种比较简单的可选办法,如下:

const Demo = () => {
 const basicCondition = flag && flag1 && !flag3;
 if (!basicCondition) return 

Derp

 if (flag4) return 

Blah

 if (flag5) return 

Meh

 return 

Herp

}

最后一种使用jsx插件的就不详述和举例了,有兴趣的可以直接查看文档。

组件的传值

在单个jsx文件中可以写很多函数式组件来切分更小的粒度,例如之前的文章Vue后台管理系统开发日常总结__组件PageHeader,组件的形态有两种,一种是普通标题,另一种是带有选项卡的标题,那么在写的时候就可以这样写:

render() {
 // partial html
 const TabHeader = (
  
 )    // function partial  const Header = () => (   
 )       {this.withTab ? TabHeader : 
}  
}

注意在拆分的时候,如果不需要做任何判断可以纯粹是HTML片段赋值给变量,如果需要条件判断就使用函数式组件的方式来写。需要注意的是由于render函数会多次被调用,写的时候注意一下对性能的影响,目前能力有限这方面就不作展开了。

既然使用函数式组件,那么同样可以在函数中传递参数了,参数是一个对象中,包含了以下属性

children  # VNode数组,类似于React的children
data   # 绑定的属性
 attrs  # Attribute
 domProps # DOM property
 on    # 事件
injections # 注入的对象
listeners: # 绑定的事件类型
 click   # 点击事件
 ...
parent   # 父组件
props    # 属性
scopedSlots # 对象,作用域插槽,使用中发现作用域插槽也挂在这个下面
slots    # 函数,插槽

虽然可以在函数式组件中传参数、事件、slot但是个人觉得不建议这样做,反而搞复杂了。

render() {
 const Demo = props => {
 return (
  
   

Jsx中的内部组件 { props.data.title }

  { props.children }      { props.scopedSlots.bar() }   
 )  }    return (   
      

我是Children

      

我是Slot内容

       
 ) }

上面的示例最终生成的HTML中会将