![](/img/trans.png)
[英]keyDown handler being fired twice instead of once in custom React hook
[英]React custom hook scroll listener fired only once
我尝试使用自定义钩子在组件中实现滚动指示器。
这是组件:...
const DetailListInfo: React.FC<Props> = props => {
const container = useRef(null)
const scrollable = useScroll(container.current)
const { details } = props
return (
<div
ref={container}
className="content-list-info content-list-info-detailed"
>
{details && renderTypeDetails(details)}
{scrollable && <ScrollIndicator />}
</div>
)
}
export default inject("store")(observer(DetailListInfo))
还有 useScroll 钩子:
import React, { useState, useEffect } from "react"
import { checkIfScrollable } from "../utils/scrollableElement"
export const useScroll = (container: HTMLElement) => {
const [isScrollNeeded, setScrollValue] = useState(true)
const [isScrollable, setScrollable] = useState(false)
const checkScrollPosition = (): void => {
const scrollDiv = container
const result =
scrollDiv.scrollTop < scrollDiv.scrollHeight - scrollDiv.clientHeight ||
scrollDiv.scrollTop === 0
setScrollValue(result)
}
useEffect(() => {
console.log("Hook called")
if (!container) return null
container.addEventListener("scroll", checkScrollPosition)
setScrollable(checkIfScrollable(container))
return () => container.removeEventListener("scroll", checkScrollPosition)
}, [isScrollable, isScrollNeeded])
return isScrollNeeded && isScrollable
}
所以在这个传递的组件中的每个滚动(容器不同,这就是我想制作可定制的钩子的原因)我想检查当前的滚动 position 以有条件地显示或隐藏指示器。 问题是,当组件被渲染时,该钩子只被调用一次。 它没有监听滚动事件。 当这个钩子在组件内部时,它工作正常。 这里有什么问题?
让我们研究一下您的代码:
const container = useRef(null)
const scrollable = useScroll(container.current) // initial container.current is null
// useScroll
const useScroll = (container: HTMLElement) => {
// container === null at the first render
...
// useEffect depends only from isScrollable, isScrollNeeded
// these variables are changed inside the scroll listener and this hook body
// but at the first render the container is null so the scroll subscription is not initiated
// and hook body won't be executed fully because there's return statement
useEffect(() => {
if (!container) return null
...
}, [isScrollable, isScrollNeeded])
}
为了让一切正常工作,你的useEffect
钩子应该在钩子体内使用所有依赖项。 请注意文档中的警告说明。
您也不能只将ref.current
传递给钩子。 该字段是可变的,并且当ref.current
更改(挂载时)时,您的钩子将不会被通知(重新执行)。 您应该通过整个ref
object 以便能够通过ref.current
中的useEffect
获得 HTML 元素。
此 function 的正确版本应如下所示:
export const useScroll = (ref: React.RefObject<HTMLElement>) => {
const [isScrollNeeded, setScrollValue] = useState(true);
const [isScrollable, setScrollable] = useState(false);
useEffect(() => {
const container = ref.current;
if (!container) return;
const checkScrollPosition = (): void => {
const scrollDiv = container;
const result =
scrollDiv.scrollTop < scrollDiv.scrollHeight - scrollDiv.clientHeight ||
scrollDiv.scrollTop === 0;
setScrollValue(result);
setScrollable(checkIfScrollable(scrollDiv));
};
container.addEventListener("scroll", checkScrollPosition);
setScrollable(checkIfScrollable(container));
return () => container.removeEventListener("scroll", checkScrollPosition);
// this is not the best place to depend on isScrollNeeded or isScrollable
// because every time on these variables are changed scroll subscription will be reinitialized
// it seems that it is better to do all calculations inside the scroll handler
}, [ref]);
return isScrollNeeded && isScrollable
}
// somewhere in a render:
const ref = useRef(null);
const isScrollable = useScroll(ref);
内部有一个滚动监听器的钩子:
export const ScrollIndicator: React.FC<Props> = props => {
const { container } = props
const [isScrollNeeded, setScrollValue] = useState(true)
const [isScrollable, setScrollable] = useState(false)
const handleScroll = (): void => {
const scrollDiv = container
const result =
scrollDiv.scrollTop < scrollDiv.scrollHeight - scrollDiv.clientHeight ||
scrollDiv.scrollTop === 0
setScrollValue(result)
}
useEffect(() => {
setScrollable(checkIfScrollable(container))
container.addEventListener("scroll", handleScroll)
return () => container.removeEventListener("scroll", handleScroll)
}, [container, handleScroll])
return isScrollable && isScrollNeeded && <Indicator />
}
在渲染组件中,需要检查 ref 容器是否存在。 仅当容器已经在 DOM 中时才会调用钩子。
const scrollDiv = useRef(null)
{scrollDiv.current && <ScrollIndicator container={scrollDiv.current} />}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.