首頁  >  文章  >  web前端  >  Vue中什麼是JSX?什麼時候用?怎麼用?

Vue中什麼是JSX?什麼時候用?怎麼用?

青灯夜游
青灯夜游轉載
2023-01-16 20:23:093011瀏覽

Vue中什麼是JSX?以下這篇文章給大家了解Vue中的JSX,介紹一下什麼時候使用JSX、在Vue2中的基本使用,希望對大家有幫助!

Vue中什麼是JSX?什麼時候用?怎麼用?

JSX簡介

JSX是一種Javascript的語法擴展,即具備了Javascript的全部功能,同時兼具html的語意化與直覺。它可以讓我們在JS中寫模板語法:

const el = <div>Vue 2</div>;

上面這段程式碼既不是 HTML 也不是字串,被稱為 JSX,是 JavaScript 的擴充語法。 JSX 可能會使人聯想到模板語法,但它具備 Ja​​vascript 的完全程式設計能力。 【相關推薦:vuejs影片教學web前端開發

#什麼時候使用JSX

##當開始寫一個只能透過 

level prop 動態產生標題(heading) 的元件時,你可能很快就會想到這樣實作:

<script type="text/x-template" id="anchored-heading-template">
<h1 v-if="level === 1"> 
    <slot></slot> 
</h1> 
<h2 v-else-if="level === 2"> 
    <slot></slot> 
</h2> 
<h3 v-else-if="level === 3"> 
    <slot></slot> 
</h3> 
</script>

這裡用template模板並不是最好的選擇,在每一個在等級的標題重複寫下了部分程式碼,不夠簡潔優雅。如果嘗試用JSX 來寫,程式碼就會變得簡單很多:

const App = {
  render() {
    const tag = `h${this.level}`
    return <tag>{this.$slots.default}</tag>
  }
}

或如果你寫了很多 

render 函數,可能會覺得下面這樣的程式碼寫起來很痛苦:

createElement(  
    &#39;anchored-heading&#39;, {  
        props: {  
            level: 1  
        }  
    }, [  
    createElement(&#39;span&#39;, &#39;Hello&#39;),  
        &#39; world!&#39;  
    ]  
)

特別是對應的模板如此簡單的情況下:

<anchored-heading :level="1">  
    <span>Hello</span> world!  
</anchored-heading>

這時候就可以在Vue 中使用JSX 語法,它可以讓我們回到更接近於模板的語法:

import AnchoredHeading from &#39;./AnchoredHeading.vue&#39;  
  
new Vue({  
    el: &#39;#demo&#39;,  
    render: function (h) {  
        return (  
            <AnchoredHeading level={1}>  
                <span>Hello</span> world!  
            </AnchoredHeading>  
        )  
    }  
})

在開發過程中,常常會用到訊息提示元件Message,可能的一種寫法是這樣的:

Message.alert({
  messge: &#39;确定要删除?&#39;,
  type: &#39;warning&#39;
})

但是希望

message可以自訂一些樣式,這時候你可能就需要讓Message.alert支援JSX了(當然也可以用插槽/html等方式解決)

Message.alert({
  messge: <div>确定要删除<span style="color:red">xxx</span>的笔记?</div>,
  type: &#39;warning&#39;
})

此外,一個

.vue 檔案裡面只能寫一個元件,這個在一些場景下可能不太方便,很多時候寫一個頁面的時候其實可能會需要把一些小的節點片段拆分到小元件裡面進行重複使用,這些小元件其實寫個簡單的函數元件就能搞定了。平時可能會因為SFC的限制讓我們習慣全部寫在一個文件裡,但不得不說可以嘗試這種方式。

// 一个文件写多个组件
const Input = (props) => <input {...props} />
export const Textarea = (props) => <input {...props} />
export const Password = (props) => <input type="password" {...props} />

export default Input

例如這裡封裝了一個Input 元件,我們希望同時匯出Password 元件和Textarea 元件來方便使用者根據實際需求使用,而這兩個元件本身內部就是用的Input 元件,只是客製了一些props 。在 JSX 裡面就很方便,寫個簡單的函數元件基本上就夠用了,透過 interface 來宣告 props 就好了。但如果是用範本來寫,可能就要將拆成三個文件,或許還要再增加一個

index.js 的入口文件來匯出三個元件。

由於 JSX 的本質就是 JavaScript,所以它具有 JavaScript 的完全程式設計能力。再舉個例子,我們需要透過一段邏輯來對一組 DOM 節點做一次 reverse,如果在模板裡面寫,那估計要寫兩段程式碼。

雖然這個例子可能不太常見,但不得不承認,在某些場景下,JSX 還是要比模板寫起來更順手。

從 Vue 2 開始,template 在運作之前,會被編譯成 JavaScript 的

render function

Vue中什麼是JSX?什麼時候用?怎麼用?

