React常用自定义Hook汇总
# UseSetState
UseSetState: 用于方便更新 JSON 数据
import { useCallback, useState } from "react";
export type SetState<S extends Record<string, any>> = <K extends keyof S>(
state: Pick<S, K> | null | ((prevState: Readonly<S>) => Pick<S, K> | S | null)
) => void;
const useSetState = <S extends Record<string, any>>(
initialState: S | (() => S)
): [S, SetState<S>] => {
const [state, setState] = useState<S>(initialState);
const setMergeState = useCallback((patch) => {
setState((prevState) => {
const newState = typeof patch === "function" ? patch(prevState) : patch;
return newState ? { ...prevState, ...newState } : prevState;
});
}, []);
return [state, setMergeState];
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# useUpdateEffect
useUpdateEffect: 用于跳过组件第一次初始化时的 effect,只关注后续的 update 变化
import React, { useEffect, useRef } from "react";
const useUpdateEffect = (effect: Function, deps: any[]) => {
const isMounted = useRef<boolean>(false);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
useEffect(() => {
if (!isMounted.current) {
isMounted.current = true;
} else {
return effect();
}
}, [deps]);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 控制监听 DOM 元素
export const LogContext = createContext({});
export const useLog = () => {
/* 定义一些公共参数 */
const message = useContext(LogContext);
const listenDOM = useRef(null);
/* 分清依赖关系 */
const reportMessage = useCallback(
function (data, type) {
if (type === "pv") {
// 页面浏览量上报
console.log("组件 pv 上报", message);
} else if (type === "click") {
// 点击上报
console.log("组件 click 上报", message, data);
}
},
[message]
);
useEffect(() => {
const handleClick = function (e) {
reportMessage(e.target, "click");
};
if (listenDOM.current) {
listenDOM.current.addEventListener("click", handleClick);
}
return function () {
listenDOM.current &&
listenDOM.current.removeEventListener("click", handleClick);
};
}, [reportMessage]);
return [listenDOM, reportMessage];
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
使用
const Home = () => {
const [dom, reportMessage] = useLog();
return (
<div>
{/* 监听内部点击 */}
<div ref={dom}>
<button> 按钮 1 (内部点击) </button>
<button> 按钮 2 (内部点击) </button>
<button> 按钮 3 (内部点击) </button>
</div>
{/* 外部点击 */}
<button
onClick={() => {
console.log(reportMessage);
}}
>
外部点击
</button>
</div>
);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 离开页面前确认
import { useCallback, useEffect, useRef } from 'react'
import { useHistory } from 'react-router-dom'
export function usePromt(
when?: boolean | (() => boolean),
msg = '修改内容未保存,确定要关闭页面吗?'
) {
const history = useHistory()
const curStatus = useRef(when)
const curMsg = useRef(msg)
useEffect(() => {
curStatus.current = when
}, [when])
useEffect(() => {
curMsg.current = msg
}, [msg])
const onBeforeUnload = useCallback((event: any) => {
if (typeof curStatus.current === 'function' && curStatus.current()) {
event.preventDefault()
event.returnValue = curMsg.current
} else if (typeof curStatus.current === 'boolean' && curStatus.current) {
event.preventDefault()
event.returnValue = curMsg.current
}
}, [])
useEffect(() => {
const unblock = history.block(() => {
if (
(typeof curStatus.current === 'function' && curStatus.current()) ||
(typeof curStatus.current === 'boolean' && curStatus.current)
) {
return curMsg.current
}
})
return () => unblock()
}, [])
useEffect(() => {
window.addEventListener('beforeunload', onBeforeUnload)
return () => {
window.removeEventListener(
'beforeunload',
onbeforeunload as EventListenerOrEventListenerObject
)
}
}, [])
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
使用
usePromt(true);
1
# 请求轮询
import { useState, useRef, useEffect } from "react";
import axios from "axios";
/**
* React Hook: usePolling
*
* This hook allows for making repeated API calls at a set interval (in ms).
* The polling can be started, stopped, and manually triggered.
*
* @param {string} url - API endpoint to poll data from
* @param {number} interval - interval (in ms) to poll the API
*
* @return An array containing the following:
* - [0] boolean indicating if the polling is actively taking place
* - [1] function to manually trigger the poll (optional)
* - [2] function to stop the polling (optional)
*/
const usePolling = (url, interval) => {
const [polling, setPolling] = useState(false);
const [manualTrigger, setManualTrigger] = useState(false);
const timerRef = useRef(null);
const fetchData = async () => {
try {
const response = await axios.get(url);
console.log(response.data); // do something with response data
} catch (error) {
console.error("Error fetching data:", error);
}
if (polling || manualTrigger) {
timerRef.current = setTimeout(fetchData, interval);
}
};
const startPolling = () => {
setPolling(true);
timerRef.current = setTimeout(fetchData, interval);
};
const stopPolling = () => {
setPolling(false);
clearTimeout(timerRef.current);
};
useEffect(() => {
return () => clearTimeout(timerRef.current);
}, []);
return [polling, startPolling, stopPolling];
};
export default usePolling;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
使用示例:
import { useEffect } from "react";
import usePolling from "./usePolling";
const App = () => {
const [polling, startPolling, stopPolling] = usePolling(
"https://example.com/api/data",
5000
);
useEffect(() => {
// Manually trigger the poll every 10 seconds
const manualPolling = setInterval(() => {
startPolling();
}, 10000);
return () => clearInterval(manualPolling);
}, [startPolling]);
return (
<div>
<button onClick={() => startPolling()}>Start polling</button>
<button onClick={() => stopPolling()}>Stop polling</button>
{polling && <p>Polling in progress</p>}
</div>
);
};
export default App;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# createContext
function createContext<ContextValueType extends object | null>(
rootComponentName: string,
defaultContext?: ContextValueType
) {
const Context = React.createContext<ContextValueType | undefined>(defaultContext);
function Provider(props: ContextValueType & { children: React.ReactNode }) {
const { children, ...context } = props;
// Only re-memoize when prop values change
// eslint-disable-next-line react-hooks/exhaustive-deps
const value = React.useMemo(() => context, Object.values(context)) as ContextValueType;
return <Context.Provider value={value}>{children}</Context.Provider>;
}
function useContext(consumerName: string) {
const context = React.useContext(Context);
if (context) return context;
if (defaultContext !== undefined) return defaultContext;
// if a defaultContext wasn't specified, it's a required context.
throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``);
}
Provider.displayName = rootComponentName + 'Provider';
return [Provider, useContext] as const;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
这么做的作用是什么呢? 可以利用缓存来达到无需向另外的组件传递参数实现弹窗的开关等用途,例如:
import { createContext } from "../../utils/createContext";
const DIALOG_NAME = "Dialog";
import React, { useState } from "react";
type DialogContextValue = {
open: boolean;
onOpenChange(open: boolean): void;
onOpenToggle(): void;
modal: boolean;
};
const [DialogProvider, useDialogContext] =
createContext<DialogContextValue>(DIALOG_NAME);
const Dialog: React.FC = (props: any) => {
const {
__scopeDialog,
children,
open: openProp,
defaultOpen,
onOpenChange,
modal = true,
} = props;
const [open, setOpen] = useState(false);
return (
<DialogProvider
open={open}
onOpenChange={setOpen}
onOpenToggle={React.useCallback(
() => setOpen((prevOpen) => !prevOpen),
[setOpen]
)}
modal={modal}
>
{children}
</DialogProvider>
);
};
Dialog.displayName = DIALOG_NAME;
const TRIGGER_NAME = "DialogTrigger";
const DialogTrigger = React.forwardRef((props: any, forwardedRef) => {
const { ...triggerProps } = props;
const context = useDialogContext(TRIGGER_NAME);
const handleClick = () => {
console.log({ context });
};
return (
<button
type="button"
aria-haspopup="dialog"
aria-expanded={context.open}
{...triggerProps}
onClick={handleClick}
/>
);
});
DialogTrigger.displayName = TRIGGER_NAME;
export { Dialog, DialogTrigger };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 右键菜单位置
let vw = document.documentElement.clientWidth;
let vh = document.documentElement.clientHeight;
window.addEventListener('resize', () => {
vw = document.documentElement.clientWidth;
vh = document.documentElement.clientHeight;
})
1
2
3
4
5
6
7
2
3
4
5
6
7
编辑 (opens new window)
上次更新: 2023/06/05, 16:50:36