alibaba / hooks Goto Github PK
View Code? Open in Web Editor NEWA high-quality & reliable React Hooks library. https://ahooks.pages.dev/
Home Page: https://ahooks.js.org/
License: MIT License
A high-quality & reliable React Hooks library. https://ahooks.pages.dev/
Home Page: https://ahooks.js.org/
License: MIT License
根据之前尽龙的需求:
有的 service 会被封装成函数。useAPI 不适用的情况
目前初步的想法是用 useAsync 代替,dummyCode:
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() 才会执行.
focusUpdate 不是很理解, Table 的 onChange 没有这个属性,我们需要代理一下吗?
Originally posted by @brickspert in #11 (comment)
原来是为了解决当前分页的操作问题加的。比如 table 中有一列是 switch,状态改变后需要刷新当前页码的数据,但这种情况下 current、pageSize 都没变。用 deps 实现还不如暴露 reload 方法给用户,用来刷新数据。
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.
create
scenario, name should always be unique.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.
Ideas and comments are welcomed!
参考业内优秀案例,完成主题。
将 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;
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;
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;
版本: 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>
此下拉组件是经过简单封装,但是提交的时候不能获取到此组件数据。
sunflower 都是 antd 相关的 react-hooks
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} />
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}
/>
通常的做法应该是让项目自己去加。
现在的影响是 @umijs/hooks 没法在 remax 里用,因为 regenerator-runtime 0.13 版本用了 Function
,小程序里不支持。
在我看来 useAPI 现在的设计和实现有两个问题:
axios
强耦合在绝大部分业务中,request 库都是有自己封装的,可能用的是 fetch
又或者是 umi-request
等等,useAPI
作为一个基础 hook,不应该钦点用户使用 axios
再者,在钦点的情况下,对于请求参数也仅仅透传了 url、method、body 三者,对于复杂请求也是不够的
因此,这个 api 设计的没有可扩展性的,我实际使用中更倾向自己封装服务的 Promise 然后去使用类似 useAsync
的 hook
这里说的竞态处理不是指取消请求,而是
情况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]);
}
处理常见的Modal的显示与隐藏
useModal(defaultValue:boolean);
const { visible , show ,close } = useModal(false);
<Button onClick={show}>显示</Button>
<Modal visible={visible} onCancel={close}>
</Modal>
在useAsync onSuccess 中调用useAntTable的submit方法不会重新刷新表格。
用例是:使用useAsync新增一条数据后需要刷新表格数据。
https://codesandbox.io/s/hooks-demo-u3l66
contribute_template
和 .github
这种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;
适用于在同一个列表中,点击加载更多,或者上拉加载更多的应用场景。
我们假设,在加载更多场景中,一般是 page 一直是 1,而 pageSize 不断变大
const {
loading,
loadMoreIng,
data,
reload,
loadMore,
noMore,
total
} = useLoadMore(loadData, deps ,options: { filterData ,initPageSize, peerPageSize, ref});
输入
输出
应用场景
适用于 点击加载 更多的场景,另外一种 上拉加载 更多同理
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>
)
}
不再依赖 react-use,改由我们自己实现基础 Hooks
把请求参数存下来。
列表中有10个元素,ID分别为 1-10.
const update = useAsync(
(spuId: string, autoFill: boolean) => {
return postUpdate({
id,
categoryId:props.categoryId
});
},
[props.categoryId],
{
manual: true,
}
);
上面这个 hooks,是没办法知道那个元素应该 loading 的。只能知道整体是处于 loading 状态。
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 时,开始轮询的方法
}
数据存储有效期的方案,支持 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;
支持把 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', '')
灵感来自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>
))
}
可以用来处理多个组件之间的事件通知,并且自带订阅的清理逻辑。
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];
}
适用于边输入边查询的场景。内置了 防抖控制,请求时序控制,加载状态 等逻辑。
const {
data,
loading,
bind: {onChange, value}
} = useSearch(loadData, deps);
输入
输出
应用场景
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>
)
}
何不直接依赖这些库,再加上我们自己特有的东西,避免重复造轮子。
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;
集成了常见的 包含 精简搜索,复杂搜索,分页 等的 列表页。
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 | 搜索类型,可选值 simple 、advance ,用于切换搜索表单 |
form.changeSearchType | Function | 搜索类型切换处理函数 |
form.search | Function | 表单搜索处理函数,用于设置 form.submit,与 changeTable 的差异是会重置分页状态 |
目前 useAsync 默认开启竞态处理,在有些场景下不太合适。
比如,一个列表页有 10 条数据,每一条都有自己单独的删除,当我同时删除好几条的时候,下一次请求总是会把上一次请求废弃掉,这样是不符合预期的。
个人建议是默认不开启竞态处理,可以提供个开启的开关。
在 const {
list,
remove,
getKey,
push,
} = useDynamicList(usersList);
中数据(usersList)如果是后台获取的,在获取成功时候list数据不会变化,是否能支持一下。
适用展示大量数据需要做虚拟列表做性能优化的场景。可以和 useLoadMore 结合,形成大量数据展示的最佳实践。
const {
list,
containerProps,
getItemProps,
getItemVisibility,
isScrolling,
noMore,
} = useVirtual(data, initialSize, incrementalSize, threshold);
输入
输出
应用场景
适用展示大量数据需要做虚拟列表做性能优化的场景。可以和 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 && <>已经加载完毕</>}
</>
)
}
基于现在的 Hooks 类型,建议分类为
Todo
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in DownloadButton (created by Page)
单独处理分页的 Hooks,支持各种情况的分页处理。
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
}
)
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
}
)
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
}
)
1. useDebounce
2. useThrottle
3. useBoolean
4. useToggle
5. useTimeout
6. useInterval
7. useUpdateEffect
8. useUpdateLayoutEffect
9. useTitle
10. useExpireStorage
默认英文,支持中文。参考社区优秀案例~
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.