Vue 推薦在絕大多數情況下使用 template 來建立你的 HTML。然而在某些場景中,就需要使用 render 函數,它比 template 更有彈性。這些 

render function 在運行時階段,就是傳說中的 Virtual DOM

Vue中什麼是JSX?什麼時候用?怎麼用?

JSX在Vue2中的基本使用

設定

在Vue 2中,JSX 的編譯需要依賴 

@vue/babel-preset-jsx 和 @vue/babel-helper-vue-jsx-merge-props 這兩個套件。前面這個套件來負責編譯 JSX 的語法,後面的套件用來引入運行時的 mergeProps 函數。

npm install @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props

並在babel.config.js中加入設定:

module.exports = {
  presets: [&#39;@vue/babel-preset-jsx&#39;],
}

#文字插值

模板程式碼裡文字插值預設是用雙大括號:

<h1>{{ msg }}</h1>

在JSX中則需要使用單大括號:

const name = &#39;Vue&#39;
const element = <h1>Hello, { name }</h1>

和模板語法中的文字插值一樣,大括號內支援任何有效的JS表達式,例如:

2 2user.firstNameformatName(user)等。

条件与循环渲染

在模板代码里面我们通过v-for去遍历元素,通过v-if去判断是否渲染元素,在JSX中,对于v-for,可以使用for循环或者array.map来代替,对于v-if,可以使用if-else语句,三元表达式等来代替

使用if-else语句

const element = (name) => {
  if (name) {
    return <h1>Hello, { name }</h1>
  } else {
    return <h1>Hello, Stranger</h1>
  }
}

使用三元表达式

const element = icon ? <span class="icon"></span> : null;

使用数组的map方法

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

属性绑定

在模板代码中,一般通过 v-bind:prop="value":prop="value"来给组件绑定属性,在JSX里面就不能继续使用v-bind指令了,而是通过单大括号的形式进行绑定:

const href = &#39;https://xxx.com&#39;
const element = <a href={href}>xxx</a>
const properties = {a: 1, b: 2}

此外,模板代码中能通过<div v-bind="properties"></div>批量绑定标签属性。

在JSX中也有相应的替换方案:<div></div>

class绑定同样也是使用单大括号的形式

const element = <div className={`accordion-item-title ${ disabled ? &#39;disabled&#39; : &#39;&#39; }`}></div>
const element = <div class={
    [ &#39;accordion-item-title&#39;, disabled && &#39;disabled&#39; ]
  }
>Item</div>

style绑定需要使用双大括号

const width = &#39;100px&#39;
const element = <button style={{ width, fontSize: &#39;16px&#39; }}></button>

事件绑定

在模板代码中通过v-on指令监听事件,在JSX中通过on + 事件名称的大驼峰写法来监听,且绑定事件也是用大括号,比如click事件要写成onClick,mouseenter事件要写成onMouseenter

const confirm = () => {
  // 确认提交
}
<button onClick={confirm}>确定</button>

有时候我们希望可以监听一个组件根元素上面的原生事件,这时候会用到.native修饰符,但是在JSX中同样也不能使用,不过也有替代方案,监听原生事件的规则与普通事件是一样的,只需要将前面的on替换为nativeOn,如下

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

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

  render() {
    return (
      <ElInput
        value={this.content}
        on={{
          focus: this.handleFocus,
          input: this.handleInput
        }}
        nativeOn={{
          click: this.handleClick
        }}
      ></ElInput>
    )
  }

对于 .passive.capture 和 .once 这些事件修饰符,Vue 提供了相应的前缀可以用于 on

Vue中什麼是JSX?什麼時候用?怎麼用?

例如:

on: {  
    &#39;!click&#39;: this.doThisInCapturingMode,  
    &#39;~keyup&#39;: this.doThisOnce,  
    &#39;~!mouseover&#39;: this.doThisOnceInCapturingMode  
}

对于所有其它的修饰符,私有前缀都不是必须的,因为你可以在事件处理函数中使用事件方法:Vue中什麼是JSX?什麼時候用?怎麼用?具体可查阅Vue规范文档。

v-show与v-model

大多数指令并不能在JSX中使用,对于原生指令,只有v-show是支持的。

v-modelVue提供的一个语法糖,它本质上是由 value属性(默认) + input事件(默认)组成的,所以,在JSX中,我们便可以回归本质,通过传递value属性并监听input事件来手动实现数据的双向绑定:

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

此外,在脚手架vue-cli4中,已经默认集成了对v-model的支持,可以直接使用<input v-model="{this.value}">,如果项目比较老,也可以安装插件babel-plugin-jsx-v-model来进行支持。

同样的,在JSX中,对于.sync也需要用属性+事件来实现,如下代码所示:

export default {
  methods: {
    handleChangeVisible(value) {
      this.visible = value
    }
  },
  render() {
    return (
      <ElDialog
        title="测试.sync"
        visible={this.visible}
        on={{ &#39;update:visible&#39;: this.handleChangeVisible }}
      ></ElDialog>
    )
  }
}

插槽

(1)默认插槽:

使用element-uiDialog时,弹框内容就使用了默认插槽,在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>
    )
  }
}

(2)具名插槽

有时候我们一个组件需要多个插槽,这时候就需要为每一个插槽起一个名字,比如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>
    )
  }

(3)作用域插槽

有时让插槽内容能够访问子组件中才有的数据是很有用的,这时候就需要用到作用域插槽,在JSX中,因为没有v-slot指令,所以作用域插槽的使用方式就与模板代码里面的方式有所不同了。比如在element-ui中,我们使用el-table的时候可以自定义表格单元格的内容,这时候就需要用到作用域插槽

data() {
    return {
      data: [
        {
          name: &#39;xxx&#39;
        }
      ]
    }
  },
  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>
    )
  }

