Created
April 22, 2020 02:50
-
-
Save alanthai/0e4bdae4ad65114cc6608136f053e901 to your computer and use it in GitHub Desktop.
A more performant useReducer that only re-renders when the modified part of state is being read.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// example: https://stackblitz.com/edit/react-ts-hesbky?file=use-reducer.ts | |
// inspired by https://github.com/zeit/swr/pull/186/files | |
import {useState, useRef} from "react"; | |
type StateDependencies<T> = { | |
[K in keyof T]?: boolean; | |
}; | |
function equals(compareA, compareB) { | |
return compareA === compareB; | |
} | |
// only for shallow objects | |
export function useReducer<State extends object, Action>( | |
reducerFn: (state: State, action: Action) => State, | |
initialState: State, | |
comparator = equals | |
) { | |
const rerender = useState<any>({})[1]; | |
const stateDependencies = useRef<StateDependencies<State>>({}); | |
const stateRef = useRef<State>(initialState); | |
const countRef = useRef(0); | |
const trackersRef = useRef<Record<string, number>>({}); | |
let getTracker = () => { | |
countRef.current++; | |
getTracker = () => countRef.current; | |
return getTracker(); | |
}; | |
const dispatch = (action: Action) => { | |
let shouldUpdateState = false; | |
const state = { ...stateRef.current }; | |
const newState = reducerFn(stateRef.current, action); | |
stateRef.current = newState; | |
for (let k in newState) { | |
if (!comparator(state[k], newState[k])) { | |
if (stateDependencies.current[k] && trackersRef.current[k] === countRef.current) { | |
rerender({}); | |
break; | |
} | |
} | |
} | |
}; | |
const state: Readonly<State> = (() => { | |
const properties = Object.keys(stateRef.current).reduce((props, key) => { | |
props[key] = { | |
get: function () { | |
trackersRef.current[key] = getTracker(); | |
stateDependencies.current[key] = true; | |
return stateRef.current[key]; | |
}, | |
}; | |
return props; | |
}, {} as Readonly<State>); | |
return Object.defineProperties({}, properties); | |
})(); | |
return [state, dispatch] as const; | |
} | |
export function useMerge<State extends object>(initialState: State) { | |
return useReducer(Object.assign, initialState); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment