像对待组件一样考虑 React 上下文,如果你想更新一个值并显示它,那么你需要使用state。在这种情况下,您呈现上下文提供程序的AppWrapper 是您需要跟踪状态的位置。
import React, {useContext, useState, useCallback, useEffect} from 'react'
const PageContext = React.createContext({})
function Home() {
const {setPageContext, page} = useContext(PageContext)
// essentially a componentDidMount
useEffect(() => {
if (page.title !== 'Home')
setPageContext({title: 'Home'})
}, [setPageContext])
return <p>Hello, World!</p>
}
function App() {
const {page} = useContext(PageContext)
return (
<>
<h1>Title: {page.title}</h1>
<Home />
</>
)
}
function AppWrapper() {
const [state, setState] = useState({page: {}})
const setPageContext = useCallback(
newState => {
setState({page: {...state.page, ...newState}})
},
[state, setState],
)
const getContextValue = useCallback(
() => ({setPageContext, ...state}),
[state, updateState],
)
return (
<PageContext.Provider value={getContextValue()}>
<App />
</PageContext.Provider>
)
}
编辑 - 从链接存储库更新工作解决方案
我重命名了一些更具体的东西,我不建议通过上下文传递 setState,因为这可能会与组件中的本地状态混淆和冲突。另外我省略了答案不需要的代码块,只是我更改的部分
src/AppContext.js
export const updatePageContext = (values = {}) => ({ page: values })
export const updateProductsContext = (values = {}) => ({ products: values })
export const Pages = {
help: 'Help',
home: 'Home',
productsList: 'Products list',
shoppingCart: 'Cart',
}
const AppContext = React.createContext({})
export default AppContext
src/AppWrapper.js
const getDefaultState = () => {
// TODO rehydrate from persistent storage (localStorage.getItem(myLastSavedStateKey)) ?
return {
page: { title: 'Home' },
products: {},
}
}
function AppWrapper() {
const [state, setState] = useState(getDefaultState())
// here we only re-create setContext when its dependencies change ([state, setState])
const setContext = useCallback(
updates => {
setState({ ...state, ...updates })
},
[state, setState],
)
// here context value is just returning an object, but only re-creating the object when its dependencies change ([state, setContext])
const getContextValue = useCallback(
() => ({
...state,
setContext,
}),
[state, setContext],
)
return (
<Context.Provider value={getContextValue()}>
...
src/App.js
...
import AppContext, { updateProductsContext } from './AppContext'
function App() {
const [openDrawer, setOpenDrawer] = useState(false)
const classes = useStyles()
const {
page: { title },
setContext,
} = useContext(Context)
useEffect(() => {
fetch(...)
.then(...)
.then(items => {
setContext(updateProductsContext({ items }))
})
}, [])
src/components/DocumentMeta.js
这是一个新组件,您可以使用它以声明式样式更新页面名称,从而减少每个视图中的代码复杂性/冗余
import React, { useContext, useEffect } from 'react'
import Context, { updatePageContext } from '../Context'
export default function DocumentMeta({ title }) {
const { page, setContext } = useContext(Context)
useEffect(() => {
if (page.title !== title) {
// TODO use this todo as a marker to also update the actual document title so the browser tab name changes to reflect the current view
setContext(updatePageContext({ title }))
}
}, [title, page, setContext])
return null
}
aka 用法类似于<DocumentMeta title="Whatever Title I Want Here" />
src/pages/Home.js
现在每个视图只需要导入 DocumentMeta 和 Pages“枚举”来更新标题,而不是每次都将上下文拉入并手动执行。
import { Pages } from '../Context'
import DocumentMeta from '../components/DocumentMeta'
function Home() {
return (
<>
<DocumentMeta title={Pages.home} />
<h1>WIP</h1>
</>
)
}
注意:其他页面需要复制主页正在做的事情
请记住,这不是我在生产环境中执行此操作的方式,我会编写一个更通用的帮助程序来将数据写入缓存,它可以在性能、深度合并等方面做更多的事情。但是这应该是一个很好的起点。