GithubHelp home page GithubHelp logo

alibaba / hooks Goto Github PK

View Code? Open in Web Editor NEW
13.3K 13.3K 2.6K 9.04 MB

A high-quality & reliable React Hooks library.

Home Page: https://ahooks.js.org/

License: MIT License

TypeScript 98.14% JavaScript 1.36% CSS 0.08% Less 0.01% HTML 0.39% Shell 0.02%
ahooks hooks-library react react-hooks umi-hooks

hooks's Introduction

A high-quality & reliable React Hooks library.

NPM version NPM downloads npm npm Coverage Status gzip size Percentage of issues still open Average time to resolve an issue GitHub

English | 简体中文

📚 Documentation

✨ Features

  • Easy to learn and use
  • Supports SSR
  • Special treatment for functions, avoid closure problems
  • Contains a large number of advanced Hooks that are refined from business scenarios
  • Contains a comprehensive collection of basic Hooks
  • Written in TypeScript with predictable static types

📦 Install

$ npm install --save ahooks
# or
$ yarn add ahooks
# or
$ pnpm add ahooks
# or
$ bun add ahooks

🔨 Usage

import { useRequest } from 'ahooks';

💻 Online Demo

Edit demo for ahooks

🤝 Contributing

$ git clone [email protected]:alibaba/hooks.git
$ cd hooks
$ pnpm run init
$ pnpm start

Open your browser and visit http://127.0.0.1:8000

We welcome all contributions, please read our CONTRIBUTING.MD first, let's build a better hooks library together.

Thanks to all the contributors:

contributors

👥 Discuss

hooks's People

Contributors

awmleer avatar brickspert avatar crazylxr avatar dependabot[bot] avatar diamondyuan avatar dolov avatar enclairfarron avatar getwebhb avatar hchlq avatar headwindz avatar hurryhuang1007 avatar huruji avatar ityuany avatar jinixx avatar jsun969 avatar kangxinzhi avatar leftstick avatar li-jia-nan avatar linbudu599 avatar liuyib avatar luchx avatar miracles1919 avatar monkindey avatar qkxinq avatar rayhomie avatar ttys026 avatar turkyden avatar wangtianlun avatar yaogengzhu avatar zhaofinger avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hooks's Issues

useExpireStorage

数据存储有效期的方案,支持 localStorage 和 sessionStorage 两种实现。

import { useState, useCallback, useMemo } from 'react';

type IStorage = {
  [key: string]: any;
}

type IType = 'localStorage' | 'sessionStorage' | undefined;

function useExpireStorage<T>(
  key: string,
  defaultValue?: T,
  type: IType = 'localStorage'
) {
  const privateKey: string = `private_storage_key_${key}`;
  const storage: IStorage = window[type];
  const [state, setState] = useState(defaultValue);

  /**
   * 设置键值
   * @param [any] val 值
   * @param [number = 0] maxAge 存储时间:s | ms
   */
  const setValue = useCallback((val: T, maxAge: number = 0) => {
    // 设置持续时间小于10的自动转化
    const duration = (maxAge < 10) ? (maxAge * 1000) : maxAge;
    // 返回有效期
    const expires = maxAge === 0 ? 0 : Date.now() + duration;
    const data = {
      val,
      expires,
    };
    storage[privateKey] = JSON.stringify(data);
    setState(val);
  }, []);

  // 移除键值
  const remove = useCallback(() => {
    delete storage[privateKey];
    setState(defaultValue);
  }, []);

  // 返回当前存储的值
  const value: T = useMemo(() => {
    // 防止首次取不到值出现的错误
    const data = storage[privateKey] && JSON.parse(storage[privateKey]);
    if (!data) {
      return state;
    }

    // 不设定期限或者处于有效期内
    if (data.expires === 0 || Date.now() < data.expires) {
      return data.val;
    }

    // 移除键值,防止超期缓存
    remove()
    return state;
  }, [state]);

  return {
    value,
    setValue,
    remove
  }
}

export default useExpireStorage;

[RFC] useLocalStorageState

将 useLocalStorageState 进行重构,抽离出底层支持 useStorageState

import { useState } from 'react';

type StorageType = 'localStorage' | 'sessionStorage';

function useStorageState<T = undefined>(type: StorageType, key: string): [T | undefined, (value?: T) => void];
function useStorageState<T>(type: StorageType, key: string, value: T): [T, (value?: T) => void];

