- Published on
useInViewport
- Authors

- Name
- 李丹秋
getTargetElement
源码
import type { MutableRefObject } from 'react';
import { isFunction } from './index';
type TargetValue<T> = T | undefined | null;
type TargetType = HTMLElement | Element | Window | Document;
export type BasicTarget<T extends TargetType = Element> =
| (() => TargetValue<T>)
| TargetValue<T>
| MutableRefObject<TargetValue<T>>;
export function getTargetElement<T extends TargetType>(target: BasicTarget<T>, defaultElement?: T) {
if (!target) {
return defaultElement;
}
let targetElement: TargetValue<T>;
if (isFunction(target)) {
targetElement = target();
} else if ('current' in target) {
targetElement = target.current;
} else {
targetElement = target;
}
return targetElement;
}
interface MutableRefObject<T> {
current: T;
}
getTargetElement这个函数的主要用途就是用函数、ref、element中,获取到element元素
createEffectWithTarget
源码
import type { DependencyList, EffectCallback, useEffect, useLayoutEffect } from 'react';
import { useRef } from 'react';
import useUnmount from '../useUnmount';
import depsAreSame from './depsAreSame';
import type { BasicTarget } from './domTarget';
import { getTargetElement } from './domTarget';
const createEffectWithTarget = (useEffectType: typeof useEffect | typeof useLayoutEffect) => {
/**
*
* @param effect
* @param deps
* @param target target should compare ref.current vs ref.current, dom vs dom, ()=>dom vs ()=>dom
*/
const useEffectWithTarget = (
effect: EffectCallback,
deps: DependencyList,
target: BasicTarget<any> | BasicTarget<any>[],
) => {
const hasInitRef = useRef(false);
const lastElementRef = useRef<(Element | null)[]>([]);
const lastDepsRef = useRef<DependencyList>([]);
const unLoadRef = useRef<any>();
useEffectType(() => {
const targets = Array.isArray(target) ? target : [target];
const els = targets.map((item) => getTargetElement(item));
// init run
if (!hasInitRef.current) {
hasInitRef.current = true;
lastElementRef.current = els;
lastDepsRef.current = deps;
unLoadRef.current = effect();
return;
}
if (
els.length !== lastElementRef.current.length ||
!depsAreSame(els, lastElementRef.current) ||
!depsAreSame(deps, lastDepsRef.current)
) {
unLoadRef.current?.();
lastElementRef.current = els;
lastDepsRef.current = deps;
unLoadRef.current = effect();
}
});
useUnmount(() => {
unLoadRef.current?.();
// for react-refresh
hasInitRef.current = false;
});
};
return useEffectWithTarget;
};
export default createEffectWithTarget;
这个函数的effect执行,主要依赖于target和deps的变化,所以正常情况下只会执行一次,当dom发生改变的时候,会执行useEffect中相应的卸载操作。
useInViewport
源码
import 'intersection-observer';
import { useState } from 'react';
import type { BasicTarget } from '../utils/domTarget';
import { getTargetElement } from '../utils/domTarget';
import useEffectWithTarget from '../utils/useEffectWithTarget';
export interface Options {
rootMargin?: string;
threshold?: number | number[];
root?: BasicTarget<Element>;
}
function useInViewport(target: BasicTarget, options?: Options) {
const [state, setState] = useState<boolean>();
const [ratio, setRatio] = useState<number>();
useEffectWithTarget(
() => {
const el = getTargetElement(target);
if (!el) {
return;
}
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
setRatio(entry.intersectionRatio);
setState(entry.isIntersecting);
}
},
{
...options,
root: getTargetElement(options?.root),
},
);
// 检测目标元素
observer.observe(el);
return () => {
observer.disconnect();
};
},
[options?.rootMargin, options?.threshold],
target,
);
return [state, ratio] as const;
}
export default useInViewport;
主要是使用了IntersectionObserver API去监听元素的位置变化情况