前端轉(zhuǎn)鴻蒙開發(fā)幾個(gè)比較難受的地方
從剛開始接觸鴻蒙開發(fā)時(shí),arkTS 版本還在 API 8,眨眼之間一年多時(shí)間過去了,現(xiàn)在已經(jīng)更新了到 API 12,API 12 對應(yīng)的是 harmonyOS Next 的 beta 版本。各方面的發(fā)展和之前的版本相比,都逐漸開始有了自己的特性,變得更加成熟。
雖然說,arkTS 是在 TypeScript 的基礎(chǔ)之上進(jìn)行的擴(kuò)展和改變,因此很多人都認(rèn)為,前端轉(zhuǎn)鴻蒙開發(fā)的成本非常低,但是發(fā)展到 API 12,還是有一些開發(fā)習(xí)慣逐漸與純粹的前端開發(fā)有了非常大的區(qū)別,上手難度也沒有想象中的那么低了。

這篇文章,結(jié)合我這一年多以來的鴻蒙應(yīng)用開發(fā)經(jīng)驗(yàn),給大家分享一下,鴻蒙開發(fā)與前端開發(fā)在編碼習(xí)慣上,我個(gè)人認(rèn)為幾個(gè)比較重要的差異。
一、更多的使用 class 來定義數(shù)據(jù)
在前端開發(fā)中,大多數(shù)時(shí)候,我們更習(xí)慣于忽略 class 語法的存在,因?yàn)槲覀兛梢噪S意的使用 {} 來創(chuàng)建一個(gè)對象就可以開始隨意使用了。如果需要類型,則額外使用 interface 來單獨(dú)定義即可。
interface Point {
x: number,
y: number
}
const p: Point = {
x: 1,
y: 2
}但是在 arkTS 中,隨意使用這種方式來創(chuàng)建對象,往往意味著不確定的類型風(fēng)險(xiǎn)。
例如,arkTS 嚴(yán)格禁止在運(yùn)行的過程中刪除對象中的某一個(gè)屬性。
delete p.x
因此,當(dāng)我們習(xí)慣了在 TS 中使用 interface + {} 來定義一個(gè)對象時(shí),在 arkTS 的應(yīng)用中經(jīng)常會遇到一些不支持的報(bào)錯(cuò)。例如使用字符串來訪問屬性值。

我們需要轉(zhuǎn)變思路,重新以面向?qū)ο蟮乃悸啡ヂ暶髅恳粋€(gè)對象。
class Point<T = number> {
x: T;
y: T;
constructor(x: T, y: T) {
this.x = x
this.y = y
}
}
const p = new Point(10, 20)這樣處理之后,我們就可以不需要把類型和值分開寫。這里需要注意的是,并不是我們需要全部放棄 {} 的寫法,而是在某些時(shí)候,需要限制 {} 用法的靈活性,從而提高底層引擎的解析性能。
這個(gè)思路的轉(zhuǎn)變對于部分前端開發(fā)來說可能比較困難。例如在嵌套數(shù)據(jù)時(shí),我們需要單獨(dú)為子數(shù)據(jù)聲明一個(gè) class 并 new 一個(gè)實(shí)例出來。
例如在我們需要深度監(jiān)聽某一個(gè)數(shù)據(jù)時(shí),就必須要明確聲明 class。
// 監(jiān)聽數(shù)據(jù)這一層
@State
private persons: Array<Person> = [
new Person('TOM', 20),
new Person('Jake', 22)
]// 監(jiān)聽到數(shù)組項(xiàng)元素的變化
@Observed
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}因此,總的來說,我們在 arkTS 中,會更加多的使用 class 來表達(dá)數(shù)據(jù)。如果你不喜歡它的話,可能會在開發(fā)中感覺到比較難受。
從我個(gè)人的角度來說,我也能接受這種方式,因?yàn)?class 自帶類型。但是一個(gè)比較難受的點(diǎn)時(shí),我們必須在構(gòu)造函數(shù)中明確表示創(chuàng)建函數(shù)時(shí)的初始化方式。{} 的寫法在 arkUI 中,更多的會應(yīng)用于參數(shù)的傳遞這種場景。例如:
interface PointPM<T = number> {
x: T;
y: T;
}
class Point<T = number> {
x: T;
y: T;
constructor(params: PointPM<T>) {
this.x = params.x
this.y = params.y
}
}
const p = new Point({x: 1, y: 2})?
通常情況下,這里定義的 PointPM 不會有其他動態(tài)的操作,僅作為函數(shù)的入?yún)ⅰ?/p>
二、不支持 any、unknown
一個(gè)可能會讓部分 TypeScript 基礎(chǔ)不扎實(shí)的同學(xué)感覺到很難受的點(diǎn),就是 arkTS 非常注重類型安全。因?yàn)楹?TS 不同,arkTS 的類型會直接參與運(yùn)行。因此,在這個(gè)前提之下,arkTS 直接不支持 any,unknown 這種的類型,在聲明時(shí),我們必須明確給出具體的類型。

