<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] # Hook 簡單來說 Hook 擁抱了函數式編程,Fiber 架構從底層優化了 React 的性能。 使用 Hook ```js import React, { useState, useEffect } from 'react'; // 自定義hook function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); // 將 setState 的統一操作抽離為一個個的函數 function handleStatusChange(status) { setIsOnline(status.isOnline); } // useEffect 簡化了事件監聽器的添加與移除的書寫 useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; } // 使用自定義hook function FriendListItem(props) { const isOnline = useFriendStatus(props.friend.id); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); } ``` 不使用 Hook ```js class XXX extend Component { state = { isOnline: false } handleStatusChange (xxx) { this.setState({ isOnline: xxx }) } componentDidMount =()=> { ChatAPI.subscribeToFriendStatus(friendID, this.handleStatusChange); } componentWillUnmount = () => { ChatAPI.unsubscribeFromFriendStatus(friendID, this.handleStatusChange); } render () { XXXXXXX } } ``` ## state hook(讓函數組件擁有 state) ```js import React, { useState } from 'react'; function Example() { // 聲明一個叫 "count" 的 state 變量 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } ``` 等價的 Class 示例: ```js class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } } ``` `const [count, setCount] = useState(0);` 這條語句到底做了哪些事情? 我們聲明了一個叫`count`的 state 變量,然后把它設為`0`。React 會在重復渲染時記住它當前的值,并且提供最新的值給我們的函數。我們可以通過調用`setCount`來更新當前的`count`。 下面具體分析下`useState()`方法 1、調用 useState 方法的時候做了什么?它定義一個 “state 變量”。我們的變量叫`count`, 這是一種在函數調用時保存變量的方式 ——`useState`是一種新方法,它與 class 里面的`this.state`提供的功能完全相同。一般來說,在函數退出后變量就就會”消失”,而 state 中的變量會被 React 保留。 2、`useState`需要哪些參數?`useState()`方法里面唯一的參數就是初始 state。不同于 class 的是,我們可以按照需要使用數字或字符串對其進行賦值,而不一定是對象。在示例中,只需使用數字來記錄用戶點擊次數,所以我們傳了`0`作為變量的初始 state。(如果我們想要在 state 中存儲兩個不同的變量,只需調用`useState()`兩次即可。) 3、`useState`方法的返回值是什么?返回值為:當前 state 以及更新 state 的函數。這就是我們寫`const [count, setCount] = useState()`的原因。這與 class 里面`this.state.count`和`this.setState`類似,唯一區別就是你需要成對的獲取它們。 ### 讀取與更新 state 讀取 state: 當我們想在 class 中顯示當前的 count,我們讀取`this.state.count`: ~~~ <p>You clicked {this.state.count} times</p> ~~~ 在函數中,我們可以直接用`count`: ~~~ <p>You clicked {count} times</p> ~~~ 更新 state: 在 class 中,我們需要調用`this.setState()`來更新`count`值: ~~~ <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> ~~~ 在函數中,我們已經有了`setCount`和`count`變量,所以我們不需要`this`: ~~~ <button onClick={() => setCount(count + 1)}> Click me </button> ~~~ ## Effect Hook(讓函數組件擁有生命周期) 你可以把`useEffect`Hook 看做`componentDidMount`,`componentDidUpdate`和`componentWillUnmount`這三個函數的組合。 <br/> *Effect Hook* 可以讓你在函數組件中執行副作用操作,在 React 組件中有兩種常見副作用操作:需要清除的和不需要清除的,所以對應的 Effect Hook 也分為無需清除的 effect 和需要清除的 effect。 <br/> 默認情況下,effect 將在每輪渲染結束后執行,但也可以選擇讓其在只有某些值改變的時候才執行。 ### 無需清除的 effect 有時候,我們只想**在 React 更新 DOM 之后運行一些額外的代碼**。比如發送網絡請求,手動變更 DOM,記錄日志,這些都是常見的無需清除的操作。因為我們在執行完這些操作之后,就可以忽略他們了。讓我們對比一下使用 class 和 Hook 都是怎么實現這些副作用的。 ```js // 使用 class class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } // 我們基本上都希望在 React 更新 DOM 之后才執行我們的操作 componentDidMount() { document.title = `You clicked ${this.state.count} times`; } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } } ``` 在這個 class 中,我們需要在兩個生命周期函數中編寫重復的代碼。 這是因為很多情況下,我們希望在組件加載和更新時執行同樣的操作。從概念上說,我們希望它在每次渲染之后執行 —— 但 React 的 class 組件沒有提供這樣的方法。即使我們提取出一個方法,我們還是要在兩個地方調用它。 ```js // 使用 hook import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // 通過使用這個 Hook,你可以告訴 React 組件需要在渲染后執行某些操作。 // React 會保存你傳遞的函數(我們將它稱之為 “effect”),并且在執行 DOM 更新之后調用它。 useEffect(() => { document.title = `You clicked ${count} times`; // 可以直接訪問 count state 變量或其他 prop,它們保存在函數作用域中 }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } ``` ### 需要清除的 effect 之前,我們研究了如何使用不需要清除的副作用,還有一些副作用是需要清除的。例如**訂閱外部數據源**。這種情況下,清除工作是非常重要的,可以防止引起內存泄露!現在讓我們來比較一下如何用 Class 和 Hook 來實現。(常見的如手動添加事件處理程序,需要在組件銷毀之前移除) ```js // 使用 class class FriendStatus extends React.Component { constructor(props) { super(props); this.state = { isOnline: null }; this.handleStatusChange = this.handleStatusChange.bind(this); } componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } // 與 componentDidMount 邏輯相對應的,我們必須這樣拆分代碼~~~ import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // Specify how to clean up after this effect: return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; } ``` ### effect 的條件執行 默認情況下,effect 會在每輪組件渲染完成后執行。這樣的話,一旦 effect 的依賴發生變化,它就會被重新創建。 <br/> 然而,在某些場景下這么做可能會矯枉過正。比如,在上一章節的訂閱示例中,我們不需要在每次組件更新時都創建新的訂閱,而是僅需要在`source`prop 改變時重新創建。 <br/> 要實現這一點,可以給`useEffect`傳遞第二個參數,它是 effect 所依賴的值數組。更新后的示例如下: ``` useEffect( () => { const subscription = props.source.subscribe(); return () => { subscription.unsubscribe(); }; }, [props.source], ); ``` 此時,只有當`props.source`改變后才會重新創建訂閱。 如果想執行只運行一次的 effect(僅在組件掛載和卸載時執行),可以傳遞一個空數組(`[]`)作為第二個參數。這就告訴 React 你的 effect 不依賴于 props 或 state 中的任何值,所以它永遠都不需要重復執行。這并不屬于特殊情況 —— 它依然遵循輸入數組的工作方式。 [關于依賴列表是否為空的注意事項](https://react.docschina.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) ## 自定義 Hook [https://zh-hans.reactjs.org/docs/hooks-custom.html#gatsby-focus-wrapper](https://zh-hans.reactjs.org/docs/hooks-custom.html#gatsby-focus-wrapper) - 將組件邏輯提取到可重用的函數中(使組件可以共享某一重復的邏輯) 例如,將一個獲取鼠標位置的狀態邏輯寫成自定義 Hook: ``` import React, { useState, useEffect } from 'react' // 自定義 Hook 是一個函數,其名稱必須以 "use" 開頭(約定) const useMousePosition = () => { const [ positions, setPositions ] = useState({x: 0, y: 0}) useEffect(() => { const updateMouse = (event) => { setPositions({ x: event.clientX, y: event.clientY }) } document.addEventListener('mousemove', updateMouse) return () => { document.removeEventListener('mousemove', updateMouse) } }) return positions } export default useMousePosition ``` 在組件中使用自定義 Hook 也很簡單: ``` function App() { const position = useMousePosition() return ( <div className="App"> <header className="App-header"> <h1>{position.x}</h1> </header> </div> ) } ``` 在兩個組件中使用相同的 Hook 不會共享 state。自定義 Hook 是一種重用狀態邏輯的機制(例如設置為訂閱并存儲當前值),所以每次使用自定義 Hook 時,其中的所有 state 和副作用都是完全隔離的。 # useRef 的使用 ~~~ const refContainer = useRef(initialValue); ~~~ `useRef`返回一個可變的 ref 對象,其`.current`屬性被初始化為傳入的參數(`initialValue`)。返回的 ref 對象在組件的整個生命周期內保持不變。可以將其視為多次渲染之間的紐帶。另外,修改 ref 對象的值并不會觸發組件重新渲染。 一個常見的用例便是命令式地訪問子組件: ~~~ function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // `current` 指向已掛載到 DOM 上的文本輸入元素 inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); } ~~~ 注意這里 ref 訪問 DOM 的方式,將 ref 對象以`<div ref={myRef} />`形式傳入組件,則無論該節點如何改變,React 都會將 ref 對象的`.current`屬性設置為相應的 DOM 節點。 # useContext 解決多層屬性傳遞 ~~~ const value = useContext(MyContext); ~~~ 接收一個 context 對象(`React.createContext`的返回值)并返回該 context 的當前值。當前的 context 值由上層組件中距離當前組件最近的`<MyContext.Provider>`的`value`prop 決定。 當組件上層最近的`<MyContext.Provider>`更新時,該 Hook 會觸發重渲染,并使用最新傳遞給`MyContext`provider 的 context`value`值。 ~~~ const themes = { light: { foreground: "#000000", background: "#eeeeee" }, dark: { foreground: "#ffffff", background: "#222222" } }; const ThemeContext = React.createContext(themes.light); // ① createContext 創建 Context 對象 function App() { return ( <ThemeContext.Provider value={themes.dark}> // ② Provider 包裹組件 <Toolbar /> </ThemeContext.Provider> ); } function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton() { const theme = useContext(ThemeContext); return ( // ③ 子組件先引入 Context 對象(import ...)然后使用 useContext 獲取 <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); } ~~~ # Fiber ## Fiber 架構解決了什么問題 在頁面元素很多,且需要頻繁刷新的場景下,React 15 會出現掉幀的現象。請看以下例子: ![](https://img.kancloud.cn/9a/f4/9af4c676d8771189740c052dac66c489_550x280.gif =400x) 其根本原因,是大量的同步計算任務阻塞了瀏覽器的 UI 渲染。默認情況下,JS 運算、頁面布局和頁面繪制都是運行在瀏覽器的主線程當中,他們之間是互斥的關系。如果 JS 運算持續占用主線程,頁面就沒法得到及時的更新。當我們調用`setState`更新頁面的時候,React 會遍歷應用的所有節點,計算出差異,然后再更新 UI。整個過程是一氣呵成,不能被打斷的。如果頁面元素很多,整個過程占用的時機就可能超過 16 毫秒,就容易出現掉幀的現象。 針對這一問題,React 團隊從框架層面對 web 頁面的運行機制做了優化,得到很好的效果。 ![](https://img.kancloud.cn/70/6c/706ceaa233acb9d0d3b713ccf7948da7_550x280.gif =400x) ## 實現淺析 React 框架內部的運作可以分為 3 層: * Virtual DOM 層,描述頁面長什么樣。 * Reconciler 層,負責調用組件生命周期方法,進行 Diff 運算等。 * Renderer 層,根據不同的平臺,渲染出相應的頁面,比較常見的是 ReactDOM 和 ReactNative。 這次改動最大的當屬 Reconciler 層了,React 團隊也給它起了個新的名字,叫`Fiber Reconciler`。這就引入另一個關鍵詞:Fiber。 先看一下`stack-reconciler`下的 React 是怎么工作的。代碼中創建(或更新)一些元素, React 會根據這些元素創建(或更新)Virtual DOM,然后 React 根據更新前后 Virtual DOM 的區別,去修改真正的 DOM。注意,**在 stack reconciler 下,DOM 的更新是同步的,也就是說,在 Virtual DOM 的比對過程中,發現一個 Instance 有更新,會立即執行 DOM 操作**。 ![](https://img.kancloud.cn/9d/d9/9dd907076cd23c005f57ae1a1aad358e_1133x639.png =400x) 而`Fiber Reconciler`下,操作是可以分成很多小部分,并且可以被中斷的,所以同步操作 DOM 可能會導致 fiber-tree 與實際 DOM 的不同步。對于每個節點來說,其不光存儲了對應元素的基本信息,還要保存一些用于任務調度的信息。因此,fiber 僅僅是一個對象,表征 reconciliation 階段所能拆分的最小工作單元,和上圖中的 react instance一一對應。通過`stateNode`屬性管理 Instance 自身的特性。通過`child`和`sibling`表征當前工作單元的下一個工作單元,`return`表示處理完成后返回結果所要合并的目標,通常指向父節點。整個結構是一個鏈表樹。每個工作單元(fiber)執行完成后,都會查看是否還繼續擁有主線程時間片,如果有繼續下一個,如果沒有則先處理其他高優先級事務,等主線程空閑下來繼續執行。 Fiber 就是一種數據結構,它可以用一個純 JS 對象來表示: ```js const fiber = { stateNode: {}, // 管理 Instance 自身的特性 child: {}, // 表征當前工作單元的下一工作單元 sibling: {}, // 表征當前工作單元的下一工作單元 return: {}, // 表示處理完成后返回結果所要合并的目標,通常指向父節點 } ``` ### 舉個例子 當前頁面包含一個列表,通過該列表渲染出一個 button 和一組 Item,Item 中包含一個 div,其中的內容為數字。通過點擊 button,可以使列表中的所有數字進行平方。另外有一個按鈕,點擊可以調節字體大小。 ![](https://img.kancloud.cn/b6/7d/b67d7f9ffd717781b31aea6a99ed89b7_678x673.png =200x) 頁面渲染完成后,就會初始化生成一個`fiber-tree`,這一過程與初始化 Virtual DOM Tree 類似。 ![](https://img.kancloud.cn/70/7a/707ad710a5ea7ee428f4738ad9df1821_451x817.png =250x) 同時,React 還會維護一個`workInProgressTree`,`workInProgressTree`用于計算更新,完成 reconciliation 過程。 ![](https://img.kancloud.cn/b3/1f/b31f5c759e308e6b1706c1ab80855f47_653x844.png =300x) 用戶點擊平方按鈕后,利用各個元素平方后的 list 調用 setState,React 會把當前的更新送入 list 組件對應的`update queue`中。但是 React 并不會立即執行對比并修改 DOM 的操作。而是交給 scheduler 去處理。 scheduler 會根據當前主線程的使用情況去處理這次 update。為了實現這種特性,使用了`requestIdelCallback`API。對于不支持這個 API 的瀏覽器,react 會加上 pollyfill。 總的來講,通常,客戶端線程執行任務時會以幀的形式劃分,大部分設備控制在 30-60 幀是不會影響用戶體驗;在兩個執行幀之間,主線程通常會有一小段空閑時間,`requestIdleCallback`可以在這個 **空閑期(Idle Period)** 調用 **空閑期回調(Idle Callback)**,執行一些任務 ![](https://img.kancloud.cn/32/5d/325d0263851b3180b058effdee674b37_737x139.png) 1、低優先級任務由`requestIdleCallback`處理; 2、高優先級任務,如動畫相關的由`requestAnimationFrame`處理; 3、`requestIdleCallback`可以在多個空閑期調用空閑期回調,執行任務; 4、`requestIdleCallback`方法提供 deadline,即任務執行限制時間,以切分任務,避免長時間執行,阻塞 UI 渲染而導致掉幀; ![](https://img.kancloud.cn/6d/4e/6d4e202604ed0584204e6c5a48ef3ceb_1048x786.png =450x) 整個過程,簡單來說,先通過`requestIdleCallback`獲得可用的時間片,然后檢查節點的`update queue`看是否需要更新,每處理完一個節點都會檢查時間片是否用完,如果沒用完,根據其保存的下一個工作單元的信息處理下一個節點。詳細過程見第四個參考鏈接。 # 參考資料 [React Hook 探究](https://www.jianshu.com/p/d6e2bd342476) [官方文檔](https://react.docschina.org/docs/hooks-reference.html) [React Fiber 原理](https://segmentfault.com/a/1190000018250127?utm_source=tag-newest) [https://juejin.im/post/5ab7b3a2f265da2378403e57#heading-2](https://juejin.im/post/5ab7b3a2f265da2378403e57#heading-2) [https://usehooks.com/ 其他人使用 Hook 的小技巧](https://usehooks.com/)
                  <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>

                              哎呀哎呀视频在线观看