<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] # 一、監聽數據變化的實現原理不同 ***** - Vue 通過 getter/setter 以及一些函數的劫持,能精確知道數據變化,不需要特別的優化就能達到很好的性能 - React 默認是通過比較引用的方式進行的,如果不優化(PureComponent/shouldComponentUpdate)可能導致大量不必要的 VDOM 的重新渲染 # 二、數據流的不同 ***** ![](https://img.kancloud.cn/17/fa/17fabbfa1c7385a862ebb5c3463b19aa_922x453.png =500x) 在 Vue1.0 中我們可以實現兩種雙向綁定(這里的雙向綁定指的是數據流而不是 View 與 Model): 1. 父子組件之間,props 可以雙向綁定 2. 組件與 DOM 之間可以通過 v-model 雙向綁定 在 Vue2.x 中去掉了第一種,也就是父子組件之間不能雙向綁定了(但是提供了一個語法糖自動幫你通過事件的方式修改),并且 Vue2.x 已經不鼓勵組件對自己的 props 進行任何修改了。 所以現在我們只有組件 DOM 之間的雙向綁定這一種。 然而 React 從誕生之初就不支持雙向綁定,React 一直提倡的是單向數據流,他稱之為 onChange/setState() 模式。 不過由于我們一般都會用 Vuex 以及 Redux 等單向數據流的狀態管理框架,因此很多時候我們感受不到這一點的區別了。 # 三、組件通信的區別 ***** 單純地看父子組件通信: - Vue 中父組件可以通過 props 向子組件傳遞數據和回調,但是我們一般都只傳數據,通過事件 `$emit` 的機制來處理子組件向父組件的通信。 - React 也可以向子組件傳遞數據和回調,在 React 中子組件向父組件通信都是采用回調的方式進行的。 # 四、Vuex 和 Redux 的區別 ***** 在 Vuex 中,store 被直接注入到了所有的組件實例中,因此可以比較靈活的使用: - 使用 dispatch 和 commit 提交更新 - 通過 mapState 或者直接通過 this.$store 來讀取數據 在 Redux 中,我們每一個組件都需要顯式地用 connect 把需要的 props 和 dispatch 連接起來。 另外 Vuex 更加靈活一些,組件中既可以 dispatch action 也可以 commit updates,而 Redux 中只能進行 dispatch,并不能直接調用 reducer 進行修改。 從實現原理上來說,最大的區別是兩點: * Redux 使用的是不可變數據,而 Vuex 的數據是可變的。Redux 每次都是用新的 state 替換舊的 state,而 Vuex 是直接修改 * Redux 在檢測數據變化的時候,是通過 diff 的方式比較差異的,而 Vuex 其實和 Vue 的原理一樣,是通過 getter/setter 來比較的(如果看 Vuex 源碼會知道,其實他內部直接創建一個 Vue 實例用來跟蹤數據變化) 而這兩點的區別,其實也是因為 React 和 Vue 的設計理念上的區別。React 更偏向于構建穩定大型的應用,非常的科班化。相比之下,Vue 更偏向于簡單迅速的解決問題,更靈活,不那么嚴格遵循條條框框。因此也會給人一種大型項目用 React,小型項目用 Vue 的感覺。 # 五、css 管理的區別 ***** Vue 中我們直接給 \<style> 加上 scoped 屬性就能保證它的樣式只作用于當前組件: ```html <style scoped> .example { color: red; } </style> <template> <div class="example">hi</div> </template> ``` ```html <style> .example[data-v-5558831a] { color: red; } </style> <template> <div class="example" data-v-5558831a>hi</div> </template> ``` PostCSS 給一個組件中的所有 dom 添加了<span style="color: red">一個獨一無二的動態屬性</span>,然后,給 CSS 選擇器額外添加一個對應的屬性選擇器來選擇該組件中 dom,這種做法使得樣式只作用于含有該屬性的 dom——組件內部 dom >為什么 Vue 在開發環境下我們能看到原本的樣式?而 React 使用 Styled-Component 看到的就直接是哈希后的樣式?能做轉換嗎? 這使得我們寫一個 Vue 組件只需要一個 .vue 文件即可,而 React 中有多種樣式管理方案,比如直接寫 css 文件 import 導入,使用 styled-component 等,文件結構看起來就不那么整潔了。 # 六、diff 算法的差異 ## 傳統 diff 算法 ![](https://img.kancloud.cn/d4/fa/d4fa369fa849f51a8f9c939af5b36b6a_1000x440.png =500x) 計算兩顆樹形結構差異并進行轉換,傳統 diff 算法是這樣做的:循環遞歸每一個節點 比如左側樹 a 節點依次進行如下對比,左側樹節點 b、c、d、e 亦是與右側樹每個節點對比 算法復雜度能達到 O(n^2),n 代表節點的個數 ```js a->e、a->d、a->b、a->c、a->a ``` 查找完差異后還需計算最小轉換方式,最終達到的算法復雜度是 O(n^3) ## React 的 diff 策略 React 的 diff 策略將時間復雜度優化到了 O(n),這一壯舉是通過以下三條策略來實現的: * Web UI 中 DOM 節點跨層級的移動操作特別少,可以忽略不計,所以 React 的 diff 是同層級比較 * 擁有相同類型的兩個組件將會生成相似的樹形結構,不同類型的兩個組件將會生成不同的樹形結構 * 對于同一層級的一組子節點,它們可以通過唯一 id 進行區分 這三個策略分別對于 tree diff、component diff 和 element diff ### tree diff 既然 DOM 節點跨層級的移動操作少到可以忽略不計,針對這一現象,React 只會對相同層級的 DOM 節點進行比較,即同一個父節點下的所有子節點。當發現節點已經不存在時,則該節點及其子節點會被完全刪除掉,不會用于進一步的比較。這樣只需要對樹進行一次遍歷,便能完成整個 DOM 樹的比較。 ![](https://img.kancloud.cn/10/d4/10d4ca61b2debe4a05ab7202912dac12_918x492.png =400x) 這個策略的前提是操作 DOM 時跨層級操作較少,那么如果發生了跨層級操作應該如何處理呢? ![](https://img.kancloud.cn/dc/14/dc145d9c99650747b5657b28aa37a79e_710x415.png =400x) 這一過程可以用下圖來描述: ![](https://img.kancloud.cn/b6/34/b634dc3fd6f1d1ecb7042bd771228b13_1289x270.png) A 節點(包括其子節點)整個被移動到 D 節點下,由于 React 只會簡單地考慮同層級節點的位置變換,而對于不同層級的節點,只有創建和刪除操作。當根節點發現子節點中 A 消失了,就會直接銷毀 A;當 D 發現多了一個子節點 A,則會創建新的 A(包括子節點)作為其子節點。此時,diff 的執行情況:create A → create B → create C → delete A。 由此可以發現,當出現節點跨層級移動時,并不會出現想象中的移動操作,而是以 A 為根節點的整個樹被重新創建。這是一種影響 React 性能的操作,因此官方建議不要進行 DOM 節點跨層級的操作。 ### component diff React 是基于組件構建應用的,對于組件間的比較所采取的策略也是非常簡潔、高效的: * 如果是同一類型的組件,按照原策略繼續比較 Virtual DOM 樹即可 * 如果不是,則將該組件判斷為 dirty component,從而替換整個組件下的所有子節點 * 對于同一類型的組件,有可能其 Virtual DOM 沒有任何變化,如果能夠確切知道這點,那么就可以節省大量的 diff 運算時間。因此,React 允許用戶通過 shouldComponentUpdate() 來判斷該組件是否需要進行 diff 算法分析,但是如果調用了 forceUpdate 方法,shouldComponentUpdate 則失效 接下來我們看下面這個例子是如何實現轉換的: ![](https://img.kancloud.cn/b2/29/b2290d26c07c3f4997be48204b22fb1a_745x232.png =500x) 轉換流程如下: ![](https://img.kancloud.cn/1d/fa/1dfa56cb64b6d96c3e01d3fef382a20c_1115x241.png =800x) 當組件 D 變為組件 G 時,即使這兩個組件結構相似,一旦 React 判斷 D 和 G 是不同類型的組件,就不會比較二者的結構,而是直接刪除組件 D,重新創建組件 G 及其子節點。雖然當兩個組件是不同類型但結構相似時,diff 會影響性能,但正如 React 官方博客所言:不同類型的組件很少存在相似 DOM 樹的情況,因此這種極端因素很難在實際開發過程中造成重大的影響。 ### element diff 當節點處于同一層級時,diff 提供了 3 種節點操作,分別為 INSERT\_MARKUP (插入)、MOVE\_EXISTING (移動)和 REMOVE\_NODE (刪除)。 * INSERT\_MARKUP :新的組件類型不在舊集合里,即全新的節點,需要對新節點執行插入操作。 * MOVE\_EXISTING :舊集合中有新組件類型,且 element 是可更新的類型,generateComponentChildren 已調用 receiveComponent ,這種情況下 prevChild = nextChild ,就需要做移動操作,可以復用以前的 DOM 節點。 * REMOVE\_NODE :舊組件類型,在新集合里也有,但對應的 element 不同則不能直接復用和更新,需要執行刪除操作,或者舊組件不在新集合里的,也需要執行刪除操作。 我們可以忽略上面的說明直接來看例子 (-.-) 當一個組件包含多個子組件的情況: ```html <ul> <TodoItem text="First" completed={false} /> <TodoItem text="Second" completed={false} /> </ul> // 更新為 <ul> <TodoItem text="Zero" completed={false} /> <TodoItem text="First" completed={false} /> <TodoItem text="Second" completed={false} /> </ul> ``` 直觀上看,只需要創建一個新組件,更新之前的兩個組件;但是實際情況并不是這樣的,React 并沒有找出兩個序列的精確差別,而是直接挨個比較每個子組件。 在上面的新的 TodoItem 實例插入在第一位的例子中,React 會首先認為把 text 為 First 的 TodoItem 組件實例的 text 改成了 Zero,text 為 Second 的 TodoItem 組件實例的 text 改成了 First,在最后面多出了一個 TodoItem 組件實例。這樣的操作的后果就是,現存的兩個實例的 text 屬性被改變了,強迫它們完成了一個更新過程,創造出來的新的 TodoItem 實例用來顯示 Second。 我們可以看到,理想情況下只需要增加一個 TodoItem 組件,但實際上其還強制引發了其他組件實例的更新。 假設有 100 個組件實例,那么就會引發 100 次更新,這明顯是一個浪費;所以就需要開發人員在寫代碼的時候提供一點小小的幫助,這就是接下來要講的 key 的作用 ```html <ul> <TodoItem key={1} text="First" completed={false} /> <TodoItem key={2} text="Second" completed={false} /> </ul> // 新增一個 TodoItem 實例 <ul> <TodoItem key={0} text="Zero" completed={false} /> <TodoItem key={1} text="First" completed={false} /> <TodoItem key={2} text="Second" completed={false} /> </ul> ``` React 根據 key 值,就可以知道現在的第二個和第三個 TodoItem 實例其實就是之前的第一個和第二個實例,所以 React 就會把新創建的 TodoItem 實例插在第一位,對于原有的兩個 TodoItem 實例只用原有的 props 來啟動更新過程,這樣 shouldComponentUpdate 就會發生作用,避免無謂的更新操作; 了解了這些之后,我們就知道 key 值應該是**唯一**且**穩定不變的** 比如用數組下標值作為 key 就是一個典型的 “錯誤”,看起來 key 值是唯一的,但是卻不是穩定不變的(但是一般用數組下標就行了) 比如:\[a, b, c\] 值與下標的對應關系:a: 0 b:1 c:2 刪除a -> \[b, c\] 值與下標的對應關系 b:0 c:1 無法用 key 值來確定比對關系(新的 b 應該與舊的 b 比,如果按 key 值則是與 a 比) ![](https://box.kancloud.cn/843f670bb548fca2492bf347a2d0c3f6_501x135.png) >需要注意,雖然 key 是一個 prop,但是接受 key 的組件并不能讀取到 key 的值,因為 key 和 ref 是 React 保留的兩個特殊 prop,并沒有預期讓組件直接訪問 ## Vue 的 diff 策略 首先與傳統 diff 策略相比,Vue 也采用了同層節點對比的方式。 接下來就是與 React 的策略的第一個區別了:通知更新的方式 Vue 通過 Watcher 來監聽數據變化實現視圖更新,而 React 顯然沒有這些監聽器,至于 React 是怎么通知組件更新的我沒看過源碼,我猜是 setState 或其他操作造成 state 或 prop 改變時觸發某些函數吧。 下面看下 Vue 的 dff 流程圖: ![](https://img.kancloud.cn/54/23/5423ccebb441c247066578eabc5a8999_668x606.png) 可以大致地梳理下其流程: 首先要知道 VNode 大致是怎樣的: ```js // body下的 <div id="v" class="classA"><div> 對應的 oldVnode 就是 { el: div // 對真實的節點的引用,本例中就是document.querySelector('#id.classA') tagName: 'DIV', // 節點的標簽 sel: 'div#v.classA' // 節點的選擇器 data: null, // 一個存儲節點屬性的對象,對應節點的 el[prop] 屬性,例如 onclick , style children: [], // 存儲子節點的數組,每個子節點也是 vnode 結構 text: null, // 如果是文本節點,對應文本節點的 textContent,否則為 null } ``` ### patch 然后看下`patch`是如何判斷新舊 Vnode 是否相同的: ```js function patch (oldVnode, vnode) { if (sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode) } else { const oEl = oldVnode.el let parentEle = api.parentNode(oEl) createEle(vnode) if (parentEle !== null) { api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) api.removeChild(parentEle, oldVnode.el) oldVnode = null } } return vnode } ``` patch 函數內第一個`if`判斷`sameVnode(oldVnode, vnode)`就是判斷這兩個節點是否為同一類型節點,以下是它的實現: ```js function sameVnode(oldVnode, vnode){ // 兩節點 key 值相同,并且 sel 屬性值相同,即認為兩節點屬同一類型,可進行下一步比較 return vnode.key === oldVnode.key && vnode.sel === oldVnode.sel } ``` 也就是說,即便同一個節點元素比如 div,他的`className`不同,Vue 就認為是兩個不同類型的節點,執行刪除舊節點、插入新節點操作。這與 React diff 的實現是不同的,React 對于同一個元素節點認為是同一類型節點,只更新其節點上的屬性。 ### patchVnode 之后就看`patchVnode`,對于同類型節點調用`patchVnode(oldVnode, vnode)`進一步比較,偽代碼如下: ```js patchVnode (oldVnode, vnode) { const el = vnode.el = oldVnode.el // 讓 vnode.el 引用到現在的真實 dom,當 el 修改時,vnode.el 會同步變化 let i, oldCh = oldVnode.children, ch = vnode.children if (oldVnode === vnode) return // 新舊節點引用一致,認為沒有變化 // 1.文本節點的比較 if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) { api.setTextContent(el, vnode.text) }else { updateEle(el, vnode, oldVnode) // 2.對于擁有子節點(兩者的子節點不同)的兩個節點,調用 updateChildren if (oldCh && ch && oldCh !== ch) { updateChildren(el, oldCh, ch) }else if (ch){ // 3.只有新節點有子節點,添加新的子節點 createEle(vnode) //create el's children dom }else if (oldCh){ // 4.只有舊節點內存在子節點,執行刪除子節點操作 api.removeChildren(el) } } } ``` 其中第二種情況是一種比較常見的情況,執行`updateChildren`函數 ### updateChildren 源碼就不貼出來了,其思路大致如下: ![](https://img.kancloud.cn/1a/f7/1af729ed569c5ad77f801e7443f90ba9_1000x728.png =400x) oldCh 和 newCh 各有兩個頭尾的變量 StartIdx 和 EndIdx,它們的 2 個變量相互比較,一共有 4 種比較方式。如果 4 種比較都沒匹配,則以遍歷的方式作比較;如果設置了 key,就會用 key 進行比較,在比較的過程中,變量會往中間靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一個已經遍歷完了,就會結束比較。 這一具體過程可以參考這篇文章:[https://www.cnblogs.com/wind-lanyan/p/9061684.html](https://www.cnblogs.com/wind-lanyan/p/9061684.html) 有空的話我把他的圖重畫一下...... 同樣的條件下,React 采取的比較方式是從左至右一一比較(如果沒有 key),而 Vue 采取的是這種指針匹配的形式,這也是其 diff 策略的一個不同之處。 # 參考鏈接 [https://juejin.im/post/5b8b56e3f265da434c1f5f76#heading-0](https://juejin.im/post/5b8b56e3f265da434c1f5f76#heading-0) [https://segmentfault.com/a/1190000017508285](https://segmentfault.com/a/1190000017508285) [https://segmentfault.com/a/1190000018914249?utm\_source=tag-newest](https://segmentfault.com/a/1190000018914249?utm_source=tag-newest) [https://www.jianshu.com/p/398e63dc1969](https://www.jianshu.com/p/398e63dc1969) [https://www.cnblogs.com/wind-lanyan/p/9061684.html](https://www.cnblogs.com/wind-lanyan/p/9061684.html)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看