這樣的話,對于前端開發(fā)來說,門檻就上來了一點(diǎn),因?yàn)檫€是有很大部分同學(xué)對 TS 的使用比較依賴 any,這就比較難受了。
三、許多常用能力遭到限制
例如:
不支持展開運(yùn)算符展開對象。
const p0: PointPM = {x: 1, y: 2}
const p = new Point(...p0)不支持結(jié)構(gòu)賦值。

const {x} = p0
說實(shí)話,用慣了解構(gòu),到這里不支持了,確實(shí)很難受。不過在面向?qū)ο笾械脑O(shè)想中,也確實(shí)需要用到解構(gòu)的地方非常少。
不支持映射類型。
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean
}
四、arkTS 特性再解讀
總之,一年多的開發(fā)經(jīng)驗(yàn)下來,遇到的之前很常用,但是在鴻蒙應(yīng)用開發(fā)中卻不支持的語法非常多。一篇文章肯定總結(jié)不完。但是我們可以總結(jié)出來一個(gè)非常明顯的發(fā)展特性,那就是限制 TS 的類型靈活性
由于 TS 是基于 JavaScript 發(fā)展而來,雖然在類型上面做了很多努力,但是由于需要在很多場景兼容和支持 JS 的類型靈活性,因此 TS 到現(xiàn)在為止,已經(jīng)發(fā)展成為了一個(gè)市面上,擁有最復(fù)雜類型系統(tǒng)的編程語言,它一方面擁有強(qiáng)大的類型推導(dǎo),另外一方面又兼顧了 JS 的類型靈活。因此,隨著經(jīng)驗(yàn)的積累,我們很容易慢慢開始寫出復(fù)雜的類型體操。
和 TypeScript 相比,arkTS 的發(fā)展目標(biāo)完全不一樣。在鴻蒙應(yīng)用的開發(fā)泛式中,arkTS 擁有獨(dú)立的編譯引擎,因此他完全不需要顧及 JS 的任何歷史包袱。因此,arkTS 可以輕裝上陣,把自己發(fā)展成為一門真正的、類型可預(yù)測的、類型安全的強(qiáng)類型語言。
因此,在語法設(shè)計(jì)上,arkTS 在 TS 的基礎(chǔ)之上做了非常多的減法,用以削弱類型靈活性。
基于這個(gè)判斷,我們可以很容易判斷出來哪些語法是不被支持的。例如,在普通函數(shù)中使用 this 就不會被支持。

