vuehook


Vuehook

很多react hook我们可以直接拿过来改造一下用,

这里就不赘述了,

我们来实现一个 vue 的  useRequest  数据请求 hook,

基本仿照ahooks的配置和功能实现。
import { onMounted, onUnmounted, ref, watch, watchEffect } from 'vue';
import { ElMessage } from 'element-plus';
import _ from 'lodash';

import { useBoolean, useFuncDebounce, useThrottle } from '.';
import { cleanObject } from '@/tools';

/**
 * https://ahooks.js.org/zh-CN/hooks/use-request/index
 */
interface OptionsConfig {
  loop?: number;
  debounceWait?: number;
  throttleWait?: number;
  cacheKey?: string;
  ready?: boolean;
  loadingDelay?: number;
  refreshOnWindowFocus?: boolean;
  refreshDeps?: unknown[];
  retryNum?: number;
  manual?: boolean;
  responsePath?: string;
}

interface EndConfig {
  success?: (res: unknown) => void;
  error?: (error: Error) => void;
}

const RESPONSRCODE = 200;
const CODEPATH = 'data.code';
const FAILEDMESSAGE = '获取数据失败';

const ENDCONFIG = {
  success: (res: unknown) => {},
  error: (error: Error) => {},
};

export const useRequest = (
  syncFunc: (config?: any) => Promise<unknown>,
  options: OptionsConfig = {},
  end: EndConfig = ENDCONFIG
) => {
  const {
    loop = 0,
    ready = true,
    retryNum = 0,
    cacheKey = '',
    manual = false,
    refreshDeps = [],
    debounceWait = 0,
    throttleWait = 0,
    loadingDelay = 0,
    responsePath = '',
    refreshOnWindowFocus = false,
  } = options;
  const throttleCallback = useThrottle();
  const debouncedCallback = useFuncDebounce();
  const [loading, ___, loadingOn, loadingOff] = useBoolean();

  const data = ref<unknown>({});
  const retryNumRef = ref<number>(0);
  const requestConfig = ref<unknown>({});

  /**
   * refreshDeps
   */
  refreshDeps.forEach(ele => {
    watch(
      () => ele,
      () => {
        console.warn('useRequest refreshDeps element is change!', ele);
        getSyncDataWrap(requestConfig.value);
      },
      {
        deep: true,
      }
    );
  });

  /**
   *
   * request func
   */
  const retry = (config?: any) => {
    if (!retryNum) return;
    if (retryNumRef.value < retryNum) {
      retryNumRef.value += 1;
      getSyncData(config);
    }
  };

  const run = (config?: unknown) => {
    getSyncDataWrap(config);
  };

  const saveData = (res: unknown) => {
    if (responsePath) {
      data.value = _.get(res, responsePath, {}) || {};
    } else {
      data.value = res;
    }
  };
  const getSyncData = (config?: unknown) => {
    console.warn('useRequest getSyncData config', config);
    try {
      loadingOn();
      if (ready) {
        if (cacheKey) {
          const locationCacheData = JSON.parse(localStorage.getItem(cacheKey) || '{}');
          if (!_.isEmpty(locationCacheData)) {
            // console.log('locationCacheKey', locationCacheData);
            data.value = locationCacheData;

            saveData(locationCacheData);
            end.success && end.success(locationCacheData);
            loadingOff();
          }
        } else {
          if (!_.isEmpty(config)) {
            requestConfig.value = config;
          }
          syncFunc(cleanObject(config as { [key: string]: unknown }))
            .then(res => {
              if (_.get(res, CODEPATH) === RESPONSRCODE) {
                saveData(res);
                end.success && end.success(res);
                cacheKey && localStorage.setItem(cacheKey, JSON.stringify(res));
                loadingOff();
              } else {
                ElMessage.error(FAILEDMESSAGE);
                loading.value = false;
                Promise.reject(new Error(FAILEDMESSAGE));
              }
            })
            .catch(error => {
              loading.value = false;
              end.error && end.error(error);
              console.log('useRequest error catch!', error);

              retry(config);
            });
        }
      }
    } catch (error) {
      console.log(error);
      loadingOff();
    }
  };

  const getSyncDataWrap = (config?: unknown) => {
    if (debounceWait) {
      return debouncedCallback(getSyncData, debounceWait)(config);
    }
    if (throttleWait) {
      return throttleCallback(getSyncData, throttleWait)(config);
    }
    return getSyncData(config);
    // return () => getSyncData(config);
  };

  /**
   * is loadingDelay
   */
  const loadingDelatyTimer = ref<NodeJS.Timeout | undefined>(undefined);
  onMounted(() => {
    if (manual) {
      return;
    }
    if (loadingDelay) {
      loadingDelatyTimer.value = setTimeout(() => {
        getSyncDataWrap(requestConfig.value);
      }, loadingDelay);
      return;
    }
    getSyncDataWrap(requestConfig.value);
  });
  onUnmounted(() => {
    if (loadingDelatyTimer.value) {
      clearTimeout(loadingDelatyTimer.value);
    }
  });

  /**
   * loop
   */
  const timer = ref<NodeJS.Timeout | undefined>(undefined);
  const loopFunc = () => {
    console.warn('useRequest loop is start!', loop);
    // if (timer.value) {
    //   clearTimeout(timer.value);
    // }
    timer.value = setTimeout(() => {
      loopFunc();
      getSyncDataWrap(requestConfig.value);
    }, loop);
  };
  watchEffect(() => {
    if (loop) {
      loopFunc();
    }
    if (!loop && timer.value) {
      clearTimeout(timer.value);
    }
  });
  onUnmounted(() => {
    if (timer.value) clearTimeout(timer.value);
  });

  /**
   * refreshOnWindowFocus
   */
  const windowFocusFunc = () => {
    if (document.visibilityState === 'visible' && refreshOnWindowFocus) {
      debouncedCallback(run, 5000)(requestConfig.value);
    }
  };
  onMounted(() => {
    document.addEventListener('visibilitychange', windowFocusFunc);
  });
  onUnmounted(() => {
    document.removeEventListener('visibilitychange', windowFocusFunc);
  });

  return {
    data,
    loading,
    run,
  };
};
// use 
    const data = reactive<unkonw[]>([]);
  const { data, run, loading } = useRequest(evaluateList, {}, {
      success(res) {
          data.length = 0;
          total.value = _.get(res, 'data.data.total', 0);
          data.push(...(_.get(res, 'data.data.records', []) || []))
      },
  })
  
  // 页面loading加载
  // template
  
   <ContentContainer v-loading.lock="loading">
     ...
   </ContentContainer>

  // jsx
  <ContentContainer
    v-loading={loading.value}
    v-slots={{
      default: () => (
        <div class="{styles.wrap}">
          {renderHeader()}
        </div>
      ),
    }}
/>

文章作者: KarlFranz
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 reprint policy. If reproduced, please indicate source KarlFranz !
评论
  目录