偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

從前端視角看 SwiftUI

開發(fā) 前端
從 UI 的角度來看,前端與手機開發(fā)會遇到問題是類似的,盡管使用的語言或是開發(fā)手法不盡相同,我們都需要打造一個易用的使用者介面。

前言

我對 iOS 開發(fā)、手機開發(fā)、SwiftUI 開發(fā)經(jīng)驗有限,若有理解錯誤的部分歡迎指正。

從 UI 的角度來看,前端與手機開發(fā)會遇到問題是類似的,盡管使用的語言或是開發(fā)手法不盡相同,我們都需要打造一個易用的使用者介面。既然如此,彼此也會遇到類似的問題,元件化開發(fā)、狀態(tài)管理、資料流、管理副作用(API 或是IO)等等,對我來說是個很適合互相學(xué)習(xí)的領(lǐng)域。

從過往的經(jīng)驗可以發(fā)現(xiàn),像是 ReSwift[1](Redux 的中心思想)這樣的函式庫,或多或少也借鑒了前端不斷演進的開發(fā)手法,不難看出雙方會遇到的問題其實有類似的地方。

雖然這個時間點提起已經(jīng)有點后話了,但還是想把我入門 SwiftUI 后的感想寫下。

SwiftUI 與 React 的類似之處

我們可以將前端框架歸納為幾個要素:

  • 元件化
  • 響應(yīng)式機制
  • 狀態(tài)管理
  • 事件監(jiān)聽
  • 生命周期

在下面的段落中,我們也會以這幾個主題為核心做討論。為了不模糊焦點,我會盡可能只用 React 當做舉例,但套用到其他前端框架原理應(yīng)該也相同。

從 class 邁向 struct;從 class 邁向 function

在寫 SwiftUI 的時候總是讓我想到 React 的發(fā)展史。最初 React 建立元件的方式是透過 JavaScript 的 class 語法,每個 React 的元件都是一個類別。


class MyComponent extends React.Component {
constructor() {
this.state = {
name: 'kalan'
}
}

componentDidMount() {
console.log('component is mounted')
}

render() {
return <div>my name is {this.state.name}</div>
}
}

透過類別定義元件雖為前端元件化帶來了很大的影響,但也因為繁瑣的方法定義與 this 混淆,在 React16 hooks 出現(xiàn)之后,逐漸提倡使用 function component 與 hooks 的方式來建立元件。

省去了繼承與各種 OO 的花式設(shè)計模式,建構(gòu)元件的心智負擔(dān)變得更小了。從 SwiftUI 當中我們也可以看到類似的演進,原本 ViewController 龐大的 class 以及職責(zé),要負責(zé) view 與 model 的互動,掌管生命周期,轉(zhuǎn)為更輕量的 struct,讓開發(fā)者可以更專注在 UI 互動上,減輕認知負擔(dān)。

元件狀態(tài)管理

React 16 采取了 hooks 來做元件的邏輯復(fù)用與狀態(tài)管理,例如 useState。

const MyComponent = () => {
const [name, setName] = useState({ name: 'kalan' })
useEffect(() => { console.log('component is mounted') }, [])

return <div>my name is {name}</div>
}

在 SwiftUI 當中,我們可以透過修飾符 @State 讓 View 也具有類似效果。兩者都具備響應(yīng)式機制,當狀態(tài)變數(shù)發(fā)生改變時,React/Vue 會偵測改變并反映到畫面當中。雖然不知道 SwiftUI 背后的實作,但背后應(yīng)該也有類似 diff 機制的東西來達到響應(yīng)式機制與最小更新的效果。

然而 SwiftUI 的狀態(tài)管理與 React hooks 仍有差異。在 React 當中我們可以將 hook 拆成獨立的函數(shù),并且在不同的元件當中使用,例如:

function useToggle(initialValue) {
const [toggle, set] = useState(initialValue)
const setToggle = useCallback(() => { set((state) => !state) }, [toggle])
useEffect(() => { console.log('toggle is set') }, [toggle])
return [toggle, setToggle]
}
const MyComponent = () => {
const [toggle, setToggle] = useToggle(false)
return <button onClick={() => setToggle()}>Click me</button>
}
const MyToggle = () => {
const [toggle, setToggle] = useToggle(true)
return <button onClick={() => setToggle()}>Toggle, but fancy one</button>
}