function useStorageState<T>(type: StorageType = 'localStorage', key: string, defaultValue?: T) {
  const storage: Storage = window[type];
  const [state, setState] = useState<T | undefined>(() =>
    (storage.getItem(key) === null ? defaultValue : JSON.parse(storage.getItem(key)!)),
  );
  function updateState(value?: T) {
    if (typeof value === 'undefined') {
      storage.removeItem(key);
      setState(defaultValue);
    } else {
      storage.setItem(key, JSON.stringify(value));
      setState(value);
    }
  }
  return [state, updateState];
}

export default useStorageState;

useLocalStorageState

import useStorageState from '../useStorageState';

function useLocalStorageState<T = undefined>(key: string): [T | undefined, (value?: T) => void];
function useLocalStorageState<T>(key: string, value: T): [T, (value?: T) => void];

function useLocalStorageState<T>(key: string, defaultValue?: T) {
  return useStorageState('localStorage', key, defaultValue)
}

export default useLocalStorageState;

useSessionStorageState

import useStorageState from '../useStorageState';

function useSessionStorageState<T = undefined>(key: string): [T | undefined, (value?: T) => void];
function useSessionStorageState<T>(key: string, value: T): [T, (value?: T) => void];

function useSessionStorageState<T>(key: string, defaultValue?: T) {
  return useStorageState('sessionStorage', key, defaultValue)
}

export default useSessionStorageState;

去 react-use

不再依赖 react-use,改由我们自己实现基础 Hooks

antd 相关是否一起来开发 sunflower ?

sunflower 都是 antd 相关的 react-hooks

1. sunflower

useFormTable

https://ant-design.github.io/sunflower/docs-hooks-sunflower-antd-form-table

const { list, loading } = useFormTable({ search });
<Form>
  <Form.Item> 
     <Input />
  </Form.Item>
</Form>
<Table loading={loading} />

2. #4

useAntdTable

const {
  table: { data, loading, changeTable, },
  form: { search, }
} = useTable({ form, service: getMyApp, id: 'tableId' });

<Form onSubmit={search}>
</Form>

<Table
  loading={loading}
  onChange={changeTable}
  dataSource={data.list}
/>

[RFC] usePagination

单独处理分页的 Hooks,支持各种情况的分页处理。

  1. 常见的异步请求分页
const {
  data,
  loading,
  onChange: (current, pageSize)=>void, //一起修改 current 和 pageSize
  changeCurrent: (current)=>void, // 单独修改 current
  changePageSize: (pageSize)=>void, // 单独修改 pageSize

  pagination:{
      current,
      pageSize,
      total,
      totalPage,
  },
  refresh
} = usePagination(
  asyncFn: (current, pageSize)=> Promise,
  deps,
  options:{
    defaultCurrent,
    defaultPageSize
  }
)
  1. 对本地数据进行分页
const {
  data,
  onChange: (current, pageSize)=>void, //一起修改 current 和 pageSize
  changeCurrent: (current)=>void, // 单独修改 current
  changePageSize: (pageSize)=>void, // 单独修改 pageSize
  pagination:{
      current,
      pageSize,
      total,
      totalPage,
  },
  setSourceData,
} = usePagination(
  sourceData, // 本地数组
  deps,
  options:{
    defaultCurrent,
    defaultPageSize
  }
)
  1. 对本地数据进行分组,第一个参数也支持传入函数
const {
  data,
  onChange: (current, pageSize)=>void, //一起修改 current 和 pageSize
  changeCurrent: (current)=>void, // 单独修改 current
  changePageSize: (pageSize)=>void, // 单独修改 pageSize
  pagination:{
      current,
      pageSize,
      total,
      totalPage,
  },
  setSourceData,
} = usePagination(
  fn: ({current,pageSize})=>{ return [] }, // 对本地数据进行分页
  deps,
  options:{
    defaultCurrent,
    defaultPageSize
  }
)

New hook request: useEventEmitter

灵感来自angular的 EventEmitter文档

Api:

const event$ = useEventEmitter()
event$.useSubscription(callback)

Demo:

function App() {
  const newMessage$ = useEventEmitter()
  return (
    <>
      <List newMessage$={newMessage$}/>
      <button onClick={() => {newMessage$.emit('hello')}}>New Message</button>
    </>
  )
}

function List(props) {
  const [state, setState] = useState([])
  props.newMessage$.useSubscription(value => {
    setState([...state, value])
  })

  return state.map((message, index) => (
    <p key={index}>{message}</p>
  ))
}

可以用来处理多个组件之间的事件通知,并且自带订阅的清理逻辑。

useVirtualList

适用展示大量数据需要做虚拟列表做性能优化的场景。可以和 useLoadMore 结合,形成大量数据展示的最佳实践。

const {
  list,
  containerProps,
  getItemProps,
  getItemVisibility,
  isScrolling,
  noMore,
} = useVirtual(data, initialSize, incrementalSize, threshold);

输入

  • data 原始的全量数据,
  • initialSize 初始载入条目数
  • incrementalSize 每次增加条目数
  • threshold 距离底部多少条时开始加载
  • 待讨论是否要把 loadMore 的 asyncFunction 也传进去,触及数据底部自动 append list

输出

  • list 需要展示的部分数据,
  • containerProps 滚动容器的 props, 包含部分 style 和 onScroll 之类属性,
  • getItemProps 一个函数,getItemProps(index) => itemProps, 包含 style (position,height)
  • getItemVisibility 一个函数,getItemVisibility(index) => visible, 是否在视区内, boolean
  • isScrolling 当前是否在滚动,boolean,
  • noMore 是否载入了全部数据,boolean,

应用场景

适用展示大量数据需要做虚拟列表做性能优化的场景。可以和 useLoadMore 结合,形成大量数据展示的最佳实践。

const Demo = (type) => {

const {
  list,
  containerProps,
  getItemProps,
  getItemVisibility,
  isScrolling,
  noMore,
} = useVirtual([1,2,3, ..., 9999], 10, 10, 3);

  return (
    <>
        <div {...containerProps}>
            {list.map((ele, index)=>(<div {...getItemProps(index)}>{ele}</div>))}
        </div>
        {noMore && <>已经加载完毕</>}
    </>
  )
}

[RFC] useValidator

Like useSearch, this hook is designed to check whether a unique name already exists in database. In such case, you should make an API call in validator when necessary.

  • in create scenario, name should always be unique.
  • in edit scenario, name can be the same as initialValue, other than that it should be unique.

Here's a possible use case when using with antd form.

image

Ideas and comments are welcomed!

建议不要依赖 regenerator-runtime

通常的做法应该是让项目自己去加。

现在的影响是 @umijs/hooks 没法在 remax 里用,因为 regenerator-runtime 0.13 版本用了 Function,小程序里不支持。

[discuss] what basic hooks should we use ?

basic hooks list

1. useDebounce
2. useThrottle
3. useBoolean
4. useToggle
5. useTimeout
6. useInterval
7. useUpdateEffect
8. useUpdateLayoutEffect
9. useTitle
10. useExpireStorage

关于useDynamicList使用

在 const {
list,
remove,
getKey,
push,
} = useDynamicList(usersList);
中数据(usersList)如果是后台获取的,在获取成功时候list数据不会变化,是否能支持一下。

[RFC] useAsync 的 state 里面增加一个参数

https://github.com/umijs/hooks/blob/1968b7ac6fd2ad9a5b819a0b8a351cf2ccdebaa9/src/useAsync/index.ts#L111

把请求参数存下来。

实际场景

列表中有10个元素,ID分别为 1-10.

const update = useAsync(
    (spuId: string, autoFill: boolean) => {
      return postUpdate({
        id,
        categoryId:props.categoryId
      });
    },
    [props.categoryId],
    {
      manual: true,
    }
  );

上面这个 hooks,是没办法知道那个元素应该 loading 的。只能知道整体是处于 loading 状态。

useAsync

根据之前尽龙的需求:
image
有的 service 会被封装成函数。useAPI 不适用的情况

目前初步的想法是用 useAsync 代替,dummyCode:
carbon

useAsync 接受三个参数:
func: 一个 async function,用于被执行。
dependencies?: 依赖数组,在依赖改变是,会重新执行函数(当 initExecute 为 true 时)
initExecute?: 布尔值,是否默认执行,如果不传此参数,则默认执行,否则只有在运行 run() 时才会执行函数。

返回值:
data: promise 的 resolve 值.
loading: promise 是否在进行中.
error: promise 的 reject 值.
cancel: 如果在 loading,运行 cancel() 可以取消此次 promise
run: run() 可以无视 deps 立即执行函数,如果 initExecute 为 false,则只有先运行 run() 才会执行.

useStorageState

