前端轉鴻蒙開發幾個比較難受的地方
從剛開始接觸鴻蒙開發時,arkTS 版本還在 API 8,眨眼之間一年多時間過去了,現在已經更新了到 API 12,API 12 對應的是 harmonyOS Next 的 beta 版本。各方面的發展和之前的版本相比,都逐漸開始有了自己的特性,變得更加成熟。
雖然說,arkTS 是在 TypeScript 的基礎之上進行的擴展和改變,因此很多人都認為,前端轉鴻蒙開發的成本非常低,但是發展到 API 12,還是有一些開發習慣逐漸與純粹的前端開發有了非常大的區別,上手難度也沒有想象中的那么低了。
這篇文章,結合我這一年多以來的鴻蒙應用開發經驗,給大家分享一下,鴻蒙開發與前端開發在編碼習慣上,我個人認為幾個比較重要的差異。
一、更多的使用 class 來定義數據
在前端開發中,大多數時候,我們更習慣于忽略 class 語法的存在,因為我們可以隨意的使用 {} 來創建一個對象就可以開始隨意使用了。如果需要類型,則額外使用 interface 來單獨定義即可。
interface Point {
x: number,
y: number
}
const p: Point = {
x: 1,
y: 2
}
但是在 arkTS 中,隨意使用這種方式來創建對象,往往意味著不確定的類型風險。
例如,arkTS 嚴格禁止在運行的過程中刪除對象中的某一個屬性。
delete p.x
因此,當我們習慣了在 TS 中使用 interface + {} 來定義一個對象時,在 arkTS 的應用中經常會遇到一些不支持的報錯。例如使用字符串來訪問屬性值。
我們需要轉變思路,重新以面向對象的思路去聲明每一個對象。
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)
這樣處理之后,我們就可以不需要把類型和值分開寫。這里需要注意的是,并不是我們需要全部放棄 {} 的寫法,而是在某些時候,需要限制 {} 用法的靈活性,從而提高底層引擎的解析性能。
這個思路的轉變對于部分前端開發來說可能比較困難。例如在嵌套數據時,我們需要單獨為子數據聲明一個 class 并 new 一個實例出來。
例如在我們需要深度監聽某一個數據時,就必須要明確聲明 class。
// 監聽數據這一層
@State
private persons: Array<Person> = [
new Person('TOM', 20),
new Person('Jake', 22)
]
// 監聽到數組項元素的變化
@Observed
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
因此,總的來說,我們在 arkTS 中,會更加多的使用 class 來表達數據。如果你不喜歡它的話,可能會在開發中感覺到比較難受。
從我個人的角度來說,我也能接受這種方式,因為 class 自帶類型。但是一個比較難受的點時,我們必須在構造函數中明確表示創建函數時的初始化方式。{} 的寫法在 arkUI 中,更多的會應用于參數的傳遞這種場景。例如:
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 不會有其他動態的操作,僅作為函數的入參。
二、不支持 any、unknown
一個可能會讓部分 TypeScript 基礎不扎實的同學感覺到很難受的點,就是 arkTS 非常注重類型安全。因為和 TS 不同,arkTS 的類型會直接參與運行。因此,在這個前提之下,arkTS 直接不支持 any,unknown 這種的類型,在聲明時,我們必須明確給出具體的類型。
這樣的話,對于前端開發來說,門檻就上來了一點,因為還是有很大部分同學對 TS 的使用比較依賴 any,這就比較難受了。
三、許多常用能力遭到限制
例如:
不支持展開運算符展開對象。
const p0: PointPM = {x: 1, y: 2}
const p = new Point(...p0)
不支持結構賦值。
const {x} = p0
說實話,用慣了解構,到這里不支持了,確實很難受。不過在面向對象中的設想中,也確實需要用到解構的地方非常少。
不支持映射類型。
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean
}
四、arkTS 特性再解讀
總之,一年多的開發經驗下來,遇到的之前很常用,但是在鴻蒙應用開發中卻不支持的語法非常多。一篇文章肯定總結不完。但是我們可以總結出來一個非常明顯的發展特性,那就是限制 TS 的類型靈活性
由于 TS 是基于 JavaScript 發展而來,雖然在類型上面做了很多努力,但是由于需要在很多場景兼容和支持 JS 的類型靈活性,因此 TS 到現在為止,已經發展成為了一個市面上,擁有最復雜類型系統的編程語言,它一方面擁有強大的類型推導,另外一方面又兼顧了 JS 的類型靈活。因此,隨著經驗的積累,我們很容易慢慢開始寫出復雜的類型體操。
和 TypeScript 相比,arkTS 的發展目標完全不一樣。在鴻蒙應用的開發泛式中,arkTS 擁有獨立的編譯引擎,因此他完全不需要顧及 JS 的任何歷史包袱。因此,arkTS 可以輕裝上陣,把自己發展成為一門真正的、類型可預測的、類型安全的強類型語言。
因此,在語法設計上,arkTS 在 TS 的基礎之上做了非常多的減法,用以削弱類型靈活性。
基于這個判斷,我們可以很容易判斷出來哪些語法是不被支持的。例如,在普通函數中使用 this 就不會被支持。
在 js 的函數中,this 指向誰,是一個動態的屬性,誰調用這個函數,那么在該函數上下文創建時,this
的指向才會明確。這種不確定性,明顯違背了 arkTS 的發展目標。
arkTS 這樣做的一個非常重要的好處,就是類型體操這個事情基本上不會有了。從另外一個角度來說,反而降低了復雜度。
五、總結
鴻蒙應用開發使用 arkTS 作為編程語言,他雖然是在 TypeScript 的基礎之上發展而來,但是由于發展目標不一樣,因此使用時,對于前端開發而言,實際上還是有一定的適應難度。因為強類型在開發體驗上肯定是有所犧牲的,當數據類型特別復雜時,處理起來要比 TS 麻煩很多。
一個最主要的區別就是,TS 不需要編譯通過,我們在開發環境中,依然會將 TS 打包成 JS 參與到程序的運行中去,因此,就算是你的代碼存在大量的 TS 報錯,但是你的程序有可能依然可以正常運行而且不會出現一點問題。
但是 arkTS 有自己的編譯器,我們寫的代碼會直接參與運行,因此,任何語法報錯都無法通過編譯,程序也無法正常運行。
大家不要小看這個區別。這個區別的差異會導致在生態上面,arkTS 的發展會被 TS 要正常很多。因為 TS 程序是可以在報錯的情況下依然正常執行的,于是,例如我封裝一個函數
function add(p: number) {
return p + 1
}
此時,當我不按照類型約定傳入 number,而是直接傳入非法字符串。此時 TS 肯定會報錯,但是在一些不規范不嚴謹的使用者看來,這種報錯是可以被接受的,他可能就不會去在意這個報錯。
也就是說,TS 報錯失去了他應該具備的威懾力。
因此,這個時候就會發生一種很難受的事情,那就是作為封裝者預知了這種風險:有的不規范的使用者無視 TS 的報錯繼續使用,就會導致潛在的 bug 出現。所以封裝者就需要在封裝 add 函數時,對其他的意外類型做一個兜底,從而支持更多的類型傳入。讓這個函數的封裝平白變得更加復雜。
因此我們作為使用者有的時候會發現,一些開源庫的類型入參為了支持更多的類型而變得特別復雜,很多都讀不懂。從而又從另外一個角度加劇了 TS 的使用成本。普通開發者想要解決掉所有的 TS 類型報錯可能是一件工作量非常大的事情,從而進一步加劇了他們接受項目代碼中存在報錯,陷入一個惡性循環。
從這個角度來說,arkTS 的生態發展會更加正常一些。相關的使用成本也會比 TS 要低很多。
arkTS 對前端開發的啟示
實際上,在團隊范圍的可控范圍以內,不管是作為個人還是作為項目 Leader,我們都可以借鑒 arkTS 的這個強類型的思路去制定我們的團隊規范,從團隊規范的角度,主動犧牲掉一些 TS 的能力,從而降低 TS 的使用難度和推廣難度。