天天看点

Vue 之 JSX 初识篇

Vue 之 JSX 初识篇

介绍一下 JSX

JSX 简介

JSX 是一种 Javascript 的语法扩展,​

​JSX​

​​ = ​

​Javascript​

​​ + ​

​XML​

​​,即在​

​Javascript​

​​里面写​

​XML​

​​,因为​

​JSX​

​​的这个特性,所以他即具备了​

​Javascript​

​​的灵活性,同时又兼具​

​html​

​的语义化和直观性。

学习 JSX,先了解一下 createElement

提到​

​JSX​

​​,不可避免的就要提到​

​createElement​

​,当你看完本节,你会发现,奇怪的知识又增多了。

无论是​

​Vue​

​​还是​

​React​

​​,都存在​

​createElement​

​​,而且作用基本一致。可能你对​

​createElement​

​​不是很了解,函数名翻译过来就是增加一个元素,但他的返回值你一定知道。​

​createElement​

​​函数返回的值称之为虚拟节点,即​

​VNode​

​​,而由​

​VNode​

​​扎堆组成的树便是大名鼎鼎,面试必问的​

​虚拟DOM​

​。

​createElement​

​​函数的参数,在这里抄一下​

​Vue​

​官方文档

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一个 HTML 标签名、组件选项对象,或者
  // resolve 了上述任何一种的一个 async 函数。必填项。
  'div',

  // {Object}
  // 一个与模板中 attribute 对应的数据对象。可选。
  {
    // (详情见下一节)
  },

  // {String | Array}
  // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  // 也可以使用字符串来生成“文本虚拟节点”。可选。
  [
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)      

从上面可以看出​

​createElement​

​一共有三个参数,三个参数分别是

  • 第一个参数是需要渲染的组件,可以是组件的标签,比如​

    ​div​

    ​​;或者是一个组件对象,也就是你天天写的​

    ​export default {}​

    ​;亦或者可以是一个异步函数。
  • 第二个参数是这个组件的属性,是一个对象,如果组件没有参数,可以传 null(关于组件的属性,下文将依次介绍)
  • 第三个参数是这个组件的子组件,可以是一个字符串(textContent)或者一个由 VNodes 组成的数组

没有 v-model 怎么办,还有其他指令可以用吗?

当你选择使用​

​JSX​

​​的时候,你就要做好和指令说拜拜的时候了,在​

​JSX​

​​中, 你唯一可以使用的指令是​

​v-show​

​​,除此之外,其他指令都是不可以使用的,有没有感到很慌,这就对了。不过呢,换一个角度思考,指令只是​

​Vue​

​​在模板代码里面提供的语法糖,现在你已经可以写​

​Js​

​​了,那些语法糖用​

​Js​

​都可以代替了。

v-model

​v-model​

​​是​

​Vue​

​​提供的一个语法糖,它本质上是由 ​

​value​

​​属性(默认) + ​

​input​

​​事件(默认)组成的, 所以,在​

​JSX​

​​中,我们便可以回归本质,通过传递​

​value​

​​属性并监听​

​input​

​事件来实现数据的双向绑定

export default {
  data() {
    return {
      name: ''
    }
  },
  methods: {
    // 监听 onInput 事件进行赋值操作
    $_handleInput(e) {
      this.name = e.target.value
    }
  },
  render() {
    // 传递 value 属性 并监听 onInput事件
    return <input value={this.name} onInput={this.$_handleInput}></input>
  }
}      

v-if 与 v-for

在模板代码里面我们通过​

​v-for​

​​去遍历元素,通过​

​v-if​

​​去判断是否渲染元素,在​

​jsx​

​​中,对于​

​v-for​

​​,你可以使用​

​for​

​​循环,​

​array.map​

​​来代替,对于​

​v-if​

​​,可以使用​

​if​

​​语句,​

​三元表达式​

​等来代替

循环遍历列表

const list = ['java', 'c++', 'javascript', 'c#', 'php']
return (
  <ul>
  {list.map(item => {
   return <li>{item}</li>
  })}
  </ul>
)      

 使用条件判断

const isGirl = false
return      

v-bind

在模板代码中,我们一般通过 ​

​v-bind:prop="value"​

​​或​

​:prop="value"​

​​来给组件绑定属性,在​

​JSX​

​里面写法也类似

render() {
  return <input value={this.name}></input>
}      

v-html 与 v-text

在说​

​v-html​

​​与​

​v-text​

​​之前,我们需要先了解一下​

​Vue​

​​中的属性,​

​Vue​

​中的属性一共分为三种:

第一种是大家写 bug 时候最常用的​

​props​

​,即组件自定义的属性;

第二种是​

​attrs​

​,是指在父作用域里面传入的,但并未在子组件内定义的属性。

第三种比较特殊,是​

​domProps​

​​,经小编不完全测试,在​

​Vue​

​​中,​

​domProps​

​​主要包含三个,分别是​

​innerHTML​

​​,​

​textContent/innerText​

​​和​

​value​

​。

  • ​v-html​

    ​​: 在模板代码中,我们用​

    ​v-html​

    ​​指令来更新元素的​

    ​innerHTML​

    ​​内容,而在​

    ​JSX​

    ​​里面,如果要操纵组件的​

    ​innerHTML​

    ​​,就需要用到​

    ​domProps​

export default {
    data() {
      return {
        content: '<div>这是子君写的一篇新的文章</div>'
      }
    },
    render() {
      // v-html 指令在JSX的写法是 domPropsInnerHTML
      return <div domPropsInnerHTML={this.content}></div>
    }
  }      
  • ​v-text​

    ​​: 看了上面的​

    ​v-html​

    ​​,你是不是立即就想到了​

    ​v-text​

    ​​在​

    ​JSX​

    ​​的写法​

    ​domPropsInnerText​

    ​,是的,你没有想错
export default {
    data() {
      return {
        content: '这是子君写的一篇新的文章的内容'
      }
    },
    render() {
      return <div domPropsInnerText={this.content}></div>
    }
  }      

但实际上我们不需要使用​

​domPropsInnerText​

​,而是将文本作为元素的子节点去使用即可

<div>{this.content}</div>      

实际上,对于​

​domProps​

​​,只有​

​innerHTML​

​​才需要使用​

​domPropsInnerHTML​

​的写法,其他使用正常写法即可

我还要监听事件呢

监听事件与原生事件

当我们开发一个组件之后,一般会通过​

​this.$emit('change')​

​​的方式对外暴露事件,然后通过​

​v-on:change​

​​的方式去监听事件,很遗憾,在​

​JSX​

​​中你无法使用​

​v-on​

​指令,但你将解锁一个新的姿势

render() {
    return <CustomSelect onChange={this.$_handleChange}></CustomSelect>
  }      

​JSX​

​​中,通过​

​on​

​​ + 事件名称的大驼峰写法来监听,比如事件​

​icon-click​

​​,在​

​JSX​

​​中写为​

​onIconClick​

有时候我们希望可以监听一个组件根元素上面的原生事件,这时候会用到​

​.native​

​修饰符,有点绝望啊,修饰符也是不能用了,但好在也有替代方案,如下代码

render() {
    // 监听下拉框根元素的click事件
    return <CustomSelect nativeOnClick={this.$_handleClick}></CustomSelect>
  }      

监听原生事件的规则与普通事件是一样的,只需要将前面的​

​on​

​​替换为​

​nativeOn​

除了上面的监听事件的方式之外,我们还可以使用对象的方式去监听事件

render() {
    return (
      <ElInput
        value={this.content}
        on={{
          focus: this.$_handleFocus,
          input: this.$_handleInput
        }}
        nativeOn={{
          click: this.$_handleClick
        }}
      ></ElInput>
    )
  }      

事件修饰符

和指令一样,除了个别的之外,大部分的事件修饰符都无法在​

​JSX​

​中使用,这时候你肯定已经习惯了,肯定有替代方案的。

  • ​.stop​

    ​​ : 阻止事件冒泡,在​

    ​JSX​

    ​​中使用​

    ​event.stopPropagation()​

    ​来代替
  • ​.prevent​

    ​​:阻止默认行为,在​

    ​JSX​

    ​​中使用​

    ​event.preventDefault()​

    ​ 来代替
  • ​.self​

    ​:只当事件是从侦听器绑定的元素本身触发时才触发回调,使用下面的条件判断进行代替
if (event.target !== event.currentTarget){
    return
}      
  • ​.enter​

    ​​与​

    ​keyCode​

    ​: 在特定键触发时才触发回调
