react取消捕获_React 面试指南 (上)
使用 React 進行項目開發也有好幾個項目了,趁著最近有空來對 React 的知識做一個簡單的復盤。
完整目錄概覽
- React 是單向數據流還是雙向數據流?它還有其他特點嗎?
- setState
- React 通過什么方式來更新數據
- React 不能直接修改 State 嗎?
- setState 是同步還是異步的?
- setState 小測
- React 生命周期
- constructor (構造函數)
- static getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidMount
- componentDidUpdate
- componentWillUnmount
- 生命周期階段
- 其他生命周期
- React 組件通信
- React.Context 怎么使用
- 函數組件是什么?與類組件有什么區別?
- Hooks
- Hook vs class
- Hooks 的使用
- 自定義 Hook 的使用
- Hook 使用約束
- class 組件與 Hook 之間的映射與轉換
- 生命周期
- Hooks 沒有實現的生命周期鉤子
- 轉換實例變量
- 強制更新 Hook 組件
- 獲取舊的 props 和 state
- 受控組件與非受控組件的區別
- Portals 是什么?
React 是單向數據流還是雙向數據流?它還有其他特點嗎?
React 是單向數據流,數據是從上向下流。它的其他主要特點時:
- 數據驅動視圖
- 聲明式編寫 UI
- 組件化開發
React 通過什么方式來更新數據
React 是通過 setState 來更新數據的。調用多個 setState 不會立即更新數據,而會批量延遲更新后再將數據合并。
除了 setState 外還可以使用 forceUpdate 跳過當前組件的 shouldComponentUpdate diff,強制觸發組件渲染(避免使用該方式)。
React 不能直接修改 State 嗎?
setState 是同步還是異步的?
出于性能的考慮,React 可能會把多個 setState 合并成一個調用。
React 內有個 batchUpdate(批量更新) 的機制,在 React 可以控制的區域 (如組件生命周期、React 封裝的事件處理器) 設置標識位 isBatchingUpdate 來決定是否觸發更新。
比如在 React 中注冊的 onClick 事件或是 componentDidMount 中直接使用 setState 都是異步的。若想拿到觸發更新后的值,可以給 setState 第二個參數傳遞一個函數,該函數在數據更新后會觸發的回調函數,函數的參數就是更新后最新的值。
不受 React 控制的代碼快中使用 setState 是同步的,比如在 setTimeout 或是原生的事件監聽器中使用。
setState 小測
輸出以下結果:
componentDidMount() {this.setState({ count: this.state.count + 1 });console.log("1 -->", this.state.count);this.setState({ count: this.state.count + 1 });console.log("2 -->", this.state.count);setTimeout(() => {this.setState({ count: this.state.count + 1 });console.log("3 -->", this.state.count);}, 0);setTimeout(() => {this.setState({ count: this.state.count + 1 });console.log("4 -->", this.state.count);}, 0); }輸出結果為:
1 --> 0 2 --> 0 3 --> 2 4 --> 3解答: 調用 setState 后不會立即更新 state,開頭兩次調用會被異步合并調用,因此只有一次調用。一輪事件循環結束后,調用第 3、4 次 setState。由于在 setTimeout 中調用是同步更新的,因此都能正常的疊加數據。
React 生命周期
React 的生命周期主要是指組件在特定階段會執行的函數。以下是 class 組件的部分生命周期圖譜:
React 生命周期速查圖從上圖可以看出:React 的生命周期按照類型劃分,可分為 掛載時(Mounting)、更新時(Updating)、卸載時(Unmounting) 。圖中的生命周期函數效果如下:
constructor (構造函數)
- 觸發條件: 組件初始化時
- 是否可以使用 setState: X
- 使用場景: 初始化 state 或者對方法綁定 this
static getDerivedStateFromProps
Tips: 不常用方法- 觸發條件: 調用 render 函數之前
- 是否可以使用 setState: X
- 函數行為: 函數可以返回一個對象用于更新組件內部的 state 數據,若返回 null 則什么都不更新。
- 使用場景: 用于 state 依賴 props 的情況,也就是狀態派生。值得注意的是派生 state 會導致代碼冗余,并使組件難以維護。
shouldComponentUpdate
Tips: 不常用方法- 觸發條件: 當 props/state 發生變化
- 是否可以使用 setState: X
- 函數行為: 函數的返回值決定組件是否觸發 render,返回值為 true 則觸發渲染,反之則阻止渲染。(組件內不寫該函數的話,則調用默認函數。默認函數只會返回 true,即只要 props/state 發生變化,就更新組件)
- 使用場景: 組件的性能優化,僅僅是淺比較 props 和 state 的變化的話,可以使用內置的 PureComponent 來代替 Component 組件。
render
- 觸發條件: 渲染組件時
- 是否可以使用 setState: X
- 函數行為: 函數的返回值決定視圖的渲染效果
- 使用場景: class 組件中唯一必須要實現的生命周期函數。
getSnapshotBeforeUpdate
Tips: 不常用方法- 觸發條件: 在最近一次渲染輸出(提交到 DOM 節點)之前調用
- 是否可以使用 setState: X
- 函數行為: 函數的返回值將傳入給 componentDidUpdate 第三個參數中。若只實現了該函數,但沒有使用 componentDidUpdate 的話,React 將會在控制臺拋出警告
- 使用場景: 可以在組件發生更改之前從 DOM 中捕獲一些信息(例如,列表的滾動位置)
componentDidMount
- 觸發條件: 組件掛載后(插入 DOM 樹中)立即調用
- 是否可以使用 setState: Y (可以直接調用,但會觸發額外渲染)
- 使用場景: 從網絡請求中獲取數據、訂閱事件等
componentDidUpdate
- 觸發條件: 組件更新完畢后(首次渲染不會觸發)
- 是否可以使用 setState: Y (更新語句須放在條件語句中,不然可能會造成死循環)
- 使用場景: 對比新舊值的變化,進而判斷是否需要發送網絡請求。比如監聽路由的變化
componentWillUnmount
- 觸發條件: 組件卸載及銷毀之前直接調用
- 是否可以使用 setState: X
- 使用場景: 清除 timer,取消網絡請求或清除在 componentDidMount 中創建的訂閱等
生命周期階段
針對 React 生命周期中函數的調用順序,筆者寫了一個簡易的 Demo 用于演示: React 生命周期示例
React 組件掛載階段先后會觸發 constuctor、static getDerivedStateFromProps、render、componentDidMount 函數。若 render 函數內還有子組件存在的話,則會進一步遞歸:
[Parent]: constuctor [Parent]: static getDerivedStateFromProps [Parent]: render [Children]: constuctor [Children]: static getDerivedStateFromProps [Children]: render [Children]: componentDidMount [Children]: 掛載階段結束! [Parent]: componentDidMount [Parent]: 掛載階段結束!React 組件更新階段主要是組件的 props 或 state 發生變化時觸發。若組件內還子組件,則子組件會判斷是否也需要觸發更新。默認情況下 component 組件是只要父組件發生了變化,子組件也會跟著變化。以下是更新父組件 state 數據時所觸發的生命周期函數:
[Parent]: static getDerivedStateFromProps [Parent]: shouldComponentUpdate [Parent]: render [Children]: static getDerivedStateFromProps [Children]: shouldComponentUpdate [Children]: render [Children]: getSnapshotBeforeUpdate [Parent]: getSnapshotBeforeUpdate [Children]: componentDidUpdate [Children]: 更新階段結束! [Parent]: componentDidUpdate [Parent]: 更新階段結束!值得注意的是: 在本例 Demo 中沒有給子組件傳參,但子組件也觸發了渲染。但從應用的角度上考慮,既然你子組件沒有需要更新的東西,那就沒有必要觸發渲染吧?
因此 Component 組件上可以使用 shouldComponentUpdate 或者將 Component 組件替換為 PureComponment 組件來做優化。在生命周期圖中也可以看到: shouldComponentUpdate 返回 false 時,將不再繼續觸發下面的函數。
有時你可能在某些情況下想主動觸發渲染而又不被 shouldComponentUpdate 阻止渲染該怎么辦呢?可以使用 force-Update() 跳過 shouldComponentUpdate 的 diff,進而渲染視圖。(需要使用強制渲染的場景較少,一般不推薦這種方式進行開發)
React 組件銷毀階段也沒啥好說的了。父組件先觸發銷毀前的函數,再逐層向下觸發:
[Parent]: componentWillUnmount [Parent]: 卸載階段結束! [Children]: componentWillUnmount [Children]: 卸載階段結束!其他生命周期
除了上圖比較常見的生命周期外,還有一些過時的 API 就沒有額外介紹了。因為它們可能在未來的版本會被移除。
- UNSAFE_componentWillMount()
- UNSAFE_componentWillUpdate()
- UNSAFE_componentWillReceiveProps()
當渲染過程,生命周期,或子組件的構造函數中拋出錯誤時,會調用如下方法:
- static getDerivedStateFromError()
- componentDidCatch()
React 組件通信
React.Context 怎么使用
Context 可以共享對于組件樹而言是全局的數據,比如全局主題、首選語言等。使用方式如下:
2. Context 對象中有一個 Provider(提供者) 組件,Provider 組件接受一個 value 屬性用以將數據傳遞給消費組件。
<ThemeContext.Provider value="dark"><page /> </ThemeContext.Provider>3. 獲取 Context 提供的值可以通過 contextType 或者 Consumer(消費者) 組件中獲取。contextType 只能用于類組件,并且只能掛載一個 Context:
class MyClass extends React.Component {componentDidMount() {let value = this.context;/* 在組件掛載完成后,使用 MyContext 的值執行一些有副作用的操作 */}render() {let value = this.context;/* 基于 MyContext 的值進行渲染 */} } MyClass.contextType = MyContext;若想給組件掛載多個 Context, 或者在函數組件內使用 Context 可以使用 Consumer 組件:
<ThemeContext.Consumer>{theme => (<UserContext.Consumer>{user => (<ProfilePage user={user} theme={theme} />)}</UserContext.Consumer>)}</ThemeContext.Consumer>Context 通常適用于傳遞較為簡單的數據信息,若數據太過復雜,還是需要引入狀態管理(Redux/Mbox)。
函數組件是什么?與類組件有什么區別?
函數組件本質上是一個純函數,它接受 props 屬性,最后返回 JSX。
與類組件的差別在于: 它沒有實例、不能通過 extends 繼承于其他方法、也沒有生命周期和 state。以前函數組件常作為無狀態組件,React 16.8+ 可以引入 Hooks 為函數組件支持狀態和副作用操作。
Hook vs class
類組件的不足:
- 狀態邏輯復用難,缺少復用機制。渲染屬性和高階組件導致層級冗余。
- 復雜組件變得難以理解。
- this 指向令人困擾。
Hooks 的優點:
- 自定義 Hook 方便復用狀態邏輯
- 副作用的關注點分離
- 函數組件沒有 this 問題
Hooks 現有的不足:
- 不能完全取代 class 組件的生命周期,部分不常用的生命周期暫時沒有實現。
- Hooks 的運作方式帶來了一定的學習成本,需要轉換現有的編程思維,增加了心智負擔。
Hooks 的使用
描述 Hooks 有哪些常用的方法和大致用途除此之外,useEffect 還可以返回一個函數用于做清除操作,這個清除操作時可選的。常用于清理訂閱事件、DOM 事件等。
// 綁定 DOM 事件 useEffect(() => {document.addEventListener('click', handleClick);// useEffect 回調函數的返回值是函數的話,當組件卸載時會執行該函數// 若沒有需要清除的東西,則可以忽略這一步驟return () => {document.removeEventListener('click', handleClick);}; }, [handleClick]);}, [handleClick]); ```3. useLayoutEffect: useEffect 的 effect 執行的時機是在瀏覽器完成布局和繪制之后會延遲調用。若想要 DOM 變更的同時同步執行 effect 的話可以使用 useLayoutEffect。它們之間只是執行的時機不同,其他都一樣。
4. useContext: 接收一個 Context 對象,并返回 Context 的當前值。相當于類組件的 static contextType = MyContext。
5. useReducer 是 useState 的代替方案,它的工作方式有點類似于 Redux,通過函數來操作 state。適合 state 邏輯較為復雜且包含多個子值,或是新的 state 依賴于舊的 state 的場景。
6. useMemo 主要用于性能優化,它可以緩存變量的值,避免每次組件更新后都需要重復計算值。
7. useCallbck 用于緩存函數,避免函數被重復創建,它是 useMemo 的語法糖。useCallback(fn, deps) 的效果相當于是 useMemo(() => fn, deps)。
自定義 Hook 的使用
自定義 Hook 的命名規則是以 use 開頭的函數,比如 useLocalStorage 就符合自定義 Hook 的命名規范。 使用自定義 Hook 的場景有很多,如表單處理、動畫、訂閱聲明、定時器等等可復用的邏輯都能通過自定義 Hook 來抽象實現。
在自定義 Hook 中,可以使用 Hooks 函數將可復用的邏輯和功能提取出來,并將內部的 state 或操作的方法從自定義 Hook 函數中返回出來。函數組件使用時就可以像調用普通函數一祥調用自定義 Hook 函數, 并將自定義 Hook 返回的 state 和操作方法通過解構保存到變量中。
下面是 useLocalStorage 的實現,它將 state 同步到本地存儲,以使其在頁面刷新后保持不變。 用法與 useState 相似,不同之處在于我們傳入了本地存儲鍵,以便我們可以在頁面加載時默認為該值,而不是指定的初始值。
import { useState } from 'react';// Usage function App() {// Similar to useState but first arg is key to the value in local storage.const [name, setName] = useLocalStorage('name', 'Bob');return (<div><inputtype="text"placeholder="Enter your name"value={name}onChange={e => setName(e.target.value)}/></div>); }// Hook function useLocalStorage(key, initialValue) {// State to store our value// Pass initial state function to useState so logic is only executed onceconst [storedValue, setStoredValue] = useState(() => {try {// Get from local storage by keyconst item = window.localStorage.getItem(key);// Parse stored json or if none return initialValuereturn item ? JSON.parse(item) : initialValue;} catch (error) {// If error also return initialValueconsole.log(error);return initialValue;}});// Return a wrapped version of useState's setter function that ...// ... persists the new value to localStorage.const setValue = value => {try {// Allow value to be a function so we have same API as useStateconst valueToStore =value instanceof Function ? value(storedValue) : value;// Save statesetStoredValue(valueToStore);// Save to local storagewindow.localStorage.setItem(key, JSON.stringify(valueToStore));} catch (error) {// A more advanced implementation would handle the error caseconsole.log(error);}};return [storedValue, setValue]; }注意: 自定義 Hook 函數在定義時,也可以使用另一個自定義 Hook 函數。
Hook 使用約束
class 組件與 Hook 之間的映射與轉換
函數組件相比 class 組件會缺少很多功能,但大多可以通過 Hook 的方式來實現。
生命周期
- constructor:class 組件的構造函數一般是用于初始化 state 數據或是給事件綁定 this 指向的。函數組件內沒有 this 指向的問題,因此可以忽略。而 state 可以通過 useState/useReducer 來實現。
- getDerivedStateFromProps:getDerivedStateFromProps 一般用于在組件 props 發生變化時派生 state。Hooks 實現同等效果如下:
``` jsx function ScrollView({row}) { const [isScrollingDown, setIsScrollingDown] = useState(false); const [prevRow, setPrevRow] = useState(null);
function ScrollView({row}) {const [isScrollingDown, setIsScrollingDown] = useState(false);const [prevRow, setPrevRow] = useState(null);if (row !== prevRow) {// Row 自上次渲染以來發生過改變。更新 isScrollingDown。setIsScrollingDown(prevRow !== null && row > prevRow);setPrevRow(row);}return `Scrolling down: ${isScrollingDown}`; }- shouldComponentUpdate: 使用 React.memo 應用到函數組件中后,當 props 發生變化時,會對 props 的新舊值進行前對比,相當于是 PureComponent 的功能。如果你還想自己定義比較函數的話,可以給 React.memo 的第二個參數傳一個函數,若函數返回 true 則跳過更新。
- render: 函數組件本身就是一個 render 函數。
- componentDidMount / componentDidUpdate / componentWillUnmount:
useEffect 第二個參數的依賴項為空時,相當于 componentDidMount,組件掛載后只會執行一次。每個 useEffect 返回的函數相當于是 componentWillUnmount 同等效果的操作。若有依賴,則 effect 函數相當于是 componentDidUpdate:
// 沒有依賴項,僅執行一次 useEffect(() => {const subscription = props.source.subscribe();// 相當于 componentWillUnmountreturn () => {subscription.unsubscribe();}; }, []);// 若有依賴項,相當于 componentDidUpdate // 當 page 發生變化時會觸發 effect 函數 useEffect(() => {fetchList({ page }); }, [page]);Hooks 沒有實現的生命周期鉤子
- getSnapshotBeforeUpdate
- getDerivedStateFromError
- componentDidCatch
轉換實例變量
使用 useRef 設置可變數據。
強制更新 Hook 組件
設置一個沒有實際作用的 state,然后強制更新 state 的值觸發渲染。
const Todo = () => {// 使用 useState,用隨機數據更新也行const [ignored, forceUpdate] = useReducer(x => x + 1, 0);function handleClick() {forceUpdate();}return <button click={handleClick}>強制更新組件</button> }獲取舊的 props 和 state
可以通過 useRef 來保存數據,因為渲染時不會覆蓋掉可變數據。
function Counter() {const [count, setCount] = useState(0);const prevCountRef = useRef();useEffect(() => {prevCountRef.current = count;}, []);const prevCount = prevCountRef.current;return <h1>Now: {count}, before: {prevCount}</h1>; }受控組件與非受控組件的區別
受控組件主要是指表單的值受到 state 的控制,它需要自行監聽 onChange 事件來更新 state。
由于受控組件每次都要編寫事件處理器才能更新 state 數據、可能會有點麻煩,React 提供另一種代替方案是非受控組件。
非受控組件將真實數據儲存在 DOM 節點中,它可以為表單項設置默認值,不需要手動更新數據。當需要用到表單數據時再通過 ref 從 DOM 節點中取出數據即可。
注意: 多數情況下React 推薦編寫受控組件。
擴展資料: 受控和非受控制使用場景的選擇
Portals 是什么?
Portals 就像個傳送門,它可以將子節點渲染到存在于父組件以外的 DOM 節點的方案。
比如 Dialog 是一個全局組件,按照傳統渲染組件的方式,Dialog 可能會受到其容器 css 的影響。因此可以使用 Portals 讓組件在視覺上渲染到 <body> 中,使其樣式不受 overflow: hidden 或 z-index 的影響。
- 下一篇文章: React 面試指南 (下)
- 原文出自: React 知識回顧 (使用篇) | Anran758's blog
- 前端面試筆記: front-end-lab
總結
以上是生活随笔為你收集整理的react取消捕获_React 面试指南 (上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pano2vr怎么制作漫游_春节7天长假
- 下一篇: 分区助手扩大c盘后自动修复_C盘空间不够