支持把 state 支持化的存储在 localStorage 中,例如暂存用户输入的内容,保存用户的设置。

API

function useStorageState<T extends string>(key: string, defaultValue: T): [T, (value: T) => void]

第一个参数为 localStorage 的 key ,第二个参数为 state 的默认值。

返回值的类型和 useState 一致。

Demo

const [input, setInput] = useStorageState('chat-input', '')

打包优化

  • 支持 .d.ts
  • 支持 babel-plugin-import
  • npm发布的时候只发布 dist 目录即可,避免 package 体积过大或发布过多无用的文件比如 contribute_template.github 这种

useTable 无法获取经过封装的表单的值

版本: 1.3.1, 1.2.0.

 <Form className="simpleForm" onSubmit={submit}>
      <Row gutter={24}>
        <Col span={6}>
          <Form.Item label="角色">
            {getFieldDecorator('role_id')(<RoleSelect placeholder="选择角色" />)}
          </Form.Item>
        </Col>
        <Col span={6}>
          <Button type="primary" htmlType="submit" >
            搜索
          </Button>
        </Col>
      </Row>

此下拉组件是经过简单封装,但是提交的时候不能获取到此组件数据。

focusUpdate 不是很理解, Table 的 onChange 没有这个属性,我们需要代理一下吗?

focusUpdate 不是很理解, Table 的 onChange 没有这个属性,我们需要代理一下吗?

Originally posted by @brickspert in #11 (comment)

原来是为了解决当前分页的操作问题加的。比如 table 中有一列是 switch,状态改变后需要刷新当前页码的数据,但这种情况下 current、pageSize 都没变。用 deps 实现还不如暴露 reload 方法给用户,用来刷新数据。

useToggle/useBoolean

import { useCallback, useState } from 'react';

const useToggle = (defaultValue: any = false, reverseValue?: any) => {
  const [state, setState] = useState(defaultValue);

  const toggle = useCallback((value?: any) => {
    // 强制返回状态值,适用于点击操作
    if (value !== undefined) {
      setState(value);
      return;
    }
    // useBoolean
    if (reverseValue === undefined) {
      setState((s: any) => !s);
    } else {
      const data = state === defaultValue ? reverseValue : defaultValue;
      setState(data);
    }
  }, [state]);

  return [state, toggle];
}

export default useToggle;

useSearch

适用于边输入边查询的场景。内置了 防抖控制,请求时序控制,加载状态 等逻辑。

image

const {
  data,
  loading,
  bind: {onChange, value}
} = useSearch(loadData, deps);

输入

  • loadData 一个 Promise 函数,接收参数为搜索文本
  • deps 依赖数组,如果依赖变化,则 reload

输出

  • data loadData 返回的数据
  • loading 是否正在加载

应用场景

const Demo = (type) => {
  const loadData = searchText => asyncGetList(searchText);

  const { data, loading, bind } = useSearch(loadData, [type]);

  return (
    <div>
      <Input {...bind} />
      
      <Spin spinning={loading}>
        {/* 从 data 中拿到列表的数据 */}
        {data.result.map(i => <List data={i} key={i.id} />)}
      </Spin>
    </div>
  )
}

useAPI

useAPI 跟 useAsync 实现的功能类似,相当于 useAsync 更高一层的封装。
接受四个入参:

request: request 的方法,一般业务会对 umi-request 做一层封装,例如,添加错误码,统一错误处理等。一般位于 src/util/request.ts
url: fetch 函数的第一个参数。
options: fetch 函数的第二个参数。包括 header, method 等
polling: 是否需要轮询,单位为毫秒,如果传了此参数,则会定时轮询。

返回:

{
    data: 接口返回结果
    loading: 加载状态, 
    cancel:取消正在进行的请求的方法, 
    reload: 强制重新请求的方法,
    stop: 当设置了 polling 时,停止轮询的方法,
    pause:当设置了 polling 时,暂停轮询的方法,
    resume:当设置了 polling 时,开始轮询的方法
}

[RFC] useSelect

import { useState, useMemo } from 'react';

