React
React 原始碼
react 為什麼實用 JSX?
蘿蔔青菜各有所愛。
但是 react 團隊認為模板方案不好:
- 模板分離了技術棧,增加了技術點。
- 看起來像 html 的 jsx。
- react 規矩性太強,需要 jsx 來輔助
例如使用 vue 上面是 html 模板,下面是js邏輯從而形成一個元件功能。但是 react 將 html 和執行邏輯都執行在一個 class。
JSX 對映虛擬 DOM 的原理?
JSX 是 creatElement 的語法糖,使用的是 babel 的轉義器,實際是通過兩個方法完成,一個是 creatElement,一個 reactElement
// 定義一個 react 物件
reactElement(type, key, ref, self, source, ReactCurrentOwner.current, props)
Rreact 資料流管理
資料驅動檢視?UI = render(data)
data 與 state 的區別是什麼?
狀態(state)與資料(data),元件中的通訊就是 data 的通訊,react 核心就是對資料的管理
資料流動:
- 父 -> 子
- 子 -> 父
- 兄弟
- 無關係
單向資料流
MVC的劣勢雙向資料流。引起混亂。
Action -> Dispatcher -> Store -> View
|-------<------Action<-|
ACTION 檢視層傳送的訊息
redux 是 js 的狀態容器
Action -> Reducer -> Store[state, state]
|-------<------View--<-|
redux 優缺點
缺點:
- 使用流程複雜。
- state 不會隨著元件銷燬,狀態殘留。
- 頻繁更新 store 時,會更加卡頓。
- 不支援 ts。
mobx
原理是利用 es6 proxy 資料劫持,不會想 redux 使用複雜。
React Fiber
超過 16 毫秒後會掉幀。
原來的 React 是通過利用js的執行棧,一直執行到棧空位置。新版的React 維護了自己的執行棧,通過連結串列的形式,遍歷樹形結構,優化了執行過程。
虛擬 DOM 和 diff演算法
虛擬 DOM
React 渲染 dom 的一種優化手段,其原理是利用 createFragment ,即“創造碎片”。所有的 DOM 操作都將在“碎片”中操作,直到操作完成,再一起渲染。
diff 演算法
是 React 更新 dom 元素的一套演算法,其核心思想是,比較“節點樹”的各個節點,根據比較的結果來決定是否更新該節點。通過其優化手段,將更新樹的時間複雜度從O(n^3)變為O(n)。
diff 策略
- 忽略跨元件的移動操作
- 同類元件建立相同樹,不同類則建立不同類的數(React Component 和 function Component)
- 兄弟,key
分為3個更新策略 tree diff, component diff, element diff:
tree diff: 當遇到樹形結構更新時,僅比較同層節點,起子節點僅有建立和刪除。這就意味這,如果存在移動某個樹形結構的中間節點,那麼原樹將直接刪除該節點及其子節點,在新樹中建立起節點及其子節點。另外,官方不建議存在跨節點的移動操作。
component diff: 當節點更新為不同型別的節點時,成為 dirty component,react 認為更新成不同型別的節點,其結構一定是不一樣的(樹形結構),因此直接刪除該節點後,直接建立。
element diff: 計算兄弟節點直接的移動操作,由原來的將目標位節點刪除再建立的更新策略,更改為通過標識key,判斷是為原來的節點,另外也獲得了是否產生移動操作的判斷依據。
由此也得出了3條優化建議:
- 設定 key
- 不要將元件更新為不同型別
- 減少將兄弟元件從末尾移至頭部的操作
Redux
待補充
React 優化
常見的優化細節:
- function 元件代替 class 元件 -> 為什麼?參考1 參考2
- HOC
- 使用 redux 這類狀態管理工具時,部分不公用的state 不用掛載在 model 中 --> 為什麼?
- 首屏優化方案
- lazy 懶載入元件
- react-router 的 loading 改善體驗
- ssr (service side rendering)和 csr (client side rendering)
- pureComponent 和 shouldUpdate 優化更新頻率
- componentDidCatch 探測錯誤邊界
- 通過 Fragment 減少標籤深度
- render 函式中的變數宣告提升到外面,減少 GC
React-Hooks
useState 相當於 class component 中的 setState。
function render() {
ReactDom.render(<App />, document.getElementById('root'))
}
function myState(initState) {
let state = initState
const update = (newState) => {
state = newState
render()
}
return [state, update]
}
function App () {}
useEffect 相當於 class component 中的若干宣告周期函式
為什麼16.8加入Hook?或者為什麼增加 function component?
- class component 複用狀態會很難。HOC 雖然解決了,但是有巢狀地獄的問題。
- class component 宣告週期如果存在更改,其他生命週期可能均需要更改。
- es6 的 class 不如 function 對初學者更友好
- this 的指向
站在設計者的角度來思考。
SSR
next 主要是用在 React。
npx create-next-app
react-router
根據 history 開發的無重新整理路由器。
主要有3個型別:hashHistory、borwerHistory、memoryHistory,常用的是 hashHistory 和 browerHistory。
新版本是 react-router-dom,
import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
import PropTypes from "prop-types";
import warning from "tiny-warning";
/**
* The public API for a <Router> that uses HTML5 history.
*/
class BrowserRouter extends React.Component {
history = createHistory(this.props);
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
export default BrowserRouter;
區別
略
browerHistory 與 hashHistory 區別
hash 路由是是根據改變 # 後面的錨點來重新整理,通過 window.onhashChange 監聽變化,從而根據路由處找到相應的檔案
history 是通過 html5 的 window.history 這個 web API 來實現
hashHistory的特點:
- 使用的是 history.location.hash 即 history.location.hash = "abc" (相當於是 localhost/#abc)
- 只能通過修改 # 後面的地址實現重新整理
- 通過 window.onhashchange 監聽 hash 變化,window.addEventListener('onhashchange', hashchangeHandler)
- 不能想 window.history.go(-1) 這種方式,只能通過字串改變 url
- 對搜尋引擎不友好,且不好追蹤
browerHistory的特點:
- 使用的是 History 物件,提供 go, back, forward 的方法
- 相同的 url 會發生更新,並且壓入歷史記錄中
- 通過 push 和 replace 方法,實際是通過 pushState,replaceState 實現無重新整理跳轉。其中 pushState 會壓入瀏覽器歷史棧,即 History.length + 1,replaceState 則不會。
問題
使用 history 更新路由且正常渲染後,再重新整理,會引起404。(待驗證)
是由於 history 更新了 url,也就是訪問地址,此時向後端伺服器請求時,如果沒有匹配地址的資源則會 404。
例如 nginx 配置 /test 地址訪問。通過 history router 從 /test 跳轉至 /say ,重新整理時,伺服器將找不到資源,可以通過配置父級路徑規避掉。
window.location 也會更改 url 但是會引起重新整理