在随后的任何调用之后,通过“调度程序”设置为“搜索项”的第一个值仍然存在,我试图找出原因或错误所在。
我有一个<ContextProvider />
,其中定义了“搜索词”的状态,并且“搜索词”的值可能会因<ContextConsumer />
触发的事件而改变,或者由“调度程序”嵌套<ContextConsumer />
组件。我发现在调用“ reducer”之后没有找到所需的状态,即使考虑到“ state”更改不是立即发生的。
为简便起见,下面发布的组件或代码经过简化以隔离主题,因此可能会有一些拼写错误,例如未声明的变量(因为我删除了不相关的代码块)。
上下文提供者看起来像:
import React from 'react'
export const POSTS_SEARCH_RESULTS = 'POSTS_SEARCH_RESULTS'
export const GlobalStateContext = React.createContext()
export const GlobalDispatchContext = React.createContext()
const initialState = {
posts: [],
searchTerm: ''
}
const reducer = (state, action) => {
switch (action.type) {
case POSTS_SEARCH_RESULTS: {
return {
...state,
posts: action.posts,
searchTerm: action.searchTerm
}
}
default:
throw new Error('Bad Action Type')
}
}
const GlobalContextProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(reducer, initialState)
return (
<GlobalStateContext.Provider value={state}>
<GlobalDispatchContext.Provider value={dispatch}>
{children}
</GlobalDispatchContext.Provider>
</GlobalStateContext.Provider>
)
}
export default GlobalContextProvider
消费者看起来像:
const Search = () => {
const state = useContext(GlobalStateContext)
const { searchTerm, posts } = state
useEffect(() => {
console.log('[debug] <Search />: searchTerm: ', searchTerm);
}, [searchTerm])
return (
<>
<LoadMoreScroll searchTerm={searchTerm} posts={posts} postCursor={postCursor} />
</>
)
}
export default Search
接下来是嵌套的Consumer Children组件。 useEffect
具有对searchTerm
的依赖关系;此值是通过“分发程序”设置的,并通过Consumer中的useContenxt获取。
dispatch({ type: POSTS_SEARCH_RESULTS, posts: postsCached, searchTerm: term })
并像这样消耗:
const state = useContext(GlobalStateContext)
const { searchTerm, posts } = state
并传递给例如<LoadMoreScroll searchTerm={searchTerm} />
所以,我所拥有的但失败的是:
const LoadMoreScroll = ({ searchTerm, posts, postCursor }) => {
const dispatch = useContext(GlobalDispatchContext)
const [postsCached, setPostsCached] = useState(posts)
const [loading, setLoading] = useState(false)
const refScroll = useRef(null)
const [first] = useState(POSTS_SEARCH_INITIAL_NUMBER)
const [after, setAfter] = useState(postCursor)
const [isVisible, setIsVisible] = useState(false)
const [term, setTerm] = useState(searchTerm)
useEffect(() => {
loadMore({ first, after, term })
}, [isVisible])
useEffect(() => {
dispatch({ type: POSTS_SEARCH_RESULTS, posts: postsCached, searchTerm })
}, [postsCached])
useEffect(() => {
setTerm(searchTerm)
const handler = _debounce(handleScroll, 1200)
window.addEventListener('scroll', handler)
return () => window.removeEventListener('scroll', handler)
}, [searchTerm])
const handleScroll = () => {
const offset = -(window.innerHeight * 0.1)
const top = refScroll.current.getBoundingClientRect().top
const isVisible = (top + offset) >= 0 && (top - offset) <= window.innerHeight
isVisible && setIsVisible(true)
}
const loadMore = async ({ first, after, term }) => {
if (loading) return
setLoading(true)
const result = await searchFor({
first,
after,
term
})
const nextPosts = result.data
setPostsCached([...postsCached, ...nextPosts])
setAfter(postCursor)
setLoading(false)
setIsVisible(false)
}
return (
<div ref={refScroll} className={style.loaderContainer}>
{ loading && <Loader /> }
</div>
)
}
export default LoadMoreScroll
预期结果是让<LoadMoreScroll />
将由“调度程序”分配的“ searchTerm”的最新值传递给“ loadMore”函数,该值失败。相反,它所做的是从第一次调用“调度程序”时就消耗了“初始值”。这是在首次调用“调度程序”之后进行的任何后续“调度程序”调用之后:
dispatch({ type: POSTS_SEARCH_RESULTS, posts: postsCached, searchTerm: term })
该应更新上下文“ searchTerm”,但无法执行。在上面的源代码中,loadmore保存了设置的初始值!
单独的示例具有类似的逻辑,可以正常工作(https://codesandbox.io/s/trusting-booth-1w40e?fontsize=14&hidenavigation=1&theme=dark)
希望尽快用解决方案更新上述问题,以防万一有人发现该问题,请告诉我!
答案 0 :(得分:1)
codesandbox链接有效,但是在创建和使用context
时似乎没有使用与上面的代码相同的模式。
在提供的代码中,您创建了两个单独的提供程序。一个具有状态值,一个具有调度值。
<GlobalStateContext.Provider value={state}>
<GlobalDispatchContext.Provider value={dispatch}>
codesandbox在同一state
中同时使用dispatch
和provider
。
<Application.Provider value={{ state, dispatch }}>
另外,似乎GlobalContextProvider
已导出,但是我不确定是否用于包装任何使用者。
由于dispatch
和state
之间是分开的,因此我将使用它作为建议的解决方案。
该实现似乎是正确的,但我认为您可以更进一步,并创建两个自定义钩子,这些钩子仅公开提供上下文值的一种方法和使用它的唯一方法。
import React from "react";
export const POSTS_SEARCH_RESULTS = "POSTS_SEARCH_RESULTS";
//
// notice that we don't need to export these anymore as we are going to be
//
// using them in our custom hooks useGlobalState and useGlobalDispatch
//
//
const GlobalStateContext = React.createContext();
const GlobalDispatchContext = React.createContext();
const initialState = {
posts: [],
searchTerm: "",
};
const reducer = (state, action) => {
switch (action.type) {
case POSTS_SEARCH_RESULTS: {
return {
...state,
posts: action.posts,
searchTerm: action.searchTerm
};
}
default:
throw new Error("Bad Action Type");
}
};
const GlobalContextProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<GlobalStateContext.Provider value={state}>
<GlobalDispatchContext.Provider value={dispatch}>
{children}
</GlobalDispatchContext.Provider>
</GlobalStateContext.Provider>
);
};
// If any of these hooks is not being called within a function component
// that is rendered within the `GlobalContextProvider`,
// we throw an error
const useGlobalState = () => {
const context = React.useContext(GlobalStateContext);
if (context === undefined) {
throw new Error(
"useGlobalState must be used within a GlobalContextProvider"
);
}
return context;
};
const useGlobalDispatch = () => {
const context = React.useContext(GlobalDispatchContext);
if (context === undefined) {
throw new Error(
"useGlobalDispatch must be used within a GlobalContextProvider"
);
}
return context;
};
// We only export the custom hooks for state and dispatch
// and of course our`GlobalContextProvider`, which we are
// going to wrap any part of our app that
// needs to make use of this state
export { GlobalContextProvider, useGlobalState, useGlobalDispatch };
我在这里添加的只是几个自定义钩子,它们公开了每个上下文,即GlobalStateContext
和GlobalDispatchContext
并将它们与GlobalContextProvider
一起导出。
如果要在整个应用程序中全局使用它,可以将GlobalContextProvider
包裹在App
组件周围。
function App() {
return (
<div className="App">
<Search />
</div>
);
}
// If you forget to wrap the consumer with your provider, the custom hook will
// throw an error letting you know that the hook is not being called
// within a function component that is rendered within the
// GlobalContextProvider as it's supposed to
const AppContainer = () => (
<GlobalContextProvider>
<App />
</GlobalContextProvider>
);
export default AppContainer;
如果要在应用程序的任何部分使用state
或进行任何操作dispatch
,则需要导入之前创建的相关自定义钩子。
在您的“搜索”组件中,它看起来像下面的示例:
import { useGlobalState, useGlobalDispatch } from "./Store";
const Search = () => {
// Since we are doing this in our custom hook that is not needed anymore
// const state = useContext(GlobalStateContext)
// if you need to dispatch any actions you can
// import the useGlobalDispatch hook and use it like so:
// const dispatch = useGlobalDispatch();
const state = useGlobalState();
const { searchTerm, posts } = state
useEffect(() => {
console.log('[debug] <Search />: searchTerm: ', searchTerm);
}, [searchTerm])
return (
<>
<LoadMoreScroll searchTerm={searchTerm} posts={posts} postCursor={postCursor} />
</>
)
}
export default Search
由于问题中提供的代码和框中缺少一些内容,因此我将其重构为该概念here的简化工作版本,希望可以帮助您解决问题。
当我对Context API和挂钩有问题时,我还发现this article很有帮助。
它遵循相同的模式,我一直在生产中使用这种模式,并对结果非常满意。
希望有帮助:)