export default function useSelect<T>(items: T[], subItems?: T[]) {
  const [selected, setSelected] = useState<T[]>([]);

  const { subItemsMap, selectedMap, subMode, subSelectedSet } = useMemo(() => {
    const selectedMap = new Set<T>(selected);
    const subItemsMap = new Set<T>(subItems);
    const subMode = Array.isArray(subItems);
    const subSelectedSet = new Set<T>(selected.filter(o => subItemsMap.has(o)));
    return { subItemsMap, selectedMap, subMode, subSelectedSet };
  }, [items, subItems, selected]);

  function isSelected(item: T) {
    return selectedMap.has(item);
  }

  function selectItem(item: T, callback?: (s: T[]) => void) {
    selectedMap.add(item);
    setSelected(Array.from(selectedMap));
    if (callback && typeof callback === 'function') {
      callback(Array.from(selectedMap));
    }
  }

  function deleteItem(item: T, callback?: (s: T[]) => void) {
    selectedMap.delete(item);
    setSelected(Array.from(selectedMap));
    if (callback && typeof callback === 'function') {
      callback(Array.from(selectedMap));
    }
  }

  function selectAll(callback?: (s: T[]) => void) {
    if (subMode) {
      subItems.forEach(o => {
        selectedMap.add(o);
      });
    } else {
      items.forEach(o => {
        selectedMap.add(o);
      });
    }
    setSelected(Array.from(selectedMap));
    if (callback && typeof callback === 'function') {
      callback(Array.from(selectedMap));
    }
  }

  function removeAll(callback?: (s: T[]) => void) {
    let result = [];
    if (subMode) {
      result = selected.filter(o => !subItemsMap.has(o));
    }
    setSelected(result);
    if (callback && typeof callback === 'function') {
      callback(result);
    }
  }

  const noneSelected = subMode ? subSelectedSet.size === 0 : selectedMap.size === 0;

  const allSelected =
    (subMode ? subItems.every(o => subSelectedSet.has(o)) : items.every(o => selectedMap.has(o))) &&
    !noneSelected;

  const selectToggle = (o: T, callback?: (s: T[]) => void) => {
    if (!isSelected(o)) {
      return () => selectItem(o, callback);
    }
    return () => deleteItem(o, callback);
  };

  const toggleAll = (callback?: (s: T[]) => void) => {
    return allSelected ? removeAll(callback) : selectAll(callback);
  };

  const halfSelected = !noneSelected && !allSelected;

  const helper = {
    isSelected,
    selectAll,
    removeAll,
    selectItem,
    allSelected,
    noneSelected,
    deleteItem,
    selectToggle,
    toggleAll,
    halfSelected,
    setSelected,
  };

  return [selected, helper] as [typeof selected, typeof helper];
}

[refactor] remove useUpdateEffect of react-use

A hook only works after component mounted,which replaced useUpdateEffect of reactUse.

import { useEffect, useRef } from 'react';

const useUpdateEffect: typeof useEffect = (effect, deps) => {
  const isMounted = useRef(false);

  useEffect(() => {
    if (!isMounted.current) {
      isMounted.current = true;
    }else {
      return effect();
    }
  }, deps);
};

export default useUpdateEffect;

useAntdTable

集成了常见的 包含 精简搜索,复杂搜索,分页 等的 列表页。

功能

  1. 包含分页能力的表格数据展示;
  2. 搜索表单与表格联动;
  3. 支持简单、复杂 2 种参数独立的搜索模式切换,并且能自动载入上一次表单填充数据(需要配置 id);
  4. 跳出当前页后返回时自动还原上一次的表单状态,并刷新之前的页码数据(需要配置 id);

用法

userAntdTable({ service: Function, form?: Antd.WrappedFormUtils, id?: string })

例子

const {
  table: { data, loading, changeTable, },
  form: { search, searchType, changeSearchType}
} = useTable({ form, service: getMyApp });

<Form onSubmit={search}>
</Form>

<Table
  loading={loading}
  onChange={changeTable}
  dataSource={data.list}
/>

参数说明

参数名 必填 类型 说明
service Promise 请求表格数据的 service 方法
form Antd.WrappedFormUtils 搜索 From,为空时 useTable 的 search 方法也不存在
id string 缓存 id,为空时不会缓存数据

约定

  • 分页参数名:current
  • useTable 调用 service 时,会把分页参数(current)和搜索表单的查询参数合并

返回值

参数名 类型 说明
table.data Object 调用 service 时服务端返回的数据,主要用于渲染表格数据
table.loading boolean 是否正在加载中,用于给 table 增加 loading 状态
table.changeTable Function 表格翻页处理函数,用于设置 table.onChange,传入 focusUpdate 可以使用当前搜索条件强制刷新数据
form.searchType string 搜索类型,可选值 simpleadvance,用于切换搜索表单
form.changeSearchType Function 搜索类型切换处理函数
form.search Function 表单搜索处理函数,用于设置 form.submit,与 changeTable 的差异是会重置分页状态

