javascript - Komponent återrenderas på grund av oanvänd egenskap i context
Jag har en AsyncContext som låter mig starta/stoppa alla typer av asynkron beräkning. Bakom kulisserna hanterar den en global laddare och en snackbar.
export type Context = {
loading: boolean
start: () => void
stop: (message?: string) => void
}
const defaultContext: Context = {
loading: false,
start: noop,
stop: noop,
}
export const AsyncContext = createContext(defaultContext)
Här en konsument:
const MyChild: FC = () => {
const {start, stop} = useContext(AsyncContext)
async function fetchUser() {
try {
start()
const res = await axios.get('/user')
console.log(res.data)
stop()
} catch (e) {
stop('Error: ' + e.message)
}
}
return (
<button onClick={fetchData}>
Fetch data
</button>
)
}
Som du kan se, bryr sig MyChild inte om loading. Men det ingår i kontexten, så komponenten återrenderas 2 gånger i onödan.
För att förhindra detta är mitt första försök att dela upp min komponent i två och använda en memo:
type Props = {
start: AsyncContext['start']
stop: AsyncContext['stop']
}
const MyChild: FC = () => {
const {start, stop} = useContext(AsyncContext)
return <MyChildMemo start={start} stop={stop} />
}
const MyChildMemo: FC<Props> = memo(props => {
const {start, stop} = props
async function fetchUser() {
try {
start()
const res = await axios.get('/user')
console.log(res.data)
stop()
} catch (e) {
stop('Error: ' + e.message)
}
}
return (
<button onClick={fetchData}>
Fetch data
</button>
)
})
Det fungerar, men jag vill inte dela alla barn som använder AsyncContext.
Det andra försöket är att använda useMemo direkt på JSX:
const MyChild: FC = () => {
const {start, stop} = useContext(AsyncContext)
async function fetchUser() {
try {
start()
const res = await axios.get('/user')
console.log(res.data)
stop()
} catch (e) {
stop('Error: ' + e.message)
}
}
return useMemo(() => (
<button onClick={fetchData}>
Fetch data
</button>
), [])
}
Det fungerar också, det är mer kondenserat, men jag är inte säker på om detta är en bra praxis.
Är någon av mina två metoder korrekt? Om inte, vad skulle du råda?
Svar #1
Jag tror att jag har hittat det bästa tillvägagångssättet, tack vare https://kentcdodds.com/blog/how-to-use-react-context-effectively: att separera kontexten i två kontexter. En för tillståndet, en för dispatchen:
type StateContext = boolean
type DispatchContext = {
start: () => void
stop: (message?: string | void) => void
}
export const AsyncStateContext = createContext(false)
export const AsyncDispatchContext = createContext({start: noop, stop: noop})
Om en konsument inte behöver staten, lägger jag bara till const {start, stop}=useContext(AsyncDispatchContext) och jag slipper omrenderingar.
