摘要:可以看到,這樣不僅沒有占用組件自己的,也不需要手寫回調(diào)函數(shù)進(jìn)行處理,這些處理都壓縮成了一行。效果通過拿到周期才執(zhí)行的回調(diào)函數(shù)。實現(xiàn)等價于的回調(diào)僅執(zhí)行一次時,因此直接把回調(diào)函數(shù)拋出來即可。
1 引言
上周的 精讀《React Hooks》 已經(jīng)實現(xiàn)了對 React Hooks 的基本認(rèn)知,也許你也看了 React Hooks 基本實現(xiàn)剖析(就是數(shù)組),但理解實現(xiàn)原理就可以用好了嗎?學(xué)的是知識,而用的是技能,看別人的用法就像刷抖音一樣(哇,飯還可以這樣吃?),你總會有新的收獲。
這篇文章將這些知識實踐起來,看看廣大程序勞動人民是如何發(fā)掘 React Hooks 的潛力的(造什么輪子)。
首先,站在使用角度,要理解 React Hooks 的特點是 “非常方便的 Connect 一切”,所以無論是數(shù)據(jù)流、Network,或者是定時器都可以監(jiān)聽,有一點 RXJS 的意味,也就是你可以利用 React Hooks,將 React 組件打造成:任何事物的變化都是輸入源,當(dāng)這些源變化時會重新觸發(fā) React 組件的 render,你只需要挑選組件綁定哪些數(shù)據(jù)源(use 哪些 Hooks),然后只管寫 render 函數(shù)就行了!
2 精讀參考了部分 React Hooks 組件后,筆者按照功能進(jìn)行了一些分類。
由于 React Hooks 并不是非常復(fù)雜,所以就不按照技術(shù)實現(xiàn)方式去分類了,畢竟技術(shù)總有一天會熟練,而且按照功能分類才有持久的參考價值。DOM 副作用修改 / 監(jiān)聽
做一個網(wǎng)頁,總有一些看上去和組件關(guān)系不大的麻煩事,比如修改頁面標(biāo)題(切換頁面記得改成默認(rèn)標(biāo)題)、監(jiān)聽頁面大小變化(組件銷毀記得取消監(jiān)聽)、斷網(wǎng)時提示(一層層裝飾器要堆成小山了)。而 React Hooks 特別擅長做這些事,造這種輪子,大小皆宜。
由于 React Hooks 降低了高階組件使用成本,那么一套生命周期才能完成的 “雜耍” 將變得非常簡單。
下面舉幾個例子:
修改頁面 title效果:在組件里調(diào)用 useDocumentTitle 函數(shù)即可設(shè)置頁面標(biāo)題,且切換頁面時,頁面標(biāo)題重置為默認(rèn)標(biāo)題 “前端精讀”。
useDocumentTitle("個人中心");
實現(xiàn):直接用 document.title 賦值,不能再簡單。在銷毀時再次給一個默認(rèn)標(biāo)題即可,這個簡單的函數(shù)可以抽象在項目工具函數(shù)里,每個頁面組件都需要調(diào)用。
function useDocumentTitle(title) { useEffect( () => { document.title = title; return () => (document.title = "前端精讀"); }, [title] ); }
在線 Demo
監(jiān)聽頁面大小變化,網(wǎng)絡(luò)是否斷開效果:在組件調(diào)用 useWindowSize 時,可以拿到頁面大小,并且在瀏覽器縮放時自動觸發(fā)組件更新。
const windowSize = useWindowSize(); return頁面高度:{windowSize.innerWidth};
實現(xiàn):和標(biāo)題思路基本一致,這次從 window.innerHeight 等 API 直接拿到頁面寬高即可,注意此時可以用 window.addEventListener("resize") 監(jiān)聽頁面大小變化,此時調(diào)用 setValue 將會觸發(fā)調(diào)用自身的 UI 組件 rerender,就是這么簡單!
最后注意在銷毀時,removeEventListener 注銷監(jiān)聽。
function getSize() { return { innerHeight: window.innerHeight, innerWidth: window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth }; } function useWindowSize() { let [windowSize, setWindowSize] = useState(getSize()); function handleResize() { setWindowSize(getSize()); } useEffect(() => { window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, []); return windowSize; }
在線 Demo
動態(tài)注入 css效果:在頁面注入一段 class,并且當(dāng)組件銷毀時,移除這個 class。
const className = useCss({ color: "red" }); returnText.;
實現(xiàn):可以看到,Hooks 方便的地方是在組件銷毀時移除副作用,所以我們可以安心的利用 Hooks 做一些副作用。注入 css 自然不必說了,而銷毀 css 只要找到注入的那段引用進(jìn)行銷毀即可,具體可以看這個 代碼片段。
DOM 副作用修改 / 監(jiān)聽場景有一些現(xiàn)成的庫了,從名字上就能看出來用法:document-visibility、network-status、online-status、window-scroll-position、window-size、document-title。組件輔助
Hooks 還可以增強(qiáng)組件能力,比如拿到并監(jiān)聽組件運行時寬高等。
獲取組件寬高效果:通過調(diào)用 useComponentSize 拿到某個組件 ref 實例的寬高,并且在寬高變化時,rerender 并拿到最新的寬高。
const ref = useRef(null); let componentSize = useComponentSize(ref); return ( <> {componentSize.width} > );
實現(xiàn):和 DOM 監(jiān)聽類似,這次換成了利用 ResizeObserver 對組件 ref 進(jìn)行監(jiān)聽,同時在組件銷毀時,銷毀監(jiān)聽。
其本質(zhì)還是監(jiān)聽一些副作用,但通過 ref 的傳遞,我們可以對組件粒度進(jìn)行監(jiān)聽和操作了。
useLayoutEffect(() => { handleResize(); let resizeObserver = new ResizeObserver(() => handleResize()); resizeObserver.observe(ref.current); return () => { resizeObserver.disconnect(ref.current); resizeObserver = null; }; }, []);
在線 Demo,對應(yīng)組件 component-size。
拿到組件 onChange 拋出的值效果:通過 useInputValue() 拿到 Input 框當(dāng)前用戶輸入的值,而不是手動監(jiān)聽 onChange 再騰一個 otherInputValue 和一個回調(diào)函數(shù)把這一堆邏輯寫在無關(guān)的地方。
let name = useInputValue("Jamie"); // name = { value: "Jamie", onChange: [Function] } return ;
可以看到,這樣不僅沒有占用組件自己的 state,也不需要手寫 onChange 回調(diào)函數(shù)進(jìn)行處理,這些處理都壓縮成了一行 use hook。
實現(xiàn):讀到這里應(yīng)該大致可以猜到了,利用 useState 存儲組件的值,并拋出 value 與 onChange,監(jiān)聽 onChange 并通過 setValue 修改 value, 就可以在每次 onChange 時觸發(fā)調(diào)用組件的 rerender 了。
function useInputValue(initialValue) { let [value, setValue] = useState(initialValue); let onChange = useCallback(function(event) { setValue(event.currentTarget.value); }, []); return { value, onChange }; }
這里要注意的是,我們對組件增強(qiáng)時,組件的回調(diào)一般不需要銷毀監(jiān)聽,而且僅需監(jiān)聽一次,這與 DOM 監(jiān)聽不同,因此大部分場景,我們需要利用 useCallback 包裹,并傳一個空數(shù)組,來保證永遠(yuǎn)只監(jiān)聽一次,而且不需要在組件銷毀時注銷這個 callback。
在線 Demo,對應(yīng)組件 input-value。
做動畫利用 React Hooks 做動畫,一般是拿到一些具有彈性變化的值,我們可以將值賦給進(jìn)度條之類的組件,這樣其進(jìn)度變化就符合某種動畫曲線。
在某個時間段內(nèi)獲取 0-1 之間的值這個是動畫最基本的概念,某個時間內(nèi)拿到一個線性增長的值。
效果:通過 useRaf(t) 拿到 t 毫秒內(nèi)不斷刷新的 0-1 之間的數(shù)字,期間組件會不斷刷新,但刷新頻率由 requestAnimationFrame 控制(不會卡頓 UI)。
const value = useRaf(1000);
實現(xiàn):寫起來比較冗長,這里簡單描述一下。利用 requestAnimationFrame 在給定時間內(nèi)給出 0-1 之間的值,那每次刷新時,只要判斷當(dāng)前刷新的時間點占總時間的比例是多少,然后做分母,分子是 1 即可。
在線 Demo,對應(yīng)組件 use-raf。
彈性動畫效果:通過 useSpring 拿到動畫值,組件以固定頻率刷新,而這個動畫值以彈性函數(shù)進(jìn)行增減。
實際調(diào)用方式一般是,先通過 useState 拿到一個值,再通過動畫函數(shù)包住這個值,這樣組件就會從原本的刷新一次,變成刷新 N 次,拿到的值也隨著動畫函數(shù)的規(guī)則變化,最后這個值會穩(wěn)定到最終的輸入值(如例子中的 50)。
const [target, setTarget] = useState(50); const value = useSpring(target); returnsetTarget(100)}>{value};
實現(xiàn):為了實現(xiàn)動畫效果,需要依賴 rebound 庫,它可以實現(xiàn)將一個目標(biāo)值拆解為符合彈性動畫函數(shù)過程的功能,那我們需要利用 React Hooks 做的就是在第一次接收到目標(biāo)值是,調(diào)用 spring.setEndValue 來觸發(fā)動畫事件,并在 useEffect 里做一次性監(jiān)聽,再值變時重新 setValue 即可。
最神奇的 setTarget 聯(lián)動 useSpring 重新計算彈性動畫部分,是通過 useEffect 第二個參數(shù)實現(xiàn)的:
useEffect( () => { if (spring) { spring.setEndValue(targetValue); } }, [targetValue] );
也就是當(dāng)目標(biāo)值變化后,才會進(jìn)行新的一輪 rerender,所以 useSpring 并不需要監(jiān)聽調(diào)用處的 setTarget,它只需要監(jiān)聽 target 的變化即可,而巧妙利用 useEffect 的第二個參數(shù)可以事半功倍。
在線 Demo
Tween 動畫明白了彈性動畫原理,Tween 動畫就更簡單了。
效果:通過 useTween 拿到一個從 0 變化到 1 的值,這個值的動畫曲線是 tween??梢钥吹?,由于取值范圍是固定的,所以我們不需要給初始值了。
const value = useTween();
實現(xiàn):通過 useRaf 拿到一個線性增長的值(區(qū)間也是 0 ~ 1),再通過 easing 庫將其映射到 0 ~ 1 到值即可。這里用到了 hook 調(diào)用 hook 的聯(lián)動(通過 useRaf 驅(qū)動 useTween),還可以在其他地方舉一反三。
const fn: Easing = easing[easingName]; const t = useRaf(ms, delay); return fn(t);發(fā)請求
利用 Hooks,可以將任意請求 Promise 封裝為帶有標(biāo)準(zhǔn)狀態(tài)的對象:loading、error、result。
通用 Http 封裝效果:通過 useAsync 將一個 Promise 拆解為 loading、error、result 三個對象。
const { loading, error, result } = useAsync(fetchUser, [id]);
實現(xiàn):在 Promise 的初期設(shè)置 loading,結(jié)束后設(shè)置 result,如果出錯則設(shè)置 error,這里可以將請求對象包裝成 useAsyncState 來處理,這里就不放出來了。
export function useAsync(asyncFunction) { const asyncState = useAsyncState(options); useEffect(() => { const promise = asyncFunction(); asyncState.setLoading(); promise.then( result => asyncState.setResult(result);, error => asyncState.setError(error); ); }, params); }
具體代碼可以參考 react-async-hook,這個功能建議僅了解原理,具體實現(xiàn)因為有一些邊界情況需要考慮,比如組件 isMounted 后才能相應(yīng)請求結(jié)果。
Request Service業(yè)務(wù)層一般會抽象一個 request service 做統(tǒng)一取數(shù)的抽象(比如統(tǒng)一 url,或者可以統(tǒng)一換 socket 實現(xiàn)等等)。假如以前比較 low 的做法是:
async componentDidMount() { // setState: 改 isLoading state try { const data = await fetchUser() // setState: 改 isLoading、error、data } catch (error) { // setState: 改 isLoading、error } }
后來把請求放在 redux 里,通過 connect 注入的方式會稍微有些改觀:
@Connect(...) class App extends React.PureComponent { public componentDidMount() { this.props.fetchUser() } public render() { // this.props.userData.isLoading | error | data } }
最后會發(fā)現(xiàn)還是 Hooks 簡潔明了:
function App() { const { isLoading, error, data } = useFetchUser(); }
而 useFetchUser 利用上面封裝的 useAsync 可以很容易編寫:
const fetchUser = id => fetch(`xxx`).then(result => { if (result.status !== 200) { throw new Error("bad status = " + result.status); } return result.json(); }); function useFetchUser(id) { const asyncFetchUser = useAsync(fetchUser, id); return asyncUser; }填表單
React Hooks 特別適合做表單,尤其是 antd form 如果支持 Hooks 版,那用起來會方便許多:
function App() { const { getFieldDecorator } = useAntdForm(); return (); }
不過雖然如此,getFieldDecorator 還是基于 RenderProps 思路的,徹底的 Hooks 思路是利用之前說的 組件輔助方式,提供一個組件方法集,用解構(gòu)方式傳給組件。
Hooks 思維的表單組件效果:通過 useFormState 拿到表單值,并且提供一系列 組件輔助 方法控制組件狀態(tài)。
const [formState, { text, password }] = useFormState(); return ();
上面可以通過 formState 隨時拿到表單值,和一些校驗信息,通過 password("pwd") 傳給 input 組件,讓這個組件達(dá)到受控狀態(tài),且輸入類型是 password 類型,表單 key 是 pwd。而且可以看到使用的 form 是原生標(biāo)簽,這種表單增強(qiáng)是相當(dāng)解耦的。
實現(xiàn):仔細(xì)觀察一下結(jié)構(gòu),不難發(fā)現(xiàn),我們只要結(jié)合 組件輔助 小節(jié)說的 “拿到組件 onChange 拋出的值” 一節(jié)的思路,就能輕松理解 text、password 是如何作用于 input 組件,并拿到其輸入狀態(tài)。
往簡單的來說,只要把這些狀態(tài) Merge 起來,通過 useReducer 聚合到 formState 就可以實現(xiàn)了。
為了簡化,我們只考慮對 input 的增強(qiáng),源碼僅需 30 幾行:
export function useFormState(initialState) { const [state, setState] = useReducer(stateReducer, initialState || {}); const createPropsGetter = type => (name, ownValue) => { const hasOwnValue = !!ownValue; const hasValueInState = state[name] !== undefined; function setInitialValue() { let value = ""; setState({ [name]: value }); } const inputProps = { name, // 給 input 添加 type: text or password get value() { if (!hasValueInState) { setInitialValue(); // 給初始化值 } return hasValueInState ? state[name] : ""; // 賦值 }, onChange(e) { let { value } = e.target; setState({ [name]: value }); // 修改對應(yīng) Key 的值 } }; return inputProps; }; const inputPropsCreators = ["text", "password"].reduce( (methods, type) => ({ ...methods, [type]: createPropsGetter(type) }), {} ); return [ { values: state }, // formState inputPropsCreators ]; }
上面 30 行代碼實現(xiàn)了對 input 標(biāo)簽類型的設(shè)置,監(jiān)聽 value onChange,最終聚合到大的 values 作為 formState 返回。讀到這里應(yīng)該發(fā)現(xiàn)對 React Hooks 的應(yīng)用都是萬變不離其宗的,特別是對組件信息的獲取,通過解構(gòu)方式來做,Hooks 內(nèi)部再做一下聚合,就完成表單組件基本功能了。
實際上一個完整的輪子還需要考慮 checkbox radio 的兼容,以及校驗問題,這些思路大同小異,具體源碼可以看 react-use-form-state。
模擬生命周期有的時候 React15 的 API 還是挺有用的,利用 React Hooks 幾乎可以模擬出全套。
componentDidMount效果:通過 useMount 拿到 mount 周期才執(zhí)行的回調(diào)函數(shù)。
useMount(() => { // quite similar to `componentDidMount` });
實現(xiàn):componentDidMount 等價于 useEffect 的回調(diào)(僅執(zhí)行一次時),因此直接把回調(diào)函數(shù)拋出來即可。
useEffect(() => void fn(), []);componentWillUnmount
效果:通過 useUnmount 拿到 unmount 周期才執(zhí)行的回調(diào)函數(shù)。
useUnmount(() => { // quite similar to `componentWillUnmount` });
實現(xiàn):componentWillUnmount 等價于 useEffect 的回調(diào)函數(shù)返回值(僅執(zhí)行一次時),因此直接把回調(diào)函數(shù)返回值拋出來即可。
useEffect(() => fn, []);componentDidUpdate
效果:通過 useUpdate 拿到 didUpdate 周期才執(zhí)行的回調(diào)函數(shù)。
useUpdate(() => { // quite similar to `componentDidUpdate` });
實現(xiàn):componentDidUpdate 等價于 useMount 的邏輯每次執(zhí)行,除了初始化第一次。因此采用 mouting flag(判斷初始狀態(tài))+ 不加限制參數(shù)確保每次 rerender 都會執(zhí)行即可。
const mounting = useRef(true); useEffect(() => { if (mounting.current) { mounting.current = false; } else { fn(); } });Force Update
效果:這個最有意思了,我希望拿到一個函數(shù) update,每次調(diào)用就強(qiáng)制刷新當(dāng)前組件。
const update = useUpdate();
實現(xiàn):我們知道 useState 下標(biāo)為 1 的項是用來更新數(shù)據(jù)的,而且就算數(shù)據(jù)沒有變化,調(diào)用了也會刷新組件,所以我們可以把返回一個沒有修改數(shù)值的 setValue,這樣它的功能就僅剩下刷新組件了。
const useUpdate = () => useState(0)[1];
對于 getSnapshotBeforeUpdate, getDerivedStateFromError, componentDidCatch 目前 Hooks 是無法模擬的。isMounted
很久以前 React 是提供過這個 API 的,后來移除了,原因是可以通過 componentWillMount 和 componentWillUnmount 推導(dǎo)。自從有了 React Hooks,支持 isMount 簡直是分分鐘的事。
效果:通過 useIsMounted 拿到 isMounted 狀態(tài)。
const isMounted = useIsMounted();
實現(xiàn):看到這里的話,應(yīng)該已經(jīng)很熟悉這個套路了,useEffect 第一次調(diào)用時賦值為 true,組件銷毀時返回 false,注意這里可以加第二個參數(shù)為空數(shù)組來優(yōu)化性能。
const [isMount, setIsMount] = useState(false); useEffect(() => { if (!isMount) { setIsMount(true); } return () => setIsMount(false); }, []); return isMount;
在線 Demo
存數(shù)據(jù)上一篇提到過 React Hooks 內(nèi)置的 useReducer 可以模擬 Redux 的 reducer 行為,那唯一需要補(bǔ)充的就是將數(shù)據(jù)持久化。我們考慮最小實現(xiàn),也就是全局 Store + Provider 部分。
全局 Store效果:通過 createStore 創(chuàng)建一個全局 Store,再通過 StoreProvider 將 store 注入到子組件的 context 中,最終通過兩個 Hooks 進(jìn)行獲取與操作:useStore 與 useAction:
const store = createStore({ user: { name: "小明", setName: (state, payload) => { state.name = payload; } } }); const App = () => (); function YourApp() { const userName = useStore(state => state.user.name); const setName = userAction(dispatch => dispatch.user.setName); }
實現(xiàn):這個例子的實現(xiàn)可以多帶帶拎出一篇文章了,所以筆者從存數(shù)據(jù)的角度剖析一下 StoreProvider 的實現(xiàn)。
對,Hooks 并不解決 Provider 的問題,所以全局狀態(tài)必須有 Provider,但這個 Provider 可以利用 React 內(nèi)置的 createContext 簡單搞定:
const StoreContext = createContext(); const StoreProvider = ({ children, store }) => ({children} );
剩下就是 useStore 怎么取到持久化 Store 的問題了,這里利用 useContext 和剛才創(chuàng)建的 Context 對象:
const store = useContext(StoreContext); return store;
更多源碼可以參考 easy-peasy,這個庫基于 redux 編寫,提供了一套 Hooks API。
封裝原有庫是不是 React Hooks 出現(xiàn)后,所有的庫都要重寫一次?當(dāng)然不是,我們看看其他庫如何做改造。
RenderProps to Hooks這里拿 react-powerplug 舉例。
比如有一個 renderProps 庫,希望改造成 Hooks 的用法:
import { Toggle } from "react-powerplug" function App() { return ({({ on, toggle }) => ( ) } ↓ ↓ ↓ ↓ ↓ ↓ import { useToggle } from "react-powerhooks" function App() { const [on, toggle] = useToggle() return)} }
效果:假如我是 react-powerplug 的維護(hù)者,怎么樣最小成本支持 React Hook? 說實話這個沒辦法一步做到,但可以通過兩步實現(xiàn)。
export function Toggle() { // 這是 Toggle 的源碼 // balabalabala.. } const App = wrap(() => { // 第一步:包 wrap const [on, toggle] = useRenderProps(Toggle); // 第二步:包 useRenderProps });
實現(xiàn):首先解釋一下為什么要包兩層,首先 Hooks 必須遵循 React 的規(guī)范,我們必須寫一個 useRenderProps 函數(shù)以符合 Hooks 的格式,那問題是如何拿到 Toggle 給 render 的 on 與 toggle?正常方式應(yīng)該拿不到,所以退而求其次,將 useRenderProps 拿到的 Toggle 傳給 wrap,讓 wrap 構(gòu)造 RenderProps 執(zhí)行環(huán)境拿到 on 與 toggle 后,調(diào)用 useRenderProps 內(nèi)部的 setArgs 函數(shù),讓 const [on, toggle] = useRenderProps(Toggle) 實現(xiàn)曲線救國。
const wrappers = []; // 全局存儲 wrappers export const useRenderProps = (WrapperComponent, wrapperProps) => { const [args, setArgs] = useState([]); const ref = useRef({}); if (!ref.current.initialized) { wrappers.push({ WrapperComponent, wrapperProps, setArgs }); } useEffect(() => { ref.current.initialized = true; }, []); return args; // 通過下面 wrap 調(diào)用 setArgs 獲取值。 };
由于 useRenderProps 會先于 wrap 執(zhí)行,所以 wrappers 會先拿到 Toggle,wrap 執(zhí)行時直接調(diào)用 wrappers.pop() 即可拿到 Toggle 對象。然后構(gòu)造出 RenderProps 的執(zhí)行環(huán)境即可:
export const wrap = FunctionComponent => props => { const element = FunctionComponent(props); const ref = useRef({ wrapper: wrappers.pop() }); // 拿到 useRenderProps 提供的 Toggle const { WrapperComponent, wrapperProps } = ref.current.wrapper; return createElement(WrapperComponent, wrapperProps, (...args) => { // WrapperComponent => Toggle,這一步是在構(gòu)造 RenderProps 執(zhí)行環(huán)境 if (!ref.current.processed) { ref.current.wrapper.setArgs(args); // 拿到 on、toggle 后,通過 setArgs 傳給上面的 args。 ref.current.processed = true; } else { ref.current.processed = false; } return element; }); };
以上實現(xiàn)方案參考 react-hooks-render-props,有需求要可以拿過來直接用,不過實現(xiàn)思路可以參考,作者的腦洞挺大。
Hooks to RenderProps好吧,如果希望 Hooks 支持 RenderProps,那一定是希望同時支持這兩套語法。
效果:一套代碼同時支持 Hooks 和 RenderProps。
實現(xiàn):其實 Hooks 封裝為 RenderProps 最方便,因此我們使用 Hooks 寫核心的代碼,假設(shè)我們寫一個最簡單的 Toggle:
const useToggle = initialValue => { const [on, setOn] = useState(initialValue); return { on, toggle: () => setOn(!on) }; };
在線 Demo
然后通過 render-props 這個庫可以輕松封裝出 RenderProps 組件:
const Toggle = ({ initialValue, children, render = children }) => renderProps(render, useToggle(initialValue));
在線 Demo
其實 renderProps 這個組件的第二個參數(shù),在 Class 形式 React 組件時,接收的是 this.state,現(xiàn)在我們改成 useToggle 返回的對象,也可以理解為 state,利用 Hooks 機(jī)制驅(qū)動 Toggle 組件 rerender,從而讓子組件 rerender。
封裝原本對 setState 增強(qiáng)的庫Hooks 也特別適合封裝原本就作用于 setState 的庫,比如 immer。
useState 雖然不是 setState,但卻可以理解為控制高階組件的 setState,我們完全可以封裝一個自定義的 useState,然后內(nèi)置對 setState 的優(yōu)化。
比如 immer 的語法是通過 produce 包裝,將 mutable 代碼通過 Proxy 代理為 immutable:
const nextState = produce(baseState, draftState => { draftState.push({ todo: "Tweet about it" }); draftState[1].done = true; });
那這個 produce 就可以通過封裝一個 useImmer 來隱藏掉:
function useImmer(initialValue) { const [val, updateValue] = React.useState(initialValue); return [ val, updater => { updateValue(produce(updater)); } ]; }
使用方式:
const [value, setValue] = useImmer({ a: 1 }); value(obj => (obj.a = 2)); // immutable3 總結(jié)
本文列出了 React Hooks 的以下幾種使用方式以及實現(xiàn)思路:
DOM 副作用修改 / 監(jiān)聽。
組件輔助。
做動畫。
發(fā)請求。
填表單。
模擬生命周期。
存數(shù)據(jù)。
封裝原有庫。
歡迎大家的持續(xù)補(bǔ)充。
4 更多討論討論地址是:精讀《怎么用 React Hooks 造輪子》 · Issue #112 · dt-fe/weekly
如果你想?yún)⑴c討論,請點擊這里,每周都有新的主題,周末或周一發(fā)布。前端精讀 - 幫你篩選靠譜的內(nèi)容。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/108696.html
摘要:未來可能成為官方之一。討論地址是精讀組件如果你想?yún)⑴c討論,請點擊這里,每周都有新的主題,周末或周一發(fā)布。前端精讀幫你篩選靠譜的內(nèi)容。 1. 引言 為什么要了解 Function 寫法的組件呢?因為它正在變得越來越重要。 那么 React 中 Function Component 與 Class Component 有何不同? how-are-function-components-di...
摘要:拿到的都是而不是原始值,且這個值會動態(tài)變化。精讀對于的與,筆者做一些對比。因此采取了作為優(yōu)化方案只有當(dāng)?shù)诙€依賴參數(shù)變化時才返回新引用。不需要使用等進(jìn)行性能優(yōu)化,所有性能優(yōu)化都是自動的。前端精讀幫你篩選靠譜的內(nèi)容。 1. 引言 Vue 3.0 的發(fā)布引起了軒然大波,讓我們解讀下它的 function api RFC 詳細(xì)了解一下 Vue 團(tuán)隊是怎么想的吧! 首先官方回答了幾個最受關(guān)注的...
摘要:今天我們就來解讀一下的源碼。比較有意思,將定時器以方式提供出來,并且提供了方法。實現(xiàn)方式是,在組件內(nèi)部維護(hù)一個定時器,實現(xiàn)了組件更新銷毀時的計時器更新銷毀操作,可以認(rèn)為這種定時器的生命周期綁定了組件的生命周期,不用擔(dān)心銷毀和更新的問題。 1. 引言 React PowerPlug 是利用 render props 進(jìn)行更好狀態(tài)管理的工具庫。 React 項目中,一般一個文件就是一個類,...
摘要:引言于發(fā)布版本,時至今日已更新到,且引入了大量的令人振奮的新特性,本文章將帶領(lǐng)大家根據(jù)更新的時間脈絡(luò)了解的新特性。其作用是根據(jù)傳遞的來更新。新增等指針事件。 1 引言 于 2017.09.26 Facebook 發(fā)布 React v16.0 版本,時至今日已更新到 React v16.6,且引入了大量的令人振奮的新特性,本文章將帶領(lǐng)大家根據(jù) React 更新的時間脈絡(luò)了解 React1...
摘要:需要說明是的,這里說的專家不再關(guān)心細(xì)節(jié),不代表成為專家后學(xué)不會細(xì)節(jié),也不代表專家不了解細(xì)節(jié)。本文將從三個點去解釋,為什么專家看上去越來越原理技術(shù)細(xì)節(jié)。試想一個不能理解業(yè)務(wù)要做什么的人,即便懂得再多技術(shù)細(xì)節(jié),對業(yè)務(wù)也是沒有價值的。1. 引言 本周的精讀是有感而發(fā)。 筆者接觸前端已有八年,觀察了不少前端大牛的發(fā)展路徑,發(fā)現(xiàn)成功的人都具有相似的經(jīng)歷: 初期技術(shù)熱情極大 -> 大量標(biāo)志性技術(shù)項目 -...
閱讀 937·2023-04-26 01:34
閱讀 3371·2023-04-25 20:58
閱讀 3324·2021-11-08 13:22
閱讀 2126·2019-08-30 14:17
閱讀 2537·2019-08-29 15:27
閱讀 2687·2019-08-29 12:45
閱讀 3011·2019-08-29 12:26
閱讀 2824·2019-08-28 17:51