<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【Vue】1788- 一起玩轉(zhuǎn) Vue 中的 JSX

          共 29295字,需瀏覽 59分鐘

           ·

          2023-08-29 08:14

          5a5892b2c46631ec22247921716299a8.webp

          作者: Rockky

          https://juejin.cn/post/7188898676993949752

          JSX簡介

          JSX是一種Javascript的語法擴(kuò)展,即具備了Javascript的全部功能,同時(shí)又兼具html的語義化和直觀性。它可以讓我們?cè)贘S中寫模板語法:

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

          上面這段代碼既不是 HTML 也不是字符串,被稱之為 JSX,是 JavaScript 的擴(kuò)展語法。JSX 可能會(huì)使人聯(lián)想到模板語法,但是它具備 Javascript 的完全編程能力。

          什么時(shí)候使用JSX

          當(dāng)開始寫一個(gè)只能通過 level prop 動(dòng)態(tài)生成標(biāo)題 (heading) 的組件時(shí),你可能很快想到這樣實(shí)現(xiàn):

                
                
                  <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模板并不是最好的選擇,在每一個(gè)級(jí)別的標(biāo)題中重復(fù)書寫了部分代碼,不夠簡潔優(yōu)雅。如果嘗試用 JSX 來寫,代碼就會(huì)變得簡單很多:

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

          或者如果你寫了很多 render 函數(shù),可能會(huì)覺得下面這樣的代碼寫起來很痛苦:

                
                createElement(  
              'anchored-heading', {  
                  props: {  
                      level1  
                  }  
              }, [  
              createElement('span''Hello'),  
                  ' world!'  
              ]  
          )

          特別是對(duì)應(yīng)的模板如此簡單的情況下:

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

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

                
                import AnchoredHeading from './AnchoredHeading.vue'  
            
          new Vue({  
              el'#demo',  
              renderfunction (h{  
                  return (  
                      <AnchoredHeading level={1}>  
                          <span>Hello</span> world!  
                      </AnchoredHeading>
            
                  )  
              }  
          })

          在開發(fā)過程中,經(jīng)常會(huì)用到消息提示組件Message,可能的一種寫法是這樣的:

                
                Message.alert({
            messge'確定要?jiǎng)h除?',
            type'warning'
          })

          但是希望message可以自定義一些樣式,這時(shí)候你可能就需要讓Message.alert支持JSX了(當(dāng)然也可以使用插槽/html等方式解決)

                
                Message.alert({
            messge<div>確定要?jiǎng)h除<span style="color:red">xxx</span>的筆記?</div>,
            type'warning'
          })

          此外,一個(gè) .vue 文件里面只能寫一個(gè)組件,這個(gè)在一些場景下可能不太方便,很多時(shí)候?qū)懸粋€(gè)頁面的時(shí)候其實(shí)可能會(huì)需要把一些小的節(jié)點(diǎn)片段拆分到小組件里面進(jìn)行復(fù)用,這些小組件其實(shí)寫個(gè)簡單的函數(shù)組件就能搞定了。平時(shí)可能會(huì)由于SFC的限制讓我們習(xí)慣于全部寫在一個(gè)文件里,但不得不說可以嘗試一下這種方式。

                
                // 一個(gè)文件寫多個(gè)組件
          const Input = (props) => <input {...props} />
          export const Textarea = (props) => <input {...props} />
          export const Password = (props) => <input type="password" {...props} />

          export default Input

          比如這里封裝了一個(gè) Input 組件,我們希望同時(shí)導(dǎo)出 Password 組件和 Textarea 組件來方便用戶根據(jù)實(shí)際需求使用,而這兩個(gè)組件本身內(nèi)部就是用的 Input 組件,只是定制了一些 props。在 JSX 里面就很方便,寫個(gè)簡單的函數(shù)組件基本上就夠用了,通過 interface 來聲明 props 就好了。但是如果是用模板來寫,可能就要給拆成三個(gè)文件,或許還要再加一個(gè) index.js 的入口文件來導(dǎo)出三個(gè)組件。

          由于 JSX 的本質(zhì)就是 JavaScript,所以它具有 JavaScript 的完全編程能力。再舉個(gè)例子,我們需要通過一段邏輯來對(duì)一組 DOM 節(jié)點(diǎn)做一次 reverse,如果在模板里面寫,那估計(jì)要寫兩段代碼。

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

          從 Vue 2 開始,template 在運(yùn)行之前,會(huì)被編譯成 JavaScript 的 render function

          f89353a3d3a00fbaefd364c54cbeaee6.webp

          Vue 推薦在絕大多數(shù)情況下使用 template 來創(chuàng)建你的 HTML。然而在一些場景中,就需要使用 render 函數(shù),它比 template 更加靈活。這些 render function 在運(yùn)行時(shí)階段,就是傳說中的 Virtual DOM

          6ecc87586567b662462994f9b9e991cf.webp
          JSX在Vue2中的基本使用

          配置

          在 Vue 2 中,JSX 的編譯需要依賴 @vue/babel-preset-jsx 和 @vue/babel-helper-vue-jsx-merge-props 這兩個(gè)包。前面這個(gè)包來負(fù)責(zé)編譯 JSX 的語法,后面的包用來引入運(yùn)行時(shí)的 mergeProps 函數(shù)。

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

          并在babel.config.js中添加配置:

                
                module.exports = {
            presets: ['@vue/babel-preset-jsx'],
          }

          文本插值

          模板代碼里文本插值默認(rèn)是用雙大括號(hào):

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

          在JSX中則需要使用單大括號(hào):

                
                const name = 'Vue'
          const element = <h1>Hello, { name }</h1>

          和模板語法中的文本插值一樣,大括號(hào)內(nèi)支持任何有效的JS表達(dá)式,比如:2 + 2user.firstNameformatName(user)等。

          條件與循環(huán)渲染

          在模板代碼里面我們通過v-for去遍歷元素,通過v-if去判斷是否渲染元素,在JSX中,對(duì)于v-for,可以使用for循環(huán)或者array.map來代替,對(duì)于v-if,可以使用if-else語句,三元表達(dá)式等來代替

          使用if-else語句

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

          使用三元表達(dá)式

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

          使用數(shù)組的map方法

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

          )

          屬性綁定

          在模板代碼中,一般通過 v-bind:prop="value":prop="value"來給組件綁定屬性,在JSX里面就不能繼續(xù)使用v-bind指令了,而是通過單大括號(hào)的形式進(jìn)行綁定:

                
                const href = 'https://xxx.com'
          const element = <a href={href}>xxx</a>

                
                const properties = {a1b2}

          此外,模板代碼中能通過<div v-bind="properties"></div>批量綁定標(biāo)簽屬性。

          在JSX中也有相應(yīng)的替換方案:<div {...properties}></div>

          class綁定同樣也是使用單大括號(hào)的形式

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


          style綁定需要使用雙大括號(hào)

                
                const width = '100px'
          const element = <button style={{ width, fontSize: '16px' }}></button>

          事件綁定

          在模板代碼中通過v-on指令監(jiān)聽事件,在JSX中通過on + 事件名稱的大駝峰寫法來監(jiān)聽,且綁定事件也是用大括號(hào),比如click事件要寫成onClick,mouseenter事件要寫成onMouseenter

                
                const confirm = () => {
            // 確認(rèn)提交
          }
          <button onClick={confirm}>確定</button>

          有時(shí)候我們希望可以監(jiān)聽一個(gè)組件根元素上面的原生事件,這時(shí)候會(huì)用到.native修飾符,但是在JSX中同樣也不能使用,不過也有替代方案,監(jiān)聽原生事件的規(guī)則與普通事件是一樣的,只需要將前面的on替換為nativeOn,如下

                
                 render() {
              // 監(jiān)聽下拉框根元素的click事件
              return <CustomSelect nativeOnClick={this.handleClick}></CustomSelect>
            }

          除了上面的監(jiān)聽事件的方式之外,我們還可以使用對(duì)象的方式去監(jiān)聽事件

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

              )
            }

          對(duì)于 .passive.capture 和 .once 這些事件修飾符,Vue 提供了相應(yīng)的前綴可以用于 on

          25f4ef6adf629163e718f198aae5847a.webp

          例如:

                
                on: {  
              '!click'this.doThisInCapturingMode,  
              '~keyup'this.doThisOnce,  
              '~!mouseover'this.doThisOnceInCapturingMode  
          }

          對(duì)于所有其它的修飾符,私有前綴都不是必須的,因?yàn)槟憧梢栽谑录幚砗瘮?shù)中使用事件方法:7b20a0014c637b071419664008fb778f.webp具體可查閱Vue規(guī)范文檔。

          v-show與v-model

          大多數(shù)指令并不能在JSX中使用,對(duì)于原生指令,只有v-show是支持的。

          v-modelVue提供的一個(gè)語法糖,它本質(zhì)上是由 value屬性(默認(rèn)) + input事件(默認(rèn))組成的,所以,在JSX中,我們便可以回歸本質(zhì),通過傳遞value屬性并監(jiān)聽input事件來手動(dòng)實(shí)現(xiàn)數(shù)據(jù)的雙向綁定:

                
                export default {
            data() {
              return {
                name''
              }
            },
            methods: {
              // 監(jiān)聽 onInput 事件進(jìn)行賦值操作
              handleInput(e) {
                this.name = e.target.value
              }
            },
            render() {
              // 傳遞 value 屬性 并監(jiān)聽 onInput事件
              return <input value={this.name} onInput={this.handleInput}></input>
            }
          }

          此外,在腳手架vue-cli4中,已經(jīng)默認(rèn)集成了對(duì)v-model的支持,可以直接使用<input v-model={this.value}>,如果項(xiàng)目比較老,也可以安裝插件babel-plugin-jsx-v-model來進(jìn)行支持。

          同樣的,在JSX中,對(duì)于.sync也需要用屬性+事件來實(shí)現(xiàn),如下代碼所示:

                
                export default {
            methods: {
              handleChangeVisible(value) {
                this.visible = value
              }
            },
            render() {
              return (
                <ElDialog
                  title="測試.sync"
                  visible={this.visible}
                  on={{ 'update:visible': this.handleChangeVisible }}
                >
          </ElDialog>

              )
            }
          }

          插槽

          (1)默認(rèn)插槽:

          使用element-uiDialog時(shí),彈框內(nèi)容就使用了默認(rèn)插槽,在JSX中使用默認(rèn)插槽的用法與普通插槽的用法基本是一致的,如下

                
                 render() {
              return (
                <ElDialog title="彈框標(biāo)題" visible={this.visible}>
                  {/*這里就是默認(rèn)插槽*/}
                  <div>這里是彈框內(nèi)容</div>
                </ElDialog>

              )
            }

          自定義默認(rèn)插槽:

          Vue的實(shí)例this上面有一個(gè)屬性$slots,這個(gè)上面就掛載了一個(gè)這個(gè)組件內(nèi)部的所有插槽,使用this.$slots.default就可以將默認(rèn)插槽加入到組件內(nèi)部

                
                export default {
            props: {
              visible: {
                typeBoolean,
                defaultfalse
              }
            },
            render() {
              return (
                <div class="custom-dialog" vShow={this.visible}>
                  {/**通過this.$slots.default定義默認(rèn)插槽*/}
                  {this.$slots.default}
                </div>

              )
            }
          }

          (2)具名插槽

          有時(shí)候我們一個(gè)組件需要多個(gè)插槽,這時(shí)候就需要為每一個(gè)插槽起一個(gè)名字,比如element-ui的彈框可以定義底部按鈕區(qū)的內(nèi)容,就是用了名字為footer的插槽

                
                 render() {
              return (
                <ElDialog title="彈框標(biāo)題" visible={this.visible}>
                  <div>這里是彈框內(nèi)容</div>
                  {/** 具名插槽 */}
                  <template slot="footer">
                    <ElButton>確定</ElButton>
                    <ElButton>取消</ElButton>
                  </template>
                </ElDialog>

              )
            }

          自定義具名插槽: 在上節(jié)自定義默認(rèn)插槽時(shí)提到了$slots,對(duì)于默認(rèn)插槽使用this.$slots.default,而對(duì)于具名插槽,可以使用this.$slots.footer進(jìn)行自定義

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

              )
            }

          (3)作用域插槽

          有時(shí)讓插槽內(nèi)容能夠訪問子組件中才有的數(shù)據(jù)是很有用的,這時(shí)候就需要用到作用域插槽,在JSX中,因?yàn)闆]有v-slot指令,所以作用域插槽的使用方式就與模板代碼里面的方式有所不同了。比如在element-ui中,我們使用el-table的時(shí)候可以自定義表格單元格的內(nèi)容,這時(shí)候就需要用到作用域插槽

                
                data() {
              return {
                data: [
                  {
                    name'xxx'
                  }
                ]
              }
            },
            render() {
              return (
                {/**scopedSlots即作用域插槽,default為默認(rèn)插槽,如果是具名插槽,將default該為對(duì)應(yīng)插槽名稱即可*/}
                <ElTable data={this.data}>
                  <ElTableColumn
                    label="姓名"
                    scopedSlots={{
                      default: ({ row }) =>
           {
                        return <div style="color:red;">{row.name}</div>
                      }
                    }}
                  ></ElTableColumn>

                </ElTable>
              )
            }

          自定義作用域插槽:

          使用作用域插槽不同,定義作用域插槽也與模板代碼里面有所不同。加入我們自定義了一個(gè)列表項(xiàng)組件,用戶希望可以自定義列表項(xiàng)標(biāo)題,這時(shí)候就需要將列表的數(shù)據(jù)通過作用域插槽傳出來。

                
                render() {
              const { data } = this
              // 獲取標(biāo)題作用域插槽
              const titleSlot = this.$scopedSlots.title
              return (
                <div class="item">
                  {/** 如果有標(biāo)題插槽,則使用標(biāo)題插槽,否則使用默認(rèn)標(biāo)題 */}
                  {titleSlot ? titleSlot(data) : <span>{data.title}</span>}
                </div>

              )
            }

          使用自定義組件

          只需要導(dǎo)入進(jìn)來,不用再在components屬性聲明了,直接寫在jsx中:

                
                import MyComponent from './my-component'

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

          在method里返回JSX

          我們可以定義method,然后在method里面返回JSX,然后在render函數(shù)里面調(diào)用這個(gè)方法,不僅如此,JSX還可以直接賦值給變量,比如:

                
                 methods: {
              renderFooter() {
                return (
                  <div>
                    <ElButton>確定</ElButton>
                    <ElButton>取消</ElButton>
                  </div>

                )
              }
            },
            render() {
              const buttons = this.renderFooter()
              return (
                <ElDialog visible={this.visible}>
                  <div>內(nèi)容</div>
                  <template slot="footer">{buttons}</template>
                </ElDialog>

              )
            }

          用JSX實(shí)現(xiàn)簡易聊天記錄

          假設(shè)該消息聊天記錄的消息類型只有三種:文本,圖片,引用。一條消息里面可以包括任意類型的內(nèi)容,引用類型消息內(nèi)部可以不斷嵌套引用其他任意類型消息。效果圖大致如下:

          65dcc30120dc51c0a54b7fbdd7d7bf00.webp

          消息數(shù)據(jù)結(jié)構(gòu)如下:

                
                message: [
                // 每個(gè)數(shù)組的第一個(gè)參數(shù)為消息類型:0:文本 1:圖片 2:引用。第二個(gè)參數(shù)為具體內(nèi)容
                [
                  0,
                  '文本'
                ],
                [
                  1,
                  '圖片鏈接xxx'
                ],
                [
                  2,
                  [
                    [
                      0,
                      '引用文本文本文本'
                    ],
                    [
                      1,
                      '引用圖片鏈接xxx'
                    ]
                  ]
                ]
              ]

          主要有兩個(gè)思路:

          1、思路一:在render里返回一段用array.map渲染的消息模板,對(duì)于三種消息類型,使用if-else進(jìn)行判斷分別渲染,對(duì)于引用類型的消息,可以封裝一個(gè)方法進(jìn)行渲染,方法里面如果還有引用類型消息就繼續(xù)遞歸渲染。

                
                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('YYYY-mm-dd HH:MM:SS', 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中渲染消息內(nèi)容的代碼基本相似,因此其實(shí)現(xiàn)方式不夠優(yōu)雅。其實(shí)可以將整個(gè)消息的渲染封裝成一個(gè)組件,在該組件內(nèi)引入自己,然后再渲染自己。由于具體細(xì)節(jié)代碼與上述類似,這里只給出思路代碼,具體細(xì)節(jié)請(qǐng)忽略

                
                // 當(dāng)前組件就是RecordMessage組件,自己引入自己
          import RecordMessage from './RecordMessage.vue'

          export default {
              props: {
                  message: {
                      typeArray,
                      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)
              }



          往期回顧
          #

          如何使用 TypeScript 開發(fā) React 函數(shù)式組件?

          #

          11 個(gè)需要避免的 React 錯(cuò)誤用法

          #

          6 個(gè) Vue3 開發(fā)必備的 VSCode 插件

          #

          3 款非常實(shí)用的 Node.js 版本管理工具

          #

          6 個(gè)你必須明白 Vue3 的 ref 和 reactive 問題

          #

          6 個(gè)意想不到的 JavaScript 問題

          #

          試著換個(gè)角度理解低代碼平臺(tái)設(shè)計(jì)的本質(zhì)

          回復(fù)“加群”,一起學(xué)習(xí)進(jìn)步

          瀏覽 77
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  奇米一区 | 日日噜狠狠色综合 | 国产日批视频 | 台湾无码 | 国产高清在线无码 |