if(event.keyCode === 13) {
    // 执行逻辑      

除了上面这些修饰符之外,尤大大为了照顾我们这群 CV 仔,还是做了一点优化的,对于​

​.once​

​​,​

​.capture​

​​,​

​.passive​

​​,​

​.capture.once​

​,尤大大提供了前缀语法帮助我们简化代码

render() {
    return (
      <div
        on={{
          // 相当于 :click.capture
          '!click': this.$_handleClick,
          // 相当于 :input.once
          '~input': this.$_handleInput,
          // 相当于 :mousedown.passive
          '&mousedown': this.$_handleMouseDown,
          // 相当于 :mouseup.capture.once
          '~!mouseup': this.$_handleMouseUp
        }}
      ></div>
    )
  }      

插槽

插槽就是子组件中提供给父组件使用的一个占位符,插槽分为默认插槽,具名插槽和作用域插槽,下面小编依次为你带来每种在​

​JSX​

​中的用法与如何去定义插槽。

默认插槽

  • 使用默认插槽

使用​

​element-ui​

​​的​

​Dialog​

​​时,弹框内容就使用了默认插槽,在​

​JSX​

​中使用默认插槽的用法与普通插槽的用法基本是一致的,如下代码所示:

render() {
    return (
      <ElDialog title="弹框标题" visible={this.visible}>
        {/*这里就是默认插槽*/}
        <div>这里是弹框内容</div>
      </ElDialog>
    )
  }      
  • 自定义默认插槽

在​

​Vue​

​​的实例​

​this​

​​上面有一个属性​

​$slots​

​​,这个上面就挂载了一个这个组件内部的所有插槽,使用​

​this.$slots.default​

​就可以将默认插槽加入到组件内部

export default {
    props: {
      visible: {
        type: Boolean,
        default: false
      }
    },
    render() {
      return (
        <div class="custom-dialog" vShow={this.visible}>
          {/*通过this.$slots.default定义默认插槽/}
          {this.$slots.default}
        </div>
      )
    }
  }      

具名插槽

  • 使用具名插槽

有时候我们一个组件需要多个插槽,这时候就需要为每一个插槽起一个名字,比如​

​element-ui​

​​的弹框可以定义底部按钮区的内容,就是用了名字为​

​footer​

​的插槽

render() {
    return (
      <ElDialog title="弹框标题" visible={this.visible}>
        <div>这里是弹框内容</div>
        {/** 具名插槽 */}
        <template slot="footer">
          <ElButton>确定</ElButton>
          <ElButton>取消</ElButton>
        </template>
      </ElDialog>
    )
  }      
  • 自定义具名插槽

在上节自定义默认插槽时提到了​

​$slots​

​​,对于默认插槽使用​

​this.$slots.default​

​​,而对于具名插槽,可以使用​

​this.$slots.footer​

​进行自定义

render() {
    return (
      <div class="custom-dialog" vShow={this.visible}>
        {this.$slots.default}
        {/**自定义具名插槽*/}
        <div class="custom-dialog__foolter">{this.$slots.footer}</div>
      </div>
    )
  }      

作用域插槽

  • 使用作用域插槽

    有时让插槽内容能够访问子组件中才有的数据是很有用的,这时候就需要用到作用域插槽,在​

    ​JSX​

    ​中,因为没有​

    ​v-slot​

    ​指令,所以作用域插槽的使用方式就与模板代码里面的方式有所不同了。比如在​

    ​element-ui​

    ​中,我们使用​

    ​el-table​

    ​的时候可以自定义表格单元格的内容,这时候就需要用到作用域插槽
data() {
      return {
        data: [
          {
            name: '子君'
          }
        ]
      }
    },
    render() {
      return (
        {/*scopedSlots即作用域插槽,default为默认插槽,如果是具名插槽,将default该为对应插槽名称即可/}
        <ElTable data={this.data}>
          <ElTableColumn
            label="姓名"
            scopedSlots={{
              default: ({ row }) => {
                return <div style="color:red;">{row.name}</div>
              }
            }}
          ></ElTableColumn>
        </ElTable>
      )
    }      
  • 自定义作用域插槽

使用作用域插槽不同,定义作用域插槽也与模板代码里面有所不同。加入我们自定义了一个列表项组件,用户希望可以自定义列表项标题,这时候就需要将列表的数据通过作用域插槽传出来。

render() {
  const { data } = this
  // 获取标题作用域插槽
  const titleSlot = this.$scopedSlots.title
  return (
    <div class="item">
    {/* 如果有标题插槽,则使用标题插槽,否则使用默认标题 /}
       {titleSlot ? titleSlot(data) : <span>{data.title}</span>}
     </div>
    )
}      

只能在 render 函数里面使用 JSX 吗

 当然不是,你可以定义​

​method​

​​,然后在​

​method​

​​里面返回​

​JSX​

​​,然后在​

​render​

​​函数里面调用这个方法,不仅如此,​

​JSX​

​还可以直接赋值给变量,比如下面这段代码

methods: {
    $_renderFooter() {
      return (
        <div>
          <ElButton>确定</ElButton>
          <ElButton>取消</ElButton>
        </div>
      )
    }
  },
  render() {
    const buttons = this.$_renderFooter()
    return (
      <ElDialog visible={this.visible}>
        <div>这里是一大坨内容</div>
        <template slot="footer">{buttons}</template>
      </ElDialog>
    )
  }