useModal

处理常见的Modal的显示与隐藏

Api

useModal(defaultValue:boolean);

用法

const { visible , show ,close } = useModal(false);

<Button onClick={show}>显示</Button>
<Modal visible={visible} onCancel={close}>
</Modal>

useAPI 的两个问题

useAPI 讨论

在我看来 useAPI 现在的设计和实现有两个问题:

axios 强耦合

在绝大部分业务中,request 库都是有自己封装的,可能用的是 fetch 又或者是 umi-request 等等,useAPI 作为一个基础 hook,不应该钦点用户使用 axios

再者,在钦点的情况下,对于请求参数也仅仅透传了 url、method、body 三者,对于复杂请求也是不够的

因此,这个 api 设计的没有可扩展性的,我实际使用中更倾向自己封装服务的 Promise 然后去使用类似 useAsync 的 hook

没有竞态处理

这里说的竞态处理不是指取消请求,而是

  1. 重复请求的覆盖
  2. 请求返回之前组件就没卸载

情况1经常发生于快速翻页表单一类的情况下,所需要做处理接近 rxjs 中的 switchMap

后者如果不处理 React 会在 console 给你报 error

这里我给一个处理思路参考

function useAPI(apiService, deps) {
  // ...
  // 处理竞态请求
  const serviceUID = useRef(0);
  useEffect(() => {
    const thisUID = serviceUID.current;
    // 请求
    apiService().then(/res/ => {
      // 原请求被新请求覆盖
      if (thisUID !== serviceUID.current) {
        return;
      }
      // ...
    })

    return () => {
      // 抛弃未完成的请求
      serviceUID.current += 1;
    };
  }, [deps]);
}

[讨论]useAsync 是否需要竞态处理

目前 useAsync 默认开启竞态处理,在有些场景下不太合适。

比如,一个列表页有 10 条数据,每一条都有自己单独的删除,当我同时删除好几条的时候,下一次请求总是会把上一次请求废弃掉,这样是不符合预期的。

个人建议是默认不开启竞态处理,可以提供个开启的开关。

useLoadMore

适用于在同一个列表中,点击加载更多,或者上拉加载更多的应用场景。

我们假设,在加载更多场景中,一般是 page 一直是 1,而 pageSize 不断变大

const {
  loading,
  loadMoreIng,
  data,
  reload,
  loadMore,
  noMore,
  total
} = useLoadMore(loadData, deps ,options: { filterData ,initPageSize, peerPageSize, ref});

输入

  • loadData 一个 Promise 函数,接收参数为总的 pageSize
  • deps 依赖数组,如果变化后,则触发 reload
  • options
    • filterData 一个处理 loadData 返回数据的函数,我们需要通过这个函数,拿到总的 total, 以便知道是否还需要加载更多
    • initPageSize 第一页需要加载的数据数量,默认为 10
    • peerPageSize 加载更多时,每次加载更多的页数,默认等于 initPageSize
    • ref 滚动区域的 dom 节点,如果有 ref,在滚动到底部时,会自动触发 loadMore

输出

  • loading 是否正在进行第一次加载
  • loadMoreIng 是否正在加载更多
  • data loadData 返回的数据
  • reload 触发重新加载
  • loadMore 触发加载更多
  • noMore 是否还有更多数据
  • total 数据总量

应用场景

适用于 点击加载 更多的场景,另外一种 上拉加载 更多同理

const Demo = (type) => {
  const loadData = pageSize => asyncGetList(pageSize);

  const {
    loading,
    loadMoreIng,
    data,
    loadMore,
    reload,
    noMore,
    total
  } = useLoadMore(loadData, [ type ], {
    filterData: data => data.total,
    initPageSize: 10,
    peerPageSize: 5
  });

  const renderLoadMore = () => {
    if (!noMore) {
      return '没有更多数据了'
    }
    if (loadMoreIng) {
      return '正在加载更多数据'
    }
    return <div onClick={loadMore}>点击加载更多</div>
  }

  return (
    <Spin spinning={loading}>
      总共有{total}条数据
      {/* 从 data 中拿到列表的数据 */}
      {data.result.map(i => <List data={i} key={i.id} />)}
      {/* 加载更多 */}
      {renderLoadMore()}
    </Spin>
  )
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.