用setTimeout写一个轮询的hook

91 阅读2分钟

随便记录一下在打工中写的一些简单的hook

1.setTimeout写轮询

注意判断卸载情况

import { useEffect, useRef } from 'react';
const useLoopRequest = (
    request: (...args: any) => Promise<any>,
    interval: number,
    immediate: boolean = false,
) => {
    const timerRef = useRef<any>(null);
    const requestRef = useRef<() => Promise<any>>(request);
    const intervalRef = useRef<number>(interval);
    const mountedRef = useRef(true);
    const requestFn = async (intervalTime: number = intervalRef.current) => {
        if (timerRef.current) clearTimeout(timerRef.current);
        //防止在组件销毁的时候,定时器任务还在运行,即使销毁定时器,因为异步任务后就是requestFn,这个函数还是会重新开启定时器
        if (!mountedRef.current) return; //卸载后就停止
        timerRef.current = setTimeout(async () => {
            await requestRef.current();
            requestFn();
        }, intervalTime);
    };
    const switchTabFunc = () => {
        if (document.visibilityState === 'hidden' && timerRef.current) {
            clearTimeout(timerRef.current);
        } else if (document.visibilityState === 'visible') {
            requestFn(0);
        }
    };
    useEffect(() => {
        //切换同pathName的时候,不会销毁组件触发该回调
        return () => {
            mountedRef.current = false;
            if (timerRef.current) {
                clearTimeout(timerRef.current);
            }
            document.removeEventListener('visibilitychange', switchTabFunc);
        };
    }, []);
    return {
        startLoop: () => {
            document.removeEventListener('visibilitychange', switchTabFunc);
            document.addEventListener('visibilitychange', switchTabFunc);
            requestFn(immediate ? 0 : intervalRef.current);
        },
    };
    //考虑切屏的情况
};
export default useLoopRequest;

2. 使用antd pro-table的受控列状态hook

这是把所有表格受控列存在一个localStorage/sessionStorage的一个key里,也可以修改一下不放在同个key

业务场景:tab组件,每个tab一个表格,每个表格列都受控,需要浏览器缓存,并且几个表格间会有相同列

import { useEffect, useState } from 'react';
const useColumnState = (
    key: string,
    defaultValue: any,
    persistenceType: 'localStorage' | 'sessionStorage',
) => {
    if(!['localStorage', 'sessionStorage'].includes(persistenceType)) {
        throw new Error('Invalid persistenceType');
    }
    const keyPrefix = 'sys_tableColumnsState';
    const [columnsStateMap, setColumnsStateMap] = useState<any>(defaultValue);
    useEffect(() => {
        const obj = JSON.parse(window[persistenceType].getItem(keyPrefix) || '{}');
        const hasAnyProperty = (objj: any) => {
            //这里没有用Object.keys()去判断是否是空对象,是因为听过Object.keys()性能一般,
            //就用for in取代了,但感觉for in遍历原型链也不见得比Object.keys()好到哪里:)
            for (const keyy in objj) {
                if (objj.hasOwnProperty(keyy)) {
                    return true;
                }
            }
            return false;
        };
        if (obj[key] && hasAnyProperty(obj[key])) {
            setColumnsStateMap(obj[key]);
        } else {
            obj[key] = columnsStateMap;
            window[persistenceType].setItem(keyPrefix, JSON.stringify(obj));
        }
    }, []);
    useEffect(() => {
        const obj = JSON.parse(window[persistenceType].getItem(keyPrefix) || '{}');
        obj[key] = columnsStateMap;
        window[persistenceType].setItem(keyPrefix, JSON.stringify(obj));
    }, [columnsStateMap]);
    return {
        value: columnsStateMap,
        onChange: setColumnsStateMap,
        persistenceType: persistenceType,
    };
};
export default useColumnState;

3. 异步toast

import { message } from 'antd';
const useLoadingToast = () => {
    const toast = (content: React.ReactNode) => {
        return message.loading(content, 0);
    };
    return {
        toast,
    };
};
export default useLoadingToast;

4. select按照options顺序排序不按照选择顺序

数据量小不分页的options可以这样做,其他还是算了,都是为了应付产品奇葩需求不考虑任何一点性能

const useSortOptions = (options: any[], handleChange: (...r: any) => void) => {
    // console.log('useSortOptions--', options);
    const onChange = (...args: any) => {
        // console.log(args[0]);
        if (Array.isArray(args[0])) {
            args[0] = options.reduce((acc: any[], item: any) => {
                if (args[0].includes(item.value)) {
                    acc.push(item.value);
                }
                return acc;
            }, []);
        }
        // console.log('sort--', args[0]);
        handleChange(...args);
    };
    return { onChange };
};
export default useSortOptions;

5. 使用antd select发现的一个问题

  1. 远程搜索
  2. 多选
  3. 初始有选中项

满足这三个情形的时候就会出现这样的问题,再次搜索选中触发的onChange函数的option参数会丢失初始选中项的值,变成空对象{}。

import React, { useEffect, useRef } from 'react';
import { Select } from 'antd';
const SearchSelect = (props: any) => {
    const initSelectedOption = useRef([]);
    const first = useRef(true)
    const onChange = (value: any, option: any[] | any) => {
        if (props.mode === 'multiple' && props.onSearch && first.current) {
            const selectedOption = initSelectedOption.current.filter((item: any) => value.includes(item.value))//历史选中的option
            props.onChange(value, selectedOption.concat(option).filter((item: any) => !!item.value))
            first.current = false
            return;
        }
        if (props.onChange) {
            props.onChange(value, option)
        }
    }

    useEffect(() => {
        initSelectedOption.current = props.value || [];
    }, [])
    return (
        <Select
            {...props}
            onChange={onChange}
        />
    )
}
export default SearchSelect;