在 React 當中,我們可以將 toggle 的邏輯拆出,并在不同元件之間使用。由于 useToggle 是一個純函數(shù),因此內(nèi)部的狀態(tài)也不會互相影響。

然而在 SwiftUI 當中 @State 只能作用在 struct 的 private var 當中,不能進一步拆出。如果想要將重復(fù)的邏輯抽出,需要另外使用 @Observable 與 @StateObject 這樣的修飾符,另外建立一個類別來處理。

class ToggleUtil: ObservableObject {
@Published var toggle = false

func setToggle() {
self.toggle = !self.toggle
}
}
struct ContentView: View {
@StateObject var toggleUtil = ToggleUtil()
var body: some View {
Button("Text") {
toggleUtil.setToggle()
}
if toggleUtil.toggle {
Text("Show me!")
}
}
}

在這個例子當中把 toggle 的邏輯拆成一個 class 似乎有點小題大作了,不過仔細想想像 React 提供的 hook 功能,讓輕量的邏輯共用就算單獨拆成 hook 也不會覺得過于冗長,若要封裝更復(fù)雜的邏輯也可以再拆分成更多 hooks,從這點來看 hook 的確是一個相當優(yōu)秀的機制。后來看到了 SwiftUI-Hooks[2],不知道實際使用的效果如何。

以 React 來說,在還沒有出現(xiàn) hooks 之前,主要有三個方式來實作邏輯共用:

  • HOC(Higher Order Component)[3]:將共同邏輯包裝成函數(shù)后返回全新的 class,避免直接修改元件內(nèi)部的實作。例如早期 react-redux 中的 connect。
  • render props[4]:將實際渲染的元件當作屬性(props)傳入,并提供必要的參數(shù)供實作端使用。
  • children function:children 只傳入必要的參數(shù),由實作端自行決定要渲染的元件。

盡管 hooks 用更優(yōu)雅的方式解決邏輯共用的問題,我認為上面的開發(fā)手法演變也很值得參考。

Redux 與 TCA

受到 Redux 的影響,在 Swift 當中也有部分開發(fā)者使用了采用了類似手法,甚至也有相對應(yīng)的實作 ReSwift的說明文。從說明文可以看到主要原因。傳統(tǒng)的 ViewController 職責(zé)曖昧,容易變得肥大導(dǎo)致難以維護,透過 Reducer、Action、Store 訂閱來確保單向資料流,所有的操作都是向 store dispatch 一個action,而資料的改動(mutation)則在 reducer 處理。

而最近的趨勢似乎從 Redux 演變成了 TCA(The Composable Architecture),跟 Redux 的中心思想類似,更容易與 SwiftUI 整合,比較不一樣的地方在于以往涉及 side effect 的操作在 Redux 當中會統(tǒng)一由 middleware 處理,而在 TCA 的架構(gòu)中 reducer 可以回傳一個 Effect,代表接收 action 時所要執(zhí)行的 IO 操作或是 API 呼叫。

既然采用了類似 redux 的手法,不知道 SwiftUI 是否會遇到與前端開發(fā)類似的問題,例如 immutability 確保更新可以被感知;透過優(yōu)化 subscribe 機制確保 store 更新時只有對應(yīng)的元件會更新;reducer 與 action 帶來的 boilerplate 問題。

雖然 Redux 在前端仍然具有一定地位,也仍然有許多公司正在導(dǎo)入,然而在前端也越來越多棄用 Redux 的聲音,主要因為 redux 對 pure function 的追求以及 reducer、action 的重復(fù)性極高,在應(yīng)用沒有到一定復(fù)雜程度之前很難看出帶來的好處,甚至連 Redux 作者本人也開始棄坑 redux 了 4。與此同時,react-redux 仍然有在持續(xù)更新,也推出了 redux-toolkit 來試圖解決導(dǎo)入 redux 時常見的問題。

取而代之的是更加輕量的狀態(tài)管理機制,在前端也衍生出了幾個流派:

  • GraphQL → 使用 apollo[5] 或是 relay[6]
  • react-query[7]
  • react-swr[8]
  • recoil[9]
  • jotai[10]

全域狀態(tài)管理

