-
Notifications
You must be signed in to change notification settings - Fork 0
/
1-6-6.html
232 lines (231 loc) · 12.9 KB
/
1-6-6.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="./public/favicon.ico" />
<meta http-equiv="cache-control" content="no-cache" />
<title></title>
<link rel="stylesheet" href=" https://necolas.github.io/normalize.css/8.0.1/normalize.css" />
<link rel="stylesheet" href="./hightlight/default.min.css" />
<link rel="stylesheet" href="./css/main.css" />
<link rel="stylesheet" href="./css/copybutton.css" />
<link rel="stylesheet" href="./css/hightlight.css" />
<script src="./hightlight/hightlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BEVZJDBC7Z"></script>
<script src="./js/gtag.js"></script>
</head>
<body>
<header>
<nav>
<h1>
<span id="toggle-menu"></span>
<a href="index.html"></a>
</h1>
</nav>
</header>
<main>
<aside>
<nav></nav>
</aside>
<article>
<h2 id="1-6-6">1-6-6 前端架構類別庫之間的比較</h2>
<p>
由於 React和 Vue 已經形成了各自的生態,不再是單純的視圖層類別庫,因此下面將 React 和 Vue
統稱為「架構」。作為開發者,可以首選 React 或 Vue 中的來用。
以下將兩者從5大方面進行比較:資料綁定、元件化和資料流程、資料狀態管理、繪製和更新、社區。
</p>
<h4>資料綁定</h4>
<p>
Vue 在資料綁定上採取了雙向綁定策略,依靠 Object.defineProperty (Vue 3.0已遷移到
Proxy)及監聽 DOM
事件實現。實作方式方法已經在前面章節剖析過了,簡單來說就是,為了監聽資料的改變,需要對資料進行攔截/代理;對於監聽視圖的改變,需要對DOM
事件(如 onInput、 onChange等) 進行監聽。Vue 實例中的data
和範本展現在一條線上,無論誰被修改,另一方都會發生變動。
</p>
<p>
Vue 也支援計算屬性,即 computed 屬性,這個策略和 React-redux 中的 mapStateToProps
有異曲同工之妙,都是透過計算將所需要的資料植入給元件使用。
</p>
<p>
需要區分清楚的是,雙向綁定和單向資料流程並沒有直接連結,雙向綁定是指數據和視圖之間的綁定關係,而單向資料流程是指元件之間的資料傳遞。
React
中並沒有資料和視圖之間的雙向綁定,它的策略是「局部更新」。當資料發生變化時,直接重新繪製元件,便可以獲得最新的視圖。這種「無腦」更新的做法看似粗暴,卻換來了簡單直觀的效果:當應用中的資料發生變化時,只需要更新即可,而且架構能夠對這種更新在效能上提供一定的保障。
</p>
<h4>元件化和資料流程</h4>
<p>
Vue 元件不像 React元件,它不是完全以元件功能和UI 為維度進行劃分的,Vue 元件本質上是一個Vue
實例。每個Vue 實例在建立時都需要經過以下幾個步驟:設定資料監聽,編譯範本,將範本應用到 DOM
上,而且要在更新時根據資料變化更新 DOM 的過程。在這個過程中,Vue 也像
React一樣提供了類似元件的生命週期方法。
</p>
<p>
Vue 元件間通訊,或說元件間資料流程,同 React
一樣,也是單向的。它們在資料流向上也很類似,即透過 props實現父元件向下傳遞資料,Vue 基於
events 實現子元件向上發送訊息給父元件。React 以 props
為基礎的回呼來實現子元件向父元件傳遞資料(Vue 也支援)。
</p>
<p>
讓兩種架構也分別透過 context 和 provide/inject
實現了跨層級資料通訊,它們在這方面的實現也是非常類似的。
</p>
<h4>資料狀態管理</h4>
<p>
對於較為複雜的資料狀態,Redux 是 React
應用程式開發中最常用的解決方案。這裡需要說明的是,Redux
和視圖無關,它只是提供了資料狀態管理的流程,因此在 Vue 應用程式開發中使用 Redux
也是完全沒有問題的。
</p>
<p>
當然,在 Vue 應用程式開發中更常用的是 Vuex,其參考了 Redux,具有和 Redux 相似的 store
概念,不允許元件直接修改 store state,而是需要使用 dispatch action 來通知 store
的變化。這個過程不同於 Redux 的函數式思想,其中的差別是,Vuex 改變 store
的方法支援提交一個 mutation,而Redux 並不支援。mutation 類似事件發佈/訂閱系統:每個
mutation
都透過一個字串來表示事件類型(type),並透過一個回呼函數(handler)來進行對應的資料修改。另一個顯著的區別是,在
Vuex 中,store 是被直接植入元件實例中的,因此用起來更加方便;而 Redux 需要透過 connect
方法把 prop 和 dispatch 植入元件中。
</p>
<p>造成這些不同的本質原因可能有以下2個。</p>
<ul>
<li>
Redux 建議不可變性,而 Vuex 的資料是可變的,Redux 中的 reducer 每次都會產生新的 state
以替代舊的 state,而 Vuex 是直接修改。
</li>
<li>
Redux 在檢測資料變化時,是透過淺比較的方式比較差異的,而 Vuex 其實和 Vue
的原理一樣,是透過檢查資料的 getter / setter 來比較的。
</li>
</ul>
<h4>繪製和更新</h4>
<p>
React 和 Redux 宣導不可變性,更新需要維持不可變原則;而 Vue
對資料進行了攔截/代理,因此它不要求維持不可變性,而允許開發者修改資料,以引起響應式更新。
</p>
<p>
React 更像 MVC 或 MVVM 模式中的 View 層,但是與 Redux 等搭配後,它也是一個完整的 MVVM
類別庫。Vue 直接是 MVVM 模式的典型表現,雖然它一直標榜自己也只是 View
層,但是毫無疑問它本身包含了對資料的操作。舉例來說,Vue
文件中經常會使用VM(ViewModel簡稱),這個變數名稱表示 Vue 實例,其命名會讓人想到
MVVM,這是 MVVM 模式的表現。
</p>
<p>
React 所有元件的繪製都依靠靈活且強大的 JSX。JSX 並不是一種範本語言,而是 JavaScript
運算式和函數呼叫的語法糖。在編譯之後,JSX 被轉化為普通的 JavaScript 物件,用來表示虛擬
DOM。
</p>
<p>
Vue template 是典型的範本,比 JSX 在表達上更加自然。在底層實現上,Vue 範本被編譯成 DOM
繪製函數,結合回應系統,進行資料依賴的收集。Vue 繪製的過程如下。
</p>
<ul>
<li>透過 new Vue 敘述產生實體 Vue 物件。</li>
<li>
掛載 $mount 方法,透過自訂 Render 方法、template、el 等產生 Render 函數,準備繪製內容。
</li>
<li>透過 Watcher 進行依賴收集。</li>
<li>當資料發生變化時,執行 Render 函數並產生 VNode 物件。</li>
<li>
透過 patch 方法,比較新舊 VNode 物件,透過 DOM diff
演算法增加、修改、刪除真正的DOM元素。
</li>
</ul>
<p>當然,Vue 也可以支援 JSX。</p>
<p>
關於更新時的效能問題,簡單來説,在 React
應用中,営某個元件的狀態發生變化時,它就會以該元件為根,重新繪製整個元件子樹。當然,我們可以使用
PureComponent,或是手動實現 shouldComponentUpdate
方法,來避開不必要的繪製。但是,這個實現過程要知悉資料狀態結構,會產生一定的額外負擔,舉例來説,要進行精細的値比較等。
</p>
<p>
在 Vue
應用中,元件的依賴是在繪製過程中自動追蹤的,因此系統能精確地知曉哪個元件需要被重繪製。從理論上看,Vue
的繪製更新機制更加細粒度,也更加精確。
</p>
<h4>社區</h4>
<p>
這兩個架構都具有非常強大的社區,但是在社區理念方面,Vue 和 React
稍有不同。以路由系統的實現為例,Vue
的路由函數庫和狀態管理函數庫都是主官方維護的,並且與核心函數庫是同步更新的;而
React把這件事情交給了社區,舉例來說,在 React 應用中需要引用 react-router
函數庫來實現路由系統。
</p>
<h3>新版本發佈的思考</h3>
<p>
開發者可以從架構新版本的疊代 changelog
中汲取非常多的養分。因為每次發版都是經過開放原始碼架構的維護團隊精心設計的,更新點或有關
bug
修復或有關新的特性,所以我們可以透過思考「為什麼會有這些變動」、「為什麼這樣解決bug」等問題來學習其中的設計理念。
除此之外,建議開發者從更高的層次「開啟上帝角度」,抓住某一個話題、某一次變更進行研究、學習。這裡以
Vue 3.0 帶來的一些思考為例。
</p>
<p>
尤雨溪在上海的 VueConf 上進行分享,演講主題為 State of
Vue,其中有關新版本發佈。從這次新版本發佈對 Vue
的改動內容來看,找到很多切入點進行分析,如使用 Proxy 實現資料代理、重構虛擬 DOM。
</p>
<p>
尤雨溪表示,Vue 3.0 與以往的版本相比更加快速。那麼「更加快速」在 Vue 3.0中,Vue 團隊就是用
Proxy 代替 Object.defineProperty 來達到更好的效能保障的。
</p>
<p>
除此之外,Vue 新版本還重構了虛擬 DOM,這裡可以談一談。傳統虛擬 DOM 的效能瓶頸在於虛擬 DOM
的
diff,也許某次更新只有一個很小的變動,但還是需要比較整個虛擬DOM樹,這顯然是非常不划算的。
</p>
<p>
Vue 新版本將虛擬 DOM 的節點分為動態節點和靜態節點。靜態節點是指不會發生改變的節點,在進行
diff
操作時應該避開這些節點,只需要比較動態節點。如何了解動態節點和靜態節點呢?先來看一下下面的程式。
</p>
<pre><code class="language-html">
<template>
<div>
<div id="1">
<p>前端進階</p>
<p>前端進階</p>
<p>前端進階</p>
<p> {{data.foo}} </p>
<p>前端進階</p>
<p>前端進階</p>
</div>
</div>
</template>
</code></pre>
<p>
對於以上程式,最理想的情況是只需要比較可能會發生變化的 p 標籤。再來看一看下面這段程式。
</p>
<pre><code class="language-html">
<template>
<diV>
前端進階
</div>
<div v-if="XXX">
<span>前端進階</span>
<span>{{data.foo}}</span>
</div>
</template>
</code></pre>
<p>
對上面這段程式來說,最理想的情況是只需要比較 <div v-if="XXX">
及<span>{{data.foo}}<span>,因為前者可能會根據判斷條件消失或出現,後者直接取決於範本變數的值,都屬於動態節點。
</p>
<p>
這樣一來,我們便可以根據範本將動態節點切割為區塊,在進行 diff
操作時,對區塊中的動態節點進行遞迴比較即可。因此,以新的 diff
策略為基礎,更新時的效能不再取決於範本整體節點數量的多少,而和動態內容的數量成正相關。
</p>
<h3>從架構再談基礎</h3>
<p>
每一個開發者都應該意識到基礎的重要性。如果基礎薄弱,你可能不會明白『為什麼 React
事件處理函式需要手動綁定 this,而 React 生命週期函數卻不需要手動綁定this」、「為什麼 Vue
可以實現雙向綁定」。
</p>
</article>
</main>
</body>
<script type="module" src="./js/main.js"></script>
</html>