Vue中JSX的基本用法

基本用法

首先需要约定一下,使用JSX组件命名采用首字母大写的驼峰命名方式,样式可以少的可以直接基于vue-styled-components写在同一个文件中,复杂的建议放在单独的_Styles.js_文件中,当然也可以不采用CSS-IN-JS的方式,使用Less/Sass来写,然后在文件中import进来。

下面是一个通用的骨架:

import styled from 'vue-styled-components'

const Container = styled.div`
    heigth: 100%;
`

const Dashboard = {
  name: 'Dashboard',
  
  render() {
    return (
        <Container>内容</Container>
    )
  }
}

export default Dashboard

插值

在JSX中使用单个括号来绑定文本插值

<span>Message: {this.messsage}</span>
<!-- 类似于v-html -->
<div domPropsInnerHTML={this.dangerHtml}/>
<!-- v-model -->
<el-input v-model={this.vm.name} />

在jsx中不需要把v-model分成事件绑定和赋值二部分分开来写,因为有相应的babel插件来专门处理。

样式

在JSX中可以直接使用class="xx"来指定样式类,内联样式可以直接写成style="xxx"

<div class="btn btn-default" style="font-size: 12px;">Button</div>

<!-- 动态指定 -->
<div class={`btn btn-${this.isDefault ? 'default' : ''}`}></div>
<div class={{'btn-default': this.isDefault, 'btn-primary': this.isPrimary}}></div>
<div style={{color: 'red', fontSize: '14px'}}></div>

遍历

在JSX中没有v-forv-if等指令的存在,这些全部需要采用Js的方式来实现

{/* 类似于v-if */}
{this.withTitle && <Title />}

{/* 类似于v-if 加 v-else */}
{this.isSubTitle ? <SubTitle /> : <Title />}

{/* 类似于v-for */}
{this.options.map(option => {
  <div>{option.title}</div>
})}

事件绑定

事件绑定需要在事件名称前端加上on前缀,原生事件添加nativeOn

<!-- 对应@click -->
<el-buton onClick={this.handleClick}>Click me</el-buton>
<!-- 对应@click.native -->
<el-button nativeOnClick={this.handleClick}>Native click</el-button>
<!-- 传递参数 -->
<el-button onClick={e => this.handleClick(this.id)}>Click and pass data</el-button>

注意:如果需要给事件处理函数传参数,需要使用箭头函数来实现。如果不使用箭头函数那么接收的将会是事件的对象event属性。

高级部分

在Vue中基于jsx也可以把组件拆分成一个个小的函数式组件,但是有一个限制是必需有一个外层的包裹元素,不能直接写类似:

const Demo = () => (
    <li>One</li>
  <li>Two</li>
)

必需写成:

const Demo = () => (
    <div>
      <li>One</li>
    <li>Two</li>
  </div>
)

而在React中可以使用空标签<></><react.Fragment></react.Fragment>来实现包裹元素,这里的空标签其实只是react.Fragment的一个语法糖。同时在React 16中直接支持返回数组的形式:

const Demo = () => [
  <li>One</li>
  <li>Two</li>
]

那么在Vue中就只能通过遍历来实现类似的功能,大体思路就是把数据先定义好数据然后直接一个map生成,当然如果说元素的标签是不同类型的那就需要额外添加标识来判断了。

{
  data() {
    return {
      options: ['one', 'two']
    }
  },
    
    render() {
    const LiItem = () => this.options.map(option => <li>{option}</li>)
                                          
    return (
      <div>
            <ul>
              <LiItem />
          </ul>
         </div>
    )
  }
}

事件修饰符

在基础部分简单介绍了事件的绑定用法,这里主要是补充一下事件修饰符的写法。

在模板语法中Vue提供了很多事件修饰符来快速处理事件的冒泡、捕获、事件触发频率、按键识别等。可以直接查看官方文档的事件&按键修饰符部分,这里把相关内容原样搬运过来:

修饰符 前缀
.passive &
.capture !
.once ~
.capture.once.once.capture ~!

使用方式如下:

<el-button {...{
    '!click': this.doThisInCapturingMode,
  '!keyup': this.doThisOnce,
  '~!mouseover': this.doThisOnceInCapturingMode
}}>Click Me!</el-button>

下面给出的事件修饰符是需要在事件处理函数中写出对应的等价操作

修饰符 处理函数中的等价操作
.stop event.stopPropagation()
.prevent event.preventDefault()
.self if (event.target !== event.currentTarget) return
按键: .enter, .13 if (event.keyCode !== 13) return (对于别的按键修饰符来说,可将 13 改为另一个按键码)
修饰键: .ctrl, .alt, .shift, .meta if (!event.ctrlKey) return (将 ctrlKey 分别修改为 altKeyshiftKey 或者 metaKey)

下面是在事件处理函数中使用修饰符的例子:

methods: {
  keyup(e) {
    // 对应`.self`
    if (e.target !== e.currentTarget) return
    
    // 对应 `.enter`, `.13`
    if (!e.shiftKey || e.keyCode !== 13) return
    
    // 对应 `.stop`
    e.stopPropagation()
    
    // 对应 `.prevent`
    e.preventDefault()
    
    // ...
  }
}

ref和refInFor

在Vue中ref被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件。

注意

  • 因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在
  • $refs不是响应式的,因此你不应该试图用它在模板中做数据绑定。

 v-for 用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实例的数组。

假如在jsx中想要引用遍历元素或组件的时候,例如:

const LiArray = () => this.options.map(option => (
  <li ref="li" key={option}>{option}</li>
))

会发现从this.$refs.li中获取的并不是期望的数组值,这个时候就需要使用refInFor属性,并置为true来达到在模板中v-for中使用ref的效果:

const LiArray = () => this.options.map(option => (
  <li ref="li" refInFor={true} key={option}>{option}</li>
))

插槽(v-slot)

在jsx中可以使用this.$slots来访问静态插槽的内容。

注意:在Vue 2.6.x版本后废弃了slotslot-scope,在模板中统一使用新的统一语法v-slot指令。v-slot只能用于Vue组件和template标签。

<div class="page-header__title">
    {this.$slots.title ? this.$slots.title : this.title}
</div>

等价于模板的

<div class="page-header__title">
  <slot name="title">{{ title }}</slot>
</div>

在Vue官方文档中提到:**父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。**因此像下面的示例是无法正常工作的

<current-user>
    {{ user.firstName }}
</current-user>

<current-user>组件中可以访问到user属性,但是提供的内容却是在父组件渲染的。如果想要达到期望的效果,这个时候就需要使用作用域插槽了。下面是改写后的代码,更多知识点可以直接查看官方文档的作用域插槽

<!-- current-user组件定义部分 -->
<span>
    <slot v-bind:user="user">
      {{ user.lastName }}
  </slot>
</span>

<!-- current-user 使用 -->
<current-user>
    <template v-slot:default="slotProps">
      {{ slotProps.user.firstName }}
  </template>
</current-user>

上面的示例其实就是官方的示例,这里需要说明的是,其实在Vue中所谓的作用域插槽功能类似于React中的Render Props的概念,只不过在React中我们更多时候不仅提供了属性,还提供了操作方法。但是在Vue中更多的是提供数据供父作用域渲染展示,当然我们也可以把方法提供出去,例如:

<template>
    <div>
    <slot v-bind:injectedProps="slotProps">
      {{ user.lastName }}
      </slot>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        user: {
          firstName: 'snow',
          lastName: 'wolf'
        }
      }
    },
    
    computed: {
      slotProps() {
        return {
          user: this.user,
          logFullName: this.logFullName
        }
      }
    },
    
    methods: {
      logFullName() {
        console.log(`${this.firstName} ${this.lastName}`)
      }
    }
  }
</script>

在父组件中使用:

<current-user>
    <template v-slot:default="{ injectedProps }">
      <div>{{ injectedProps.user.firstName }}</div>
        <el-button @click="injectedProps.logFullName">Log Full Name</el-button>
  </template>
</current-user>

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

<current-user v-slot="{ user = { firstName: 'Guest' } }">
  {{ user.firstName }}
</current-user>

如果组件只有一个默认的插槽还可以使用缩写语法,将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 (
        <div>
        {this.$scopedSlots.subTitle({
          injectedProps: this.slotProps
        })}
      </div>
    )
  }
}

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

<current-user {...{
  scopedSlots: {
    subTitle: ({ injectedProps }) => (
        <div>
          <h3>injectedProps.user</h3>
        <el-button onClick={injectedProps.logFullName}>Log Full Name</el-button>
      </div>
    )
  }
}}></current-user>

指令

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

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

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

<input type="text" v-focus={{ value: true }} />

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

{
  directives:{
    focus: {
      inserted: function(el) {
        el.focus()
      }
    }
  },
    
  render() {
    const directives = [
      { name: 'focus', value: true }
    ]
      
    return (
      <div>
          <input type="text" {...{ directives }} />
      </div>
    )
  }
}

过滤器

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

在模板中的用法如下:

<!-- 在双花括号中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

在jsx中使用方法为:

<div>{this.$options.filters('formatDate')('2019-07-01')}</div>

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

源码附件已经打包好上传到百度云了,大家自行下载即可~

链接: https://pan.baidu.com/s/14G-bpVthImHD4eosZUNSFA?pwd=yu27
提取码: yu27

百度云链接不稳定,随时可能会失效,大家抓紧保存哈。

如果百度云链接失效了的话,请留言告诉我,我看到后会及时更新~

开源地址

码云地址:
http://github.crmeb.net/u/defu

Github 地址:
http://github.crmeb.net/u/defu

{{o.name}}
{{m.name}}

猜你喜欢

转载自my.oschina.net/u/5168284/blog/5535741