vue中正確使用jsx語法的姿勢分享
前言
又到瞭愉快的摸魚時間,我覺得不能荒廢,H5頁面我一直用的vant,出於對源碼的好奇,我從git上拉瞭一份vant源碼,裡面竟然都是jsx寫的組件,於是我開始瞭對在vue中使用jsx的探索
虛擬DOM
什麼是虛擬DOM
在這之前,先瞭解下虛擬DOM,vue和react框架都在內部使用瞭虛擬DOM,這樣做的原因是通過js操作DOM的計算成本很高,雖然js更新速度很快,但是查找dom並更新的成本很高。那麼有什麼方法可以優化呢,vue等框架使用js對象,通過改變js對象,最後進行批量處理,一次更新DOM,所以虛擬DOM本質上就是一個js對象
虛擬DOM的優點
- 從原先的操作真實DOM到操作虛擬DOM,降低查找成本
- 通過diff比對,我們可以更快的定位數據的變化,從而更新DOM
- 更好的ui更新
- 抽象渲染過程,帶來瞭實現跨平臺的能力,如vue3中的createRenderer API
渲染函數是什麼
渲染函數是用來生成虛擬DOM的。我們在vue單文件中編寫模板語法,最終會在底層實現中被編譯成渲染函數
Vue 推薦在絕大多數情況下使用模板來創建你的 HTML。然而在一些場景中,你真的需要 JavaScript 的完全編程的能力。這時你可以用渲染函數,它比模板更接近編譯器。
當出現以下場景,雖然下列寫法也能實現想要的效果,但是他不僅冗長,而且我們為每個級別標題重復書寫瞭 。當我們添加錨元素時,我們必須在每個 v-if/v-else-if 分支中再次重復它
const { createApp } = Vue const app = createApp({}) app.component('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> <h4 v-else-if="level === 4"> <slot></slot> </h4> <h5 v-else-if="level === 5"> <slot></slot> </h5> <h6 v-else-if="level === 6"> <slot></slot> </h6> `, props: { level: { type: Number, required: true } } })
雖然模板在大多數組件中都非常好用,但是顯然在這裡它就不合適瞭。那麼,我們來嘗試使用 render 函數重寫上面的例子:
const { createApp, h } = Vue const app = createApp({}) app.component('anchored-heading', { render() { return h( 'h' + this.level, // tag name {}, // props/attributes this.$slots.default() // array of children ) }, props: { level: { type: Number, required: true } } })
jsx
這樣寫渲染函數有點痛苦,有沒有更接近模板的寫法呢,vue提供瞭一個babel-plugin-jsx babel插件來讓vue支持jsx寫法
我這邊使用的vuecli創建的vue3 + ts項目,腳手架已經集成瞭jsx和ts的相關依賴
在vue3中編寫jsx的兩種方式
直接將文件後綴名從vue改成tsx或者jsx
在vue3中,可以直接使用render選項編寫
import { defineComponent } from "vue"; export default defineComponent({ name: "Jsx", render() { return <div>我是一個div</div>; }, });
也可以在setup中返回
import { defineComponent } from "vue"; export default defineComponent({ name: "Jsx", setup() { return () => <div>我是div</div>; }, });
兩種方式都可以,具體看個人習慣,setup中訪問不到this,但是render中可以通過this訪問當前vue實例
用法
class綁定,和react的jsx綁定的有區別,react中使用className,vue中使用class
setup() { return () => <div class="test">我是div</div>; },
style綁定
setup() { return () => <div style={{ color: "red" }}>我是div</div>; },
props綁定
// 父組件 setup() { return () => ( <div style={{ color: "red" }}> <span>我是父組件</span> <Mycom msg={"我是父組件傳的值"} /> </div> );
// 子組件,setup的第一個參數,可以獲取props裡的值 setup(props) { return () => <div>我是子組件{props.msg}</div>; },
事件綁定
setup() { function eventClick() { console.log("點擊"); } return () => <button onClick={eventClick}>按鈕</button>; },
組件自定義事件
// 子組件 import { defineComponent } from "vue"; export default defineComponent({ name: "Mycom", emits: ["event"], setup(props, { emit }) { function sendData() { emit("event", "子組件傳遞的數據"); } return () => ( <div> <span>自定義事件</span> <button onClick={sendData}>傳遞數據</button> </div> ); }, });
// 父組件 // @ts-nocheck // 這樣寫在jsx中沒問題,但是在tsx中會報ts類型錯誤,所以我在上面忽略瞭當前文件ts監測@ts-nocheck import { defineComponent } from "vue"; import Mycom from "./mycom"; export default defineComponent({ name: "Jsx", setup() { function getSon(msg: string) { console.log(msg); } return () => ( <div> <Mycom onEvent={getSon} /> </div> ); }, });
也可以這樣解決ts類型報錯
setup() { function getSon(msg: string) { console.log(msg); } return () => ( <div> <Mycom {...{ onEvent: getSon }} /> </div> ); },
插槽
// 父組件 setup() { const slots = { test: () => <div>具名插槽</div>, default: () => <div>默認插槽</div>, }; return () => ( <div> <Mycom v-slots={slots}></Mycom> </div> ); },
setup(props, { slots }) { // 子組件 return () => ( <div> <span>插槽</span> {slots.default?.()} {slots.test?.()} </div> ); },
指令,v-if,v-for等指令在jsx中無法使用,jsx隻支持v-model和v-show指令
setup() { const inputData = ref(""); return () => ( <div> <span v-show={true}>顯示</span> <span v-show={false}>隱藏</span> <input type="text" v-model={inputData.value} /> <span>{inputData.value}</span> </div> ); },
最後
話不多說,我先打開vant源碼,準備開啟我的第一個組件源碼閱讀 src =>button=>button.tsx
到此這篇關於vue中正確使用jsx的文章就介紹到這瞭,更多相關vue中使用jsx內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
參考
- vue渲染函數
- vuejsx文檔
- issues