javascript - 組件因上下文中未使用的屬性而重新渲染
我有一個 AsyncContext,它允許我啟動/停止任何類型的異步計算。在幕後,它管理一個全局加載器和一個 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)
這裡有一個消費者:
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>
)
}
如你所見,MyChild 並不在意 loading。但它被包含在上下文中,所以這個元件無緣無故重渲染了兩次。
為了防止這種情況,我的第一個嘗試是將我的組件分成兩個,並使用 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>
)
})
它可以運作,但我不想拆分所有使用 AsyncContext 的子項目。
第二次嘗試是直接在 JSX 上使用 useMemo:
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>
), [])
}
它也可行,內容更精簡,但我不確定這是不是一個好的做法。
我的兩種方法有哪一種是正確的嗎?如果都不正確,你會建議什麼?
答案#1
我認為我找到了最佳的方法,多虧了 https://kentcdodds.com/blog/how-to-use-react-context-effectively:將 context 分成兩個 context。 一個用於 state,另一個用於 dispatch:
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})
如果消費者不需要這個狀態,我只需添加 const {start, stop}=useContext(AsyncDispatchContext),就不會有重新渲染。
