由于上下文中未使用 属性,组件重新呈现
Component re-renders because of unused property in context
我有一个 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
。但是它包含在上下文中,所以组件重新渲染了 2 次。
为了防止这种情况,我的第一个尝试是将我的组件一分为二,并使用 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>
), [])
}
它也有效,它更简洁,但我不确定这是否是一个好的做法。
我的两种方法是否正确?如果没有,你有什么建议?
我想我找到了最好的方法,多亏了https://kentcdodds.com/blog/how-to-use-react-context-effectively:在两个上下文中分离上下文。一份给国家,一份给派遣:
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)
而我没有 re-renders。
我有一个 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
。但是它包含在上下文中,所以组件重新渲染了 2 次。
为了防止这种情况,我的第一个尝试是将我的组件一分为二,并使用 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>
), [])
}
它也有效,它更简洁,但我不确定这是否是一个好的做法。
我的两种方法是否正确?如果没有,你有什么建议?
我想我找到了最好的方法,多亏了https://kentcdodds.com/blog/how-to-use-react-context-effectively:在两个上下文中分离上下文。一份给国家,一份给派遣:
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)
而我没有 re-renders。