在全域狀態(tài)管理上,SwiftUI 也有內(nèi)建機制叫做 @EnvrionmentObject,其運作機制很像 React 的 context,讓元件可以跨階層存取變數(shù),當 context 改變時也會更新元件。

class User: ObservableObject {
@Published var name = "kalan"
@Published var age = 20
}
struct UserInfo: View {
@EnvironmentObject var user: User
var body: some View {
Text(user.name)
Text(String(user.age))
}
}
struct ContentView: View {
var body: some View {
UserInfo()
}
}
ContentView().envrionmentObject(User())

從上面這個范例可以發(fā)現(xiàn),我們不需要另外傳入 user 給 UserInfo,透過 @EnvrionmentObject 可以拿到當前的 context。轉(zhuǎn)換成 React 的話會像這樣:

const userContext = createContext({})
const UserInfo = () => {
const { name, age } = useContext(userContext)
return <>
<p>{name}</p>
<p>{age}</p>
</>
}
const App = () => {
<userContext.Provider value={{ user: 'kalan', age: 20 }}>
<UserInfo />
</userContext.Provider>
}

React 的 context 可讓元件跨階層存取變數(shù),當 context 改變時也會更新元件。雖然有效避免了 prop drilling 的問題,然而 context 的存在會讓測試比較麻煩一些,因為使用 context 時代表了某種程度的耦合。

響應(yīng)機制

在 React 當中,狀態(tài)或是 props 有變動時都會觸發(fā)元件更新,透過框架實作的 diff 機制比較后反映到畫面上。在 SwfitUI 中也可以看到類似的機制:

struct MyView: View {
var name: String
@State private var isHidden = false

var body: some View {
Toggle(isOn: $isHidden) {
Text("Hidden")
}
Text("Hello world")

if !isHidden {
Text("Show me \(name)")
}
}
}

一個典型的 SwiftUI 元件是一個 struct,透過定義 body 變數(shù)來決定 UI。跟 React 相同,他們都只是對 UI 的抽象描述,透過比對資料結(jié)構(gòu)計算最小差異后,再更新到畫面上。

我還蠻想了解 SwiftUI 背后是怎么計算 diff 的,希望之后有類似的文章出現(xiàn)

@State 修飾符可用來定義元件內(nèi)部狀態(tài),當狀態(tài)改變時會更新并反映到畫面中。

在 SwiftUI 當中,屬性(MyView 當中的 name)可以由外部傳入,跟 React 當中的屬性(props)類似。

// 在其他 View 當中使用 MyView
struct ContentView: View {
var body: some View {
MyView(name: "kalan")
}
}

用 React 改寫這個元件的話會像這樣:

const MyView = ({ name }) => {
const [isHidden, setIsHidden] = useState(false)
return <div>
<button onClick={() => setIsHidden(state => !state)}>hidden</button>
<p>Hello world</p>
{isHidden ? null : `show me ${name}`}
</div>
}

在撰寫 SwiftUI 時會發(fā)現(xiàn)這跟以往用 UIKit、UIController 的開發(fā)方式不太一樣。

列表

SwiftUI 與 React 當中都可以渲染列表,而撰寫的方式也有雷同之處。在 SwiftUI 當中可以這樣寫:

struct TextListView: View {
var body: some View {
List {
ForEach([
"iPhone",
"Android",
"Mac"
], id: \.self) { value in
Text(value)
}
}
}
}

轉(zhuǎn)成 React 大概會像這樣子:

const TextList = () => {
const list = ['iPhone', 'Android', 'Mac']

return list.map(item => <p key={item}>{item}</p>)
}

在渲染列表時為了確保效能,減少不必要的比對,React 會要求開發(fā)者提供 key,而在 SwiftUI 當中也有類似的機制,開發(fā)者必須使用叫做 Identifiable[11] 的 protocol,或是顯式地傳入 id。

Binding

除了將變數(shù)綁定到畫面之外,我們也可以將互動綁定到變數(shù)之中。例如在 SwiftUI 當中我們可以這樣寫:

struct MyInput: View {
@State private var text = ""
var body: some View {
TextField("Please type something", text: $text)
}
}

在這個范例當中,就算不監(jiān)聽輸入事件,使用 $text 也可以直接改變 text 變數(shù),當使用 @State 時會加入 property wrapper,會自動加入一個前綴 $,型別為 Binding[12]。

