|
| 1 | +///<reference path="../declarations/index.d.ts" /> |
1 | 2 | import * as React from "react";
|
2 | 3 |
|
| 4 | +type RefsData = ReactContextRefs.RefsData; |
| 5 | + |
3 | 6 | const Context = React.createContext<RefsContext>({
|
4 |
| - nodes: [], |
| 7 | + refs: [], |
5 | 8 | register: () => {},
|
6 | 9 | setMeta: () => {}
|
7 | 10 | });
|
8 | 11 |
|
9 | 12 | interface RefsContext {
|
10 |
| - nodes: RefData[]; |
11 |
| - register: (key: any, ref?: RefData) => void; |
| 13 | + refs: RefsData[]; |
| 14 | + register: (key: any, ref?: RefsData) => void; |
12 | 15 | setMeta: (key: any, meta: any) => void;
|
13 | 16 | }
|
14 | 17 |
|
15 |
| -export interface RefData { |
16 |
| - value: any; |
17 |
| - meta: any; |
18 |
| -} |
19 |
| - |
20 | 18 | export const RefProvider: React.FC = ({ children }) => {
|
21 |
| - const { current: refs } = React.useRef<Map<any, RefData>>(new Map()); |
| 19 | + const { current: refsMap } = React.useRef<Map<any, RefsData>>(new Map()); |
22 | 20 |
|
23 |
| - const [nodes, setNodes] = React.useState<RefData[]>([]); |
| 21 | + const [refs, setRefs] = React.useState<RefsData[]>([]); |
24 | 22 |
|
25 | 23 | const register = React.useCallback<RefsContext["register"]>(
|
26 | 24 | (key, refData) => {
|
27 | 25 | if (refData == null) {
|
28 |
| - if (refs.has(key)) { |
29 |
| - refs.delete(key); |
30 |
| - setNodes(Array.from(refs.values())); |
| 26 | + if (refsMap.has(key)) { |
| 27 | + refsMap.delete(key); |
| 28 | + setRefs(Array.from(refsMap.values())); |
31 | 29 | }
|
32 | 30 | } else {
|
33 |
| - const oldRefData = refs.get(key) || ({} as RefData); |
| 31 | + const oldRefData = refsMap.get(key) || ({} as RefsData); |
34 | 32 | if (
|
35 |
| - oldRefData.value !== refData.value || |
36 |
| - oldRefData.meta !== refData.meta |
| 33 | + oldRefData.current !== refData.current || |
| 34 | + oldRefData.meta !== refData.meta || |
| 35 | + oldRefData.type !== refData.type |
37 | 36 | ) {
|
38 |
| - refs.set(key, refData); |
39 |
| - setNodes(Array.from(refs.values())); |
| 37 | + refsMap.set(key, refData); |
| 38 | + setRefs(Array.from(refsMap.values())); |
40 | 39 | }
|
41 | 40 | }
|
42 | 41 | },
|
43 |
| - [setNodes] |
| 42 | + [setRefs] |
44 | 43 | );
|
45 | 44 |
|
46 | 45 | const setMeta = React.useCallback<RefsContext["setMeta"]>(
|
47 | 46 | (key, meta) => {
|
48 |
| - if (!refs.has(key)) { |
| 47 | + if (!refsMap.has(key)) { |
49 | 48 | return;
|
50 | 49 | }
|
51 |
| - const refData = refs.get(key); |
| 50 | + const refData = refsMap.get(key); |
52 | 51 | if (!refData) {
|
53 | 52 | return;
|
54 | 53 | }
|
55 | 54 | refData.meta = meta;
|
56 | 55 | },
|
57 |
| - [setNodes] |
| 56 | + [setRefs] |
58 | 57 | );
|
59 | 58 |
|
60 | 59 | const context = React.useMemo(
|
61 | 60 | () => ({
|
62 |
| - nodes, |
| 61 | + refs, |
63 | 62 | register,
|
64 | 63 | setMeta
|
65 | 64 | }),
|
66 |
| - [nodes, register] |
| 65 | + [refs, register] |
67 | 66 | );
|
68 | 67 |
|
69 | 68 | return <Context.Provider value={context}>{children}</Context.Provider>;
|
70 | 69 | };
|
71 | 70 |
|
72 |
| -export function useNodes() { |
73 |
| - return React.useContext(Context).nodes; |
74 |
| -} |
| 71 | +export function useRefs(): RefsData[]; |
| 72 | +export function useRefs<T extends ReactContextRefs.RefType>( |
| 73 | + type: T |
| 74 | +): Array<ReactContextRefs.RefsValuesMap[T]>; |
| 75 | +export function useRefs<T extends ReactContextRefs.RefType>(type?: T) { |
| 76 | + const { refs } = React.useContext(Context); |
| 77 | + |
| 78 | + if (typeof type === "undefined") { |
| 79 | + return refs; |
| 80 | + } |
75 | 81 |
|
76 |
| -/** @todo переделать на getNodesByType ? */ |
77 |
| -// export function useRefs(tag?: string) { |
78 |
| -// const { nodes } = React.useContext(Context); |
| 82 | + return refs.filter(ref => ref.type === type); |
| 83 | +} |
79 | 84 |
|
80 |
| -// return React.useMemo( |
81 |
| -// () => |
82 |
| -// tag |
83 |
| -// ? nodes.filter(node => node.tag === tag).map(node => node.value) |
84 |
| -// : nodes.map(node => node.value), |
85 |
| -// [nodes, tag] |
86 |
| -// ); |
87 |
| -// } |
| 85 | +const useIsomorphicLayoutEffect = |
| 86 | + typeof window !== "undefined" && |
| 87 | + typeof window.document !== "undefined" && |
| 88 | + typeof window.document.createElement !== "undefined" |
| 89 | + ? React.useLayoutEffect |
| 90 | + : React.useEffect; |
| 91 | + |
| 92 | +export function useContextRef(meta?: any): (value: any) => void; |
| 93 | +export function useContextRef<T extends ReactContextRefs.RefType>( |
| 94 | + type: T, |
| 95 | + meta: ReactContextRefs.RefsValuesMap[T]["meta"] |
| 96 | +): (value: ReactContextRefs.RefsValuesMap[T]["current"]) => void; |
| 97 | +export function useContextRef(typeParam: any, metaParam?: any) { |
| 98 | + const type = typeof metaParam === "undefined" ? "" : typeParam; |
| 99 | + const meta = typeof metaParam === "undefined" ? typeParam : metaParam; |
88 | 100 |
|
89 |
| -export function useSetRef(meta?: any) { |
90 | 101 | const { current: key } = React.useRef({});
|
91 | 102 |
|
92 | 103 | const { register, setMeta } = React.useContext(Context);
|
93 | 104 |
|
94 | 105 | const callback = React.useCallback(
|
95 |
| - node => { |
96 |
| - register(key, node ? { value: node, meta } : null); |
| 106 | + current => { |
| 107 | + register(key, current ? { current, meta, type } : null); |
97 | 108 | },
|
98 | 109 | [register]
|
99 | 110 | );
|
100 | 111 |
|
101 |
| - React.useEffect(() => { |
| 112 | + useIsomorphicLayoutEffect(() => { |
102 | 113 | setMeta(key, meta);
|
103 | 114 | }, [meta]);
|
104 | 115 |
|
105 | 116 | return callback;
|
106 | 117 | }
|
107 | 118 |
|
108 |
| -export function ContextRef(props: { |
109 |
| - children: (ref: (value: any) => void) => React.ReactNode; |
110 |
| - meta?: any; |
| 119 | +export function ContextRef<T extends ReactContextRefs.RefType = "">(props: { |
| 120 | + children: ( |
| 121 | + ref: (current: ReactContextRefs.RefsValuesMap[T]["current"]) => void |
| 122 | + ) => React.ReactNode; |
| 123 | + type?: T; |
| 124 | + meta?: ReactContextRefs.RefsValuesMap[T]["meta"]; |
111 | 125 | }) {
|
112 |
| - const setRef = useSetRef(props.meta); |
| 126 | + const setRef = useContextRef(props.type || "", props.meta); |
113 | 127 | return props.children(setRef);
|
114 | 128 | }
|
0 commit comments