使用自定义组件

只需要导入进来,不用再在components属性声明了,直接写在jsx中:

import MyComponent from &#39;./my-component&#39;

export default {
  render() {
    return <MyComponent>hello</MyComponent>
  },
}

在method里返回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>
    )
  }

用JSX实现简易聊天记录

假设该消息聊天记录的消息类型只有三种:文本,图片,引用。一条消息里面可以包括任意类型的内容,引用类型消息内部可以不断嵌套引用其他任意类型消息。效果图大致如下:

Vue中什麼是JSX?什麼時候用?怎麼用?

消息数据结构如下:

message: [
      // 每个数组的第一个参数为消息类型:0:文本 1:图片 2:引用。第二个参数为具体内容
      [
        0,
        &#39;文本&#39;
      ],
      [
        1,
        &#39;图片链接xxx&#39;
      ],
      [
        2,
        [
          [
            0,
            &#39;引用文本文本文本&#39;
          ],
          [
            1,
            &#39;引用图片链接xxx&#39;
          ]
        ]
      ]
    ]

主要有两个思路:

1、思路一:在render里返回一段用array.map渲染的消息模板,对于三种消息类型,使用if-else进行判断分别渲染,对于引用类型的消息,可以封装一个方法进行渲染,方法里面如果还有引用类型消息就继续递归渲染。

methods: {
    // 展示引用消息
    showQuote (msg) {
      return (
        <div class="content-quote">
          <span class="quote-title">引用:</span>
          {msg.map(item => {
            if (item[0] === 0) {
              return <p class="content-text">{item[1]}</p>
            } else if (item[0] === 1) {
              return (
                <el-image
                  class="content-img"
                  src={item[1]}
                  preview-src-list={[item[1]]}>
                </el-image>
              )
            } else {
              return this.showQuote(item[1])
            }
          })}
        </div>
      )
    }
  },
  render (h) {
    return (
      <ul class="chat-record-list">
        {this.recordList.map(item => {
          return (
            <li
              class="chat-record-item"
              key={item.timeStamp}
            >
              <div class="title">
                <span class="person-info">
                  { `${item.sendUserNick}(${item.sendUserNet}) → ${item.receiverNick}(${item.receiverNet})` }
                </span>
                <span class="sendtime">
                  { this.formatTime(&#39;YYYY-mm-dd HH:MM:SS&#39;, item.timeStamp) }
                </span>
              </div>
              <div class="content">
                {item.message.map(msg => {
                  if (msg[0] === 0) {
                    return <p class="content-text">{msg[1]}</p>
                  } else if (msg[0] === 1) {
                    return (
                      <el-image
                        class="content-img"
                        src={msg[1]}
                        preview-src-list={[msg[1]]}>
                      </el-image>
                    )
                  } else {
                    // 递归渲染引用类型消息
                    return this.showQuote(msg[1])
                  }
                })}
              </div>
            </li>
          )
        })}
      </ul>
    )
  }

2、思路二:第一种思路中封装的showQuote里面的代码与render中渲染消息内容的代码基本相似,因此其实现方式不够优雅。其实可以将整个消息的渲染封装成一个组件,在该组件内引入自己,然后再渲染自己。由于具体细节代码与上述类似,这里只给出思路代码,具体细节请忽略

// 当前组件就是RecordMessage组件,自己引入自己
import RecordMessage from &#39;./RecordMessage.vue&#39;

export default {
    props: {
        message: {
            type: Array,
            default: () => []
        }
    },
    render () {
        const parseMessage = msg => {
            const type = msg[0]
            if (type === 0) {
                // 文本
            } else if (type === 2) {
                // 图片
            } else {
                // 引用类型
                return (
                    <div>
                        <div>引用:</div>
                        {
                            msg[1].map(subMsg => (
                                // 自己递归渲染自己
                                <recored-message>
                                </recored-message>
                            ))
                        }
                    </div>
                )
            }
        }
        return parseMessage(this.message)
    }

(学习视频分享:vuejs入门教程编程基础视频

以上是Vue中什麼是JSX?什麼時候用?怎麼用?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除