為什么推薦使用JSX開發(fā)Vue3
在很長(zhǎng)的一段時(shí)間中,Vue 官方都以簡(jiǎn)單上手作為其推廣的重點(diǎn)。這確實(shí)給 Vue 帶來(lái)了非常大的用戶量,尤其是最追求需求開發(fā)效率,
往往不那么在意工程代碼質(zhì)量的國(guó)內(nèi)中小企業(yè)中,Vue 占據(jù)的份額極速增長(zhǎng)。但是作為開發(fā)者自身,我們必須要認(rèn)清一個(gè)重點(diǎn),簡(jiǎn)單易用重來(lái)不應(yīng)該在技術(shù)選型中占據(jù)很大的份額,可維護(hù)性才是。
以防萬(wàn)一有的同學(xué)實(shí)在不看官方文檔,我先提一嘴,SFC 就是寫 Vue 組件的時(shí)候?qū)懙?vue文件,這一個(gè)文件就是一個(gè) SFC,全稱 Single File Component,也即單文件組件。
在開始說(shuō)我個(gè)人的觀點(diǎn)之前,我們先來(lái)看幾個(gè)事實(shí):
一是:Vue3 的定義原生支持 JSX,并且 Vue3 源碼中有jsx.d.ts來(lái)便于使用 JSX。 不知道同學(xué)們看到這里會(huì)想到什么,
我的第一反應(yīng)是:社區(qū)對(duì)于 JSX 的需求聲音是不小的,所以會(huì)反向推動(dòng) Vue3 官方對(duì)于 JSX 的支持。
二是:AntDesign 的 vue3 版本,基本全部都是用 JSX 開發(fā)的,而且 Vue3 現(xiàn)在官方的 babel-jsx 插件就是阿里的人一開始維護(hù)的,
雖然我向來(lái)不喜歡阿里系的 KPI 推動(dòng)技術(shù)方式,而且現(xiàn)在的 JSX 語(yǔ)法支持也不是很符合我的期望,但至少在使用 JSX 開發(fā)是更優(yōu)秀的選擇這點(diǎn)上,我還是很認(rèn)可 AntDesign 團(tuán)隊(duì)的。
OK,說(shuō)這些呢,主要是先擺出一些事實(shí)作為依據(jù),讓有些同學(xué)可以不需要拿什么:
- 啊,這都是你空想的,你太自以為是了
- 你再怎么想都沒(méi)用,咱們 Vue 就是應(yīng)該用 SFC 開發(fā)
這些觀點(diǎn)來(lái)批斗我,首先我都會(huì)從客觀的角度來(lái)分析為什么,至少是我是能講出優(yōu)劣勢(shì)的理由的。
OK,前言差不多到這里,接下來(lái)咱給您分析分析,為什么你應(yīng)該選擇 JSX 來(lái)開發(fā) Vue。
TypeScript 支持
其實(shí)第一點(diǎn)就已經(jīng)是殺手了,對(duì)于想要使用 TypeScript 來(lái)開發(fā) Vue3 應(yīng)用的同學(xué)來(lái)說(shuō),這簡(jiǎn)直就是 SFC 無(wú)法克服的世界難題。
一句話概括:TypeScript 原生支持 JSX 語(yǔ)法,而基本無(wú)望 TS 官方能支持 SFC 的 template 語(yǔ)法。
TS 毫無(wú)疑問(wèn)在前端社區(qū)的重要性越來(lái)越大,但凡未來(lái)對(duì)于代碼質(zhì)量有一定要求的前端團(tuán)隊(duì),都應(yīng)該會(huì)選擇使用 TS 來(lái)進(jìn)行開發(fā)。
而且現(xiàn)在基本上在 NPM 上都能看到包你都能找到對(duì)應(yīng)的 TS 定義,現(xiàn)在使用 TS 開發(fā)成本已經(jīng)只剩下你是不是會(huì) TS 語(yǔ)法了,在這種情況下是否支持 TS 則是開發(fā)模式在未來(lái)走不走的遠(yuǎn)的重要原因。
目前 SFC 只能通過(guò)shim讓 TS 可以引入.vue文件,但是對(duì)于所有 SFC 的組件的定義都是一樣的:
declare module '*.vue' { import { DefineComponent } from 'vue' const component: DefineComponent<{}, {}, {}, any> export default component }
也就是說(shuō)你引入的 SFC 組件,TS 是不知道這個(gè)組件的 Props 應(yīng)該接收什么的。所以你無(wú)法享受到這些 TS 的優(yōu)勢(shì):
- 開發(fā)時(shí)的自動(dòng)提示
- 編譯時(shí)的 TS 校驗(yàn),讓你盡早發(fā)現(xiàn)問(wèn)題
- 編譯組件生成你的組件定義(對(duì)于類庫(kù)開發(fā)尤其重要)
當(dāng)然你會(huì)說(shuō)既然 Vue 官方能開發(fā)處 SFC 的語(yǔ)法,自然會(huì)支持這些特性。我表示這當(dāng)然有可能,但是這個(gè)難度是非常大的,需要很多方面的支持,甚至可能需要 TS 官方團(tuán)隊(duì)愿意協(xié)助,
但是我想不到 TS 官方有什么理由來(lái)支持 SFC,因?yàn)檫@只是 Vue 自己創(chuàng)建的方言,在其他場(chǎng)景下是沒(méi)有使用的,TS 是面向全社區(qū)的,我覺(jué)得他們不會(huì)考慮主動(dòng)來(lái)支持 SFC。
那么有同學(xué)要問(wèn)了,JSX 不也是非原生的 JS 語(yǔ)法么,他怎么就能讓 TS 官方支持了呢,是不是 FB 和微硬之間有什么 PY 交易?
這就涉及第二點(diǎn)了,JSX 和靜態(tài)模板的靈活性區(qū)別。
JSX 其實(shí)并不是方言
很多人弄錯(cuò)了一個(gè)問(wèn)題,就是覺(jué)得 SFC 的模板語(yǔ)法和 JSX 是一樣的,都是一種別人發(fā)明的語(yǔ)法,并不是 JS 原生的。這是事實(shí),但又有一些區(qū)別,這個(gè)區(qū)別主要是體現(xiàn)在對(duì)于 JSX 的認(rèn)知上。
一句話概括:JSX 并沒(méi)有擴(kuò)展 JS 的語(yǔ)法,他只是縮略了 JS 的寫法!其本質(zhì)就是 JS 的語(yǔ)法糖
就像 es6 給增加的語(yǔ)法糖,比如
const a = 1 const b = 2 const obj = { a, b } // 其實(shí)就等價(jià)于 const obj = { a: a, b: b }
這種寫法并沒(méi)有擴(kuò)展 JS 的能力,只是簡(jiǎn)便了寫法,JSX 也是一樣的。
JSX 其實(shí)就是方法調(diào)用,他和 JS 是有一對(duì)一對(duì)應(yīng)關(guān)系的,我們來(lái)看一個(gè)例子:
const element = <div id="root">Hello World</div>
這里的 JSX 語(yǔ)法編譯之后其實(shí)就是:
const element = createElement('div', { id: 'root' }, 'Hello World')
而 JSX 就是這些了,沒(méi)有什么更多的內(nèi)容,所以說(shuō) JSX 只是方便我們寫嵌套的函數(shù)調(diào)用的語(yǔ)法糖,而其本身沒(méi)有擴(kuò)展任何其他的內(nèi)容。
但是 SFC 就不一樣了。
SFC 定義的不僅是語(yǔ)法,更是文件。
SFC 的具體定義是單文件組件,它本身就是把一個(gè)文件看作一個(gè)單位,所以他的約束性是要大很多的,你必須具有固定的文件結(jié)構(gòu)才能使用 SFC,這做了很多的限制:
- 一個(gè)文件只能寫一個(gè)組件
- 節(jié)點(diǎn)片段只能寫在 template 里面,非常不靈活
- 變量綁定只能獲取this上面的內(nèi)容,不能使用全局變量(很多時(shí)候我們都要把全局變量先掛載到this上)
我們一點(diǎn)點(diǎn)來(lái)講
一個(gè)文件只能寫一個(gè)組件
這個(gè)說(shuō)實(shí)話非常非常不方便,很多時(shí)候我們寫一個(gè)頁(yè)面的時(shí)候其實(shí)經(jīng)常會(huì)需要把一些小的節(jié)點(diǎn)片段拆分到小組件里面進(jìn)行復(fù)用(如果你現(xiàn)在沒(méi)有這個(gè)習(xí)慣可能就是因?yàn)?SFC 的限制讓你習(xí)慣了全部寫在一個(gè)文件內(nèi))。
React 生態(tài)中豐富的 css-in-js 方案就是很好的例子,我們可以通過(guò):
const StyledButton = styled('button', { color: 'red', })
如果我們這個(gè)頁(yè)面需要使用特定樣式的按鈕,通過(guò)這種方式在頁(yè)面文件里面封裝一下是非常常見(jiàn)的。因?yàn)闆](méi)必要把這個(gè)組件拆分出去,他也不是一個(gè)可復(fù)用的組件,拆分出去了還要多一次import。
Vue 生態(tài)基本沒(méi)有 css-in-js 的成熟方案其實(shí)跟這個(gè)限制也很有關(guān)系。
再來(lái)一個(gè)例子,比如我們封裝了一個(gè) Input 組件,我們希望同時(shí)導(dǎo)出 Password 組件和 Textarea 組件來(lái)方便用戶根據(jù)實(shí)際需求使用,而這兩個(gè)組件本身內(nèi)部就是用的 Input 組件,只是定制了一些 props:
const Input = { ... } export default Input export const Textarea = (props) => <Input multiline={true} {...props} /> export const Password = (props) => <Input type="password" {...props} />
在 JSX 中可以非常簡(jiǎn)單地實(shí)現(xiàn),但是如果通過(guò) SFC,你可能就要強(qiáng)行拆成三個(gè)文件,另外為了方便,你可能還要增加一個(gè)index.js來(lái)導(dǎo)出這三個(gè)組件,你能想象這多了多少工作量么。
節(jié)點(diǎn)片段只能寫在 template 里面,非常不靈活
我不知道有多少同學(xué)看過(guò) Vue 的 template 編譯出來(lái)之后的代碼,以我的經(jīng)驗(yàn)來(lái)說(shuō)看過(guò)的可能不會(huì)超過(guò) 50%(樂(lè)觀估計(jì)),建議同學(xué)們?nèi)绻€不了解的,可以去嘗試看一下。
為什么要看這個(gè)呢?因?yàn)槟憧戳酥竽銜?huì)發(fā)現(xiàn),你在 template 里面寫的類似 HTMl 的內(nèi)容,其實(shí)跟 HTML 根本沒(méi)啥關(guān)系,他們也會(huì)被編譯成類似 JSX 編譯出來(lái)的結(jié)果。
{ render(h) { return h('div', {on: {}, props: {}}, h('span')) } }
類似這樣的結(jié)果,而這里面h函數(shù)調(diào)用的結(jié)果就是一個(gè) VNode,是 Vue 中的節(jié)點(diǎn)的基礎(chǔ)單元。那么既然這些單元就是一個(gè)對(duì)象,其實(shí)理所當(dāng)然的,他們是可以作為參數(shù)傳遞的。
也就是說(shuō),理論上他們是可以通過(guò)props把節(jié)點(diǎn)當(dāng)作參數(shù)傳遞給其他組件的。
這個(gè)做法在 React 中非常常見(jiàn),叫做renderProps,并且其非常靈活:
const Comp = () => <Layout header={<MyHeader />} footer={<MyFooter />} />
但是因?yàn)?SFC 模板的限制,我們很難在 SFC 里面的 props 上寫節(jié)點(diǎn):
<template> <Layout :header="<MyHeader/>"></Layout> </template>
這樣寫是不行的,因?yàn)?SFC 定義了:header綁定接受的只能是 js 表達(dá)式,而<MyHeader/>顯然不是。
因?yàn)橥ㄟ^(guò) props 傳遞不行,所以 Vue 才發(fā)明了 slot 插槽的概念
雖然我們一直再說(shuō) Vue 簡(jiǎn)單,但是事實(shí)上ScopedSlots一度成為新手理解 Vue 的噩夢(mèng),很多同學(xué)都被這個(gè)繞來(lái)繞去的作用域整的死去活來(lái)。
我們看一個(gè)ScopedSlots的例子:
<template> <Comp> <template v-slot:scope="ctx"> <div>{{ctx.name}}</div> </template> </Comp> </template>
這里ctx是Comp里面的屬性,通過(guò)這種方式傳遞出來(lái),讓我們?cè)诋?dāng)前組件可以調(diào)用父組件里面的屬性。這簡(jiǎn)直就是理解的噩夢(mèng),但是如果用 JSX 實(shí)現(xiàn)類似功能就非常簡(jiǎn)單:
<Comp scope={name => <div>{name}</div>} />
我們只是給一個(gè)叫做scope的 props 傳遞來(lái)一個(gè)函數(shù),這個(gè)函數(shù)接受一個(gè)name屬性,在Comp里面會(huì)調(diào)用這個(gè)函數(shù)并傳入name。
簡(jiǎn)單來(lái)說(shuō)我們傳入的就是一個(gè)構(gòu)建節(jié)點(diǎn)片段的函數(shù),就是這么簡(jiǎn)單。
這就是因?yàn)?SFC 的模板的限制,導(dǎo)致靈活性不足,Vue 需要去創(chuàng)造概念,創(chuàng)造關(guān)鍵字來(lái)抹平這些能力的不足,而創(chuàng)造的概念自然就引入了學(xué)習(xí)成本。
所以其實(shí)我一直不認(rèn)可 Vue 比 React 好學(xué)的說(shuō)法的,如果你真的認(rèn)真研究所有用法,并且總是嘗試用最合理的方式實(shí)現(xiàn)功能,那么 Vue 絕對(duì)不會(huì)比 React 簡(jiǎn)單。
變量綁定只能獲取this上面的內(nèi)容,不能使用全局變量
這個(gè)體現(xiàn)在兩個(gè)方面,一個(gè)是我們定義在全局的一些固定數(shù)據(jù)如果要在組件內(nèi)使用的話,就要通過(guò)this掛載到組件上。
比如我們緩存了一份城市數(shù)據(jù),這種數(shù)據(jù)基本上是不會(huì)改的,所以也沒(méi)必要掛載到組件上讓其能夠響應(yīng)式。但是在 SFC 里面這是做不到的,
因?yàn)槟0宓膱?zhí)行上下文是在編譯時(shí)綁定。你在模板里面訪問(wèn)的變量,都會(huì)在編譯時(shí)自動(dòng)綁定到this上,因?yàn)槟0逍枰幾g,其本身也是字符串不具有作用域的概念。
而這在 JSX 中則不復(fù)存在:
const citys = [] const Comp = () => { return citys.map(c => <div>{c}</div>) }
另外一個(gè)方面則是在組件使用上,在 SFC 中,組件必須事先注冊(cè),因?yàn)槲覀冊(cè)谀0謇锩鎸懙闹荒苁亲址荒苁蔷唧w某個(gè)組件變量。
那么模板中的組件和真實(shí)的組件對(duì)象只能通過(guò)字符串匹配來(lái)實(shí)現(xiàn)綁定。這帶來(lái)了以下問(wèn)題:
- 多了注冊(cè)組件這個(gè)步驟,增加代碼量
- 通過(guò)字符串名注冊(cè)自然就會(huì)出現(xiàn)可能的沖突問(wèn)題
- 模板解析組件支持不同的樣式,比如<MyComp>和<my-comp>,容易導(dǎo)致風(fēng)格不一的問(wèn)題
在 JSX 中則沒(méi)有這些問(wèn)題,因?yàn)?JSX 里面直接使用組件引用作為參數(shù):
const Comp = {...} const App = () => <Comp />
需要通過(guò)directive來(lái)擴(kuò)展能力
其實(shí)上面能看出來(lái),除了 SFC 本身的問(wèn)題之外,Vue 使用字符串模板也會(huì)帶來(lái)很多的靈活性問(wèn)題。
最直接的證據(jù),就是 Vue 使用了directive來(lái)擴(kuò)展功能(當(dāng)然這不是 Vue 發(fā)明的,老早的模板引擎就有類似問(wèn)題)。
為什么說(shuō)directive是不得已的選擇呢?因?yàn)殪o態(tài)模板缺失邏輯處理的能力。我們拿列表循環(huán)舉例,在 JS 中我們可以非常方便地通過(guò)map函數(shù)來(lái)創(chuàng)建列表:
const list = arr.map(name => <span key={name}>{name}</span>)
而因?yàn)?JSX 本身就是函數(shù)調(diào)用,所以上面的代碼和 JSX 結(jié)合起來(lái)也非常自然:
const App = () => ( <div> <Header /> {arr.map(name => ( <span key={name}>{name}</span> ))} </div> )
上面的例子對(duì)應(yīng)到 JS 如下:
const App = () => createElement('div', {}, [ <Header />, arr.map(name => createElement('span', { key: name }, name)), ])
這仍然是因?yàn)?JSX 只是 JS 的語(yǔ)法糖的原因,所有能在 JS 中實(shí)現(xiàn)的在 JSX 里面都能實(shí)現(xiàn)。
而 SFC 的模板是基于字符串編譯的,其本身就是一段字符串,我們不能直接在模板里面寫map來(lái)循環(huán)節(jié)點(diǎn),(當(dāng)然我們可以在可以接收表達(dá)式的地方寫,比如v-on里面)。
那么我們不能循環(huán)節(jié)點(diǎn),有需要這樣的功能來(lái)渲染列表,怎么辦呢?就是發(fā)明一個(gè)標(biāo)志來(lái)告訴編譯器這里需要循環(huán),在 Vue 中的體現(xiàn)就是v-for指令。
同學(xué)們可能要問(wèn)了,既然 Vue 能實(shí)現(xiàn)v-for,為什么不直接實(shí)現(xiàn)表達(dá)式循環(huán)列表呢?他當(dāng)然也可以實(shí)現(xiàn),但是他肯定不會(huì)這么選,因?yàn)槌杀咎吡恕?br /> 他要這么做就相當(dāng)于他要實(shí)現(xiàn)一個(gè) JS 引擎,而其實(shí)里面很多內(nèi)容又是不必須的,一個(gè)v-for其實(shí)就能夠適用大部分情況了。
但有了v-for就需要v-if,那么后面還會(huì)需要其他各種能力,這就是一種方言的產(chǎn)生和發(fā)展的過(guò)程。
當(dāng)然指令也不僅僅是 JS 表達(dá)式的代替品,其本身也是增加了一些其他能力的,比如它能夠讓我們更方便地訪問(wèn) DOM 節(jié)點(diǎn),
但是嘛,我們用框架的理由不就是為了能夠盡可能的屏蔽 DOM 操作嘛~
總結(jié)
以上就是我對(duì)應(yīng)該選擇使用 JSX 還是 SFC 進(jìn)行開發(fā)的分析,其實(shí)歸根到底 SFC 的問(wèn)題在于其沒(méi)有擁抱 JS,
他的語(yǔ)法是自己發(fā)明的,他需要有一個(gè) JS 實(shí)現(xiàn)的 compiler 來(lái)讓其最終能在 JS 環(huán)境中運(yùn)行,這本質(zhì)上就是一種發(fā)明,
我們不能否認(rèn)發(fā)明確實(shí)有優(yōu)點(diǎn),但我們也不能只看有點(diǎn)不看問(wèn)題,沒(méi)能擁抱 JS 自然就很難完全復(fù)用 JS 社區(qū)的優(yōu)勢(shì)
而 JS 社區(qū)一直在蓬勃發(fā)展,好用的工具一直在涌現(xiàn),而 SFC 想要使用 JS 社區(qū)的這些工具還要自己再實(shí)現(xiàn)一份,我們可以細(xì)數(shù)以下 SFC 做了哪些兼容
- vue-loader 之于 webpack
- eslint-plugin-vue 之于 eslint
- rollup-plugin-vue 之于 rollup
- vue-jest 之于 jest
- Vetur 用來(lái)做代碼提醒
基本上常用的工具我們都需要等待 Vue 社區(qū)或者官方開發(fā)了插件之后才能運(yùn)行。而 JSX 因?yàn)橛?babel 和 typescript 的官方支持,
基本上所有新的 JS 生態(tài)工具原生都是支持的。
在這 Vue3 開始預(yù)備發(fā)力的階段,我們還是希望 Vue 社區(qū)能夠使用更優(yōu)秀更規(guī)范的方式來(lái)進(jìn)行開發(fā),
其實(shí)如果我們直接使用 JSX 開發(fā) Vue3,我們會(huì)發(fā)現(xiàn)很多時(shí)候我們都不需要用到emit、attrs這些概念,
甚至如果 Vue3 的 JSX 插件支持,我們甚至能夠拋棄slots。
但是因?yàn)?Vue3 一定要考慮兼容 Vue2,導(dǎo)致本身潛力很好的 Vue3 總是顯得縮手縮腳,這不得不說(shuō)是一種遺憾。
以上就是為什么推薦使用JSX開發(fā)Vue3的詳細(xì)內(nèi)容,更多關(guān)于用JSX開發(fā)Vue3的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue+echart?展示后端獲取的數(shù)據(jù)實(shí)現(xiàn)
本文主要介紹了Vue+echart?展示后端獲取的數(shù)據(jù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01關(guān)于Vue不能監(jiān)聽(watch)數(shù)組變化的解決方法
本文主要介紹了Vue不能監(jiān)聽(watch)數(shù)組變化的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09用Vue.js實(shí)現(xiàn)監(jiān)聽屬性的變化
響應(yīng)系統(tǒng)是Vue.js的一個(gè)顯著功能,修改屬性,可以更新視圖,這讓狀態(tài)管理變得非常簡(jiǎn)單且直觀。這篇文章主要給大家介紹如何利用Vue.js實(shí)現(xiàn)觀察屬性的變化,有需要的朋友們可以參考借鑒,感興趣的朋友們下面來(lái)一起看看吧。2016-11-11Vue中addEventListener()?監(jiān)聽事件案例講解
這篇文章主要介紹了Vue中addEventListener()?監(jiān)聽事件案例講解,包括語(yǔ)法講解和事件冒泡或事件捕獲的相關(guān)知識(shí),本文結(jié)合示例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下2022-12-12聊聊vue集成sweetalert2提示組件的問(wèn)題
這篇文章主要介紹了vue 集成 sweetalert2 提示組件的問(wèn)題,本文通過(guò)項(xiàng)目案例實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-11-11vue-router+vuex addRoutes實(shí)現(xiàn)路由動(dòng)態(tài)加載及菜單動(dòng)態(tài)加載
本篇文章主要介紹了vue-router+vuex addRoutes實(shí)現(xiàn)路由動(dòng)態(tài)加載及菜單動(dòng)態(tài)加載,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09