上下文总是让我感到困惑。我希望新的Context API会有所帮助,但到目前为止,仍然存在问题。我可以显示初始值,但是基于我的效果的更新不会使用useContext
值更新组件。我在做什么错了?
在这里“需要”上下文是过大的选择,但是我想拥有一个能够提供窗口滚动位置值的上下文API,只是为了测试Context Provider / Consumer如何与子代交互:
context.js
import React, { useState, FC, useEffect } from "react";
interface IInitialContext {
scrollY: number;
// do to - implement removeEventListener function
}
const intialContext = {
scrollY: window.scrollY,
// removeEventListener: () => {},
};
const ScrollContext = React.createContext<IInitialContext>(intialContext);
const VerticalScrollContextProvider: FC = ({ children }) => {
const [scrollY, setScrollY] = useState(intialContext.scrollY);
// correctly logs scrollY
useEffect(() => {
window.addEventListener(
"scroll",
debounce(() => setScrollY(window.scrollY), 100) // correctly captures new scrollY position
);
return () => window.removeEventListener("scroll", debounce)
}, [scrollY]); // re-add listener when scrollY changes
return (
<ScrollContext.Provider value={{ scrollY }}> //i don't really need the value passed to component
{children}
</ScrollContext.Provider>
);
};
function useScrollState() {
const context = React.useContext(ScrollContext);
if (context === undefined) {
throw new Error(
"useScrollState must be used within a VerticalScrollContextProvider"
);
}
return context; // correctly tracks initial state
}
export { VerticalScrollContextProvider, useScrollState };
和我的组件:
const Home = () => {
const { scrollY } = useScrollState();
// correctly has value on app first loading, does not receive updates.
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
console.log("clicked button");
};
return (
<VerticalScrollContextProvider>
<StyledHome>
<div id="text-wrapper">
<StyledFirstName>Phil</StyledFirstName>
<StyledLastName>Lucks</StyledLastName>
</div>
<Button text="clicker" onClick={handleClick} />
</StyledHome>
</VerticalScrollContextProvider>
);
};
export default Home;
我很感谢任何见识。
答案 0 :(得分:0)
要使上下文正常工作,组件应位于提供程序内部。
来自useContext的文档
接受一个上下文对象(从React.createContext返回的值) 并返回该上下文的当前上下文值。目前 上下文值由最近的值prop决定 在树中调用组件上方。
当组件上方最近的组件更新时, 这个钩子将通过传递的最新上下文值触发重新渲染 到该MyContext提供程序。
现在密切注意<Home />
组件。
const Home = () => {
const { scrollY } = useScrollState();
// correctly has value on app first loading, does not receive updates.
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
console.log("clicked button");
};
return (
<VerticalScrollContextProvider>
<StyledHome>
<div id="text-wrapper">
<StyledFirstName>Phil</StyledFirstName>
<StyledLastName>Lucks</StyledLastName>
</div>
<Button text="clicker" onClick={handleClick} />
</StyledHome>
</VerticalScrollContextProvider>
);
};
我们感兴趣的这一行是const { scrollY } = useScrollState();
,它试图从父context
派生Provider
但是,<Home />
组件是否在<Provider />
内?
否。 <Provider />
在<Home />
组件内部
运行以下代码片段并查看控制台日志,以查看首先运行哪个组件。
const { useState, useEffect } = React;
const intialContext = {
scrollY: window.scrollY,
};
const ScrollContext = React.createContext(intialContext);
const VerticalScrollContextProvider = ({ children }) => {
const [scrollY, setScrollY] = React.useState(intialContext.scrollY);
// correctly logs scrollY
console.log("Rendering context");
useEffect(() => {
const eventListener = () => setScrollY(window.scrollY);
window.addEventListener(
"scroll",
eventListener// correctly captures new scrollY position
);
return () => window.removeEventListener("scroll", eventListener)
}, [scrollY]); // re-add listener when scrollY changes
return (
<ScrollContext.Provider value={{ scrollY }}>
{children}
</ScrollContext.Provider>
);
};
function useScrollState() {
const context = React.useContext(ScrollContext);
if (context === undefined) {
throw new Error(
"useScrollState must be used within a VerticalScrollContextProvider"
);
}
return context; // correctly tracks initial state
}
const Home = () => {
const { scrollY } = useScrollState();
console.log("Rendering Home");
// correctly has value on app first loading, does not receive updates.
const handleClick = (e) => {
e.preventDefault();
console.log("clicked button");
};
return (
<VerticalScrollContextProvider>
<div id="text-wrapper">
<h3>Phil</h3>
<h3>Lucks</h3>
</div>
<button onClick={handleClick}>Button</button>
<h2>Scroll and see the console logs</h2>
</VerticalScrollContextProvider>
);
};
ReactDOM.render(<Home />, document.getElementById("root"));
#root {
height: 1000px;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
解决方案 ...
将Home
组件包装在Provider
内
import React from "react";
import {VerticalScrollContextProvider, useScrollState} from "./context";
const Home = () => {
const { scrollY } = useScrollState();
// correctly has value on app first loading, does not receive updates.
const handleClick = (e) => {
e.preventDefault();
console.log("clicked button");
};
return (
<div>
<div id="text-wrapper">
<h3>Phil</h3>
<h3>Lucks</h3>
{scrollY}
</div>
<button text="clicker" onClick={handleClick} />
</div>
);
};
export default () => (
<VerticalScrollContextProvider><Home /></VerticalScrollContextProvider>
);
运行下面的代码片段并滚动以查看控制台日志。
const {
useState,
useEffect
} = React;
const intialContext = {
scrollY: window.scrollY,
};
const ScrollContext = React.createContext(intialContext);
const VerticalScrollContextProvider = ({
children
}) => {
const [scrollY, setScrollY] = React.useState(intialContext.scrollY);
// correctly logs scrollY
console.log("Rendering context");
useEffect(() => {
const eventListener = () => setScrollY(window.scrollY);
window.addEventListener(
"scroll",
eventListener // correctly captures new scrollY position
);
return () => window.removeEventListener("scroll", eventListener)
}, [scrollY]); // re-add listener when scrollY changes
return ( <
ScrollContext.Provider value = {
{
scrollY
}
} > {
children
} <
/ScrollContext.Provider>
);
};
function useScrollState() {
const context = React.useContext(ScrollContext);
if (context === undefined) {
throw new Error(
"useScrollState must be used within a VerticalScrollContextProvider"
);
}
return context; // correctly tracks initial state
}
const Home = () => {
const {
scrollY
} = useScrollState();
console.log("Rendering Home", scrollY);
// correctly has value on app first loading, does not receive updates.
const handleClick = (e) => {
e.preventDefault();
console.log("clicked button");
};
return ( <
React.Fragment >
<
div id = "text-wrapper" >
<
h3 > Phil < /h3> <
h3 > Lucks < /h3> <
/div> <
button onClick = {
handleClick
} > Button < /button> <
h2 > Scroll and see the console logs < /h2> <
/React.Fragment>
);
};
const HomeWithProvider = () => {
return <VerticalScrollContextProvider >
<
Home / >
<
/VerticalScrollContextProvider>
}
ReactDOM.render( < HomeWithProvider / > , document.getElementById("root"));
#root {
height: 1000px;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>