React Hooks 完全使用指南

大家好,我是若川。最近組織了源碼共讀活動,感興趣的可以點此加我微信 ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。

React Hooks

Hook 是什么

Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。

Hook 是 React 團隊在 React 16.8 版本中提出的新特性,在遵循函數式組件的前提下,為已知的 React 概念提供了更直接的 API:props,state,context,refs 以及聲明周期,目的在于解決常年以來在 class 組件中存在的各種問題,實現更高效的編寫 react 組件

class 組件的不足

  • 難以復用組件間狀態邏輯:組件狀態邏輯的復用,需要 props render高階組件等解決方案,但是此類解決方案的抽象封裝將會導致層級冗余,形成“嵌套地獄”

  • 難以維護復雜組件

    • 許多不相干的邏輯代碼被混雜在同一個生命周期中,相關聯的邏輯代碼被拆分到不同聲明周期當中,容易遺忘導致產生bug

    • 組件常常充斥著狀態邏輯的訪問和處理,不能拆分為更小的粒度,可通過狀態管理庫集中管理狀態,但耦合了狀態管理庫又會導致組件復用性降低

  • this 指向問題:在 JavaScript 中,class 的方法默認不會綁定 this,當調用 class 的方法時 this 的值為 undefined,為了在方法中訪問 this 則必須在構造器中綁定使用 class fields 語法(實驗性語法)

    class?Example?extends?React.Component?{constructor(props)?{...//?方式1:?在構造函數中綁定?thisthis.handleClick?=?this.handleClick.bind(this);}handleClick()?{this.setState({...})}//?方式2:?使用?class?fields?語法handleClick?=?()?=>?{this.setState({...})}
    }
  • 難以對 class 進行編譯優化:由于 JavaScript 歷史設計原因,使用 class 組件會讓組件預編譯過程中變得難以進行優化,如 class 不能很好的壓縮,并且會使熱重載出現不穩定的情況

Hook 的優勢

  • Hook 使你在無需改變組件結構的情況下復用狀態邏輯(自定義 Hook)

  • Hook 將組件中互相關聯的部分拆分成更小的函數(比如設置訂閱或請求數據)

  • Hook 使你在非 class 的情況下可以使用更多的 React 特性

Hook 使用規則

Hook 就是 Javascript 函數,使用它們時有兩個額外的規則:

  • 只能在函數外層調用 Hook,不要在循環、條件判斷或者子函數中調用

  • 只能在 React 的函數組件自定義 Hook 中調用 Hook。不要在其他 JavaScript 函數中調用

在組件中 React 是通過判斷 Hook 調用的順序來判斷某個 state 對應的 useState的,所以必須保證 Hook 的調用順序在多次渲染之間保持一致,React 才能正確地將內部 state 和對應的 Hook 進行關聯

useState

useState 用于在函數組件中調用給組件添加一些內部狀態 state,正常情況下純函數不能存在狀態副作用,通過調用該 Hook 函數可以給函數組件注入狀態 state

useState 唯一的參數就是初始 state,會返回當前狀態和一個狀態更新函數,并且 useState 返回的狀態更新函數不會把新的 state 和舊的 state 進行合并,如需合并可使用 ES6 的對象結構語法進行手動合并

const?[state,?setState]?=?useState(initialState);

方法使用

import?React,?{?useState?}?from?'react';export?default?function?Counter()?{const?[count,?setCount]?=?useState(0);return?(<div><button?onClick={()?=>?setCount(count?-?1)}>-</button><input?type="text"?value={count}?onChange={(e)?=>?setCount(e.target.value)}?/><button?onClick={()?=>?setCount(count?+?1)}>+</button></div>);
}

等價 class 示例

useState 返回的狀態類似于 class 組件在構造函數中定義 this.state,返回的狀態更新函數類似于 class 組件的 this.setState

import?React?from?'react';export?default?class?Counter?extends?React.Component?{constructor(props)?{super(props);this.state?=?{count:?0};}render()?{return?(<div><button?onClick={()?=>?this.setState({?count:?this.state.count?-?1?})}>-</button><input?type="text"?value={this.state.count}?onChange={(e)?=>?this.setState({?count:?e.target.value?})}?/><button?onClick={()?=>?this.setState({?count:?this.state.count?+?1?})}>+</button></div>);}
}

函數式更新

如果新的 state 需要通過使用先前的 state 計算得出,可以往 setState 傳遞函數,該函數將接收先前的 state,并返回一個更新后的值

import?React,?{?useState?}?from?'react'export?default?function?Counter()?{const?[count,?setCount]?=?useState(0);const?lazyAdd?=?()?=>?{setTimeout(()?=>?{//?每次執行都會最新的state,而不是使用事件觸發時的statesetCount(count?=>?count?+?1);},?3000);}?return?(<div><p>the?count?now?is?{count}</p><button?onClick={()?=>?setCount(count?+?1)}>add</button><button?onClick={lazyAdd}>lazyAdd</button></div>);
}

惰性初始 state

如果初始 state 需要通過復雜計算獲得,則可以傳入一個函數,在函數中計算并返回初始的 state,此函數只會在初始渲染時被調用

import?React,?{?useState?}?from?'react'export?default?function?Counter(props)?{//?函數只在初始渲染時執行一次,組件重新渲染時該函數不會重新執行const?initCounter?=?()?=>?{console.log('initCounter');return?{?number:?props.number?};};const?[counter,?setCounter]?=?useState(initCounter);return?(<div><button?onClick={()?=>?setCounter({?number:?counter.number?-?1?})}>-</button><input?type="text"?value={counter.number}?onChange={(e)?=>?setCounter({?number:?e.target.value})}?/><button?onClick={()?=>?setCounter({?number:?counter.number?+?1?})}>+</button></div>);
}

跳過 state 更新

調用 State Hook 的更新函數時,React 將使用 Object.is 來比較前后兩次 state,如果返回結果為 true,React 將跳過子組件的渲染及 effect 的執行

import?React,?{?useState?}?from?'react';export?default?function?Counter()?{console.log('render?Counter');const?[counter,?setCounter]?=?useState({name:?'計時器',number:?0});//?修改狀態時傳的狀態值沒有變化,則不重新渲染return?(<div><p>{counter.name}:?{counter.number}</p><button?onClick={()?=>?setCounter({?...counter,?number:?counter.number?+?1})}>+</button><button?onClick={()?=>?setCounter(counter)}>++</button></div>);
}

useEffect

在函數組件主體內(React 渲染階段)改變 DOM、添加訂閱、設置定時器、記錄日志以及執行其他包含副作用的操作都是不被允許的,因為這可能會產生莫名其妙的 bug 并破壞 UI 的一致性

useEffect Hook 的使用則是用于完成此類副作用操作。useEffect 接收一個包含命令式、且可能有副作用代碼的函數

useEffect函數會在瀏覽器完成布局和繪制之后,下一次重新渲染之前執行,保證不會阻塞瀏覽器對屏幕的更新

useEffect(didUpdate);

方法使用

import?React,?{?useState,?useEffect?}?from?'react';export?default?function?Counter()?{const?[count,?setCount]?=?useState(0);//?useEffect?內的回調函數會在初次渲染后和更新完成后執行//?相當于?componentDidMount?和?componentDidUpdateuseEffect(()?=>?{document.title?=?`You?clicked?${count}?times`;});return?(<div><p>count?now?is?{count}</p><button?onClick={()?=>?setCount(count?+?1)}>+</button></div>);
}

等價 class 示例

useEffect Hook 函數執行時機類似于 class 組件的 componentDidMountcomponentDidUpdate 生命周期,不同的是傳給 useEffect 的函數會在瀏覽器完成布局和繪制之后進行異步執行

import?React?from?'react';export?default?class?Counter?extends?React.Component?{constructor(props)?{super(props);this.state?=?{count:?0,};}componentDidMount()?{document.title?=?`You?clicked?${this.state.count}?times`;}componentDidUpdate()?{document.title?=?`You?clicked?${this.state.count}?times`;}render()?{return?(<div><p>count?now?is?{this.state.count}</p><button?onClick={()?=>?this.setState({?count:?this.state.count?+?1?})}>+</button></div>);}
}

清除 effect

通常情況下,組件卸載時需要清除 effect 創建的副作用操作,useEffect Hook 函數可以返回一個清除函數,清除函數會在組件卸載前執行。組件在多次渲染中都會在執行下一個 effect 之前,執行該函數進行清除上一個 effect

清除函數的執行時機類似于 class 組件componentDidUnmount 生命周期,這的話使用 useEffect 函數可以將組件中互相關聯的部分拆分成更小的函數,防止遺忘導致不必要的內存泄漏

import?React,?{?useState,?useEffect?}?from?'react';export?default?function?Counter()?{const?[count,?setCount]?=?useState(0);useEffect(()?=>?{console.log('start?an?interval?timer')const?timer?=?setInterval(()?=>?{setCount((count)?=>?count?+?1);},?1000);//?返回一個清除函數,在組件卸載前和下一個effect執行前執行return?()?=>?{console.log('destroy?effect');clearInterval(timer);};},?[]);return?(<div><p>count?now?is?{count}</p><button?onClick={()?=>?setCount(count?+?1)}>+</button></div>);
}

優化 effect 執行

默認情況下,effect 會在每一次組件渲染完成后執行。useEffect 可以接收第二個參數,它是 effect 所依賴的值數組,這樣就只有當數組值發生變化才會重新創建訂閱。但需要注意的是:

  • 確保數組中包含了所有外部作用域中會發生變化且在 effect 中使用的變量

  • 傳遞一個空數組作為第二個參數可以使 effect 只會在初始渲染完成后執行一次

import?React,?{?useState,?useEffect?}?from?'react';export?default?function?Counter()?{const?[count,?setCount]?=?useState(0);useEffect(()?=>?{document.title?=?`You?clicked?${count}?times`;},?[count]);?//?僅在?count?更改時更新return?(<div><p>count?now?is?{count}</p><button?onClick={()?=>?setCount(count?+?1)}>+</button></div>);
}

useContext

Context 提供了一個無需為每層組件手動添加 props ,就能在組件樹間進行數據傳遞的方法,useContext 用于函數組件中訂閱上層 context 的變更,可以獲取上層 context 傳遞的 value prop 值

useContext 接收一個 context 對象(React.createContext的返回值)并返回 context 的當前值,當前的 context 值由上層組件中距離當前組件最近的 <MyContext.Provider>value prop 決定

const?value?=?useContext(MyContext);

方法使用

import?React,?{?useContext,?useState?}?from?'react';const?themes?=?{light:?{foreground:?"#000000",background:?"#eeeeee"},dark:?{foreground:?"#ffffff",background:?"#222222"}
};//?為當前?theme?創建一個?context
const?ThemeContext?=?React.createContext();export?default?function?Toolbar(props)?{const?[theme,?setTheme]?=?useState(themes.dark);const?toggleTheme?=?()?=>?{setTheme(currentTheme?=>?(currentTheme?===?themes.dark??themes.light:?themes.dark));};return?(//?使用?Provider?將當前?props.value?傳遞給內部組件<ThemeContext.Provider?value={{theme,?toggleTheme}}><ThemeButton?/></ThemeContext.Provider>);
}function?ThemeButton()?{//?通過?useContext?獲取當前?context?值const?{?theme,?toggleTheme?}?=?useContext(ThemeContext);return?(<button?style={{background:?theme.background,?color:?theme.foreground?}}?onClick={toggleTheme}>Change?the?button's?theme</button>);
}

等價 class 示例

useContext(MyContext) 相當于 class 組件中的 static contextType = MyContext 或者 <MyContext.Consumer>

useContext 并沒有改變消費 context 的方式,它只為我們提供了一種額外的、更漂亮的、更漂亮的方法來消費上層 context。在將其應用于使用多 context 的組件時將會非常有用

import?React?from?'react';const?themes?=?{light:?{foreground:?"#000000",background:?"#eeeeee"},dark:?{foreground:?"#ffffff",background:?"#222222"}
};const?ThemeContext?=?React.createContext(themes.light);function?ThemeButton()?{return?(<ThemeContext.Consumer>{({theme,?toggleTheme})?=>?(<button?style={{background:?theme.background,?color:?theme.foreground?}}?onClick={toggleTheme}>Change?the?button's?theme</button>)}</ThemeContext.Consumer>);
}export?default?class?Toolbar?extends?React.Component?{constructor(props)?{super(props);this.state?=?{theme:?themes.light};this.toggleTheme?=?this.toggleTheme.bind(this);}toggleTheme()?{this.setState(state?=>?({theme:state.theme?===?themes.dark??themes.light:?themes.dark}));}render()?{return?(<ThemeContext.Provider?value={{?theme:?this.state.theme,?toggleTheme:?this.toggleTheme?}}><ThemeButton?/></ThemeContext.Provider>)}
}

優化消費 context 組件

調用了 useContext 的組件都會在 context 值變化時重新渲染,為了減少重新渲染組件的較大開銷,可以通過使用 memoization 來優化

假設由于某種原因,您有 AppContext,其值具有 theme 屬性,并且您只想在 appContextValue.theme 更改上重新渲染一些 ExpensiveTree

  1. 方式1: 拆分不會一起更改的 context

function?Button()?{//?把?theme?context?拆分出來,其他?context?變化時不會導致?ExpensiveTree?重新渲染let?theme?=?useContext(ThemeContext);return?<ExpensiveTree?className={theme}?/>;
}
  1. 當不能拆分 context 時,將組件一分為二,給中間組件加上 React.memo

function?Button()?{let?appContextValue?=?useContext(AppContext);let?theme?=?appContextValue.theme;?//?獲取?theme?屬性return?<ThemedButton?theme={theme}?/>
}const?ThemedButton?=?memo(({?theme?})?=>?{//?使用?memo?盡量復用上一次渲染結果return?<ExpensiveTree?className={theme}?/>;
});
  1. 返回一個內置 useMemo 的組件

function?Button()?{let?appContextValue?=?useContext(AppContext);let?theme?=?appContextValue.theme;?//?獲取?theme?屬性return?useMemo(()?=>?{//?The?rest?of?your?rendering?logicreturn?<ExpensiveTree?className={theme}?/>;},?[theme])
}

useReducer

useReducer 作為 useState 的代替方案,在某些場景下使用更加適合,例如 state 邏輯較復雜且包含多個子值,或者下一個 state 依賴于之前的 state 等。

使用 useReducer 還能給那些會觸發深更新的組件做性能優化,因為父組件可以向自組件傳遞 dispatch 而不是回調函數

const?[state,?dispatch]?=?useReducer(reducer,?initialArg,?init);

方法使用

import?React,?{?useReducer?}?from?'react'const?initialState?=?{?count:?0?};function?reducer(state,?action)?{switch?(action.type)?{case?'increment':return?{count:?state.count?+?1};case?'decrement':return?{count:?state.count?-?1};default:throw?new?Error();}
}export?default?function?Counter()?{const?[state,?dispatch]?=?useReducer(reducer,?initialState);return?(<><p>Count:?{state.count}</p><button?onClick={()?=>?dispatch({type:?'decrement'})}>-</button><button?onClick={()?=>?dispatch({type:?'increment'})}>+</button></>);
}

初始化 state

useReducer 初始化 sate 的方式有兩種

//?方式1
const?[state,?dispatch]?=?useReducer(reducer,{count:?initialCount}
);//?方式2
function?init(initialClunt)?{return?{count:?initialClunt};
}const?[state,?dispatch]?=?useReducer(reducer,?initialCount,?init);

useRef

useRef 用于返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(initialValue

useRef 創建的 ref 對象就是一個普通的 JavaScript 對象,而 useRef() 和自建一個 {current: ...} 對象的唯一區別是,useRef 會在每次渲染時返回同一個 ref 對象

const?refContainer?=?useRef(initialValue);

綁定 DOM 元素

使用 useRef 創建的 ref 對象可以作為訪問 DOM 的方式,將 ref 對象以 <div ref={myRef} /> 形式傳入組件,React 會在組件創建完成后會將 ref 對象的 .current 屬性設置為相應的 DOM 節點

import?React,?{?useRef?}?from?'react'export?default?function?FocusButton()?{const?inputEl?=?useRef(null);const?onButtonClick?=?()?=>?{inputEl.current.focus();};return?(<><input?ref={inputEl}?type="text"?/><button?onClick={onButtonClick}>Focus?the?input</button></>);
}

綁定可變值

useRef 創建的 ref 對象同時可以用于綁定任何可變值,通過手動給該對象的.current 屬性設置對應的值即可

import?React,?{?useState,?useRef,?useEffect?}?from?'react';export?default?function?Counter()?{const?[count,?setCount]?=?useState(0);const?currentCount?=?useRef();//?使用?useEffect?獲取當前?countuseEffect(()?=>?{currentCount.current?=?count;},?[count]);const?alertCount?=?()?=>?{setTimeout(()?=>?{alert(`Current?count?is:?${currentCount.current},?Real?count?is:?${count}`);},?3000);}return?(<><p>count:?{count}</p><button?onClick={()?=>?setCount(count?+?1)}>Count?add</button><button?onClick={alertCount}>Alert?current?Count</button></>);
}

性能優化(useCallback & useMemo)

useCallbackuseMemo 結合 React.Memo 方法的使用是常見的性能優化方式,可以避免由于父組件狀態變更導致不必要的子組件進行重新渲染

useCallback

useCallback 用于創建返回一個回調函數,該回調函數只會在某個依賴項發生改變時才會更新,可以把回調函數傳遞給經過優化的并使用引用相等性去避免非必要渲染的子組件,在 props 屬性相同情況下,React 將跳過渲染組件的操作并直接復用最近一次渲染的結果

import?React,?{?useState,?useCallback?}?from?'react';function?SubmitButton(props)?{const?{?onButtonClick,?children?}?=?props;console.log(`${children}?updated`);return?(<button?onClick={onButtonClick}>{children}</button>);
}
//?使用?React.memo?檢查?props?變更,復用最近一次渲染結果
SubmitButton?=?React.memo(submitButton);export?default?function?CallbackForm()?{const?[count1,?setCount1]?=?useState(0);const?[count2,?setCount2]?=?useState(0);const?handleAdd1?=?()?=>?{setCount1(count1?+?1);}//?調用?useCallback?返回一個?memoized?回調,該回調在依賴項更新時才會更新const?handleAdd2?=?useCallback(()?=>?{setCount2(count2?+?1);},?[count2]);return?(<><div><p>count1:?{count1}</p><SubmitButton?onButtonClick={handleAdd1}>button1</SubmitButton></div><div><p>count2:?{count2}</p><SubmitButton?onButtonClick={handleAdd2}>button2</SubmitButton></div></>)
}

useCallback(fn, deps) 相當于 useMemo(() => fn, deps),以上 useCallback 可替換成 useMemo 結果如下:

const?handleAdd2?=?useMemo(()?=>?{return?()?=>?setCount2(count2?+?1);
},?[count2]);

useMemo

把“創建”函數和依賴項數組作為參數傳入 useMemo,它僅會在某個依賴項改變時才重新計算 memoized 值。這種優化有助于避免在每次渲染時都進行高開銷的計算

使用注意:

  • 傳入 useMemo 的函數會在渲染期間執行,不要在這個函數內部執行與渲染無關的操作

  • 如果沒有提供依賴項數組,useMemo 在每次渲染時都會計算新的值

import?React,?{?useState,?useMemo?}?from?'react';function?counterText({?countInfo?})?{console.log(`${countInfo.name}?updated`);return?(<p>{countInfo.name}:?{countInfo.number}</p>);
}
//?//?使用?React.memo?檢查?props?變更,復用最近一次渲染結果
const?CounterText?=?React.memo(counterText);export?default?function?Counter()?{const?[count1,?setCount1]?=?useState(0);const?[count2,?setCount2]?=?useState(0);const?countInfo1?=?{name:?'count1',number:?count1};//?使用?useMemo?緩存最近一次計算結果,會在依賴項改變時才重新計算const?countInfo2?=?useMemo(()?=>?({name:?'count2',number:?count2}),?[count2]);return?(<><div><CounterText?countInfo={countInfo1}?/><button?onClick={()?=>?setCount1(count1?+?1)}>Add?count1</button></div><div><CounterText?countInfo={countInfo2}?/><button?onClick={()?=>?setCount2(count2?+?1)}>Add?count2</button></div></>);
}

其他 Hook

useImperativeHandle

useImperativeHandle 可以讓你在使用 ref 時自定義暴露給父組件的實例值。在大多數情況下,應當避免使用 ref 這樣的命令式代碼。useImperativeHandle 應當與 React.forwardRef 一起使用:

import?React,?{?useRef,?useImperativeHandle,?useState?}?from?'react'function?FancyInput(props,?ref)?{const?inputRef?=?useRef();//?自定義暴露給父組件的?ref?實例值useImperativeHandle(ref,?()?=>?({focus:?()?=>?{inputRef.current.focus();}}));return?<input?ref={inputRef}?type="text"?{...props}?/>;
}
//?通過?forwardRef?向父組件傳遞暴露的?ref
const?ForwardFancyInput?=?React.forwardRef(FancyInput);export?default?function?Counter()?{const?[text,?setText]?=?useState('');const?inputRef?=?useRef();const?onInputFocus?=?()?=>?{inputRef.current.focus();};return?(<><ForwardFancyInput?ref={inputRef}?value={text}?onChange={e?=>?setText(e.target.value)}?/><button?onClick={onInputFocus}>Input?focus</button></>);
}

useLayoutEffect

useLayoutEffectuseEffect 類似,與 useEffect 在瀏覽器 layout 和 painting 完成后異步執行 effect 不同的是,它會在瀏覽器布局 layout 之后,painting 之前同步執行 effect

useLayoutEffect 的執行時機對比如下:

import?React,?{?useState,?useEffect,?useLayoutEffect?}?from?'react';export?default?function?LayoutEffect()?{const?[width,?setWidth]?=?useState('100px');//?useEffect?會在所有?DOM?渲染完成后執行?effect?回調useEffect(()?=>?{console.log('effect?width:?',?width);});//?useLayoutEffect?會在所有的?DOM?變更之后同步執行?effect?回調useLayoutEffect(()?=>?{console.log('layoutEffect?width:?',?width);});return?(<><div?id='content'?style={{?width,?background:?'red'?}}>內容</div><button?onClick={()?=>?setWidth('100px')}>100px</button><button?onClick={()?=>?setWidth('200px')}>200px</button><button?onClick={()?=>?setWidth('300px')}>300px</button></>);
}//?使用?setTimeout?保證在組件第一次渲染完成后執行,獲取到對應的?DOM
setTimeout(()?=>?{const?contentEl?=?document.getElementById('content');//?監視目標?DOM?結構變更,會在?useLayoutEffect?回調執行后,useEffect?回調執行前調用const?observer?=?new?MutationObserver(()?=>?{console.log('content?element?layout?updated');});observer.observe(contentEl,?{attributes:?true});
},?1000);

自定義Hook

通過自定義 Hook,可以將組件邏輯提取到可重用的函數中,在 Hook 特性之前,React 中有兩種流行的方式來共享組件之間的狀態邏輯:render props高階組件,但此類解決方案會導致組件樹的層級冗余等問題。而自定義 Hook 的使用可以很好的解決此類問題

創建自定義 Hook

自定義 Hook 是一個函數,其名稱以 “use” 開頭,函數內部可以調用其他的 Hook。以下就是實時獲取鼠標位置的自定義 Hook 實現:

import?{?useEffect,?useState?}?from?"react"export?const?useMouse?=?()?=>?{const?[position,?setPosition]?=?useState({x:?null,y:?null});useEffect(()?=>?{const?moveHandler?=?(e)?=>?{setPosition({x:?e.screenX,y:?e.screenY});};document.addEventListener('mousemove',?moveHandler);return?()?=>?{document.removeEventListener('mousemove',?moveHandler);};},?[]);return?position;
}

使用自定義 Hook

自定義 Hook 的使用規則與 Hook 使用規則基本一致,以下是 useMouse 自定義 Hook 的使用過程:

import?React?from?'react';
import?{?useMouse?}?from?'../hooks/useMouse';export?default?function?MouseMove()?{const?{?x,?y?}?=?useMouse();return?(<><p>Move?mouse?to?see?changes</p><p>x?position:?{x}</p><p>y?position:?{y}</p></>);
}

每次使用自定義 Hook 時,React 都會執行該函數來獲取獨立的 state 和執行獨立的副作用函數,所有 state 和副作用都是完全隔離的

參考文獻

[React Hooks 官方文檔](https://reactjs.org/docs/hooks-intro.html)

[詳解 React useCallback & useMemo](https://juejin.cn/post/6844904101445124110)

[Preventing rerenders with React.memo and useContext hook](https://github.com/facebook/react/issues/15156)

[MutationObserver MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver)

[useLayoutEffect和useEffect的區別](https://zhuanlan.zhihu.com/p/348701319)


4f198310444936db6ade09659f2428ef.gif

·················?若川簡介?·················

你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助3000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。

e550a89950d3f807a0299b5c74aeb32f.png

識別方二維碼加我微信、拉你進源碼共讀

今日話題

略。分享、收藏、點贊、在看我的文章就是對我最大的支持~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/275092.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/275092.shtml
英文地址,請注明出處:http://en.pswp.cn/news/275092.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

重新設計Videoland的登錄頁面— UX案例研究

In late October of 2019 me and our CRO lead Lucas, set up a project at Videoland to redesign our main landing page for prospect customers (if they already have a subscription, they will go to the actual streaming product).在2019年10月下旬&#xff0c;我和我…

【常見Web應用安全問題】---5、File Inclusion

Web應用程序的安全性問題依其存在的形勢劃分&#xff0c;種類繁多&#xff0c;這里不準備介紹所有的&#xff0c;只介紹常見的一些。 常見Web應用安全問題安全性問題的列表&#xff1a;   &#xff11;、跨站腳本攻擊(CSS or XSS, Cross Site Scripting)   &#xff12;、S…

全新的 Vue3 狀態管理工具:Pinia

大家好&#xff0c;我是若川。最近組織了源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。Vue3 發布已經有一段時間…

JS中變量和函數的使用

一、變量的介紹 1、啥是變量&#xff1f; 變量的本質是一塊有名字的內存空間。變量由變量名和變量值構成。變量名指的是內存空間的別名&#xff0c;一般位于賦值運算符的左邊&#xff1b;而變量值指的是內存空間中的數據&#xff0c;一般位于賦值運算符的右邊。例如:var balanc…

Win32 API消息函數:GetMessagePos

Win32 API消息函數:GetMessagePos 函數功能&#xff1a;該函數返回表示屏幕坐標下光標位置的長整數值。此位置表示當上一消息由GetMessage取得時鼠標占用的點。 函數原型&#xff1a;DWORD GetMessagePos&#xff08;VOID&#xff09; 參數&#xff1a;無。 返回值&…

都快 2022 年了,這些 Github 使用技巧你都會了嗎?

大家好&#xff0c;我是若川。最近組織了源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。最近經常有小伙伴問我如…

單線程+異步協程

一 . 線程池和進程池 可以適當的使用,在大量的IO情況下有更好的方法 import time from multiprocessing.dummy import Pool def request(url):print(正在下載->,url)time.sleep(2)print(下載完畢->,url) start time.time() urls [www.baidu.com,www.taobao.com,www.sou…

Repeater\DataList\GridView實現分頁,數據編輯與刪除

一、實現效果 1、GridView 2、DataList 3、Repeater 二、代碼 1、可以去Csdn資源下載&#xff0c;包含了Norwind中文示例數據庫噢&#xff01;&#xff08;放心下&#xff0c;不要資源分&#xff09; 下載地址&#xff1a;數據控件示例源碼Norwind中文數據庫 2、我的開發環境&a…

網站快速成型_我的老板對快速成型有什么期望?

網站快速成型Some of the top excuses I have gotten from clients when inviting them into a prototyping session are: “I am not a designer!” “I can’t draw!” “I have no creative background!”在邀請客戶參加原型制作會議時&#xff0c;我從客戶那里得到的一些主…

碎片化學前端,融入到積極上進的環境,我推薦~

眾所周知&#xff0c;關注公眾號可以了解學習掌握技術方向&#xff0c;學習優質好文&#xff0c;落實到自己項目中。還可以結交圈內好友&#xff0c;讓自己融入到積極上進的技術氛圍&#xff0c;促進自己的技術提升。話不多說&#xff0c;推薦這些優質前端公眾號前端之神 80w閱…

重學JavaScript深入理解系列(六)

JavaScript深入理解—-閉包(Closures) 概要 本文將介紹一個在JavaScript經常會拿來討論的話題 —— 閉包&#xff08;closure&#xff09;。閉包其實已經是個老生常談的話題了&#xff1b; 有大量文章都介紹過閉包的內容&#xff0c;盡管如此&#xff0c;這里還是要試著從理論角…

EXT.NET復雜布局(四)——系統首頁設計(上)

很久沒有發帖了&#xff0c;很是慚愧&#xff0c;因此給各位使用EXT.NET的朋友獻上一份禮物。 本篇主要講述頁面設計與效果&#xff0c;下篇將講述編碼并提供源碼下載。 系統首頁設計往往是個難點&#xff0c;因為往往要考慮以下因素&#xff1a; 重要通知系統功能菜單快捷操作…

figma設計_在Figma中使用隔片移交設計

figma設計I was quite surprised by how much the design community resonated with the concept of spacers since I published my 自從我發表論文以來&#xff0c;設計界對間隔件的概念產生了多少共鳴&#xff0c;我感到非常驚訝。 last story. It encouraged me to think m…

axios源碼中的10多個工具函數,值得一學~

大家好&#xff0c;我是若川。最近組織了源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。本文來自讀者Ethan01投稿…

安裝jenkins時出現 No such plugin: cloudbees-folder的解決辦法

今天安裝了一下jenkins&#xff0c;在初始化安裝插件時出現“ No such plugin: cloudbees-folder”錯誤&#xff0c;根據網上的教程&#xff1a; 1、打開鏈接“http://ftp.icm.edu.pl/packages/jenkins/plugins/cloudbees-folder/”&#xff0c;在最下面找到并打開“latest”目…

寄充氣娃娃怎么寄_我如何在5小時內寄出新設計作品集

寄充氣娃娃怎么寄Over the Easter break, I challenged myself to set aside an evening rethinking the structure, content and design of my portfolio in Notion with a focus on its 在復活節假期&#xff0c;我挑戰自己&#xff0c;把一個晚上放在一邊&#xff0c;重新思…

基于Hbase的用戶評分協同過濾推薦算法

基于Hbase的用戶評分協同過濾推薦算法 作者&#xff1a; 張保維 2012-1-3 一、 概述 本文為推薦引擎設計的基礎篇&#xff0c;介紹基于hbase 存儲方式用戶評分的方式進行推薦的主體算法及在分布式平臺環境下的實現。由于推薦算法分支眾多&#xff0c;我們先從簡單及實用的算法…

最全 JavaScript Array 方法 詳解

大家好&#xff0c;我是若川。最近組織了源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。我們在日常開發中&#…

[譯] React Hooks: 沒有魔法,只是數組

[譯] React Hooks: 沒有魔法&#xff0c;只是數組 原文鏈接&#xff1a; medium.com/ryardley/r… 我是 React 新特性 Hooks 的粉絲。但是&#xff0c;在你使用 React Hooks的過程中&#xff0c;有一些看上去 很奇怪的限制 。在本文里&#xff0c;對于那些還在為了理解這些限制…

管理溝通中移情的應用_移情在設計中的重要性

管理溝通中移情的應用One of the most important aspects of any great design is the empathetic understanding of and connection to the user. If a design is ‘selfish’, as in when a product designed with the designer in mind and not the user, it will ultimatel…