React 并沒有雙向綁定機制,必須要顯式監(jiān)聽輸入事件確保單向資料流。不過像 Vue、Svelte 都有雙向綁定機制,節(jié)省開發(fā)者手動監(jiān)聽事件的成本。

Combine 的出現(xiàn)

雖然我對 Combine 還不夠熟悉,但從官方文件與影片看起來,很像RxJS 的 Swift 特化版,提供的 API 與操作符大幅度地簡化了復(fù)雜資料流。這讓我想起了以前研究 RxJS 與 redux-observable 各種花式操作的時光,真令人懷念。

本質(zhì)上的差異

前面提到那么多,然而網(wǎng)頁與手機開發(fā)仍然有相當大的差異,其中對我來說最顯著的一點是靜態(tài)編譯與動態(tài)執(zhí)行。動態(tài)執(zhí)行可以說是網(wǎng)頁最大的特色之一。

只要有瀏覽器,JavaScript、HTML、CSS,不管在任何裝置上都可以成功執(zhí)行,網(wǎng)頁不需要事先下載 1xMB ~ 幾百 MB 的內(nèi)容,可以動態(tài)執(zhí)行腳本,根據(jù)瀏覽的頁面動態(tài)載入內(nèi)容。

由于不需要事先編譯,任何人都可以看到網(wǎng)頁的內(nèi)容與執(zhí)行腳本,加上 HTML 可以 streaming 的特性,可以一邊渲染一邊讀取內(nèi)容。難能可貴的一點是,網(wǎng)頁是去中心化的,只要有伺服器、ip 位址與網(wǎng)域,任何人都可以存取網(wǎng)站內(nèi)容;而 App 如果要上架必須事先通過審查。

不過兩者的生態(tài)圈與開發(fā)手法有很大的不同,仍然建議參考一下彼此的發(fā)展,就算平時不會碰也沒關(guān)系,從不同的角度看往往可以發(fā)現(xiàn)不同的事情,也可以培養(yǎng)對技術(shù)的敏銳度。

參考資料

[1] ReSwift: https://github.com/ReSwift/ReSwift

[2] SwiftUI-Hooks: https://github.com/ra1028/SwiftUI-Hooks

[3] HOC(Higher Order Component): https://zh-hant.reactjs.org/docs/higher-order-components.html

[4] render props: https://zh-hant.reactjs.org/docs/render-props.html

[5] apollo: https://github.com/apollographql/apollo-client

[6] relay: https://relay.dev/

[7] react-query: https://react-query.tanstack.com/

[8] react-swr: https://swr.vercel.app/zh-CN

[9] recoil: https://recoiljs.org/

[10] jotai: https://github.com/pmndrs/jotai

[11] Identifiable: https://developer.apple.com/documentation/swift/identifiable

[12] Binding: https://developer.apple.com/documentation/swiftui/binding

責(zé)任編輯:姜華 來源: Swift社區(qū)
相關(guān)推薦

2022-11-01 09:02:04

前端售后業(yè)務(wù)

2024-02-28 08:38:07

Rust前端效率

2023-03-31 09:02:37

前端客服通信

2011-01-21 17:09:06

Zimbra

2021-05-07 10:25:04

技術(shù)開發(fā)低代碼無代碼

2024-02-27 13:03:38

前端視頻合成FFmpeg

2024-06-18 13:36:29

2025-03-26 09:41:19

2024-01-08 20:05:32

2017-10-27 15:48:06

JavaScript前端全端

2021-03-15 06:24:22

Nacos集群搭建微服務(wù)

2022-01-13 10:19:34

軟件汽車 技術(shù)

2021-05-07 09:00:02

Go項目標準

2011-12-29 09:24:54

iOS應(yīng)用下載排行榜

2021-03-07 17:17:07

Java內(nèi)存閉包

2019-05-07 09:17:51

AWS西云數(shù)據(jù)IaaS

2011-07-15 15:18:06

微博Twitter

2020-05-13 08:48:16

JavaScript前端技術(shù)

2020-11-11 09:19:37

前端優(yōu)化面試

2021-07-30 19:07:27

大數(shù)據(jù)云計算云原生化
點贊
收藏

51CTO技術(shù)棧公眾號