在 js 的函數(shù)中,this 指向誰,是一個(gè)動態(tài)的屬性,誰調(diào)用這個(gè)函數(shù),那么在該函數(shù)上下文創(chuàng)建時(shí),this 的指向才會明確。這種不確定性,明顯違背了 arkTS 的發(fā)展目標(biāo)。
arkTS 這樣做的一個(gè)非常重要的好處,就是類型體操這個(gè)事情基本上不會有了。從另外一個(gè)角度來說,反而降低了復(fù)雜度。
五、總結(jié)
鴻蒙應(yīng)用開發(fā)使用 arkTS 作為編程語言,他雖然是在 TypeScript 的基礎(chǔ)之上發(fā)展而來,但是由于發(fā)展目標(biāo)不一樣,因此使用時(shí),對于前端開發(fā)而言,實(shí)際上還是有一定的適應(yīng)難度。因?yàn)閺?qiáng)類型在開發(fā)體驗(yàn)上肯定是有所犧牲的,當(dāng)數(shù)據(jù)類型特別復(fù)雜時(shí),處理起來要比 TS 麻煩很多。
一個(gè)最主要的區(qū)別就是,TS 不需要編譯通過,我們在開發(fā)環(huán)境中,依然會將 TS 打包成 JS 參與到程序的運(yùn)行中去,因此,就算是你的代碼存在大量的 TS 報(bào)錯(cuò),但是你的程序有可能依然可以正常運(yùn)行而且不會出現(xiàn)一點(diǎn)問題。
但是 arkTS 有自己的編譯器,我們寫的代碼會直接參與運(yùn)行,因此,任何語法報(bào)錯(cuò)都無法通過編譯,程序也無法正常運(yùn)行。
大家不要小看這個(gè)區(qū)別。這個(gè)區(qū)別的差異會導(dǎo)致在生態(tài)上面,arkTS 的發(fā)展會被 TS 要正常很多。因?yàn)?TS 程序是可以在報(bào)錯(cuò)的情況下依然正常執(zhí)行的,于是,例如我封裝一個(gè)函數(shù)
function add(p: number) {
return p + 1
}此時(shí),當(dāng)我不按照類型約定傳入 number,而是直接傳入非法字符串。此時(shí) TS 肯定會報(bào)錯(cuò),但是在一些不規(guī)范不嚴(yán)謹(jǐn)?shù)氖褂谜呖磥恚@種報(bào)錯(cuò)是可以被接受的,他可能就不會去在意這個(gè)報(bào)錯(cuò)。
也就是說,TS 報(bào)錯(cuò)失去了他應(yīng)該具備的威懾力。
因此,這個(gè)時(shí)候就會發(fā)生一種很難受的事情,那就是作為封裝者預(yù)知了這種風(fēng)險(xiǎn):有的不規(guī)范的使用者無視 TS 的報(bào)錯(cuò)繼續(xù)使用,就會導(dǎo)致潛在的 bug 出現(xiàn)。所以封裝者就需要在封裝 add 函數(shù)時(shí),對其他的意外類型做一個(gè)兜底,從而支持更多的類型傳入。讓這個(gè)函數(shù)的封裝平白變得更加復(fù)雜。
因此我們作為使用者有的時(shí)候會發(fā)現(xiàn),一些開源庫的類型入?yún)榱酥С指嗟念愋投兊锰貏e復(fù)雜,很多都讀不懂。從而又從另外一個(gè)角度加劇了 TS 的使用成本。普通開發(fā)者想要解決掉所有的 TS 類型報(bào)錯(cuò)可能是一件工作量非常大的事情,從而進(jìn)一步加劇了他們接受項(xiàng)目代碼中存在報(bào)錯(cuò),陷入一個(gè)惡性循環(huán)。
從這個(gè)角度來說,arkTS 的生態(tài)發(fā)展會更加正常一些。相關(guān)的使用成本也會比 TS 要低很多。
arkTS 對前端開發(fā)的啟示
實(shí)際上,在團(tuán)隊(duì)范圍的可控范圍以內(nèi),不管是作為個(gè)人還是作為項(xiàng)目 Leader,我們都可以借鑒 arkTS 的這個(gè)強(qiáng)類型的思路去制定我們的團(tuán)隊(duì)規(guī)范,從團(tuán)隊(duì)規(guī)范的角度,主動犧牲掉一些 TS 的能力,從而降低 TS 的使用難度和推廣難度。
























