rfcs
rfcs copied to clipboard
RFC: `useIsolation`
This RFC related to:
-
useIsolation
in https://github.com/reactjs/rfcs/issues/168, -
useContextSelector
in https://github.com/reactjs/rfcs/pull/119.
This is about performance & memoization
If I understand correctly you want the equivalent of the following:
Usage:
import React from 'react'
export function App() {
console.log('render App')
return (
<IsolationProvider>
<Test />
</IsolationProvider>
);
}
function Test() {
console.log('render test')
const x = useIsolation(() => {
const [x, setX] = React.useState(0)
React.useEffect(
() => {
let interval = setInterval(() => setX(x => x + 1), 1000)
return () => { clearInterval(interval) }
},
[]
)
return React.useMemo(() => x - (x % 2), [x])
})
return <p>{x}</p>
}
Non-optimized user space implementation:
const isolationContext = React.createContext(null)
function useIsolation(unsafeHook) {
const hook = useEvent(unsafeHook)
const [result, setResult] = React.useState(null)
const registerHook = React.useContext(isolationContext)
React.useEffect(
() => registerHook({ hook, callback: (...args) => setTimeout(() => setResult(...args), 0) }),
[]
)
return result
}
function IsolationProvider({ children }) {
console.log('render isolation provider')
const [hookInfo, setHookInfo] = React.useState([])
const registerHook = React.useCallback(
(hookInfoToBeIsolated) => {
setHookInfo(existing => existing.concat(hookInfoToBeIsolated))
return function cleanup() {
setHookInfo(existing => existing.filter(info => info !== hookInfoToBeIsolated))
}
},
[]
)
return (
<isolationContext.Provider value={registerHook}>
{children}
{hookInfo.map((info, i) =>
// key should be handled differently
<Isolation key={i} {...{ info }} />
)}
</isolationContext.Provider>
)
}
function Isolation({ info }) {
const { callback, hook } = info
const result = hook()
console.log('hook executed', result)
useCallOnChange({ ifChanged: result, callback })
return null
}
function useCallOnChange({ ifChanged, callback }) {
const changeRef = React.useRef(null)
if (changeRef.current !== ifChanged) callback(ifChanged)
changeRef.current = ifChanged
}
function useEvent(f) {
const fRef = React.useRef(null)
fRef.current = f
return React.useCallback((...args) => fRef.current(...args), [])
}
If I understand correctly you want the equivalent of the following:
Yes, the idea is here with 2 differences:
-
useIsolation
could run synchronously during the 1st render (like auseMemo
) - you could also provide dependencies to
useIsolation(hook, [deps])
to conditionally re-call the hook when its parent scope changes (but not if hooks within it would trigger a re-render)
Thank you for the RFC. I wanted to note that we’ve had a very similar proposal planned except that we wanted to roll this behavior into the existing useMemo
Hook.