Showing preview only (1,185K chars total). Download the full file or copy to clipboard to get everything.
Repository: alibaba/hooks
Branch: master
Commit: 289989fe688f
Files: 657
Total size: 1.0 MB
Directory structure:
gitextract_8eg352yw/
├── .babelrc
├── .coveralls.yml
├── .cursor/
│ └── rules/
│ ├── demo.mdc
│ ├── docs.mdc
│ ├── git.mdc
│ ├── project.mdc
│ ├── testing.mdc
│ └── typescript.mdc
├── .editorconfig
├── .github/
│ ├── PULL_REQUEST_TEMPLATE/
│ │ └── pr_cn.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ ├── comment-when-needs-more-info.yml
│ ├── gitleaks.yml
│ ├── issue-close-require.yml
│ ├── issue-reply.yml
│ ├── pkg.pr.new.yml
│ ├── static.yml
│ └── test.yml
├── .gitignore
├── .gitleaks.toml
├── .husky/
│ ├── commit-msg
│ └── pre-commit
├── .npmrc
├── .travis.yml
├── CONTRIBUTING.MD
├── CONTRIBUTING.zh-CN.MD
├── LICENSE
├── README.md
├── README.zh-CN.md
├── SECURITY.md
├── biome.json
├── config/
│ ├── config.ts
│ └── hooks.ts
├── docs/
│ ├── guide/
│ │ ├── blog/
│ │ │ ├── function.en-US.md
│ │ │ ├── function.zh-CN.md
│ │ │ ├── hmr.en-US.md
│ │ │ ├── hmr.zh-CN.md
│ │ │ ├── ssr.en-US.md
│ │ │ ├── ssr.zh-CN.md
│ │ │ ├── strict.en-US.md
│ │ │ └── strict.zh-CN.md
│ │ ├── dom.en-US.md
│ │ ├── dom.zh-CN.md
│ │ ├── index.en-US.md
│ │ ├── index.zh-CN.md
│ │ ├── upgrade.en-US.md
│ │ └── upgrade.zh-CN.md
│ ├── index.en-US.md
│ └── index.zh-CN.md
├── example/
│ └── .gitkeep
├── gulpfile.js
├── package.json
├── packages/
│ ├── hooks/
│ │ ├── gulpfile.js
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── createDeepCompareEffect/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ └── index.ts
│ │ │ ├── createUpdateEffect/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ └── index.ts
│ │ │ ├── createUseStorageState/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ └── index.ts
│ │ │ ├── global.d.ts
│ │ │ ├── index.spec.ts
│ │ │ ├── index.ts
│ │ │ ├── useAntdTable/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── cache.tsx
│ │ │ │ │ ├── form.tsx
│ │ │ │ │ ├── init.tsx
│ │ │ │ │ ├── ready.tsx
│ │ │ │ │ ├── table.tsx
│ │ │ │ │ └── validate.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.tsx
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ └── types.ts
│ │ │ ├── useAsyncEffect/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useBoolean/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useClickAway/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ ├── demo3.tsx
│ │ │ │ │ ├── demo4.tsx
│ │ │ │ │ ├── demo5.tsx
│ │ │ │ │ └── demo6.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useControllableValue/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ └── demo3.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useCookieState/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.tsx
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ └── demo3.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useCountDown/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ └── demo3.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useCounter/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useCreation/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useDebounce/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── debounceOptions.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useDebounceEffect/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useDebounceFn/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useDeepCompareEffect/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.tsx
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useDeepCompareLayoutEffect/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.tsx
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useDocumentVisibility/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useDrag/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ └── index.ts
│ │ │ ├── useDrop/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useDynamicList/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ ├── demo3.tsx
│ │ │ │ │ └── demo4.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useEventEmitter/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useEventListener/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ └── demo3.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useEventTarget/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useExternal/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ └── demo3.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useFavicon/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.tsx
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useFocusWithin/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.tsx
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.tsx
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useFullscreen/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ ├── demo3.tsx
│ │ │ │ │ └── demo4.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useFusionTable/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── cache.tsx
│ │ │ │ │ ├── form.tsx
│ │ │ │ │ ├── init.tsx
│ │ │ │ │ ├── table.tsx
│ │ │ │ │ └── validate.tsx
│ │ │ │ ├── fusionAdapter.ts
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.tsx
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ └── types.ts
│ │ │ ├── useGetState/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useHistoryTravel/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ └── demo3.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useHover/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.tsx
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useInViewport/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ └── demo3.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useInfiniteScroll/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── default.tsx
│ │ │ │ │ ├── mutate.tsx
│ │ │ │ │ ├── pagination.tsx
│ │ │ │ │ ├── reload.tsx
│ │ │ │ │ ├── scroll.tsx
│ │ │ │ │ └── scrollTop.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.tsx
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ └── types.ts
│ │ │ ├── useInterval/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useIsomorphicLayoutEffect/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useKeyPress/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.tsx
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ ├── demo3.tsx
│ │ │ │ │ ├── demo4.tsx
│ │ │ │ │ ├── demo5.tsx
│ │ │ │ │ ├── demo6.tsx
│ │ │ │ │ ├── demo7.tsx
│ │ │ │ │ └── demo8.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useLatest/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useLocalStorageState/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ ├── demo3.tsx
│ │ │ │ │ └── demo4.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useLockFn/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useLongPress/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ └── demo3.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useMap/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useMemoizedFn/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useMount/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useMouse/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useMutationObserver/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useNetwork/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── usePagination/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ ├── demo3.tsx
│ │ │ │ │ └── demo4.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ └── types.ts
│ │ │ ├── usePrevious/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useRafInterval/
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── index.spec.ts
│ │ │ │ │ └── node.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useRafState/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useRafTimeout/
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── index.spec.ts
│ │ │ │ │ └── node.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useReactive/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.tsx
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ ├── demo3.tsx
│ │ │ │ │ ├── demo4.tsx
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useRequest/
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── index.spec.ts
│ │ │ │ │ ├── useAutoRunPlugin.spec.ts
│ │ │ │ │ ├── useCachePlugin.spec.tsx
│ │ │ │ │ ├── useDebouncePlugin.spec.ts
│ │ │ │ │ ├── useLoadingDelayPlugin.spec.ts
│ │ │ │ │ ├── usePollingPlugin.spec.ts
│ │ │ │ │ ├── useRefreshOnWindowFocusPlugin.spec.ts
│ │ │ │ │ ├── useRetryPlugin.spec.ts
│ │ │ │ │ └── useThrottlePlugin.spec.ts
│ │ │ │ ├── doc/
│ │ │ │ │ ├── basic/
│ │ │ │ │ │ ├── basic.en-US.md
│ │ │ │ │ │ ├── basic.zh-CN.md
│ │ │ │ │ │ └── demo/
│ │ │ │ │ │ ├── cancel.tsx
│ │ │ │ │ │ ├── default.tsx
│ │ │ │ │ │ ├── lifeCycle.tsx
│ │ │ │ │ │ ├── manual-run.tsx
│ │ │ │ │ │ ├── manual-runAsync.tsx
│ │ │ │ │ │ ├── mutate.tsx
│ │ │ │ │ │ ├── params.tsx
│ │ │ │ │ │ └── refresh.tsx
│ │ │ │ │ ├── cache/
│ │ │ │ │ │ ├── cache.en-US.md
│ │ │ │ │ │ ├── cache.zh-CN.md
│ │ │ │ │ │ └── demo/
│ │ │ │ │ │ ├── cacheKey.tsx
│ │ │ │ │ │ ├── clearCache.tsx
│ │ │ │ │ │ ├── params.tsx
│ │ │ │ │ │ ├── setCache.tsx
│ │ │ │ │ │ ├── share.tsx
│ │ │ │ │ │ └── staleTime.tsx
│ │ │ │ │ ├── debounce/
│ │ │ │ │ │ ├── debounce.en-US.md
│ │ │ │ │ │ ├── debounce.zh-CN.md
│ │ │ │ │ │ └── demo/
│ │ │ │ │ │ └── debounce.tsx
│ │ │ │ │ ├── index/
│ │ │ │ │ │ ├── demo/
│ │ │ │ │ │ │ ├── default.tsx
│ │ │ │ │ │ │ └── manual.tsx
│ │ │ │ │ │ ├── index.en-US.md
│ │ │ │ │ │ └── index.zh-CN.md
│ │ │ │ │ ├── loadingDelay/
│ │ │ │ │ │ ├── demo/
│ │ │ │ │ │ │ └── loadingDelay.tsx
│ │ │ │ │ │ ├── loadingDelay.en-US.md
│ │ │ │ │ │ └── loadingDelay.zh-CN.md
│ │ │ │ │ ├── polling/
│ │ │ │ │ │ ├── demo/
│ │ │ │ │ │ │ ├── polling.tsx
│ │ │ │ │ │ │ └── pollingError.tsx
│ │ │ │ │ │ ├── polling.en-US.md
│ │ │ │ │ │ └── polling.zh-CN.md
│ │ │ │ │ ├── ready/
│ │ │ │ │ │ ├── demo/
│ │ │ │ │ │ │ ├── manualReady.tsx
│ │ │ │ │ │ │ └── ready.tsx
│ │ │ │ │ │ ├── ready.en-US.md
│ │ │ │ │ │ └── ready.zh-CN.md
│ │ │ │ │ ├── refreshDeps/
│ │ │ │ │ │ ├── demo/
│ │ │ │ │ │ │ ├── refreshDeps.tsx
│ │ │ │ │ │ │ └── refreshDepsAction.tsx
│ │ │ │ │ │ ├── refresyDeps.en-US.md
│ │ │ │ │ │ └── refresyDeps.zh-CN.md
│ │ │ │ │ ├── refreshOnWindowFocus/
│ │ │ │ │ │ ├── demo/
│ │ │ │ │ │ │ └── refreshOnWindowFocus.tsx
│ │ │ │ │ │ ├── refreshOnWindowFocus.en-US.md
│ │ │ │ │ │ └── refreshOnWindowFocus.zh-CN.md
│ │ │ │ │ ├── retry/
│ │ │ │ │ │ ├── demo/
│ │ │ │ │ │ │ └── retry.tsx
│ │ │ │ │ │ ├── retry.en-US.md
│ │ │ │ │ │ └── retry.zh-CN.md
│ │ │ │ │ └── throttle/
│ │ │ │ │ ├── demo/
│ │ │ │ │ │ └── throttle.tsx
│ │ │ │ │ ├── throttle.en-US.md
│ │ │ │ │ └── throttle.zh-CN.md
│ │ │ │ ├── index.ts
│ │ │ │ └── src/
│ │ │ │ ├── Fetch.ts
│ │ │ │ ├── plugins/
│ │ │ │ │ ├── useAutoRunPlugin.ts
│ │ │ │ │ ├── useCachePlugin.ts
│ │ │ │ │ ├── useDebouncePlugin.ts
│ │ │ │ │ ├── useLoadingDelayPlugin.ts
│ │ │ │ │ ├── usePollingPlugin.ts
│ │ │ │ │ ├── useRefreshOnWindowFocusPlugin.ts
│ │ │ │ │ ├── useRetryPlugin.ts
│ │ │ │ │ └── useThrottlePlugin.ts
│ │ │ │ ├── types.ts
│ │ │ │ ├── useRequest.ts
│ │ │ │ ├── useRequestImplement.ts
│ │ │ │ └── utils/
│ │ │ │ ├── cache.ts
│ │ │ │ ├── cachePromise.ts
│ │ │ │ ├── cacheSubscribe.ts
│ │ │ │ ├── isDocumentVisible.ts
│ │ │ │ ├── isOnline.ts
│ │ │ │ ├── limit.ts
│ │ │ │ ├── subscribeFocus.ts
│ │ │ │ └── subscribeReVisible.ts
│ │ │ ├── useResetState/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useResponsive/
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── __snapshots__/
│ │ │ │ │ │ └── index.spec.ts.snap
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useSafeState/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useScroll/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ └── demo3.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useSelections/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ └── demo3.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useSessionStorageState/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useSet/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useSetState/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useSize/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.tsx
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useTextSelection/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ ├── demo2.tsx
│ │ │ │ │ └── demo3.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useTheme/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useThrottle/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ └── throttleOptions.ts
│ │ │ ├── useThrottleEffect/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useThrottleFn/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useTimeout/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useTitle/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useToggle/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useTrackedEffect/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useUnmount/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useUnmountedRef/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.tsx
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useUpdate/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useUpdateEffect/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useUpdateLayoutEffect/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useVirtualList/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ ├── demo1.tsx
│ │ │ │ │ └── demo2.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useWebSocket/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── useWhyDidYouUpdate/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── index.spec.ts
│ │ │ │ ├── demo/
│ │ │ │ │ └── demo1.tsx
│ │ │ │ ├── index.en-US.md
│ │ │ │ ├── index.ts
│ │ │ │ └── index.zh-CN.md
│ │ │ └── utils/
│ │ │ ├── __tests__/
│ │ │ │ └── index.spec.ts
│ │ │ ├── createEffectWithTarget.ts
│ │ │ ├── depsAreSame.ts
│ │ │ ├── depsEqual.ts
│ │ │ ├── domTarget.ts
│ │ │ ├── getDocumentOrShadow.ts
│ │ │ ├── index.ts
│ │ │ ├── isAppleDevice.ts
│ │ │ ├── isBrowser.ts
│ │ │ ├── isDev.ts
│ │ │ ├── lodash-polyfill.ts
│ │ │ ├── noop.ts
│ │ │ ├── rect.ts
│ │ │ ├── testingHelpers.ts
│ │ │ ├── tests.tsx
│ │ │ ├── useDeepCompareWithTarget.ts
│ │ │ ├── useEffectWithTarget.ts
│ │ │ ├── useIsomorphicLayoutEffectWithTarget.ts
│ │ │ └── useLayoutEffectWithTarget.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.pro.json
│ │ ├── vitest.config.ts
│ │ └── webpack.config.js
│ └── use-url-state/
│ ├── README.md
│ ├── __tests__/
│ │ ├── browser.spec.tsx
│ │ ├── router.spec.tsx
│ │ └── setup.tsx
│ ├── demo/
│ │ ├── demo1.tsx
│ │ ├── demo2.tsx
│ │ ├── demo3.tsx
│ │ └── demo4.tsx
│ ├── gulpfile.js
│ ├── package.json
│ ├── src/
│ │ └── index.ts
│ ├── tsconfig.json
│ ├── tsconfig.pro.json
│ ├── use-url-state.en-US.md
│ ├── use-url-state.zh-CN.md
│ ├── vitest.config.ts
│ └── webpack.config.js
├── pnpm-workspace.yaml
├── public/
│ ├── style.css
│ └── useExternal/
│ ├── bootstrap-badge.css
│ └── test-external-script.js
├── scripts/
│ └── build-with-relative-paths.js
├── tsconfig.base.json
├── tsconfig.pro.json
├── umd.html
├── vitest.config.ts
└── webpack.common.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [["@babel/env"], "@babel/react"],
"plugins": ["@babel/plugin-transform-runtime"]
}
================================================
FILE: .coveralls.yml
================================================
service_name: travis-pro
repo_token: dO6l2UfWXIPwCzK3lmEUKQyQsfzXZUZml
================================================
FILE: .cursor/rules/demo.mdc
================================================
---
description:
globs: components/*/demo/**
alwaysApply: false
---
# Demo 规范
- demo 代码尽可能简洁
- 避免冗余代码,方便用户复制到项目直接使用
- 每个 demo 聚焦展示一个功能点
- 提供中英文两个版本的说明
- demo 文件命名:
- 英文 demo: index.en-US.md
- 中文 demo: index.zh-CN.md
- 确保 demo 在各种尺寸下都能正常展示
- 对于复杂交互提供必要的操作说明
## 文件组织
- 每个组件演示包含 `.md`(说明文档)和 `.ts`(实际代码)两部分
- 位置:hooks 目录下的 `src` 子目录,如 `packages/hooks/src/useHover`
- 文件名应简洁地描述示例内容
## MD 文档规范
- 必须包含 `## zh-CN` 和 `## en-US` 两种语言说明
- 内容简洁明了,突出组件特性和用法
- 避免冗长段落,必要时使用列表或粗体
- 标注注意事项和实验性功能
## 代码质量
- 实用且专注于单一功能
- 关键处添加简洁注释
- 使用有意义的数据和变量
- 优先使用 ahooks 内置 hook 或者公共方法,减少外部依赖
## 质量要求
- 确保代码运行正常,无控制台错误
- 适配常见浏览器
- 避免过时 API,及时更新到新推荐用法
================================================
FILE: .cursor/rules/docs.mdc
================================================
---
description: 规范项目文档和 Changelog
globs: ["**/CHANGELOG*.md", "components/**/index.*.md"]
alwaysApply: false
---
# Changelog Emoji 规范
- 🐞 Bug 修复
- 💄 样式更新或 token 更新
- 🆕 新增特性,新增属性
- 🔥 极其值得关注的新增特性
- 🇺🇸🇨🇳🇬🇧 国际化改动
- 📖 📝 文档或网站改进
- ✅ 新增或更新测试用例
- 🛎 更新警告/提示信息
- ⌨️ ♿ 可访问性增强
- 🗑 废弃或移除
- 🛠 重构或工具链优化
- ⚡️ 性能提升
# 文档规范
- 提供中英文两个版本
- 新属性需声明可用的版本号
- 属性命名符合 API 命名规则
- hook 文档包含:使用场景、基础用法、API 说明
- 文档示例应简洁明了
- 属性的描述应清晰易懂
- 对复杂功能提供详细说明
- 加入 TypeScript 定义
- 提供常见问题解答
- 更新文档时同步更新中英文版本
## 其他要求
- 新增属性时,建议用易于理解的语言描述用户可以感知的变化
- 存在破坏性改动时,尽量给出原始的 PR 链接,社区提交的 PR 改动加上提交者的链接
================================================
FILE: .cursor/rules/git.mdc
================================================
---
description:
globs:
alwaysApply: true
---
# Git 规范
## 开发流程
1. 从保护分支(通常是 `master`)创建新的功能分支
2. 在新分支上进行开发
3. 提交 Pull Request 到目标分支
4. 等待 Code Review 和 CI 通过
5. 合并到目标分支
## 分支命名规范
- 功能开发:`feat/description-of-feature`
- 例如:`feat/add-dark-mode`
- 例如:`feat/improve-table-performance`
- 问题修复:`fix/issue-number-or-description`
- 例如:`fix/button-style-issue`
- 例如:`fix/issue-1234`
- 文档更新:`docs/what-is-changed`
- 例如:`docs/update-api-reference`
- 例如:`docs/fix-typos`
- 代码重构:`refactor/what-is-changed`
- 例如:`refactor/button-component`
- 例如:`refactor/remove-deprecated-api`
- 样式修改:`style/what-is-changed`
- 例如:`style/update-button-tokens`
- 例如:`style/improve-mobile-layout`
- 测试相关:`test/what-is-changed`
- 例如:`test/add-button-test`
- 例如:`test/improve-coverage`
- 构建相关:`build/what-is-changed`
- 例如:`build/upgrade-webpack`
- 例如:`build/fix-ts-config`
- 持续集成:`ci/what-is-changed`
- 例如:`ci/add-e2e-test`
- 例如:`ci/fix-deploy-script`
- 性能优化:`perf/what-is-changed`
- 例如:`perf/optimize-render`
- 例如:`perf/reduce-bundle-size`
- 依赖升级:`deps/package-name-version`
- 例如:`deps/upgrade-react-19`
- 例如:`deps/update-dependencies`
## 分支命名注意事项
1. 使用小写字母
2. 使用连字符(-)分隔单词
3. 简短但具有描述性
4. 避免使用下划线或其他特殊字符
5. 如果与 Issue 关联,可以包含 Issue 编号
## Pull Request 规范
### PR 标题
- PR 标题始终使用英文
- 遵循格式:`类型: 简短描述`
- 例如:`fix: fix button style issues in Safari browser`
- 例如:`feat: add dark mode support`
### PR 内容
- PR 内容默认使用英文
- 尽量简洁清晰地描述改动内容和目的
- 可以视需要在英文描述后附上中文说明
### PR 提交注意事项
1. **审核流程**:
- PR 需要由至少一名维护者审核通过后才能合并
- 确保所有 CI 检查都通过
- 解决所有 Code Review 中提出的问题
2. **PR 质量要求**:
- 确保代码符合项目代码风格
- 添加必要的测试用例
- 更新相关文档
- 大型改动需要更详细的说明和更多的审核者参与
3. **工具标注**:
- 如果是用 Cursor 提交的代码,请在 PR body 末尾进行标注:`> Submitted by Cursor`
## 新增内容
- Pull Request 标题格式:[组件名]: 描述
- 从 master 分支创建新分支
- 分支命名规范:
- feature/xxx:新特性
- fix/xxx:Bug 修复
- docs/xxx:文档更新
- PR 说明中选择改动类型:
- 🆕 新特性提交
- 🐞 Bug 修复
- 📝 文档改进
- 📽️ 演示代码改进
- 💄 样式/交互改进
- 🤖 TypeScript 更新
- 📦 包体积优化
- ⚡️ 性能优化
- 🌐 国际化改进
- 提供改动背景和解决方案
- 更新日志同时提供英文和中文版本
================================================
FILE: .cursor/rules/project.mdc
================================================
---
description:
globs:
alwaysApply: true
---
# 项目背景
这是由蚂蚁团队开发的一个高质量、可靠的 React Hooks 库。
- 易学易用
- 支持 SSR
- 对输入输出函数做了特殊处理,避免闭包问题
- 包含大量提炼自业务的高级 Hooks
- 包含丰富的基础 Hooks
- 使用 TypeScript 构建,提供完整的类型定义文件
# 编码规范
- 使用 TypeScript 和 React 书写
- 避免引入新依赖,严控打包体积
- 兼容现代浏览器
- 支持服务端渲染
- 保持向下兼容,避免 breaking change
- 合理使用 React.memo、useMemo 和 useCallback 优化性能
================================================
FILE: .cursor/rules/testing.mdc
================================================
---
description:
globs: **/__tests__/**,**/*.test.tsx,**/*.test.ts
alwaysApply: false
---
# 测试规范
- 使用 vitest 和 @testing-library/react 编写单元测试
- 测试覆盖率要求 100%
- 测试文件放在 __tests__ 目录,命名格式为:index.spec.ts 或 xxx.spec.ts
================================================
FILE: .cursor/rules/typescript.mdc
================================================
# TypeScript 规范
## 基本原则
- 所有组件和函数必须提供准确的类型定义
- 尽量避免使用 `any` 类型,尽可能精确地定义类型
- 使用接口而非类型别名定义对象结构
- 导出所有公共接口类型,方便用户使用
- 严格遵循 TypeScript 类型设计原则,确保类型安全
- 确保编译无任何类型错误或警告
## hook 类型定义
- 复杂的数据结构应拆分为多个接口定义
- 所有函数类型应明确定义参数和返回值
## 泛型使用
- 适当使用泛型增强类型灵活性
- 为泛型参数提供合理的默认类型和约束
- 避免过度使用泛型导致类型复杂化
- 在泛型参数上应用限制条件(constraints)确保类型安全
- 为复杂泛型提供类型别名以提高可读性
## 类型合并与扩展
- 使用交叉类型(&)合并多个类型
- 使用 Partial<T>、Pick<T, K>、Omit<T, K> 等工具类型修改现有类型
- 扩展原生 DOM 元素属性时,继承相应的内置类型
- 使用 type 定义联合类型和交叉类型
- 优先使用自带的工具类型,避免重复定义
## 枚举和常量
- 使用字面量联合类型定义有限的选项集合
- 为复杂的枚举值提供类型守卫函数
- 避免使用 `enum`,优先使用联合类型和 `as const`
- 对于关键常量,使用 `as const` 断言确保类型严格
- 为联合类型中的每个值提供适当的注释
## 类型推断与断言
- 尽可能依赖 TypeScript 的类型推断
- 只在必要时使用类型断言(as)
- 使用类型守卫函数进行运行时类型检查
- 尽量避免使用非空断言操作符(!)
- 使用 `instanceof` 和 `typeof` 进行类型守卫
- 为自定义类型创建类型谓词(type predicates)函数
## JSDoc 注释
- 为复杂的类型、函数、hook 添加 JSDoc 注释
- 使用 `@deprecated` 标记已废弃的 API
- 在注释中提供使用示例
- 说明参数和返回值的含义与约束
- 在 interface 和重要类型定义上添加文档注释
## 类型兼容性
- 确保类型定义兼容不同版本的 React
- 避免使用实验性或不稳定的 TypeScript 特性
- 为第三方库未提供的类型编写声明文件
- 使用条件类型处理复杂的类型逻辑
- 验证类型在不同 TypeScript 版本下的兼容性
## 严格使用 TypeScript 类型
- 导出组件类型和接口
- 避免使用 any,优先使用 unknown
- 组件 Props 使用 interface 定义
- 使用明确的命名约定
- 合理使用泛型提高复用性
- 导出类型时使用 export type
- 组件属性使用 JSDoc 注释说明用途
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
================================================
FILE: .github/PULL_REQUEST_TEMPLATE/pr_cn.md
================================================
<!--
首先,感谢你的贡献!😄
新特性请提交至 master 分支。
在维护者审核通过后会合并。
请确保填写以下 pull request 的信息,谢谢!~
-->
[[English Template / 英文模板](https://github.com/alibaba/hooks/blob/master/.github/PULL_REQUEST_TEMPLATE.md)]
### 🤔 这个变动的性质是?
- [ ] 新特性提交
- [ ] 日常 bug 修复
- [ ] 站点、文档改进
- [ ] 演示代码改进
- [ ] TypeScript 定义更新
- [ ] 包体积优化
- [ ] 性能优化
- [ ] 功能增强
- [ ] 国际化改进
- [ ] 重构
- [ ] 代码风格优化
- [ ] 测试用例
- [ ] 分支合并
- [ ] 其他改动(是关于什么的改动?)
### 🔗 相关 Issue
<!--
1. 描述相关需求的来源,如相关的 issue 讨论链接。
-->
### 💡 需求背景和解决方案
<!--
1. 要解决的具体问题。
2. 列出最终的 API 实现和用法。
3. 涉及UI/交互变动需要有截图或 GIF。
-->
### 📝 更新日志
<!--
从用户角度描述具体变化,以及可能的 breaking change 和其他风险。
-->
| 语言 | 更新描述 |
| ------- | -------- |
| 🇺🇸 英文 | |
| 🇨🇳 中文 | |
### ☑️ 请求合并前的自查清单
⚠️ 请自检并全部**勾选全部选项**。⚠️
- [ ] 文档已补充或无须补充
- [ ] 代码演示已提供或无须提供
- [ ] TypeScript 定义已补充或无须补充
- [ ] Changelog 已提供或无须提供
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
First of all, thank you for your contribution! 😄
New feature please send a pull request to master branch.
Pull requests will be merged after one of the collaborators approve.
Please makes sure that these forms are filled before submitting your pull request, thank you!
-->
[[中文版模板 / Chinese template](https://github.com/alibaba/hooks/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md)]
### 🤔 This is a ...
- [ ] New feature
- [ ] Bug fix
- [ ] Site / documentation update
- [ ] Demo update
- [ ] TypeScript definition update
- [ ] Bundle size optimization
- [ ] Performance optimization
- [ ] Enhancement feature
- [ ] Internationalization
- [ ] Refactoring
- [ ] Code style optimization
- [ ] Test Case
- [ ] Branch merge
- [ ] Other (about what?)
### 🔗 Related issue link
<!--
1. Describe the source of requirement, like related issue link.
-->
### 💡 Background and solution
<!--
1. Describe the problem and the scenario.
2. GIF or snapshot should be provided if includes UI/interactive modification.
3. How to fix the problem, and list final API implementation and usage sample if that is a new feature.
-->
### 📝 Changelog
<!--
Describe changes from the user side, and list all potential break changes or other risks.
--->
| Language | Changelog |
| ---------- | --------- |
| 🇺🇸 English | |
| 🇨🇳 Chinese | |
### ☑️ Self Check before Merge
⚠️ Please check all items below before review. ⚠️
- [ ] Doc is updated/provided or not needed
- [ ] Demo is updated/provided or not needed
- [ ] TypeScript definition is updated/provided or not needed
- [ ] Changelog is provided or not needed
================================================
FILE: .github/workflows/comment-when-needs-more-info.yml
================================================
name: Comment When Needs More Info Label Added
on:
issues:
types: [labeled]
jobs:
create-comment:
runs-on: ubuntu-latest
if: github.event.label.name == 'needs more info'
steps:
- name: Create comment
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hi, ${{ github.event.issue.user.login }}.
It seems that this issue is a bit vague and lacks some necessary information.
看起来这条 issue 描述得有些模糊,缺少一些必要的信息。
================================================
FILE: .github/workflows/gitleaks.yml
================================================
name: gitleaks
on: [push, pull_request]
jobs:
gitleaks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: wget
uses: wei/wget@v1
with:
args: -O .gitleaks.toml https://raw.githubusercontent.com/ycjcl868/gitleaks/master/.gitleaks.toml
- name: gitleaks-action
uses: gitleaks/gitleaks-action@v1.6.0
================================================
FILE: .github/workflows/issue-close-require.yml
================================================
name: Issue Close Require
on:
schedule:
- cron: '0 0 * * *'
jobs:
close-issues:
runs-on: ubuntu-latest
steps:
- name: need reproduce
uses: actions-cool/issues-helper@v3
with:
actions: 'close-issues'
labels: '🤔 Need Reproduce'
inactive-day: 3
- name: needs more info
uses: actions-cool/issues-helper@v3
with:
actions: 'close-issues'
labels: 'needs more info'
inactive-day: 3
body: |
Since the issue was labeled with `needs more info`, but no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.
由于该 issue 被标记为需要更多信息,却 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。
================================================
FILE: .github/workflows/issue-reply.yml
================================================
name: Issue Reply
on:
issues:
types: [labeled]
jobs:
reply-helper:
runs-on: ubuntu-latest
steps:
- name: help wanted
if: github.event.label.name == 'help wanted'
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment'
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}. We totally like your proposal/feedback, welcome to [send us a Pull Request](https://help.github.com/en/articles/creating-a-pull-request) for it. Please send your Pull Request to proper branch (feature branch for the new feature, master for bugfix and other changes), fill the [Pull Request Template](https://github.com/alibaba/hooks/blob/master/.github/PULL_REQUEST_TEMPLATE.md) here, provide changelog/TypeScript/documentation/test cases if needed and make sure CI passed, we will review it soon. We appreciate your effort in advance and looking forward to your contribution!
你好 @${{ github.event.issue.user.login }},我们完全同意你的提议/反馈,欢迎直接在此仓库 [创建一个 Pull Request](https://help.github.com/en/articles/creating-a-pull-request) 来解决这个问题。请将 Pull Request 发到正确的分支(新特性发到 feature 分支,其他发到 master 分支),务必填写 Pull Request 内的[预设模板](https://github.com/alibaba/hooks/blob/master/.github/PULL_REQUEST_TEMPLATE.md),提供改动所需相应的 changelog、TypeScript 定义、测试用例、文档等,并确保 CI 通过,我们会尽快进行 Review,提前感谢和期待您的贡献。

- name: 🤔 Need Reproduce
if: github.event.label.name == '🤔 Need Reproduce'
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment'
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}. Please provide a online reproduction by forking this link https://codesandbox.io/s/ok2fe or a minimal GitHub repository. Issues labeled by `Need Reproduce` will be closed if no activities in 3 days.
你好 @${{ github.event.issue.user.login }}, 我们需要你提供一个在线的重现实例以便于我们帮你排查问题。你可以通过点击 [此处](https://codesandbox.io/s/ok2fe) 创建一个 codesandbox 或者提供一个最小化的 GitHub 仓库。3 天内未跟进此 issue 将会被自动关闭。

================================================
FILE: .github/workflows/pkg.pr.new.yml
================================================
name: Publish Any Commit
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'alibaba/hooks'
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm build
# https://github.com/stackblitz-labs/pkg.pr.new#readme
- run: pnpx pkg-pr-new publish './packages/*' --no-template --compact
================================================
FILE: .github/workflows/static.yml
================================================
name: Deploy static content to Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["master"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
permissions:
contents: write
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
env:
NODE_OPTIONS: --openssl-legacy-provider
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup pnpm
run: |
npm install --global corepack@latest
corepack enable
corepack prepare pnpm@latest --activate
echo "$(pnpm bin --global)" >> $GITHUB_PATH
- name: Install dependencies
run: pnpm install
- name: Cache pnpm dependencies
uses: actions/cache@v4
with:
path: |
~/.pnpm-store
node_modules
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Build documentation
run: npm run build:doc
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist # 构建后的静态文件目录
force_orphan: true
================================================
FILE: .github/workflows/test.yml
================================================
name: Test CI
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- run: pnpm install
- run: pnpm run tsc
test:
runs-on: ubuntu-latest
strategy:
matrix:
mode: ['normal', 'strict']
node-version: [20, 22]
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "pnpm_cache_dir=$(pnpm store path)" >> "$GITHUB_OUTPUT"
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: pnpm run install, build
run: |
pnpm run init
- name: test with react normal mode
if: ${{ matrix.mode == 'normal' }}
run: |
pnpm run test
- name: test with react strict mode
if: ${{ matrix.mode == 'strict' }}
run: |
pnpm run test:strict
================================================
FILE: .gitignore
================================================
dist
es
lib
.docz
node_modules
.history
.idea
.vscode
coverage
.doc
.DS_Store
.umi
.umi-production
page
lerna-debug.log
tsconfig.tsbuildinfo
packages/hooks/README.md
yarn-error.log
package-lock.json
metadata.json
.eslintcache
================================================
FILE: .gitleaks.toml
================================================
title = "gitleaks config"
[[rules]]
description = "AWS Manager ID"
regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}'''
tags = ["key", "AWS"]
[[rules]]
description = "AWS Secret Key"
regex = '''(?i)aws(.{0,20})?(?-i)['\"][0-9a-zA-Z\/+]{40}['\"]'''
tags = ["key", "AWS"]
[[rules]]
description = "AWS MWS key"
regex = '''amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'''
tags = ["key", "AWS", "MWS"]
[[rules]]
description = "Facebook Secret Key"
regex = '''(?i)(facebook|fb)(.{0,20})?(?-i)['\"][0-9a-f]{32}['\"]'''
tags = ["key", "Facebook"]
[[rules]]
description = "Facebook Client ID"
regex = '''(?i)(facebook|fb)(.{0,20})?['\"][0-9]{13,17}['\"]'''
tags = ["key", "Facebook"]
[[rules]]
description = "Twitter Secret Key"
regex = '''(?i)twitter(.{0,20})?['\"][0-9a-z]{35,44}['\"]'''
tags = ["key", "Twitter"]
[[rules]]
description = "Twitter Client ID"
regex = '''(?i)twitter(.{0,20})?['\"][0-9a-z]{18,25}['\"]'''
tags = ["client", "Twitter"]
[[rules]]
description = "Github"
regex = '''(?i)github(.{0,20})?(?-i)['\"][0-9a-zA-Z]{35,40}['\"]'''
tags = ["key", "Github"]
[[rules]]
description = "Github Token"
regex = '''[0-9a-zA-Z]{35,40}'''
tags = ["key", "Github Token"]
[[rules]]
description = "Alibaba"
regex = '''((alibaba|antfin|aliyun|alipay)(-inc|\.net)|intranetproxy\.alipay)'''
tags = ["key", "Alibaba"]
[[rules]]
description = "antfin"
regex = '''(?i)antfin(.{0,20})?(?-i)['\"][0-9a-zA-Z]{35,40}['\"]'''
tags = ["key", "Antfin"]
[[rules]]
description = "LinkedIn Client ID"
regex = '''(?i)linkedin(.{0,20})?(?-i)['\"][0-9a-z]{12}['\"]'''
tags = ["client", "LinkedIn"]
[[rules]]
description = "LinkedIn Secret Key"
regex = '''(?i)linkedin(.{0,20})?['\"][0-9a-z]{16}['\"]'''
tags = ["secret", "LinkedIn"]
[[rules]]
description = "Slack"
regex = '''xox[baprs]-([0-9a-zA-Z]{10,48})?'''
tags = ["key", "Slack"]
[[rules]]
description = "Asymmetric Private Key"
regex = '''-----BEGIN ((EC|PGP|DSA|RSA|OPENSSH) )?PRIVATE KEY( BLOCK)?-----'''
tags = ["key", "AsymmetricPrivateKey"]
[[rules]]
description = "Public Key"
regex = '''ssh-rsa'''
tags = ["keys", "public key"]
[[rules]]
description = "Gitlab Key"
regex = '''privateToken|private-token'''
tags = ["keys", "Gitlab"]
[[rules]]
description = "Generic Credential"
regex = '''(?i)(api_key|apikey|secret)(.{0,20})?['|"][0-9a-zA-Z]{16,45}['|"]'''
tags = ["key", "API", "generic"]
[[rules]]
description = "Google API key"
regex = '''AIza[0-9A-Za-z\\-_]{35}'''
tags = ["key", "Google"]
[[rules]]
description = "Heroku API key"
regex = '''(?i)heroku(.{0,20})?['"][0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}['"]'''
tags = ["key", "Heroku"]
[[rules]]
description = "MailChimp API key"
regex = '''(?i)(mailchimp|mc)(.{0,20})?['"][0-9a-f]{32}-us[0-9]{1,2}['"]'''
tags = ["key", "Mailchimp"]
[[rules]]
description = "Mailgun API key"
regex = '''(?i)(mailgun|mg)(.{0,20})?['"][0-9a-z]{32}['"]'''
tags = ["key", "Mailgun"]
[[rules]]
description = "PayPal Braintree access token"
regex = '''access_token\$production\$[0-9a-z]{16}\$[0-9a-f]{32}'''
tags = ["key", "Paypal"]
[[rules]]
description = "Picatic API key"
regex = '''sk_live_[0-9a-z]{32}'''
tags = ["key", "Picatic"]
[[rules]]
description = "SendGrid API Key"
regex = '''SG\.[\w_]{16,32}\.[\w_]{16,64}'''
tags = ["key", "SendGrid"]
[[rules]]
description = "Slack Webhook"
regex = '''https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}'''
tags = ["key", "slack"]
[[rules]]
description = "Stripe API key"
regex = '''(?i)stripe(.{0,20})?['\"][sk|rk]_live_[0-9a-zA-Z]{24}'''
tags = ["key", "Stripe"]
[[rules]]
description = "Square access token"
regex = '''sq0atp-[0-9A-Za-z\-_]{22}'''
tags = ["key", "square"]
[[rules]]
description = "Square OAuth secret"
regex = '''sq0csp-[0-9A-Za-z\\-_]{43}'''
tags = ["key", "square"]
[[rules]]
description = "Twilio API key"
regex = '''(?i)twilio(.{0,20})?['\"][0-9a-f]{32}['\"]'''
tags = ["key", "twilio"]
[whitelist]
description = "Whitelisted files"
file = '''(^\.?gitleaks.toml$|(.*?)(jpg|gif|doc|pdf|bin)$|^package-lock.json$|yarn.lock|pnpm-lock.yaml|node_modules)'''
================================================
FILE: .husky/commit-msg
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx commitlint --edit $1
================================================
FILE: .husky/pre-commit
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run pretty
================================================
FILE: .npmrc
================================================
shamefully-hoist=true
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- 'lts/*'
install:
- pnpm install
- pnpm install -g surge
script:
- pnpm run build:doc
- surge ./dist ahooks-$(git rev-parse --short HEAD).surge.sh
- pnpm run coveralls # generate static files
cache:
directories:
- 'node_modules'
================================================
FILE: CONTRIBUTING.MD
================================================
# Contributing
The following is a set of guidelines for contributing to `ahooks`. Please spend several minutes reading these guidelines before creating an issue or pull request.
## Open Development
All work on ahooks happens directly on [GitHub](https://github.com/alibaba/hooks). Pull requests sent by both core team members and external contributors will go through the same review process.
## New Features
If you want to add a new Hook, we recommend that you first create an issue that describes the scenario and usage of the Hook, see [[RFC] useLockFn](https://github.com/alibaba/hooks/issues/562).
Then you can initialize a new Hook based on an existing Hook.
## Pull Request
We are monitoring for pull requests. We will review your pull request and either merge it, request changes to it, or close it with an explanation.
Before submitting a pull request, please make sure the following is done:
1. Create your branch from the master.
2. If you've fixed a bug or added code that should be tested, add tests!
3. Ensure the test suite passes `pnpm run test`.
## Development Workflow
After cloning `ahooks`, run `pnpm run init` to fetch its dependencies. Then, you can run several commands:
1. `pnpm start` runs `ahooks` website locally.
2. `pnpm run test` runs the complete test suite.
3. `pnpm run build` to build.
================================================
FILE: CONTRIBUTING.zh-CN.MD
================================================
# 贡献指南
这篇指南会指导你如何为 `ahooks` 贡献一份自己的力量,请在你要提 issue 或者 pull request 之前花几分钟来阅读一遍这篇指南。
## 透明的开发
我们所有的工作都会放在 [GitHub](https://github.com/alibaba/hooks) 上。不管是核心团队的成员还是外部贡献者的 pull request 都需要经过同样流程的 review。
## 新增功能
如果你想新增一个 Hooks,我们建议你先建立一个 issue,说明该 Hooks 的应用场景及用法,参考 [[RFC] useLockFn](https://github.com/alibaba/hooks/issues/562)。
然后你可以基于已有 Hook 来初始化一个新的 Hook。
## Pull Request
我们会关注所有的 pull request,会 review 以及合并你的代码,也有可能要求你做一些修改或者告诉你我们为什么不能接受这样的修改。
在你发送 Pull Request 之前,请确认你是按照下面的步骤来做的:
1. 基于 master 分支做修改。
2. 如果你修复了一个 bug 或者新增了一个功能,请确保写了相应的测试,这很重要。
3. 确认所有的测试是通过的 `pnpm run test`。
## 开发流程
在你 clone 代码并且使用 `pnpm run init` 安装完依赖后,你还可以运行下面几个常用的命令:
1. `pnpm start` 在本地运行 `ahooks` 网站。
2. `pnpm run test` 运行测试。
3. `pnpm run build` 构建编译。
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019-present ahooks
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<p align="center">
<a href="https://ahooks.js.org">
<img width="200" src="https://ahooks.js.org/logo.svg">
</a>
</p>
<div align="center">
A high-quality & reliable React Hooks library.
[![NPM version][image-1]][1]
[![NPM downloads][image-2]][2]
[](https://www.npmjs.com/package/ahooks-v2)
[](https://github.com/alibaba/hooks/issues)
[](https://coveralls.io/github/alibaba/hooks?branch=master)

[](http://isitmaintained.com/project/alibaba/hooks "Percentage of issues still open")
[](http://isitmaintained.com/project/alibaba/hooks "Average time to resolve an issue")

English | [简体中文](https://github.com/alibaba/hooks/blob/master/README.zh-CN.md)
</div>
## 📚 Documentation
- [English](https://ahooks.js.org/)
- [中文](https://ahooks.js.org/zh-CN/)
## ✨ 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
```bash
$ npm install --save ahooks
# or
$ yarn add ahooks
# or
$ pnpm add ahooks
# or
$ bun add ahooks
```
## 🔨 Usage
```ts
import { useRequest } from "ahooks";
```
## 💻 Online Demo
[](https://codesandbox.io/s/demo-for-ahooks-forked-fg79k?file=/src/App.js)
## 🤝 Contributing
```bash
$ git clone git@github.com: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](https://github.com/alibaba/hooks/blob/master/CONTRIBUTING.MD) first, let's build a better hooks library together.
Thanks to all the contributors:
<a href="https://github.com/alibaba/hooks/graphs/contributors">
<img src="https://opencollective.com/ahooks/contributors.svg?width=960&button=false" alt="contributors" />
</a>
## 👥 Discuss
<img alt="ahooks discussion group 1" src="https://github.com/user-attachments/assets/0ba7a370-2a69-442f-b746-9eb16bbbc46c" width="200" style='display:inline' />
<img alt="ahooks discussion group 2" src="https://github.com/user-attachments/assets/a08693d3-bfcc-4aca-b2b0-2d9c23012858" width="200" style='display:inline' />
<img alt="ahooks discussion group 3" src="https://github.com/user-attachments/assets/15a505a7-06d1-4e72-ab02-6fad968323f1" width="200" style='display:inline' />
[1]: https://www.npmjs.com/package/ahooks
[2]: https://npmjs.org/package/ahooks
[image-1]: https://img.shields.io/npm/v/ahooks.svg?style=flat
[image-2]: https://img.shields.io/npm/dw/ahooks.svg?style=flat
================================================
FILE: README.zh-CN.md
================================================
<p align="center">
<a href="https://ahooks.js.org">
<img width="200" src="https://ahooks.js.org/logo.svg">
</a>
</p>
<div align="center">
一套高质量可靠的 React Hooks 库
[![NPM version][image-1]][1]
[![NPM downloads][image-2]][2]
[](https://www.npmjs.com/package/ahooks-v2)
[](https://coveralls.io/github/alibaba/hooks?branch=master)
[](https://github.com/alibaba/hooks/issues)

[](http://isitmaintained.com/project/alibaba/hooks "Percentage of issues still open")
[](http://isitmaintained.com/project/alibaba/hooks "Average time to resolve an issue")

[English](https://github.com/alibaba/hooks/blob/master/README.md) | 简体中文
</div>
## 📚 文档
- [English](https://ahooks.js.org/)
- [中文](https://ahooks.js.org/zh-CN/)
## ✨ 特性
- 易学易用
- 支持 SSR
- 对输入输出函数做了特殊处理,避免闭包问题
- 包含大量提炼自业务的高级 Hooks
- 包含丰富的基础 Hooks
- 使用 TypeScript 构建,提供完整的类型定义文件
## 📦 安装
```bash
$ npm install --save ahooks
# or
$ yarn add ahooks
# or
$ pnpm add ahooks
# or
$ bun add ahooks
```
## 🔨 使用
```js
import { useRequest } from "ahooks";
```
## 💻 在线体验
[](https://codesandbox.io/s/demo-for-ahooks-forked-fg79k?file=/src/App.js)
## 🤝 参与共建
```bash
$ git clone git@github.com:alibaba/hooks.git
$ cd hooks
$ pnpm run init
$ pnpm start
```
打开浏览器访问 http://127.0.0.1:8000
我们欢迎所有人参与共建,请参考[CONTRIBUTING.MD](https://github.com/alibaba/hooks/blob/master/CONTRIBUTING.zh-CN.MD)
感谢所有贡献者:
<a href="https://github.com/alibaba/hooks/graphs/contributors">
<img src="https://opencollective.com/ahooks/contributors.svg?width=960&button=false" alt="contributors" />
</a>
## 👥 交流讨论
<img src="https://github.com/user-attachments/assets/0ba7a370-2a69-442f-b746-9eb16bbbc46c" width="200" style='display:inline' />
<img src="https://github.com/user-attachments/assets/a08693d3-bfcc-4aca-b2b0-2d9c23012858" width="200" style='display:inline' />
<img src="https://github.com/user-attachments/assets/15a505a7-06d1-4e72-ab02-6fad968323f1" width="200" style='display:inline' />
[1]: https://www.npmjs.com/package/ahooks
[2]: https://npmjs.org/package/ahooks
[image-1]: https://img.shields.io/npm/v/ahooks.svg?style=flat
[image-2]: https://img.shields.io/npm/dw/ahooks.svg?style=flat
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Reporting a Vulnerability
Please report vulnerabilities to brickspert.fjl@antfin.com or guangbo.hgb@alibaba-inc.com
================================================
FILE: biome.json
================================================
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"files": {
"ignoreUnknown": true
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"linter": {
"rules": {
"style": {
"noNonNullAssertion": "off"
},
"correctness": {
"useHookAtTopLevel": "error"
},
"suspicious": {
"noExplicitAny": "off"
}
}
},
"formatter": {
"lineWidth": 100,
"indentStyle": "space"
},
"javascript": {
"parser": {
"unsafeParameterDecoratorsEnabled": true
},
"formatter": {
"quoteStyle": "single"
}
},
"css": {
"parser": {
"cssModules": true
},
"formatter": {
"enabled": true
},
"linter": {
"enabled": true
}
}
}
================================================
FILE: config/config.ts
================================================
import { menus } from './hooks';
const packages = require('../packages/hooks/package.json');
export default {
// ssr: {},
exportStatic: {},
nodeModulesTransform: {
type: 'none',
exclude: [],
},
// https://github.com/alibaba/hooks/issues/2155
extraBabelIncludes: ['filter-obj'],
extraBabelPlugins: [
[
'babel-plugin-import',
{
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
},
'antd',
],
[
'babel-plugin-import',
{
libraryName: '@alifd/next',
style: false,
},
'fusion',
],
],
mode: 'site',
title: 'ahooks 3.0',
favicon: '/simple-logo.svg',
logo: '/logo.svg',
dynamicImport: {},
manifest: {},
hash: true,
publicPath: '/',
alias: {
ahooks: process.cwd() + '/packages/hooks/src/index.ts',
'@ahooksjs/use-url-state': process.cwd() + '/packages/use-url-state/src/index.ts',
},
resolve: {
includes: ['docs', 'packages/hooks/src', 'packages/use-url-state'],
},
links: [
{
rel: 'stylesheet',
href: 'https://unpkg.com/@alifd/theme-design-pro@0.6.2/dist/next-noreset.min.css',
},
{ rel: 'stylesheet', href: '/style.css' },
],
navs: {
'zh-CN': [
{ title: '指南', path: '/zh-CN/guide' },
{ title: 'Hooks', path: '/zh-CN/hooks' },
{
title: '历史版本',
children: [
{
title: 'v2.x',
path: 'https://ahooks-v2.js.org/',
},
{
title: 'v1.x',
path: 'http://hooks.umijs.org/',
},
],
},
{ title: '更新日志', path: 'https://github.com/alibaba/hooks/releases' },
{ title: '备用镜像', path: 'https://alibaba.github.io/hooks/' },
{ title: 'GitHub', path: 'https://github.com/alibaba/hooks' },
],
'en-US': [
{ title: 'Guide', path: '/guide' },
{ title: 'Hooks', path: '/hooks' },
{
title: 'Legacy Versions',
children: [
{
title: 'v2.x',
path: 'https://ahooks-v2.js.org/',
},
{
title: 'v1.x',
path: 'http://hooks.umijs.org/',
},
],
},
{ title: 'Releases', path: 'https://github.com/alibaba/hooks/releases' },
{ title: '国内镜像', path: 'https://alibaba.github.io/hooks/' },
{ title: 'GitHub', path: 'https://github.com/alibaba/hooks' },
],
},
menus: {
'/': [
{
title: 'Home',
path: 'index',
},
],
'/zh-CN': [
{
title: '首页',
path: 'index',
},
],
'/guide': [
{
title: 'Intro',
path: '/guide',
},
{
title: 'v2 to v3',
path: '/guide/upgrade',
},
{
title: 'Hooks of dom specification',
path: '/guide/dom',
},
{
title: 'Blog',
children: [
{
title: 'ahooks function specification',
path: '/guide/blog/function',
},
{
title: 'React Hooks & SSR',
path: '/guide/blog/ssr',
},
{
title: 'React Hooks & react-refresh(HMR)',
path: '/guide/blog/hmr',
},
{
title: 'React Hooks & strict mode',
path: '/guide/blog/strict',
},
],
},
],
'/zh-CN/guide': [
{
title: '介绍',
path: '/guide',
},
{
title: 'v2 to v3',
path: '/guide/upgrade',
},
{
title: 'DOM 类 Hooks 使用规范',
path: '/guide/dom',
},
{
title: 'Blog',
children: [
{
title: 'ahooks 输入输出函数处理规范',
path: '/zh-CN/guide/blog/function',
},
{
title: 'React Hooks & SSR',
path: '/zh-CN/guide/blog/ssr',
},
{
title: 'React Hooks & react-refresh(HMR)',
path: '/zh-CN/guide/blog/hmr',
},
{
title: 'React Hooks & strict mode',
path: '/zh-CN/guide/blog/strict',
},
],
},
],
'/hooks': menus,
'/zh-CN/hooks': menus,
},
scripts: [
'https://s4.cnzz.com/z_stat.php?id=1278992092&web_id=1278992092',
`
const insertVersion = function() {
const logo = document.querySelector('.__dumi-default-navbar-logo');
if (!logo) return;
const dom = document.createElement('span');
dom.id = 'logo-version';
dom.innerHTML = '${packages.version}';
logo.parentNode.insertBefore(dom, logo.nextSibling);
};
const observer = new MutationObserver((mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
const logoVersion = document.querySelector('#logo-version');
if (logoVersion) {
observer.disconnect();
} else {
insertVersion();
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
`,
],
};
================================================
FILE: config/hooks.ts
================================================
export const menus = [
{
title: 'useRequest',
children: [
'useRequest/doc/index',
'useRequest/doc/basic',
'useRequest/doc/loadingDelay',
'useRequest/doc/polling',
'useRequest/doc/ready',
'useRequest/doc/refreshDeps',
'useRequest/doc/refreshOnWindowFocus',
'useRequest/doc/debounce',
'useRequest/doc/throttle',
'useRequest/doc/cache',
'useRequest/doc/retry',
],
},
{
title: 'Scene',
children: [
'useAntdTable',
'useFusionTable',
'useInfiniteScroll',
'usePagination',
'useDynamicList',
'useVirtualList',
'useHistoryTravel',
'useNetwork',
'useSelections',
'useCountDown',
'useCounter',
'useTextSelection',
'useWebSocket',
'useTheme',
],
},
{
title: 'LifeCycle',
children: ['useMount', 'useUnmount', 'useUnmountedRef'],
},
{
title: 'State',
children: [
'useSetState',
'useBoolean',
'useToggle',
'use-url-state',
'useCookieState',
'useLocalStorageState',
'useSessionStorageState',
'useDebounce',
'useThrottle',
'useMap',
'useSet',
'usePrevious',
'useRafState',
'useSafeState',
'useGetState',
'useResetState',
],
},
{
title: 'Effect',
children: [
'useUpdateEffect',
'useUpdateLayoutEffect',
'useAsyncEffect',
'useDebounceEffect',
'useDebounceFn',
'useThrottleFn',
'useThrottleEffect',
'useDeepCompareEffect',
'useDeepCompareLayoutEffect',
'useInterval',
'useRafInterval',
'useTimeout',
'useRafTimeout',
'useLockFn',
'useUpdate',
],
},
{
title: 'Dom',
children: [
'useEventListener',
'useClickAway',
'useDocumentVisibility',
'useDrop',
'useEventTarget',
'useExternal',
'useTitle',
'useFavicon',
'useFullscreen',
'useHover',
'useMutationObserver',
'useInViewport',
'useKeyPress',
'useLongPress',
'useMouse',
'useResponsive',
'useScroll',
'useSize',
'useFocusWithin',
],
},
{
title: 'Advanced',
children: [
'useControllableValue',
'useCreation',
'useEventEmitter',
'useIsomorphicLayoutEffect',
'useLatest',
'useMemoizedFn',
'useReactive',
],
},
{
title: 'Dev',
children: ['useTrackedEffect', 'useWhyDidYouUpdate'],
},
];
================================================
FILE: docs/guide/blog/function.en-US.md
================================================
# ahooks function specification
ahooks tries its best to help everyone avoid the closure problem by specially processing the input and output functions.
**1. All the output functions of ahooks, the references are stable**
```ts
const [state, setState] = useState();
```
As we all know, the reference of the `setState` function returned by `React.useState` is fixed, and there is no need to consider weird problems when using it, and there is no need to put `setState` in the dependencies of other Hooks.
All functions returned by ahooks Hooks have the same characteristics as `setState`, the reference will not change, just feel free to use it.
**2. For all user input functions, always use the latest one**
For the received function, ahooks will do a special process to ensure that the function called each time is always the latest.
```ts
const [state, setState] = useState();
useInterval(() => {
console.log(state);
}, 1000);
```
For example, in the above example, the function called by `useInterval` at any time is always the latest, that is, the state is always the latest.
## Principle
For the input function, we use `useRef` to make a record to ensure that the latest function can be accessed anywhere.
```js
const fnRef = useRef(fn);
fnRef.current = fn;
```
For example, the useUnmount code is as follows:
```js
const useUnmount = (fn) => {
const fnRef = useRef(fn);
fnRef.current = fn;
useEffect(
() => () => {
fnRef.current();
},
[],
);
};
```
In the above code, because we use ref for memorizing the latest function to solve the closure problem.
For the output function, we use the [useMemoizedFn](/zh-CN/hooks/use-memoized-fn) wrapped to ensure that the reference address will never change.
For a simple example, given a `useToggle` Hook, the code is like this:
```js
const useToggle = (left, right) => {
const [state, setState] = useState(left);
const toggle = useCallback(() => {
setState((s) => (s === left ? right : left));
}, [left, right]);
return [state, toggle];
};
```
The `toggle` function returned in this demo will change according to the changes of `left/right`, which is uncomfortable for users to use.
Then we replace `useCallback` with `useMemoizedFn` to realize that the `toggle` reference will never change.
```js
const useToggle = (left, right) => {
const [state, setState] = useState(left);
const toggle = useMemoizedFn(() => {
setState((s) => (s === left ? right : left));
});
return [state, toggle];
};
```
================================================
FILE: docs/guide/blog/function.zh-CN.md
================================================
# ahooks 函数处理规范
ahooks 通过对输入输出函数做特殊处理,尽力帮助大家避免闭包问题。
**1. ahooks 所有的输出函数,地址都是不会变化的**
```ts
const [state, setState] = React.useState();
```
众所周知,`React.useState` 返回的 `setState` 函数地址是固定的,使用时不需要考虑奇奇怪怪的问题,不需要把 `setState` 放到各种依赖中。
ahooks 所有 Hooks 返回的函数,都拥有和 `setState` 一样的特性,地址不会变化,放心大胆的使用即可。
**2. 所有用户输入的函数,永远使用最新的一份**
对于接收的函数,ahooks 会做一次特殊处理,保证每次调用的函数永远是最新的。
```ts
const [state, setState] = useState();
useInterval(() => {
console.log(state);
}, 1000);
```
比如以上示例,`useInterval` 任何时候调用的函数永远是最新的,也就是 state 永远是最新的。
## 实现原理
针对输入函数,我们通过 `useRef` 做一次记录,以保证在任何地方都能访问到最新的函数。
```js
const fnRef = useRef(fn);
fnRef.current = fn;
```
比如 useUnmount 代码如下:
```js
const useUnmount = (fn) => {
const fnRef = useRef(fn);
fnRef.current = fn;
useEffect(
() => () => {
fnRef.current();
},
[],
);
};
```
在上面的代码中,由于我们通过 ref 来记忆最新的函数,解决闭包问题。
针对输出函数,我们通过 ahooks 的 [useMemoizedFn](/zh-CN/hooks/use-memoized-fn) 包裹,保证地址永远不会变化。
举一个比较简单的例子,假如我们有一个 `useToggle` Hook,代码是这样的
```js
const useToggle = (left, right) => {
const [state, setState] = useState(left);
const toggle = useCallback(() => {
setState((s) => (s === left ? right : left));
}, [left, right]);
return [state, toggle];
};
```
这个 demo 中返回的 `toggle` 函数,会根据 `left/right` 的变化而变化的,用户用起来很难受。
然后我们将 `useCallback` 替换成 `useMemoizedFn`,即可实现 `toggle` 地址永远不变化。
```js
const useToggle = (left, right) => {
const [state, setState] = useState(left);
const toggle = useMemoizedFn(() => {
setState((s) => (s === left ? right : left));
});
return [state, toggle];
};
```
================================================
FILE: docs/guide/blog/hmr.en-US.md
================================================
# React Hooks & react-refresh(HMR)
## What is react-refresh?
[react-refresh-webpack-plugin](https://github.com/pmmmwh/react-refresh-webpack-plugin) is a hot module replacement (HMR) plugin provided by React.
> A Webpack plugin to enable "Fast Refresh" (also previously known as Hot Reloading) for React components.
In the development, react-refresh can keep state in component, and only change the edited part. In [umi](https://umijs.org/zh-CN/docs/fast-refresh), can enable this feature by config `fastRefresh: {}`.

This gif shows the development experience of using the react-refresh. After edit some code, the username and password that have been filled in remain unchanged, only the edited part has been changed.
## Simple Principles of react-refresh
For the Class component, react-refresh are always refresh (remount), existing state will be reset. For function components, react-refresh retains the existing state. Therefore, react-refresh provides a better experience for function components.
This article mainly explains the weird behavior of React Hooks in react-refresh mode. Now let us look at the working mechanism of react-refresh on function components.
- To maintain the state during hot replacement, the value of `useState` and `useRef` will not update.
- During hot replacement, [To avoid some problems](<(https://github.com/facebook/react/issues/21019#issuecomment-800650091)>), `useEffect`、`useCallback`、`useMemoRun` will re-executed.
> When we update the code, we need to "clean up" the effects that hold onto past values (e.g. passed functions), and "setup" the new ones with updated values. Otherwise, the values used by your effect would be stale and "disagree" with value used in your rendering, which makes Fast Refresh much less useful and hurts the ability to have it work with chains of custom Hooks.

As shown in the gif, after the text is modified, `state` remains unchanged and `useEffect` is executed again.
## Problem caused by react-refresh
Under the above working mechanism, there will be many problems. Next, I will give a few specific examples.
### First problem
```js
import React, { useEffect, useState } from 'react';
export default () => {
const [count, setState] = useState(0);
useEffect(() => {
setState((s) => s + 1);
}, []);
return <div>{count}</div>;
};
```
The above code is very simple. In normal mode, the maximum value of `count` is `1`. Because `useEffect` will only be executed once during initialization.
But in the react-refresh mode, the `state` does not change every time it is hot updated, but the re-execution of `useEffect` will cause the value of `count` to keep increasing.

As shown in the gif, `count` increases with each hot replacement.
### Second problem
If you used [ahooks v2](https://github.com/alibaba/hooks/blob/release/v2.x/packages/hooks/src/useUpdateEffect/index.ts) or [react-use](https://github.com/streamich/react-use/blob/master/docs/useUpdateEffect.md) `useUpdateEffect` will also have unexpected behavior in HMR.
```javascript
import React, { useEffect } from 'react';
import useUpdateEffect from './useUpdateEffect';
export default () => {
useEffect(() => {
console.log('useEffect');
}, []);
useUpdateEffect(() => {
console.log('useUpdateEffect');
}, []);
return <div>hello world</div>;
};
```
Compared with `useEffect`, `useUpdateEffect` ignores the first execution and only executes when the deps changes. In the normal mode of the above code, `useUpdateEffect` will never be executed, because deps is an empty array and will never change.
But in react-refresh mode, during HMR, `useUpdateEffect` and `useEffect` are executed at the same time.

The reason for this problem is that `useUpdateEffect` uses `ref` to record whether it is currently executed for the first time, see the code below.
```javascript
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;
```
The key of the above code is `isMounted`.
- During initialization, after the `useEffect` is executed, the `isMounted` is changed to `true`
- After the HMR, when the `useEffect` is re-executing, because the `isMounted` is already `true`, so the whole effect is executed again.
### Third problem
The first time discovered this problem is the `useRequest` of ahooks, after HMR, the `loading` would always be `true`. After an inspection, the reason is use the `isUnmount` ref to mark whether the component is unmount.
```javascript
import React, { useEffect, useState } from 'react';
function getUsername() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('test');
}, 1000);
});
}
export default function IndexPage() {
const isUnmount = React.useRef(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
getUsername().then(() => {
if (isUnmount.current === false) {
setLoading(false);
}
});
return () => {
isUnmount.current = true;
};
}, []);
return loading ? <div>loading</div> : <div>hello world</div>;
}
```
As the code above, during the hot replacement, `isUnmount.current` becomes `true`, causing the code to think that the component has been unmounted during the second execution.
## How to solve these problems
### First solution
The first solution is to solve it from the code, that is, when we write code, we can always remember the weird behavior in react-refresh mode.
For example, with `useUpdateEffect`, we can initialize the `isMounted` ref during initialization or hot replacement. as follows:
```diff
import { useEffect, useRef } from 'react';
const useUpdateEffect: typeof useEffect = (effect, deps) => {
const isMounted = useRef(false);
+ useEffect(() => {
+ isMounted.current = false;
+ }, []);
useEffect(() => {
if (!isMounted.current) {
isMounted.current = true;
} else {
return effect();
}
}, deps);
};
export default useUpdateEffect;
```
This solution is effective for both questions 2 and 3 above.
### Second solution
According to [Official Document](https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/API.md#reset), we can solve this problem by adding the following comment in the file .
```javascript
/* @refresh reset */
```
After adding this question, every hot replacement will remount, that is, the component will be re-executed. `useState` and `useRef` will also be reset, so the above problem will not occur.
## Official attitude
There are already many unspoken rules for React Hooks. When using react-refresh, there are still unspoken rules to pay attention to. But the official reply stated that this is expected behavior, see the [issue](https://github.com/facebook/react/issues/21019).
> Effects are not exactly "mount"/"unmount" — they're more like "show"/"hide".
================================================
FILE: docs/guide/blog/hmr.zh-CN.md
================================================
# React Hooks & react-refresh(HMR)
## 什么是 react-refresh
[react-refresh-webpack-plugin](https://github.com/pmmmwh/react-refresh-webpack-plugin) 是 React 官方提供的一个 模块热替换(HMR)插件。
> A Webpack plugin to enable "Fast Refresh" (also previously known as Hot Reloading) for React components.
在开发环境编辑代码时,react-refresh 可以保持组件当前状态,仅仅变更编辑的部分。在 [umi](https://umijs.org/zh-CN/docs/fast-refresh) 中可以通过 `fastRefresh: {}`快速开启该功能。

这张 gif 动图展示的是使用 react-refresh 特性的开发体验,可以看出,修改组件代码后,已经填写的用户名和密码保持不变,仅仅只有编辑的部分变更了。
## react-refresh 的简单原理
对于 Class 类组件,react-refresh 会一律重新刷新(remount),已有的 state 会被重置。而对于函数组件,react-refresh 则会保留已有的 state。所以 react-refresh 对函数类组件体验会更好。
本篇文章主要讲解 React Hooks 在 react-refresh 模式下的怪异行为,现在我来看下 react-refresh 对函数组件的工作机制。
- 在热更新时为了保持状态,`useState` 和 `useRef` 的值不会更新。
- 在热更新时,[为了解决某些问题](https://github.com/facebook/react/issues/21019#issuecomment-800650091),`useEffect`、`useCallback`、`useMemo` 等会重新执行。
> When we update the code, we need to "clean up" the effects that hold onto past values (e.g. passed functions), and "setup" the new ones with updated values. Otherwise, the values used by your effect would be stale and "disagree" with value used in your rendering, which makes Fast Refresh much less useful and hurts the ability to have it work with chains of custom Hooks.

如上图所示,在文本修改之后,`state` 保持不变,`useEffect` 被重新执行了。
## react-refresh 工作机制导致的问题
在上述工作机制下,会带来很多问题,接下来我会举几个具体的例子。
### 第一个问题
```js
import React, { useEffect, useState } from 'react';
export default () => {
const [count, setState] = useState(0);
useEffect(() => {
setState((s) => s + 1);
}, []);
return <div>{count}</div>;
};
```
上面的代码很简单,在正常模式下,`count`值最大为 `1`。因为 `useEffect` 只会在初始化的时候执行一次。
但在 react-refresh 模式下,每次热更新的时候,`state` 不变,但 `useEffect` 重新执行,就会导致 `count` 的值一直在递增。

如上图所示,`count` 随着每一次热更新在递增。
### 第二个问题
如果你使用了 [ahooks v2](https://github.com/alibaba/hooks/blob/release/v2.x/packages/hooks/src/useUpdateEffect/index.ts) 或者 [react-use](https://github.com/streamich/react-use/blob/master/docs/useUpdateEffect.md) 的 `useUpdateEffect`,在热更新模式下也会有不符合预期的行为。
```javascript
import React, { useEffect } from 'react';
import useUpdateEffect from './useUpdateEffect';
export default () => {
useEffect(() => {
console.log('执行了 useEffect');
}, []);
useUpdateEffect(() => {
console.log('执行了 useUpdateEffect');
}, []);
return <div>hello world</div>;
};
```
`useUpdateEffect` 与 `useEffect`相比,它会忽略第一次执行,只有在 deps 变化时才会执行。以上代码的在正常模式下,`useUpdateEffect` 是永远不会执行的,因为 deps 是空数组,永远不会变化。
但在 react-refresh 模式下,热更新时,`useUpdateEffect` 和 `useEffect` 同时执行了。

造成这个问题的原因,就是 `useUpdateEffect` 用 `ref` 来记录了当前是不是第一次执行,见下面的代码。
```javascript
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;
```
上面代码的关键在 `isMounted`
- 初始化时,`useEffect` 执行,标记 `isMounted` 为 `true`
- 热更新后,`useEffect` 重新执行了,此时 `isMounted` 为 `true`,就往下执行了
### 第三个问题
最初发现这个问题,是 ahooks 的 `useRequest` 在热更新后,`loading` 会一直为 `true`。经过分析,原因就是使用 `isUnmount` ref 来标记组件是否卸载。
```javascript
import React, { useEffect, useState } from 'react';
function getUsername() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('test');
}, 1000);
});
}
export default function IndexPage() {
const isUnmount = React.useRef(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
getUsername().then(() => {
if (isUnmount.current === false) {
setLoading(false);
}
});
return () => {
isUnmount.current = true;
};
}, []);
return loading ? <div>loading</div> : <div>hello world</div>;
}
```
如上代码所示,在热更新时,`isUnmount.current` 变为了 `true`,导致二次执行时,代码以为组件已经卸载了,不再响应异步操作。
## 如何解决这些问题
### 方案一
第一个解决方案是从代码层面解决,也就是要求我们在写代码的时候,时时能想起来 react-refresh 模式下的怪异行为。
比如 `useUpdateEffect` 我们就可以在初始化或者热替换时,将 `isMounted` ref 初始化掉。如下:
```diff
import { useEffect, useRef } from 'react';
const useUpdateEffect: typeof useEffect = (effect, deps) => {
const isMounted = useRef(false);
+ useEffect(() => {
+ isMounted.current = false;
+ }, []);
useEffect(() => {
if (!isMounted.current) {
isMounted.current = true;
} else {
return effect();
}
}, deps);
};
export default useUpdateEffect;
```
这个方案对上面的问题二和三都是有效的。
### 方案二
根据[官方文档](https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/API.md#reset),我们可以通过在文件中添加以下注释来解决这个问题。
```javascript
/* @refresh reset */
```
添加这个问题后,每次热更新,都会 remount,也就是组件重新执行。`useState` 和 `useRef` 也会重置掉,也就不会出现上面的问题了。
## 官方态度
本来 React Hooks 已经有蛮多潜规则了,在使用 react-refresh 时,还有潜规则要注意。但官方回复说这是预期行为,见该 [issue](https://github.com/facebook/react/issues/21019)。
> Effects are not exactly "mount"/"unmount" — they're more like "show"/"hide".
================================================
FILE: docs/guide/blog/ssr.en-US.md
================================================
# React Hooks & SSR
Server-Side Rendering refers to the page processing technology where the HTML structure of the page is spliced on the server side. Generally used to solve SEO problems and speed up the first screen.
Since SSR executes JS code in a non-browser environment, there will be many problems. This article mainly introduces the common problems and solutions of React Hooks in SSR mode.
## Problem 1: DOM/BOM is missing
SSR is to run React code in a node environment, while global properties such as window, document, and navigator are not available at this time. If you use these properties directly, you will get errors like `window is not defined, document is not defined, navigator is not defined`, etc.
A common misuse is that global properties, such as document, are used directly during the execution of Hooks.
```js
import React, { useState } from 'react';
export default () => {
const [state, setState] = useState(document.visibilityState);
return state;
};
```
### Solution
1. Put the code of accessing the DOM/BOM in useEffect/useLayoutEffect (the server will not execute it) to avoid errors when the server executes, for example:
```js
import React, { useState, useEffect } from 'react';
export default () => {
const [state, setState] = useState();
useEffect(() => {
setState(document.visibilityState);
}, []);
return state;
};
```
2. Differentiate the environments by `isBrowser`
```js
import React, { useState } from 'react';
function isBrowser() {
return !!(typeof window !== 'undefined' && window.document && window.document.createElement);
}
export default () => {
const [state, setState] = useState(isBrowser() && document.visibilityState);
return state;
};
```
## Problem 2: useLayoutEffect Warning
If `useLayoutEffect` is used, the following warning will appear in SSR mode
> ⚠️ Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.
### Solution
1. Use useEffect instead of useLayoutEffect (nonsense)
2. Dynamically specify whether to use useEffect or useLayoutEffect according to the environment. This is a hack solution from the community, currently in [react-redux](https://github.com/reduxjs/react-redux/blob/d16262582b2eeb62c05313fca3eb59dc0b395955/src/components/connectAdvanced.js#L40), [react-use](https://github.com/streamich/react-use/blob/master/src/useIsomorphicLayoutEffect.ts), [react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd/blob/master/src/view/use-isomorphic-layout-effect.js).
```js
import { useLayoutEffect, useEffect } from 'react';
const useIsomorphicLayoutEffect = isBrowser() ? useLayoutEffect : useEffect;
export default useIsomorphicLayoutEffect;
```
## Summary: Need to pay attention when writing Hooks
1. Do not use DOM/BOM properties directly in non-useEffect/useLayoutEffect
2. When using DOM/BOM properties other than useEffect/useLayoutEffect, use `isBrowser` to determine whether to execute in the browser environment
3. If a Hook needs to receive DOM/BOM properties, it needs to support passing the properties via a function type parameter. Take the useEventListener of ahooks as an example, it must support the function type to specify the target option.
```diff
import React, { useState } from 'react';
import { useEventListener } from 'ahooks';
export default () => {
const [value, setValue] = useState(0);
const clickHandler = () => {
setValue(value + 1);
};
useEventListener(
'click',
clickHandler,
{
- target: document.getElementById('click-btn')
+ target: () => document.getElementById('click-btn')
}
);
return (
<button id="click-btn" type="button">
You click {value} times
</button>
);
};
```
4. Use `useIsomorphicLayoutEffect` instead of `useLayoutEffect`
## Reference
- [fix: useDocumentVisiblility support ssr](https://github.com/alibaba/hooks/pull/935/files)
- [UmiJS 服务端渲染](https://umijs.org/docs/ssr#window-is-not-defined-document-is-not-defined-navigator-is-not-defined)
- [useLayoutEffect and SSR](https://medium.com/@alexandereardon/uselayouteffect-and-ssr-192986cdcf7a)
================================================
FILE: docs/guide/blog/ssr.zh-CN.md
================================================
# React Hooks & SSR
服务端渲染(Server-Side Rendering),是指由服务侧完成页面的 HTML 结构拼接的页面处理技术。一般用于解决 SEO 问题和首屏加载速度问题。
由于 SSR 是在非浏览器环境执行 JS 代码,所以会出现很多问题。本文主要介绍 React Hooks 在 SSR 模式下常见问题及解决方案。
> 更多关于 SSR 的介绍可以看 UmiJS 的文档《[服务端渲染(SSR)](https://umijs.org/zh-CN/docs/ssr#服务端渲染(ssr))》。
## 问题一:DOM/BOM 缺失
SSR 是在 node 环境下运行 React 代码,而此时 window、document、navigator 等全局属性没有。如果直接使用了这些属性,就会报错 `window is not defined, document is not defined, navigator is not defined` 等。
常见的错误用法是在 Hooks 执行过程中,直接使用了 document 等全局属性。
```js
import React, { useState } from 'react';
export default () => {
const [state, setState] = useState(document.visibilityState);
return state;
};
```
### 解决方案
1. 将访问 DOM/BOM 的方法放在 useEffect/useLayoutEffect 中(服务端不会执行),避免服务端执行时报错,例如:
```js
import React, { useState, useEffect } from 'react';
export default () => {
const [state, setState] = useState();
useEffect(() => {
setState(document.visibilityState);
}, []);
return state;
};
```
2. 通过 `isBrowser` 来做环境判断
```js
import React, { useState } from 'react';
function isBrowser() {
return !!(typeof window !== 'undefined' && window.document && window.document.createElement);
}
export default () => {
const [state, setState] = useState(isBrowser() && document.visibilityState);
return state;
};
```
## 问题二 useLayoutEffect Warning
如果使用了 `useLayoutEffect`,在 SSR 模式下,会出现以下警告
> ⚠️ Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.
### 解决方案
1. 使用 useEffect 代替 useLayoutEffect(废话)
2. 根据环境动态的指定是使用 useEffect 还是 useLayoutEffect。这是来自社区的一种 hack 解决方案,目前在 [react-redux](https://github.com/reduxjs/react-redux/blob/d16262582b2eeb62c05313fca3eb59dc0b395955/src/components/connectAdvanced.js#L40)、[react-use](https://github.com/streamich/react-use/blob/master/src/useIsomorphicLayoutEffect.ts)、[react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd/blob/master/src/view/use-isomorphic-layout-effect.js) 均使用的这种方案。
```js
import { useLayoutEffect, useEffect } from 'react';
const useIsomorphicLayoutEffect = isBrowser() ? useLayoutEffect : useEffect;
export default useIsomorphicLayoutEffect;
```
## 总结:写 Hooks 时需要注意
1. 不要在非 useEffect/useLayoutEffect 中,直接使用 DOM/BOM 属性
2. 在非 useEffect/useLayoutEffect 使用 DOM/BOM 属性时,使用 `isBrowser` 判断是否在浏览器环境执行
3. 如果某个 Hook 需要接收 DOM/BOM 属性,需要支持函数形式传参。以 ahooks 的 useEventListener 举例,必须支持函数形式来指定 target 属性。
```diff
import React, { useState } from 'react';
import { useEventListener } from 'ahooks';
export default () => {
const [value, setValue] = useState(0);
const clickHandler = () => {
setValue(value + 1);
};
useEventListener(
'click',
clickHandler,
{
- target: document.getElementById('click-btn')
+ target: () => document.getElementById('click-btn')
}
);
return (
<button id="click-btn" type="button">
You click {value} times
</button>
);
};
```
4. 使用 `useIsomorphicLayoutEffect` 来代替 `useLayoutEffect`
## 参考资料
- [fix: useDocumentVisiblility support ssr](https://github.com/alibaba/hooks/pull/935/files)
- [UmiJS 服务端渲染](https://umijs.org/zh-CN/docs/ssr#window-is-not-defined-document-is-not-defined-navigator-is-not-defined)
- [useLayoutEffect and SSR](https://medium.com/@alexandereardon/uselayouteffect-and-ssr-192986cdcf7a)
================================================
FILE: docs/guide/blog/strict.en-US.md
================================================
# React Hooks & strict mode
## What is strict mode
In React, there are many historical APIs or writing methods that will be obsolete in future versions and are now marked as deprecated. such as `componentWillMount`, in normal mode, you can use it normally. But in strict mode, a warning will be thrown:

So **strict mode is for future development, all APIs or writing methods that are not recommended will throw warnings (only effective in development mode).**
We can use `React.StrictMode` to enable strict mode.
```javascript
import React from 'react';
function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}
```
For more documents, please refer to "[Strict Mode](https://reactjs.org/docs/strict-mode.html)"
## Points to note in React Hooks
One of the most important capabilities of strict mode is "[Detecting Unexpected Side Effects](https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects)", in the upcoming concurrent mode, the component is divided into two phases:
- **Render phase**: Generate DOM tree, will execute constructor, componentWillMount, componentWillReceiveProps, componentWillUpdate, getDerivedStateFromProps, shouldComponentUpdate, render, **useState, useMemo, useCallback** and other life cycles
- **Commit stage**: Apply DOM changes, trigger componentDidMount, componentDidUpdate, **useEffect** and other life cycles
Generally, the render phase is time-consuming, and the commit phase is executed quickly. Therefore, in the upcoming concurrent mode, the render phase may be suspended and re-executed. That is, the life cycle of the rendering phase may be executed multiple times.
```javascript
constructor(){
services.getUserInfo().then(() => {
.....
});
}
```
As above, if we initiate a network request in the constructor, it may be executed multiple times. So **do not perform operations with side effects during the render phase.**
But if you perform side-effect operations during the rendering phase, React will not be able to perceive it. **But in strict mode, React will intentionally repeat the render phase method, making it easier for us to find such bugs in the development phase** (not all the life cycles of the rendering phase will be re-executed, see [Official Documentation](https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects)).
```javascript
const useTest = () => {
const [state, setState] = useState(() => {
console.log('get state');
return 'state';
});
const memoState = useMemo(() => {
console.log('get memo state');
return 'state';
}, []);
console.log('render');
return state;
};
```
In the above code, the first parameter of `useState`, `useMemo` and the Hook function body are all executed twice.
[Demo](https://codesandbox.io/s/xvv55893mp?file=/src/index.js)
Please remember the conclusion: **In strict mode, the first parameter of `useState`, `useMemo`, `useReducer` and the Hook function body will be executed twice. Do not perform operations with side effects here.**
================================================
FILE: docs/guide/blog/strict.zh-CN.md
================================================
# React Hooks & strict mode
## 什么是严格模式
在 React 中,有很多历史的 API 或写法,在未来版本中会被废弃,现在被标记为不建议使用。既然是不建议使用,那还是可以用的,比如 `componentWillMount`,在普通模式下,你可以正常使用。但在严格模式下,就会抛出警告:

所以**严格模式就是面向未来开发,所有不建议的 API 或写法,都会抛出警告(只在开发模式生效)。**
一般我们可以通过 `React.StrictMode` 来局部启用严格模式。
```javascript
import React from 'react';
function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}
```
更多文档参考《[严格模式](https://zh-hans.reactjs.org/docs/strict-mode.html)》
## 在 React Hooks 中需要注意的点
严格模式很重要的一个能力是《[检测意外的副作用](https://zh-hans.reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects)》,在未来的 concurrent 模式中,组件被分为两个阶段:
- **渲染(render)阶段**:生成 DOM 树,会执行 constructor、componentWillMount、componentWillReceiveProps、componentWillUpdate、getDerivedStateFromProps、shouldComponentUpdate、render、**useState、useMemo、useCallback** 等生命周期
- **提交(commit)阶段**:操作 DOM,触发 componentDidMount、componentDidUpdate、**useEffect** 等生命周期
一般渲染阶段会比较耗时,提交阶段执行很快。所以在未来的 concurrent 模式中,渲染阶段可能会被暂停、重新执行。也就是渲染阶段的生命周期,可能会被多次执行。
```javascript
constructor(){
services.getUserInfo().then(() => {
.....
});
}
```
如上,我们在 constructor 中发起网络请求,就可能被执行多次。所以**不要在渲染阶段执行带有副作用的操作。**
但假如你在渲染阶段执行了副作用操作,React 也是无法感知的。**但是 React 在严格模式下,会故意重复执行渲染阶段的方法,使得我们在开发阶段能更容易发现这类 bug**(并不是所有渲染阶段的生命周期都会被重新执行,具体见[官方文档](https://zh-hans.reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects))。
```javascript
const useTest = () => {
const [state, setState] = useState(() => {
console.log('get state');
return 'state';
});
const memoState = useMemo(() => {
console.log('get memo state');
return 'state';
}, []);
console.log('render');
return state;
};
```
在上面的代码中 `useState`、`useMemo` 的第一个参数、Hook 函数体均执行了两次。
[在线体验](https://codesandbox.io/s/xvv55893mp?file=/src/index.js)
请记住结论:**在严格模式下,`useState`、`useMemo`、`useReducer` 的第一个参数、Hook 函数体都会被执行两次,不要在这里执行带有副作用的操作。**
================================================
FILE: docs/guide/dom.en-US.md
================================================
## Hooks of DOM specification
Most of the DOM Hooks will receive the `target` parameter, which indicates the element to be processed.
`target` supports three types `React.MutableRefObject`, `HTMLElement`, `() => HTMLElement`.
1. Support `React.MutableRefObject`
```ts
export default () => {
const ref = useRef(null);
const isHovering = useHover(ref);
return <div ref={ref}>{isHovering ? 'hover' : 'leaveHover'}</div>;
};
```
2. Support `HTMLElement`
```ts
export default () => {
const isHovering = useHover(document.getElementById('test'));
return <div id="test">{isHovering ? 'hover' : 'leaveHover'}</div>;
};
```
3. Support `() => HTMLElement`, generally applicable in SSR scenarios
```ts
export default () => {
const isHovering = useHover(() => document.getElementById('test'));
return <div id="test">{isHovering ? 'hover' : 'leaveHover'}</div>;
};
```
In addition, **the `target` of DOM Hooks supports dynamic changes**. for example:
```ts
export default () => {
const [boolean, { toggle }] = useBoolean();
const ref = useRef(null);
const ref2 = useRef(null);
const isHovering = useHover(boolean ? ref : ref2);
return (
<>
<div ref={ref}>{isHovering ? 'hover' : 'leaveHover'}</div>
<div ref={ref2}>{isHovering ? 'hover' : 'leaveHover'}</div>
</>
);
};
```
================================================
FILE: docs/guide/dom.zh-CN.md
================================================
## DOM 类 Hooks 使用规范
ahooks 大部分 DOM 类 Hooks 都会接收 `target` 参数,表示要处理的元素。
`target` 支持三种类型 `React.MutableRefObject`、`HTMLElement`、`() => HTMLElement`。
1. 支持 `React.MutableRefObject`
```ts
export default () => {
const ref = useRef(null);
const isHovering = useHover(ref);
return <div ref={ref}>{isHovering ? 'hover' : 'leaveHover'}</div>;
};
```
2. 支持 `HTMLElement`
```ts
export default () => {
const isHovering = useHover(document.getElementById('test'));
return <div id="test">{isHovering ? 'hover' : 'leaveHover'}</div>;
};
```
3. 支持 `() => HTMLElement`,一般适用在 SSR 场景
```ts
export default () => {
const isHovering = useHover(() => document.getElementById('test'));
return <div id="test">{isHovering ? 'hover' : 'leaveHover'}</div>;
};
```
另外,**DOM 类 Hooks 的 `target` 是支持动态变化的**。比如:
```ts
export default () => {
const [boolean, { toggle }] = useBoolean();
const ref = useRef(null);
const ref2 = useRef(null);
const isHovering = useHover(boolean ? ref : ref2);
return (
<>
<div ref={ref}>{isHovering ? 'hover' : 'leaveHover'}</div>
<div ref={ref2}>{isHovering ? 'hover' : 'leaveHover'}</div>
</>
);
};
```
================================================
FILE: docs/guide/index.en-US.md
================================================
## Intro
ahooks, pronounced [eɪ hʊks], is a high-quality and reliable React Hooks library. In the current React project development process, a set of easy-to-use React Hooks library is indispensable, hope ahooks can be your choice.
## 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
```bash
$ npm install --save ahooks
# or
$ yarn add ahooks
# or
$ pnpm add ahooks
# or
$ bun add ahooks
```
## Usage
```ts
import { useRequest } from 'ahooks';
```
## Online Demo
[](https://codesandbox.io/s/demo-for-ahooks-forked-fg79k?file=/src/App.js)
================================================
FILE: docs/guide/index.zh-CN.md
================================================
# 介绍
ahooks,发音 [eɪ hʊks],是一套高质量可靠的 React Hooks 库。在当前 React 项目研发过程中,一套好用的 React Hooks 库是必不可少的,希望 ahooks 能成为您的选择。
## 特性
- 易学易用
- 支持 SSR
- 对输入输出函数做了特殊处理,且避免闭包问题
- 包含大量提炼自业务的高级 Hooks
- 包含丰富的基础 Hooks
- 使用 TypeScript 构建,提供完整的类型定义文件
## 安装
```bash
$ npm install --save ahooks
# or
$ yarn add ahooks
# or
$ pnpm add ahooks
# or
$ bun add ahooks
```
## 使用
```ts
import { useRequest } from 'ahooks';
```
## 💻 在线体验
[](https://codesandbox.io/s/demo-for-ahooks-forked-fg79k?file=/src/App.js)
================================================
FILE: docs/guide/upgrade.en-US.md
================================================
## v2 to v3
Compared with the ahooks v2 version, the changes in the ahooks v3 version mainly include:
- New `useRequest`
- Support SSR
- Special treatment for input and output functions to avoid closure problems
- Hooks of DOM support dynamic target
- Solved the problem in Strict Mode
- Solved the problem in react-refresh (HMR) mode
- Fixed known issues
- Added more Hooks
## Upgrade suggestion
We have released the `ahooks-v2` package, you can install v2 and v3 dependencies at the same time to transition upgrades.
```bash
npm install ahooks-v2 --save
npm install ahooks --save
```
## New useRequest
useRequest has been rewritten:
- Organized the source code through a plug-in pattern, the core code is extremely simple, and can be easily extended for more advanced features.
- Provides step-by-step documentation.
- Fixed the way of exception handling, provides `run` and `runAsync` two trigger functions.
- The `options` parameter supports dynamic changes.
- Deleted `pagination`, `loadMore`, `formatResult` options to avoid the overload of TypeScript, it is more convenient for encapsulating more advanced Hooks based on `useRequest`.
### Detailed changes
- Deleted `UseRequestProvider`, it is recommended to encapsulate advanced Hooks based on `useRequest` instead.
- Removed `pagination` related options, it is recommended to use `usePagination` or `useAntdTable` to achieve paging ability.
- Removed `loadMore` related options, it is recommended to use `useInfiniteScroll` to achieve unlimited loading ability.
- Removed `fetchKey`, that is, deleted concurrent request.
- Removed `formatResult`, `initialData`, and `throwOnError`.
- The request library is no longer integrated by default, and `service` no longer supports string or object.
- Added `runAsync` and `refreshAsync`, the original `run` no longer returns Promise.
- Added error retry ability.
- Added `onBefore` and `onFinally` life cycles.
- Added cache clear ability.
- All options support dynamic changes.
- In debounce/throttle mode, `runAsync` can return current Promise.
- Debounce/throttle mode supports more options.
- Only successful request data will be cached.
- Upgraded `ready` behavior
[How is useRequest compatible with deleted capabilities?](#how-is-userequest-compatible-with-deleted-capabilities)
## Support SSR
ahooks v3 fully supports SSR, and related documents can be found in "[React Hooks & SSR](/guide/blog/ssr)".
## Hooks of DOM support dynamic target
Hooks of DOM support dynamic target, and related documents can be found in "[Hooks of DOM specification](/guide/dom)".
## Avoid closure problems
Inside ahooks, we have made special treatment for the functions input by the user and the functions returned, to avoid the closure problem as much as possible.
**The reference address of all output functions of ahooks will not change.**
```ts
const [state, setState] = React.useState();
```
As we all know, the `setState` reference address returned by `React.useState` will not change.
All functions returned in ahooks have the same characteristics as `setState`, and the reference address will not change.
```ts
const [state, { toggle }] = useToggle();
```
For example, the reference address of `toggle` function returned by `useToggle` is always stable.
**All input functions of ahooks always use the latest one.**
For the received function, ahooks will do a special process to ensure that the function called each time is always the latest.
```ts
const [state, setState] = useState();
useInterval(() => {
console.log(state);
}, 1000);
```
For example, in the above example, the function called by `useInterval` is always the latest.
Related documents can be found in "[ahooks function specification](/guide/blog/function)".
## Support strict mode
v3 fixed some problems in strict mode. Refer to "[React Hooks & strict mode](/guide/blog/strict)"
## Support react-refresh (HMR) mode
v3 fixed some problems in react-refresh (HMR) mode. Refer to "[React Hooks & react-refresh (HMR)](/guide/blog/hmr)"
## More changes
### New Hooks
- [useRafState](/hooks/use-raf-state)
- [useSetState](/hooks/use-set-state)
- [useAsyncEffect](/hooks/use-async-effect)
- [useDeepCompareEffect](/hooks/use-deep-compare-effect)
- [useIsomorphicLayoutEffect](/hooks/use-isomorphic-layout-effect)
- [useLatest](/hooks/use-latest)
- [usePagination](/hooks/use-pagination)
- [useLongPress](/hooks/use-long-press)
- [useInfiniteScroll](/hooks/use-infinite-scroll)
### Breaking Changes
- useBoolean
- `toggle` no longer accepts parameters
- Added `set`
- useToggle
- `toggle` no longer accepts parameters
- Added `set`
- useSet
- Removed `has` method, use `state.has` instead
- useCookieState
- `setState(null)` is no longer supported to delete cookies, please use `setState(undefined)` or `setState()` instead
- useCountDown
- Deleted the return value of `setTargetDate`, you can dynamically change `options.targetDate` to achieve the same effect
- useLocalStorageState / useSessionStorageState
- The second parameter changed from `defaultValue` to `Options`, use `options.defaultValue` instead
- Added `options.serializer` and `options.deserializer` to support custom sequence method
- useDynamicList
- `sortForm` was renamed to `sortList`
- useDrag & useDrop
- API is redesigned and needs to be upgraded according to the new document
- useExternal
- API has undergone major adjustments, please refer to the documentation
- No longer supports image type resources
- The resource becomes globally unique and will not be loaded repeatedly. At the same time, if there are multiple references, the resource will be deleted only after all references are unloaded
- useFullscreen
- API has been renamed, please refer to the documentation
- useVirtualList
- API is redesigned and needs to be upgraded according to the new document
- Added a `data` parameter to the function type `options.itemHeight` parameter
- useInViewport
- API has been upgraded, please refer to the documentation
- Added visible ratio ability
- useScroll
- The return value type is changed from `{ left?: number, top?: number }` to `{ left: number, top: number } | undefined`
- useSize
- The return value type is changed from `{ width?: number, height?: number }` to `{ width: number, height: number } | undefined`
- useKeyPress
- All aliases have been modified, please refer to the documentation
- useAntdTable
- Removed `options.formatResult`
- More changes are the same as useRequest
- useFusionTable
- Removed `options.formatResult`
- More changes are the same as useRequest
- usePersistFn was renamed to useMemoizedFn
- Deprecated the useControlledValue naming left over from 1.0, please use useControllableValue instead
### Optimization
- useUrlState
- Supported React Router v6
- useControllableValue
- Optimized logic to avoid unnecessary re-render
- More other optimizations
## FAQ
### How is useRequest compatible with deleted capabilities?
The new version of useRequest only provides the underlying capabilities of Promise management, and more advanced capabilities can be supported by encapsulating advanced Hooks based on useRequest.
1. `options.formatResult` is deleted, and the service is expected to return the data in the final format. for example:
```ts
const { data } = useRequest(async () => {
const result = await getData();
return result.data;
});
```
2. The original concurrent mode of `options.fetchKey` is deleted. It is expected that each request action and UI will be encapsulated as a component instead of placing all requests in the parent.
3. `options.initialData` is deleted, you can do this
```ts
const { data = initialData } = useRequest(getData);
```
4. The request library is no longer integrated by default, and `service` no longer supports string or object. It is expected to be supported by encapsulating advanced Hooks based on useReqeust. for example:
```ts
const useCustomHooks = (pathname, options) => {
return useRequest(() => {
return axios(pathname);
}, options);
};
```
================================================
FILE: docs/guide/upgrade.zh-CN.md
================================================
## v2 to v3
相较于 ahooks v2 版本,ahooks v3 版本的变更主要包括:
- 全新的 `useRequest`
- 全面支持 SSR
- 对输入输出函数做特殊处理,避免闭包问题
- DOM 类 Hooks 支持 target 动态变化
- 解决了在严格模式(Strict Mode)下的问题
- 解决了在 react-refresh(HMR)模式下的问题
- 修复了已知问题
- 新增了更多的 Hooks
## 升级建议
我们发布了 `ahooks-v2` 包,你可以同时安装 v2 和 v3 依赖,以过渡升级。
```bash
npm install ahooks-v2 --save
npm install ahooks --save
```
## 全新的 useRequest
useRequest 完全进行了重写:
- 通过插件式组织代码,核心代码极其简单,可以很方便的扩展出更高级的能力。
- 提供了循序渐进的文档。
- 彻底修复了异常处理方式,提供了 `run` 和 `runAsync` 两种触发函数。
- `options` 参数支持动态变化。
- 删除了 `pagination`、`loadMore`、`formatResult` 属性,避免了 `useRequest` TypeScript 重载,可以更方便的基于 `useRequest` 封装更高级的 Hooks。
### 详细变更
- 删除了 `UseRequestProvider`,建议自行基于 `useRequest` 封装高级 Hooks 来代替。
- 删除了 `pagination` 相关属性,建议使用 `usePagination` 或 `useAntdTable` 来实现分页能力。
- 删除了 `loadMore` 相关属性,建议使用 `useInfiniteScroll` 来实现无限加载能力。
- 删除了 `fetchKey`,也就是删除了并行能力。
- 删除了 `formatResult`、`initialData`、`throwOnError`。
- 不再默认集成请求库,`service` 不再支持字符或对象。
- 新增了 `runAsync` 和 `refreshAsync`,原来的 `run` 不再返回 Promise。
- 新增了错误重试能力。
- 新增了 `onBefore`、`onFinally` 生命周期。
- 新增了缓存清理能力。
- 所有参数支持动态变化。
- 防抖/节流模式下,`runAsync` 可以返回正常 Promise。
- 防抖/节流支持更多参数。
- 只有成功的请求数据才会缓存。
- `ready` 行为升级
[被删除的参数如何兼容?](#userequest-被删除的能力如何兼容)
## SSR 支持
ahooks v3 全面支持 SSR,相关文档可见《[React Hooks & SSR](/zh-CN/guide/blog/ssr)》。
## DOM 类 Hooks 支持 target 动态变化
DOM 类 Hooks 支持 target 动态变化,相关文档可见《[DOM 类 Hooks 使用规范](/zh-CN/guide/dom)》
## 避免闭包问题
ahooks v3 通过对输入输出函数做特殊处理,尽力帮助大家避免闭包问题。
**所有的输出函数,地址是不会变化的。**
```ts
const [state, setState] = React.useState();
```
大家熟知的`React.useState`返回的 `setState` 函数,地址是不会变化的。
v3 所有 Hooks 返回的函数,也有和 `setState` 一样的特性,地址不会变化。
```ts
const [state, { toggle }] = useToggle();
```
比如 `useToggle` 返回的 `toggle` 函数,地址就是永远固定的。
**所有的输入函数,永远使用最新的一份。**
对于接收的函数,v3 会做一次特殊处理,保证每次调用的函数永远是最新的。
```ts
const [state, setState] = useState();
useInterval(() => {
console.log(state);
}, 1000);
```
比如以上示例,`useInterval` 调用的函数永远是最新的。
相关文档可见《[ahooks 输入输出函数处理规范](/zh-CN/guide/blog/function)》。
## 支持严格模式
v3 修复了在严格模式下的一些问题。参考《[React Hooks & strict mode](/zh-CN/guide/blog/strict)》
## 支持 react-refresh(HMR)模式
v3 修复了在 react-refresh(HMR)模式下的一些问题。参考《[React Hooks & react-refresh(HMR)](/zh-CN/guide/blog/hmr)》
## 更多变更
### 新增 Hooks
- [useRafState](/zh-CN/hooks/use-raf-state)
- [useSetState](/zh-CN/hooks/use-set-state)
- [useAsyncEffect](/zh-CN/hooks/use-async-effect)
- [useDeepCompareEffect](/zh-CN/hooks/use-deep-compare-effect)
- [useIsomorphicLayoutEffect](/zh-CN/hooks/use-isomorphic-layout-effect)
- [useLatest](/zh-CN/hooks/use-latest)
- [usePagination](/zh-CN/hooks/use-pagination)
- [useLongPress](/zh-CN/hooks/use-long-press)
- [useInfiniteScroll](/zh-CN/hooks/use-infinite-scroll)
### Breaking Changes
- useBoolean
- `toggle` 不再接收参数
- 增加了 `set`
- useToggle
- `toggle` 不再接收参数
- 增加了 `set`
- useSet
- 删除了 `has` 方法,使用 `state.has` 代替
- useCookieState
- 不再支持 `setState(null)` 删除 Cookie,请使用 `setState(undefined)` 或 `setState()` 替代
- useCountDown
- 删除了 `setTargetDate` 返回值,可以动态改变 `options.targetDate` 实现相同效果
- useLocalStorageState / useSessionStorageState
- 第二个参数从 `defaultValue` 变为了 `Options`,使用 `options.defaultValue` 代替
- 增加了 `options.serializer` 和 `options.deserializer`,支持自定义序列法方法
- useDynamicList
- `sortForm` 改名为 `sortList`
- useDrag & useDrop
- API 重新设计,需要对照新的文档做升级
- useExternal
- API 进行了比较大的调整,请查阅文档
- 不再支持图片类型资源
- 资源在全局变成唯一的,不会重复加载,同时如果有多处引用,只有等全部引用卸载之后,才会删除该资源
- useFullscreen
- API 进行了重命名,请查阅文档
- useVirtualList
- API 重新设计,需要对照新的文档做升级
- `options.itemHeight` 函数型参数增加了 `data` 参数
- useInViewport
- API 进行了升级,请查阅文档
- 增加了可见比例能力
- useScroll
- 返回值类型从 `{ left?: number, top?: number }` 改为 `{ left: number, top: number } | undefined`
- useSize
- 返回值类型从 `{ width?: number, height?: number }` 改为 `{ width: number, height: number } | undefined`
- useKeyPress
- 修改了所有别名,请查阅文档
- useAntdTable
- 删除了 `options.formatResult`
- 更多变更同 useRequest
- useFusionTable
- 删除了 `options.formatResult`
- 更多变更同 useRequest
- usePersistFn 更名为 useMemoizedFn
- 废弃了 1.0 遗留的 useControlledValue 命名,请使用 useControllableValue 代替
### 优化
- useUrlState
- 支持了 React Router v6
- useControllableValue
- 优化了代码逻辑,避免了不必要的 re-render
- 更多其它优化
## FAQ
### useRequest 被删除的能力如何兼容?
新版 useRequest 只做 Promise 管理的底层能力,更多高级能力可以基于 useRequest 封装高级 Hooks 来支持。
1. 原 `options.formatResult` 删除,期望 service 返回最终格式的数据。比如:
```ts
const { data } = useRequest(async () => {
const result = await getData();
return result.data;
});
```
2. 原 `options.fetchKey` 并行模式删除,期望将每个请求动作和 UI 封装为一个组件,而不是把所有请求都放到父级。
3. 原 `options.initialData` 删除,可以这样做
```ts
const { data = initialData } = useRequest(getData);
```
4. 不再默认集成请求库,`service` 不再支持字符或对象。期望基于 useReqeust 封装高级 Hooks 来支持。比如:
```ts
const useCustomHooks = (pathname, options) => {
return useRequest(() => {
return axios(pathname);
}, options);
};
```
================================================
FILE: docs/index.en-US.md
================================================
---
title: ahooks - React Hooks Library
hero:
image: /logo.svg
desc: A high-quality & reliable React Hooks library
actions:
- text: Guide
link: /guide
- text: Hooks List
link: /hooks
footer: Open-source MIT Licensed | Copyright © 2019-present<br />Powered by [dumi](https://d.umijs.org)
---
[![NPM version][image-1]][1]
[![NPM downloads][image-2]][2]
[](https://www.npmjs.com/package/ahooks-v2)
[](https://github.com/alibaba/hooks/issues)
[](https://coveralls.io/github/alibaba/hooks?branch=master)

[](http://isitmaintained.com/project/alibaba/hooks "Percentage of issues still open")
[](http://isitmaintained.com/project/alibaba/hooks "Average time to resolve an issue")

## ✨ 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
```bash
$ npm install --save ahooks
# or
$ yarn add ahooks
# or
$ pnpm add ahooks
# or
$ bun add ahooks
```
## 🔨 Usage
```ts
import { useRequest } from "ahooks";
```
## 💻 Online Demo
[](https://codesandbox.io/s/demo-for-ahooks-forked-fg79k?file=/src/App.js)
## 🤝 Contributing
```bash
$ git clone git@github.com: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](https://github.com/alibaba/hooks/blob/master/CONTRIBUTING.MD) first, let's build a better hooks library together.
Thanks to all the contributors:
<a href="https://github.com/alibaba/hooks/graphs/contributors">
<img src="https://opencollective.com/ahooks/contributors.svg?width=960&button=false" alt="contributors" />
</a>
## 👥 Discuss
<img alt="ahooks discussion group 1" src="https://github.com/user-attachments/assets/0ba7a370-2a69-442f-b746-9eb16bbbc46c" width="200" style='display:inline' />
<img alt="ahooks discussion group 2" src="https://github.com/user-attachments/assets/a08693d3-bfcc-4aca-b2b0-2d9c23012858" width="200" style='display:inline' />
<img alt="ahooks discussion group 3" src="https://github.com/user-attachments/assets/15a505a7-06d1-4e72-ab02-6fad968323f1" width="200" style='display:inline' />
[1]: https://www.npmjs.com/package/ahooks
[2]: https://npmjs.org/package/ahooks
[image-1]: https://img.shields.io/npm/v/ahooks.svg?style=flat
[image-2]: https://img.shields.io/npm/dm/ahooks.svg?style=flat
================================================
FILE: docs/index.zh-CN.md
================================================
---
title: ahooks - React Hooks Library
hero:
image: /logo.svg
desc: 一套高质量可靠的 React Hooks 库
actions:
- text: 指南
link: /zh-CN/guide
- text: Hooks 列表
link: /zh-CN/hooks
footer: Open-source MIT Licensed | Copyright © 2019-present<br />Powered by [dumi](https://d.umijs.org)
---
[![NPM version][image-1]][1]
[![NPM downloads][image-2]][2]
[](https://www.npmjs.com/package/ahooks-v2)
[](https://github.com/alibaba/hooks/issues)
[](https://coveralls.io/github/alibaba/hooks?branch=master)

[](http://isitmaintained.com/project/alibaba/hooks "Percentage of issues still open")
[](http://isitmaintained.com/project/alibaba/hooks "Average time to resolve an issue")

## ✨ 特性
- 易学易用
- 支持 SSR
- 对输入输出函数做了特殊处理,避免闭包问题
- 包含大量提炼自业务的高级 Hooks
- 包含丰富的基础 Hooks
- 使用 TypeScript 构建,提供完整的类型定义文件
## 📦 安装
```bash
$ npm install --save ahooks
# or
$ yarn add ahooks
# or
$ pnpm add ahooks
# or
$ bun add ahooks
```
## 🔨 使用
```ts
import { useRequest } from "ahooks";
```
## 💻 在线体验
[](https://codesandbox.io/s/demo-for-ahooks-forked-fg79k?file=/src/App.js)
## 🤝 参与共建
```bash
$ git clone git@github.com:alibaba/hooks.git
$ cd hooks
$ pnpm run init
$ pnpm start
```
打开浏览器访问 http://127.0.0.1:8000
我们欢迎所有人参与共建,请参考[CONTRIBUTING.MD](https://github.com/alibaba/hooks/blob/master/CONTRIBUTING.zh-CN.MD)
感谢所有贡献者:
<a href="https://github.com/alibaba/hooks/graphs/contributors">
<img src="https://opencollective.com/ahooks/contributors.svg?width=960&button=false" alt="contributors" />
</a>
## 👥 交流讨论
<img alt="ahooks 交流群1" src="https://github.com/user-attachments/assets/0ba7a370-2a69-442f-b746-9eb16bbbc46c" width="200" style='display:inline' />
<img alt="ahooks 交流群2" src="https://github.com/user-attachments/assets/a08693d3-bfcc-4aca-b2b0-2d9c23012858" width="200" style='display:inline' />
<img alt="ahooks 交流群3" src="https://github.com/user-attachments/assets/15a505a7-06d1-4e72-ab02-6fad968323f1" width="200" style='display:inline' />
[1]: https://www.npmjs.com/package/ahooks
[2]: https://npmjs.org/package/ahooks
[image-1]: https://img.shields.io/npm/v/ahooks.svg?style=flat
[image-2]: https://img.shields.io/npm/dm/ahooks.svg?style=flat
================================================
FILE: example/.gitkeep
================================================
import React from 'react';
import { useBoolean } from 'ahooks';
export default function Demo() {
const [state, { toggle, setTrue, setFalse }] = useBoolean(false);
return (
<div>
<p>Current state: {state ? 'ON' : 'OFF'}</p>
<button onClick={toggle}>Toggle</button>
<button onClick={setTrue}>Set True</button>
<button onClick={setFalse}>Set False</button>
</div>
);
}
================================================
FILE: gulpfile.js
================================================
const gulp = require('gulp');
const babel = require('gulp-babel');
const ts = require('gulp-typescript');
const del = require('del');
gulp.task('clean', async () => {
await del('lib/**');
await del('es/**');
await del('dist/**');
});
gulp.task('cjs', () =>
gulp
.src(['./es/**/*.js'])
.pipe(
babel({
configFile: '../../.babelrc',
}),
)
.pipe(gulp.dest('lib/')),
);
gulp.task('es', async () => {
const { execSync } = require('child_process');
// 使用 tsc 直接编译
console.log('Running TypeScript compilation...');
execSync('npx tsc --project tsconfig.pro.json --outDir es --module esnext', { stdio: 'inherit' });
console.log('TypeScript compilation completed');
// 然后运行 babel 转换
console.log('Running Babel transformation...');
return gulp
.src(['es/**/*.js'])
.pipe(
babel({
configFile: './.babelrc',
}),
)
.pipe(gulp.dest('es/'));
});
gulp.task('declaration', () => {
const tsProject = ts.createProject('tsconfig.pro.json', {
declaration: true,
emitDeclarationOnly: true,
});
return tsProject.src().pipe(tsProject()).pipe(gulp.dest('es/')).pipe(gulp.dest('lib/'));
});
gulp.task('copyReadme', async () => {
await gulp.src('../../README.md').pipe(gulp.dest('../../packages/hooks'));
});
exports.default = gulp.series('clean', 'es', 'cjs', 'declaration', 'copyReadme');
================================================
FILE: package.json
================================================
{
"name": "ahooks",
"private": true,
"packageManager": "pnpm@10.12.4",
"engines": {
"pnpm": ">=7 <=10"
},
"repository": {
"type": "git",
"url": "git+https://github.com/alibaba/hooks.git"
},
"scripts": {
"init": "pnpm install && pnpm run build",
"start": "pnpm run dev",
"dev": "cross-env NODE_OPTIONS=--openssl-legacy-provider dumi dev",
"clean-dist": "rimraf 'packages/*/{lib,es,node_modules,dist}'",
"clean": "pnpm run clean-dist && rimraf node_modules",
"build": "pnpm -r --filter=./packages/* run build",
"test": "pnpm --filter=./packages/* test",
"test:strict": "cross-env REACT_MODE=strict pnpm --filter=./packages/* test:cov",
"coveralls": "vitest run --coverage | coveralls",
"lint": "biome lint --fix",
"pretty": "biome format --fix --no-errors-on-unmatched",
"build:doc": "cross-env NODE_OPTIONS=--openssl-legacy-provider dumi build",
"build:doc-github": "node scripts/build-with-relative-paths.js",
"pub:doc-surge": "surge ./dist --domain ahooks.js.org",
"pub:doc-gitee": "cd ./dist && rm -rf .git && touch .spa && touch .nojekyll && git init && git remote add origin git@gitee.com:ahooks/ahooks.git && git add -A && git commit -m \"publish docs\" && git push origin main -f && echo https://gitee.com/ahooks/ahooks/pages",
"pub:doc": "pnpm run build:doc && pnpm run pub:doc-surge && pnpm run build:doc-github",
"pub": "pnpm run build && pnpm -r --filter=./packages/* publish",
"pub:beta": "pnpm run build && pnpm -r --filter=./packages/* publish --tag beta",
"preinstall": "npx only-allow pnpm",
"prepare": "husky install",
"commit": "git add -A && czg",
"tsc": "pnpm --filter=./packages/* tsc"
},
"devDependencies": {
"@alifd/next": "^1.27.32",
"@ant-design/icons": "^5.6.1",
"@babel/cli": "^7.10.1",
"@babel/core": "^7.10.2",
"@babel/plugin-transform-runtime": "^7.19.6",
"@biomejs/biome": "^2.0.6",
"@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^17.1.0",
"@testing-library/react": "^16.3.0",
"@types/lodash": "^4.17.20",
"@types/mockjs": "^1.0.7",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@types/react-router": "^5.1.19",
"@umijs/fabric": "^2.1.0",
"@vitest/coverage-istanbul": "^3.2.4",
"antd": "^5.26.3",
"babel-plugin-import": "^1.12.0",
"coveralls": "^3.1.1",
"cross-env": "^7.0.3",
"czg": "^1.12.0",
"del": "^5.1.0",
"dumi": "^1.1.54",
"fast-glob": "^3.2.11",
"fs-extra": "^10.0.1",
"gray-matter": "^4.0.3",
"gulp": "^4.0.2",
"gulp-babel": "^8.0.0",
"gulp-typescript": "^6.0.0-alpha.1",
"husky": "^8.0.0",
"jsdom": "^26.1.0",
"mockjs": "^1.1.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-drag-listview": "^0.1.6",
"react-json-view": "^1.21.3",
"react-router": "^6.4.2",
"react-shadow": "^20.6.0",
"rimraf": "^3.0.2",
"surge": "^0.21.3",
"typescript": "^5.8.3",
"vitest": "^3.2.4",
"vitest-websocket-mock": "^0.5.0",
"webpack": "^5.99.9",
"webpack-cli": "^6.0.1",
"webpack-merge": "^6.0.1"
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
}
}
================================================
FILE: packages/hooks/gulpfile.js
================================================
const commonConfig = require('../../gulpfile');
const gulp = require('gulp');
const fs = require('fs');
const fse = require('fs-extra');
const fg = require('fast-glob');
const gm = require('gray-matter');
function camelToKebab(str) {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
async function genDesc(mdPath) {
if (!fs.existsSync(mdPath)) {
return;
}
const mdFile = fs.readFileSync(mdPath, 'utf8');
const { content } = gm(mdFile);
let description =
(content.replace(/\r\n/g, '\n').match(/# \w+[\s\n]+(.+?)(?:, |\. |\n|\.\n)/m) || [])[1] || '';
description = description.trim();
description = description.charAt(0).toLowerCase() + description.slice(1);
return description;
}
async function genMetaData() {
const metadata = {
functions: [],
};
const hooks = fg
.sync('src/use*', {
onlyDirectories: true,
})
.map((hook) => hook.replace('src/', ''))
.sort();
await Promise.allSettled(
hooks.map(async (hook) => {
const description = await genDesc(`src/${hook}/index.en-US.md`);
return {
name: hook,
docs: `https://ahooks.js.org/hooks/${camelToKebab(hook)}`,
description,
};
}),
).then((res) => {
metadata.functions = res.map((item) => {
if (item.status === 'fulfilled') {
return item.value;
}
return null;
});
});
return metadata;
}
gulp.task('metadata', async function () {
const metadata = await genMetaData();
await fse.writeJson('metadata.json', metadata, { spaces: 2 });
});
exports.default = gulp.series(commonConfig.default, 'metadata');
================================================
FILE: packages/hooks/package.json
================================================
{
"name": "ahooks",
"version": "3.9.6",
"description": "react hooks library",
"keywords": [
"ahooks",
"umi hooks",
"react hooks"
],
"main": "./lib/index.js",
"module": "./es/index.js",
"types": "./lib/index.d.ts",
"unpkg": "dist/ahooks.js",
"sideEffects": false,
"authors": {
"name": "brickspert",
"email": "brickspert.fjl@alipay.com"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"repository": "https://github.com/alibaba/hooks",
"homepage": "https://github.com/alibaba/hooks",
"scripts": {
"build": "gulp && webpack-cli",
"test": "vitest run --color",
"test:cov": "vitest run --color --coverage",
"tsc": "tsc --noEmit"
},
"files": [
"dist",
"lib",
"es",
"metadata.json",
"package.json",
"README.md"
],
"dependencies": {
"@types/js-cookie": "^3.0.6",
"@babel/runtime": "^7.21.0",
"dayjs": "^1.9.1",
"intersection-observer": "^0.12.0",
"js-cookie": "^3.0.5",
"lodash": "^4.17.21",
"react-fast-compare": "^3.2.2",
"resize-observer-polyfill": "^1.5.1",
"screenfull": "^5.0.0",
"tslib": "^2.4.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"license": "MIT",
"gitHead": "11f6ad571bd365c95ecb9409ca3050cbbfc9b34a"
}
================================================
FILE: packages/hooks/src/createDeepCompareEffect/__tests__/index.spec.ts
================================================
import { act, renderHook } from '@testing-library/react';
import { useEffect, useLayoutEffect, useState } from 'react';
import { describe, expect, test } from 'vitest';
import { createDeepCompareEffect } from '../index';
describe('createDeepCompareEffect', () => {
test('should work for useEffect', async () => {
const useDeepCompareEffect = createDeepCompareEffect(useEffect);
const hook = renderHook(() => {
const [x, setX] = useState(0);
const [y, setY] = useState({ foo: 'foo', bar: ['baz'] });
useDeepCompareEffect(() => {
setX((prevX) => prevX + 1);
}, [y]);
return { x, setY };
});
expect(hook.result.current.x).toBe(1);
await act(async () => {
hook.result.current.setY({ foo: 'foo', bar: ['baz'] });
});
expect(hook.result.current.x).toBe(1);
await act(async () => {
hook.result.current.setY({ foo: 'foo', bar: ['bazz'] });
});
expect(hook.result.current.x).toBe(2);
});
test('should work for useLayoutEffect', async () => {
const useDeepCompareLayoutEffect = createDeepCompareEffect(useLayoutEffect);
const hook = renderHook(() => {
const [x, setX] = useState(0);
const [y, setY] = useState({ foo: 'foo', bar: ['baz'] });
useDeepCompareLayoutEffect(() => {
setX((prevX) => prevX + 1);
}, [y]);
return { x, setY };
});
expect(hook.result.current.x).toBe(1);
await act(async () => {
hook.result.current.setY({ foo: 'foo', bar: ['baz'] });
});
expect(hook.result.current.x).toBe(1);
await act(async () => {
hook.result.current.setY({ foo: 'foo', bar: ['bazz'] });
});
expect(hook.result.current.x).toBe(2);
});
test('deps is undefined should rerender in useEffect', async () => {
const useDeepCompareLayoutEffect = createDeepCompareEffect(useEffect);
let count = 0;
const hook = renderHook(() => {
useDeepCompareLayoutEffect(() => {
count++;
});
});
expect(count).toBe(1);
hook.rerender();
expect(count).toBe(2);
hook.rerender();
expect(count).toBe(3);
});
test('deps is undefined should rerender in useLayoutEffect', async () => {
const useDeepCompareLayoutEffect = createDeepCompareEffect(useLayoutEffect);
let count = 0;
const hook = renderHook(() => {
useDeepCompareLayoutEffect(() => {
count++;
});
});
expect(count).toBe(1);
hook.rerender();
expect(count).toBe(2);
hook.rerender();
expect(count).toBe(3);
});
});
================================================
FILE: packages/hooks/src/createDeepCompareEffect/index.ts
================================================
import { useRef } from 'react';
import type { DependencyList, useEffect, useLayoutEffect } from 'react';
import { depsEqual } from '../utils/depsEqual';
type EffectHookType = typeof useEffect | typeof useLayoutEffect;
type CreateUpdateEffect = (hook: EffectHookType) => EffectHookType;
export const createDeepCompareEffect: CreateUpdateEffect = (hook) => (effect, deps) => {
const ref = useRef<DependencyList>(undefined);
const signalRef = useRef<number>(0);
if (deps === undefined || !depsEqual(deps, ref.current)) {
signalRef.current += 1;
}
ref.current = deps;
hook(effect, [signalRef.current]);
};
================================================
FILE: packages/hooks/src/createUpdateEffect/__tests__/index.spec.ts
================================================
import { renderHook } from '@testing-library/react';
import { useEffect, useLayoutEffect } from 'react';
import { describe, expect, test } from 'vitest';
import { createUpdateEffect } from '../index';
describe('createUpdateEffect', () => {
test('should work for useEffect', () => {
const useUpdateEffect = createUpdateEffect(useEffect);
let mountedState = 1;
const hook = renderHook(() =>
useUpdateEffect(() => {
mountedState = 2;
}),
);
expect(mountedState).toBe(1);
hook.rerender();
expect(mountedState).toBe(2);
});
test('should work for useLayoutEffect', () => {
const useUpdateLayoutEffect = createUpdateEffect(useLayoutEffect);
let mountedState = 1;
const hook = renderHook(() =>
useUpdateLayoutEffect(() => {
mountedState = 2;
}),
);
expect(mountedState).toBe(1);
hook.rerender();
expect(mountedState).toBe(2);
});
});
================================================
FILE: packages/hooks/src/createUpdateEffect/index.ts
================================================
import { useRef } from 'react';
import type { useEffect, useLayoutEffect } from 'react';
type EffectHookType = typeof useEffect | typeof useLayoutEffect;
export const createUpdateEffect: (hook: EffectHookType) => EffectHookType =
(hook) => (effect, deps) => {
const isMounted = useRef(false);
// for react-refresh
hook(() => {
return () => {
isMounted.current = false;
};
}, []);
hook(() => {
if (!isMounted.current) {
isMounted.current = true;
} else {
return effect();
}
}, deps);
};
export default createUpdateEffect;
================================================
FILE: packages/hooks/src/createUseStorageState/__tests__/index.spec.ts
================================================
import { act, renderHook } from '@testing-library/react';
import { describe, expect, test } from 'vitest';
import type { Options } from '../index';
import { createUseStorageState } from '../index';
class TestStorage implements Storage {
[name: string]: any;
length = 0;
_values = new Map<string, string>();
clear(): void {
this._values.clear();
this.length = 0;
}
getItem(key: string): string | null {
return this._values.get(key) || null;
}
key(index: number): string | null {
if (index >= this._values.size) {
return null;
}
return Array.from(this._values.keys())[index];
}
removeItem(key: string): void {
if (this._values.delete(key)) {
this.length -= 1;
}
}
setItem(key: string, value: string): void {
if (!this._values.has(key)) {
this.length += 1;
}
this._values.set(key, value);
}
}
interface StorageStateProps<T> extends Pick<Options<T>, 'defaultValue'> {
key: string;
}
describe('useStorageState', () => {
const setUp = <T>(props: StorageStateProps<T>) => {
const storage = new TestStorage();
const useStorageState = createUseStorageState(() => storage);
return renderHook(
({ key, defaultValue }: StorageStateProps<T>) => {
const [state, setState] = useStorageState(key, { defaultValue });
return { state, setState };
},
{
initialProps: props,
},
);
};
test('should get defaultValue for a given key', () => {
const hook = setUp({ key: 'key1', defaultValue: 'value1' });
expect(hook.result.current.state).toBe('value1');
hook.rerender({ key: 'key2', defaultValue: 'value2' });
expect(hook.result.current.state).toBe('value2');
});
test('should get default and set value for a given key', () => {
const hook = setUp({ key: 'key', defaultValue: 'defaultValue' });
expect(hook.result.current.state).toBe('defaultValue');
act(() => {
hook.result.current.setState('setValue');
});
expect(hook.result.current.state).toBe('setValue');
hook.rerender({ key: 'key' });
expect(hook.result.current.state).toBe('setValue');
});
test('should remove value for a given key', () => {
const hook = setUp({ key: 'key' });
act(() => {
hook.result.current.setState('value');
});
expect(hook.result.current.state).toBe('value');
act(() => {
hook.result.current.setState(undefined);
});
expect(hook.result.current.state).toBeUndefined();
act(() => hook.result.current.setState('value'));
expect(hook.result.current.state).toBe('value');
act(() => hook.result.current.setState(undefined));
expect(hook.result.current.state).toBeUndefined();
});
});
================================================
FILE: packages/hooks/src/createUseStorageState/index.ts
================================================
import { useRef, useState } from 'react';
import useEventListener from '../useEventListener';
import useMemoizedFn from '../useMemoizedFn';
import useUpdateEffect from '../useUpdateEffect';
import { isFunction, isUndef } from '../utils';
export const SYNC_STORAGE_EVENT_NAME = 'AHOOKS_SYNC_STORAGE_EVENT_NAME';
export type SetState<S> = S | ((prevState?: S) => S);
export interface Options<T> {
defaultValue?: T | (() => T);
listenStorageChange?: boolean;
serializer?: (value: T) => string;
deserializer?: (value: string) => T;
onError?: (error: unknown) => void;
}
export const createUseStorageState = (getStorage: () => Storage | undefined) => {
const useStorageState = <T>(key: string, options: Options<T> = {}) => {
let storage: Storage | undefined;
const { listenStorageChange = false } = options;
const serializer = isFunction(options.serializer) ? options.serializer : JSON.stringify;
const deserializer = isFunction(options.deserializer) ? options.deserializer : JSON.parse;
const onError = isFunction(options.onError) ? options.onError : console.error;
// https://github.com/alibaba/hooks/issues/800
try {
storage = getStorage();
} catch (err) {
onError(err);
}
const getStoredValue = () => {
try {
const raw = storage?.getItem(key);
if (raw) {
return deserializer(raw);
}
} catch (e) {
onError(e);
}
if (isFunction(options.defaultValue)) {
return options.defaultValue();
}
return options.defaultValue;
};
const [state, setState] = useState<T>(getStoredValue);
const stateRef = useRef<T>(state);
stateRef.current = state;
useUpdateEffect(() => {
const nextState = getStoredValue();
if (Object.is(nextState, stateRef.current)) {
return; // 新旧状态相同,不更新 state,避免 setState 带来不必要的 re-render
}
stateRef.current = nextState;
setState(nextState);
}, [key]);
const updateState = (value: SetState<T>) => {
const previousState = stateRef.current;
const currentState = isFunction(value) ? value(previousState) : value;
if (Object.is(currentState, previousState)) {
return; // 新旧状态相同,不更新 state,避免 setState 带来不必要的 re-render
}
if (!listenStorageChange) {
stateRef.current = currentState;
setState(currentState);
}
try {
let newValue: string | null;
const oldValue = storage?.getItem(key);
if (isUndef(currentState)) {
newValue = null;
storage?.removeItem(key);
} else {
newValue = serializer(currentState);
storage?.setItem(key, newValue);
}
dispatchEvent(
// send custom event to communicate within same page
// importantly this should not be a StorageEvent since those cannot
// be constructed with a non-built-in storage area
new CustomEvent(SYNC_STORAGE_EVENT_NAME, {
detail: {
key,
newValue,
oldValue,
storageArea: storage,
},
}),
);
} catch (e) {
onError(e);
}
};
const syncState = (event: StorageEvent) => {
if (event.key !== key || event.storageArea !== storage) {
return;
}
const nextState = getStoredValue();
if (Object.is(nextState, stateRef.current)) {
return; // 新旧状态相同,不更新 state,避免 setState 带来不必要的 re-render
}
stateRef.current = nextState;
setState(nextState);
};
const syncStateFromCustomEvent = (event: CustomEvent<StorageEvent>) => {
syncState(event.detail);
};
// from another document
useEventListener('storage', syncState, {
enable: listenStorageChange,
});
// from the same document but different hooks
useEventListener(SYNC_STORAGE_EVENT_NAME, syncStateFromCustomEvent, {
enable: listenStorageChange,
});
return [state, useMemoizedFn(updateState)] as const;
};
return useStorageState;
};
================================================
FILE: packages/hooks/src/global.d.ts
================================================
declare module '*.jpg';
interface Window {
TEST_SCRIPT?: any;
}
================================================
FILE: packages/hooks/src/index.spec.ts
================================================
import { describe, expect, test } from 'vitest';
import * as ahooks from '.';
describe('ahooks', () => {
test('exports modules should be defined', () => {
Object.entries(ahooks).forEach(([key, value]) => {
expect(value).toBeDefined();
});
});
});
================================================
FILE: packages/hooks/src/index.ts
================================================
import { createUpdateEffect } from './createUpdateEffect';
import useAntdTable from './useAntdTable';
import useAsyncEffect from './useAsyncEffect';
import useBoolean from './useBoolean';
import useClickAway from './useClickAway';
import useControllableValue from './useControllableValue';
import useCookieState from './useCookieState';
import useCountDown from './useCountDown';
import useCounter from './useCounter';
import useCreation from './useCreation';
import useDebounce from './useDebounce';
import useDebounceEffect from './useDebounceEffect';
import useDebounceFn from './useDebounceFn';
import useDeepCompareEffect from './useDeepCompareEffect';
import useDeepCompareLayoutEffect from './useDeepCompareLayoutEffect';
import useDocumentVisibility from './useDocumentVisibility';
import useDrag from './useDrag';
import useDrop from './useDrop';
import useDynamicList from './useDynamicList';
import useEventEmitter from './useEventEmitter';
import useEventListener from './useEventListener';
import useEventTarget from './useEventTarget';
import useExternal from './useExternal';
import useFavicon from './useFavicon';
import useFocusWithin from './useFocusWithin';
import useFullscreen from './useFullscreen';
import useFusionTable from './useFusionTable';
import useGetState from './useGetState';
import useHistoryTravel from './useHistoryTravel';
import useHover from './useHover';
import useInfiniteScroll from './useInfiniteScroll';
import useInterval from './useInterval';
import useInViewport from './useInViewport';
import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect';
import useKeyPress from './useKeyPress';
import useLatest from './useLatest';
import useLocalStorageState from './useLocalStorageState';
import useLockFn from './useLockFn';
import useLongPress from './useLongPress';
import useMap from './useMap';
import useMemoizedFn from './useMemoizedFn';
import useMount from './useMount';
import useMouse from './useMouse';
import useNetwork from './useNetwork';
import usePagination from './usePagination';
import usePrevious from './usePrevious';
import useRafInterval from './useRafInterval';
import useRafState from './useRafState';
import useRafTimeout from './useRafTimeout';
import useReactive from './useReactive';
import useRequest, { clearCache } from './useRequest';
import useResetState from './useResetState';
import useResponsive, { configResponsive } from './useResponsive';
import useSafeState from './useSafeState';
import useScroll from './useScroll';
import useSelections from './useSelections';
import useSessionStorageState from './useSessionStorageState';
import useSet from './useSet';
import useSetState from './useSetState';
import useSize from './useSize';
import useTextSelection from './useTextSelection';
import useThrottle from './useThrottle';
import useThrottleEffect from './useThrottleEffect';
import useThrottleFn from './useThrottleFn';
import useTimeout from './useTimeout';
import useTitle from './useTitle';
import useToggle from './useToggle';
import useTrackedEffect from './useTrackedEffect';
import useUnmount from './useUnmount';
import useUnmountedRef from './useUnmountedRef';
import useUpdate from './useUpdate';
import useUpdateEffect from './useUpdateEffect';
import useUpdateLayoutEffect from './useUpdateLayoutEffect';
import useVirtualList from './useVirtualList';
import useWebSocket from './useWebSocket';
import useWhyDidYouUpdate from './useWhyDidYouUpdate';
import useMutationObserver from './useMutationObserver';
import useTheme from './useTheme';
export {
useRequest,
useControllableValue,
useDynamicList,
useVirtualList,
useResponsive,
useEventEmitter,
useLocalStorageState,
useSessionStorageState,
useSize,
configResponsive,
useUpdateEffect,
useUpdateLayoutEffect,
useBoolean,
useToggle,
useDocumentVisibility,
useSelections,
useThrottle,
useThrottleFn,
useThrottleEffect,
useDebounce,
useDebounceFn,
useDebounceEffect,
usePrevious,
useMouse,
useScroll,
useClickAway,
useFullscreen,
useInViewport,
useKeyPress,
useEventListener,
useHover,
useUnmount,
useSet,
useMemoizedFn,
useMap,
useCreation,
useDrag,
useDrop,
useMount,
useCounter,
useUpdate,
useTextSelection,
useEventTarget,
useHistoryTravel,
useCookieState,
useSetState,
useInterval,
useWhyDidYouUpdate,
useTitle,
useNetwork,
useTimeout,
useReactive,
useFavicon,
useCountDown,
useWebSocket,
useLockFn,
useUnmountedRef,
useExternal,
useSafeState,
useLatest,
useIsomorphicLayoutEffect,
useDeepCompareEffect,
useDeepCompareLayoutEffect,
useAsyncEffect,
useLongPress,
useRafState,
useTrackedEffect,
usePagination,
useAntdTable,
useFusionTable,
useInfiniteScroll,
useGetState,
clearCache,
useFocusWithin,
createUpdateEffect,
useRafInterval,
useRafTimeout,
useResetState,
useMutationObserver,
useTheme,
};
================================================
FILE: packages/hooks/src/useAntdTable/__tests__/index.spec.ts
================================================
import type { RenderHookResult } from '@testing-library/react';
import { act, renderHook, waitFor } from '@testing-library/react';
import { Form } from 'antd';
import { useEffect } from 'react';
import { describe, expect, test } from 'vitest';
import { sleep } from '../../utils/testingHelpers';
import useAntdTable from '../index';
interface Query {
current: number;
pageSize: number;
[key: string]: any;
}
describe('useAntdTable', () => {
let queryArgs: any;
const asyncFn = (query: Query, formData: any = {}) => {
queryArgs = { ...query, ...formData };
return Promise.resolve({
total: 20,
list: [],
});
};
let searchType = 'simple';
const form = {
getInternalHooks: () => {},
initialValue: {
name: 'default name',
},
fieldsValue: {
name: 'default name',
},
getFieldsValue() {
if (searchType === 'simple') {
return {
name: this.fieldsValue.name,
};
}
return this.fieldsValue;
},
setFieldsValue(values: object) {
this.fieldsValue = {
...this.fieldsValue,
...values,
};
},
resetFields() {
this.fieldsValue = { ...this.initialValue };
},
validateFields(fields: any[]) {
const targetFields: Record<string | number, any> = {};
fields.forEach((field: string | number) => {
targetFields[field] = (this.fieldsValue as any)[field];
});
return Promise.resolve(targetFields);
},
};
const changeSearchType = (type: any) => {
searchType = type;
};
const setUp = (
service: Parameters<typeof useAntdTable>[0],
options: Parameters<typeof useAntdTable>[1],
) => renderHook((o) => useAntdTable(service, o || options));
let hook: RenderHookResult<any, any>;
test('should fetch after first render', async () => {
queryArgs = undefined;
form.resetFields();
changeSearchType('simple');
act(() => {
hook = setUp(asyncFn, {});
});
expect(hook.result.current.tableProps.loading).toBe(false);
expect(hook.result.current.tableProps.pagination.current).toBe(1);
expect(hook.result.current.tableProps.pagination.pageSize).toBe(10);
await waitFor(() => expect(hook.result.current.tableProps.pagination.total).toBe(20));
});
test('should defaultParams work', async () => {
queryArgs = undefined;
form.resetFields();
changeSearchType('advance');
act(() => {
hook = setUp(asyncFn, {
form,
defaultParams: [
{
current: 2,
pageSize: 10,
},
{ name: 'hello', phone: '123' },
],
defaultType: 'advance',
});
});
const { search } = hook.result.current;
expect(hook.result.current.tableProps.loading).toBe(false);
await waitFor(() => expect(queryArgs.current).toBe(2));
expect(queryArgs.pageSize).toBe(10);
expect(queryArgs.name).toBe('hello');
expect(queryArgs.phone).toBe('123');
expect(search.type).toBe('advance');
});
test('should stop the query when validate fields failed', async () => {
queryArgs = undefined;
form.resetFields();
changeSearchType('advance');
act(() => {
hook = setUp(asyncFn, {
form: { ...form, validateFields: () => Promise.reject() },
defaultParams: [
{
current: 2,
pageSize: 10,
},
{ name: 'hello', phone: '123' },
],
defaultType: 'advance',
});
});
await sleep(1);
expect(queryArgs).toBeUndefined();
});
test('should ready work', async () => {
queryArgs = undefined;
form.resetFields();
changeSearchType('advance');
act(() => {
hook = setUp(asyncFn, {
ready: false,
form,
defaultParams: [
{
current: 2,
pageSize: 10,
},
{ name: 'hello', phone: '123' },
],
defaultType: 'advance',
});
});
await sleep(1);
expect(queryArgs).toBeUndefined();
hook.rerender({
ready: true,
form,
defaultParams: [
{
current: 2,
pageSize: 10,
},
{ name: 'hello', phone: '456' },
],
defaultType: 'advance',
});
const { search } = hook.result.current;
expect(hook.result.current.tableProps.loading).toBe(false);
await waitFor(() => expect(queryArgs.current).toBe(2));
expect(queryArgs.pageSize).toBe(10);
expect(queryArgs.name).toBe('hello');
expect(queryArgs.phone).toBe('456');
expect(search.type).toBe('advance');
});
test('should antd v3 work', async () => {
queryArgs = undefined;
form.resetFields();
changeSearchType('simple');
const v3Form = {
...form,
getInternalHooks: undefined,
validateFields: function (fields: any[], callback: (arg0: undefined, arg1: {}) => void) {
const targetFields: Record<string | number, any> = {};
fields.forEach((field: string | number) => {
targetFields[field] = (this.fieldsValue as any)[field];
});
callback(undefined, targetFields);
},
getFieldInstance(key: string) {
// 根据不同的 type 返回不同的 fieldsValues
if (searchType === 'simple') {
return ['name'].includes(key) as any;
}
return ['name', 'email', 'phone'].includes(key) as any;
},
};
act(() => {
hook = setUp(asyncFn, { form: v3Form });
});
const { search } = hook.result.current;
expect(hook.result.current.tableProps.loading).toBe(false);
await waitFor(() => expect(queryArgs.current).toBe(1));
expect(queryArgs.pageSize).toBe(10);
expect(queryArgs.name).toBe('default name');
expect(search.type).toBe('simple');
// /* 切换 分页 */
act(() => {
hook.result.current.tableProps.onChange({
current: 2,
pageSize: 5,
});
});
await waitFor(() => expect(queryArgs.current).toBe(2));
expect(queryArgs.pageSize).toBe(5);
expect(queryArgs.name).toBe('default name');
/* 改变 name,提交表单 */
v3Form.fieldsValue.name = 'change name';
act(() => {
search.submit();
});
await waitFor(() => expect(queryArgs.current).toBe(1));
expect(queryArgs.current).toBe(1);
// expect(queryArgs.pageSize).toBe(5);
expect(queryArgs.name).toBe('change name');
});
test('should reset pageSize in defaultParams', async () => {
queryArgs = undefined;
form.resetFields();
act(() => {
hook = setUp(asyncFn, {
form,
defaultParams: [
{
current: 1,
pageSize: 10,
},
],
});
});
const { search, tableProps } = hook.result.current;
expect(tableProps.loading).toBe(false);
await waitFor(() => expect(queryArgs.current).toBe(1));
expect(queryArgs.pageSize).toBe(10);
// change params
act(() => {
tableProps.onChange({
current: 2,
pageSize: 5,
});
});
await waitFor(() => {
expect(queryArgs.current).toBe(2);
expect(queryArgs.pageSize).toBe(5);
});
// reset params
act(() => {
search.reset();
});
await waitFor(() => {
expect(queryArgs.current).toBe(1);
expect(queryArgs.pageSize).toBe(10);
});
});
test('should reset pageSize in defaultPageSize', async () => {
queryArgs = undefined;
form.resetFields();
act(() => {
hook = setUp(asyncFn, {
form,
defaultParams: {
current: 1,
pageSize: 10,
} as any,
defaultPageSize: 20,
});
});
const { search, tableProps } = hook.result.current;
expect(tableProps.loading).toBe(false);
await waitFor(() => expect(queryArgs.current).toBe(1));
expect(queryArgs.pageSize).toBe(20);
// change params
act(() => {
tableProps.onChange({
current: 2,
pageSize: 5,
});
});
await waitFor(() => {
expect(queryArgs.current).toBe(2);
expect(queryArgs.pageSize).toBe(5);
});
// reset params
act(() => {
search.reset();
});
await waitFor(() => {
expect(queryArgs.current).toBe(1);
expect(queryArgs.pageSize).toBe(20);
});
});
test('search submit use default params', async () => {
queryArgs = undefined;
form.resetFields();
act(() => {
hook = setUp(asyncFn, {
form,
defaultParams: [
{
current: 2,
pageSize: 100,
},
],
});
});
const { search } = hook.result.current;
act(() => {
search.submit();
});
await waitFor(() => {
expect(queryArgs.current).toBe(2);
expect(queryArgs.pageSize).toBe(100);
});
});
test('should defaultParams work with manual is true', async () => {
queryArgs = undefined;
form.resetFields();
changeSearchType('advance');
act(() => {
renderHook((o) => {
const [myForm] = Form.useForm();
useAntdTable(
asyncFn,
o || {
form: myForm,
defaultParams: [
{
current: 2,
pageSize: 10,
},
{ name: 'hello', phone: '123' },
],
defaultType: 'advance',
},
);
useEffect(() => {
// defaultParams works
expect(myForm.getFieldValue('name')).toBe('hello');
expect(queryArgs).toBe(undefined);
}, []);
});
});
});
});
================================================
FILE: packages/hooks/src/useAntdTable/demo/cache.tsx
================================================
import { useState } from 'react';
import { Button, Col, Form, Input, Row, Table, Select } from 'antd';
import { useAntdTable, clearCache } from 'ahooks';
import ReactJson from 'react-json-view';
const { Option } = Select;
interface Item {
name: {
last: string;
};
email: string;
phone: string;
gender: 'male' | 'female';
}
interface Result {
total: number;
list: Item[];
}
const getTableData = (
{
current,
pageSize,
sorter,
filters,
extra,
}: {
current: number;
pageSize: number;
sorter: any;
filters: any;
extra: any;
},
formData: Record<string, any>,
): Promise<Result> => {
console.log(sorter, filters, extra);
let query = `page=${current}&size=${pageSize}`;
Object.entries(formData).forEach(([key, value]) => {
if (value) {
query += `&${key}=${value}`;
}
});
return fetch(`https://randomuser.me/api?results=55&${query}`)
.then((res) => res.json())
.then((res) => ({
total: res.info.results,
list: res.results,
}));
};
const UserList = () => {
const [form] = Form.useForm();
const { tableProps, search, params } = useAntdTable(getTableData, {
defaultPageSize: 5,
form,
cacheKey: 'useAntdTableCache',
});
const { sorter = {}, filters = {} } = params[0] || ({} as any);
const { type, changeType, submit, reset } = search;
const columns = [
{
title: 'name',
dataIndex: ['name', 'last'],
},
{
title: 'email',
dataIndex: 'email',
},
{
title: 'phone',
dataIndex: 'phone',
sorter: true,
sortOrder: sorter.field === 'phone' && sorter.order,
},
{
title: 'gender',
dataIndex: 'gender',
filters: [
{ text: 'male', value: 'male' },
{ text: 'female', value: 'female' },
],
filteredValue: filters.gender,
},
];
const advanceSearchForm = (
<div>
<Form form={form}>
<Row gutter={24}>
<Col span={8}>
<Form.Item label="name" name="name">
<Input placeholder="name" />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="email" name="email">
<Input placeholder="email" />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="phone" name="phone">
<Input placeholder="phone" />
</Form.Item>
</Col>
</Row>
<Row gutter={24} justify="end" style={{ marginBottom: 24 }}>
<Button type="primary" onClick={submit}>
Search
</Button>
<Button onClick={reset} style={{ marginLeft: 16 }}>
Reset
</Button>
<Button type="link" onClick={changeType}>
Simple Search
</Button>
</Row>
</Form>
</div>
);
const searchForm = (
<div style={{ marginBottom: 16 }}>
<Form form={form} style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Form.Item name="gender" initialValue="male">
<Select style={{ width: 120, marginRight: 16 }} onChange={submit}>
<Option value="">all</Option>
<Option value="male">male</Option>
<Option value="female">female</Option>
</Select>
</Form.Item>
<Form.Item name="name">
<Input.Search placeholder="enter name" style={{ width: 240 }} onSearch={submit} />
</Form.Item>
<Button type="link" onClick={changeType}>
Advanced Search
</Button>
</Form>
</div>
);
return (
<div>
{type === 'simple' ? searchForm : advanceSearchForm}
<Table columns={columns} rowKey="email" style={{ overflow: 'auto' }} {...tableProps} />
<div style={{ background: '#f5f5f5', padding: 8 }}>
<p>Current Table:</p>
<ReactJson src={params[0]!} collapsed={2} />
<p>Current Form:</p>
<ReactJson src={params[0]!} collapsed={2} />
</div>
</div>
);
};
const Demo = () => {
const [show, setShow] = useState(true);
return (
<div>
<Button
danger
onClick={() => {
setShow(!show);
}}
style={{ marginBottom: 16 }}
>
{show ? 'Click to destroy' : 'Click recovery'}
</Button>
<Button
danger
onClick={() => {
clearCache('useAntdTableCache');
}}
style={{ marginBottom: 16, marginLeft: 8 }}
>
Click to clearCache
</Button>
{show && <UserList />}
</div>
);
};
export default Demo;
================================================
FILE: packages/hooks/src/useAntdTable/demo/form.tsx
================================================
import { Button, Col, Form, Input, Row, Table, Select } from 'antd';
import { useAntdTable } from 'ahooks';
import ReactJson from 'react-json-view';
const { Option } = Select;
interface Item {
name: {
last: string;
};
email: string;
phone: string;
gender: 'male' | 'female';
}
interface Result {
total: number;
list: Item[];
}
const getTableData = (
{
current,
pageSize,
}: {
current: number;
pageSize: number;
},
formData: Object,
): Promise<Result> => {
let query = `page=${current}&size=${pageSize}`;
Object.entries(formData).forEach(([key, value]) => {
if (value) {
query += `&${key}=${value}`;
}
});
return fetch(`https://randomuser.me/api?results=55&${query}`)
.then((res) => res.json())
.then((res) => ({
total: res.info.results,
list: res.results,
}));
};
export default () => {
const [form] = Form.useForm();
const { tableProps, search, params } = useAntdTable(getTableData, {
defaultPageSize: 5,
form,
});
const { type, changeType, submit, reset } = search;
const columns = [
{
title: 'name',
dataIndex: ['name', 'last'],
},
{
title: 'email',
dataIndex: 'email',
},
{
title: 'phone',
dataIndex: 'phone',
},
{
title: 'gender',
dataIndex: 'gender',
},
];
const advanceSearchForm = (
<div>
<Form form={form}>
<Row gutter={24}>
<Col span={8}>
<Form.Item label="name" name="name">
<Input placeholder="name" />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="email" name="email">
<Input placeholder="email" />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="phone" name="phone">
<Input placeholder="phone" />
</Form.Item>
</Col>
</Row>
<Row gutter={24} justify="end" style={{ marginBottom: 24 }}>
<Button type="primary" onClick={submit}>
Search
</Button>
<Button onClick={reset} style={{ marginLeft: 16 }}>
Reset
</Button>
<Button type="link" onClick={changeType}>
Simple Search
</Button>
</Row>
</Form>
</div>
);
const searchForm = (
<div style={{ marginBottom: 16 }}>
<Form form={form} style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Form.Item name="gender" initialValue="male">
<Select style={{ width: 120, marginRight: 16 }} onChange={submit}>
<Option value="">all</Option>
<Option value="male">male</Option>
<Option value="female">female</Option>
</Select>
</Form.Item>
<Form.Item name="name">
<Input.Search placeholder="enter name" style={{ width: 240 }} onSearch={submit} />
</Form.Item>
<Button type="link" onClick={changeType}>
Advanced Search
</Button>
</Form>
</div>
);
return (
<div>
{type === 'simple' ? searchForm : advanceSearchForm}
<Table columns={columns} rowKey="email" style={{ overflow: 'auto' }} {...tableProps} />
<div style={{ background: '#f5f5f5', padding: 8 }}>
<p>Current Table:</p>
<ReactJson src={params[0]!} collapsed={2} />
<p>Current Form:</p>
<ReactJson src={params[1]!} collapsed={2} />
</div>
</div>
);
};
================================================
FILE: packages/hooks/src/useAntdTable/demo/init.tsx
================================================
import { Button, Col, Form, Input, Row, Table, Select } from 'antd';
import { useAntdTable } from 'ahooks';
import ReactJson from 'react-json-view';
const { Option } = Select;
interface Item {
name: {
last: string;
};
email: string;
phone: string;
gender: 'male' | 'female';
}
interface Result {
total: number;
list: Item[];
}
const getTableData = (
{
current,
pageSize,
}: {
current: number;
pageSize: number;
},
formData: Object,
): Promise<Result> => {
let query = `page=${current}&size=${pageSize}`;
Object.entries(formData).forEach(([key, value]) => {
if (value) {
query += `&${key}=${value}`;
}
});
return fetch(`https://randomuser.me/api?results=55&${query}`)
.then((res) => res.json())
.then((res) => ({
total: res.info.results,
list: res.results,
}));
};
export default () => {
const [form] = Form.useForm();
const { tableProps, search, params } = useAntdTable(getTableData, {
form,
defaultParams: [
{ current: 2, pageSize: 5 },
{ name: 'hello', email: 'abc@gmail.com', gender: 'female' },
],
defaultType: 'advance',
});
const { type, changeType, submit, reset } = search;
const columns = [
{
title: 'name',
dataIndex: ['name', 'last'],
},
{
title: 'email',
dataIndex: 'email',
},
{
title: 'phone',
dataIndex: 'phone',
},
{
title: 'gender',
dataIndex: 'gender',
},
];
const advanceSearchForm = (
<div>
<Form form={form}>
<Row gutter={24}>
<Col span={8}>
<Form.Item label="name" name="name">
<Input placeholder="name" />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="email" name="email">
<Input placeholder="email" />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="phone" name="phone">
<Input placeholder="phone" />
</Form.Item>
</Col>
</Row>
<Row gutter={24} justify="end" style={{ marginBottom: 24 }}>
<Button type="primary" onClick={submit}>
Search
</Button>
<Button onClick={reset} style={{ marginLeft: 16 }}>
Reset
</Button>
<Button type="link" onClick={changeType}>
Simple Search
</Button>
</Row>
</Form>
</div>
);
const searchForm = (
<div style={{ marginBottom: 16 }}>
<Form form={form} style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Form.Item name="gender" initialValue="male">
<Select style={{ width: 120, marginRight: 16 }} onChange={submit}>
<Option value="">all</Option>
<Option value="male">male</Option>
<Option value="female">female</Option>
</Select>
</Form.Item>
<Form.Item name="name">
<Input.Search placeholder="enter name" style={{ width: 240 }} onSearch={submit} />
</Form.Item>
<Button type="link" onClick={changeType}>
Advanced Search
</Button>
</Form>
</div>
);
return (
<div>
{type === 'simple' ? searchForm : advanceSearchForm}
<Table columns={columns} rowKey="email" style={{ overflow: 'auto' }} {...tableProps} />
<div style={{ background: '#f5f5f5', padding: 8 }}>
<p>Current Table:</p>
<ReactJson src={params[0]!} collapsed={2} />
<p>Current Form:</p>
<ReactJson src={params[1]!} collapsed={2} />
</div>
</div>
);
};
================================================
FILE: packages/hooks/src/useAntdTable/demo/ready.tsx
================================================
import { useState } from 'react';
import { Button, Col, Form, Input, Row, Table, Select } from 'antd';
import { useAntdTable } from 'ahooks';
import ReactJson from 'react-json-view';
const { Option } = Select;
interface Item {
name: {
last: string;
};
email: string;
phone: string;
gender: 'male' | 'female';
}
interface Result {
total: number;
list: Item[];
}
const getTableData = (
{
current,
pageSize,
}: {
current: number;
pageSize: number;
},
formData: Object,
): Promise<Result> => {
let query = `page=${current}&size=${pageSize}`;
Object.entries(formData).forEach(([key, value]) => {
if (value) {
query += `&${key}=${value}`;
}
});
return fetch(`https://randomuser.me/api?results=55&${query}`)
.then((res) => res.json())
.then((res) => ({
total: res.info.results,
list: res.results,
}));
};
export default () => {
const [form] = Form.useForm();
const [ready, setReady] = useState(false);
const { tableProps, search, params } = useAntdTable(getTableData, {
form,
ready,
cacheKey: 'demo-ready',
defaultParams: [
{ current: ready ? 2 : 1, pageSize: 5 },
{ name: ready ? 'hello' : '', email: 'abc@gmail.com', gender: 'female' },
],
defaultType: 'advance',
});
const { type, changeType, submit, reset } = search;
const columns = [
{
title: 'name',
dataIndex: ['name', 'last'],
},
{
title: 'email',
dataIndex: 'email',
},
{
title: 'phone',
dataIndex: 'phone',
},
{
title: 'gender',
dataIndex: 'gender',
},
];
const advanceSearchForm = (
<div>
<Form form={form}>
<Row gutter={24}>
<Col span={8}>
<Form.Item label="name" name="name">
<Input placeholder="name" />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="email" name="email">
<Input placeholder="email" />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label="phone" name="phone">
<Input placeholder="phone" />
</Form.Item>
</Col>
</Row>
<Row gutter={24} justify="end" style={{ marginBottom: 24 }}>
<Button type="primary" onClick={submit}>
Search
</Button>
<Button onClick={reset} style={{ marginLeft: 16 }}>
Reset
</Button>
<Button type="link" onClick={changeType}>
Simple Search
</Button>
</Row>
</Form>
</div>
);
const searchForm = (
<div style={{ marginBottom: 16 }}>
<Form form={form} style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Form.Item name="gender" initialValue="male">
<Select style={{ width: 120, marginRight: 16 }} onChange={submit}>
<Option value="">all</Option>
<Option value="male">male</Option>
<Option value="female">female</Option>
</Select>
</Form.Item>
<Form.Item name="name">
<Input.Search placeholder="enter name" style={{ width: 240 }} onSearch={submit} />
</Form.Item>
<Button type="link" onClick={changeType}>
Advanced Search
</Button>
</Form>
</div>
);
return (
<div>
<Button onClick={() => setReady((r) => !r)}>toggle ready</Button>
{type === 'simple' ? searchForm : advanceSearchForm}
<Table columns={columns} rowKey="email" style={{ overflow: 'auto' }} {...tableProps} />
<div style={{ background: '#f5f5f5', padding: 8 }}>
<p>Current Table:</p>
<ReactJson src={params[0]!} collapsed={2} />
<p>Current Form:</p>
<ReactJson src={params[1]!} collapsed={2} />
</div>
</div>
);
};
================================================
FILE: packages/hooks/src/useAntdTable/demo/table.tsx
================================================
import { Table } from 'antd';
import { useAntdTable } from 'ahooks';
interface Item {
name: {
last: string;
};
email: string;
phone: string;
gender: 'male' | 'female';
}
interface Result {
total: number;
list: Item[];
}
const getTableData = ({
current,
pageSize,
}: {
current: number;
pageSize: number;
}): Promise<Result> => {
const query = `page=${current}&size=${pageSize}`;
return fetch(`https://randomuser.me/api?results=55&${query}`)
.then((res) => res.json())
.then((res) => ({
total: res.info.results,
list: res.results,
}));
};
export default () => {
const { tableProps } = useAntdTable(getTableData);
const columns = [
{
title: 'name',
dataIndex: ['name', 'last'],
},
{
title: 'email',
dataIndex: 'email',
},
{
title: 'phone',
dataIndex: 'phone',
},
{
title: 'gender',
dataIndex: 'gender',
},
];
return <Table columns={columns} rowKey="email" style={{ overflow: 'auto' }} {...tableProps} />;
};
================================================
FILE: packages/hooks/src/useAntdTable/demo/validate.tsx
================================================
import { Form, Input, Select, Table } from 'antd';
import { useAntdTable } from 'ahooks';
import ReactJson from 'react-json-view';
const { Option } = Select;
interface Item {
name: {
last: string;
};
email: string;
phone: string;
gender: 'male' | 'female';
}
interface Result {
total: number;
list: Item[];
}
const getTableData = (
{
current,
pageSize,
}: {
current: number;
pageSize: number;
},
formData: Object,
): Promise<Result> => {
let query = `page=${current}&size=${pageSize}`;
Object.entries(formData).forEach(([key, value]) => {
if (value) {
query += `&${key}=${value}`;
}
});
return fetch(`https://randomuser.me/api?results=55&${query}`)
.then((res) => res.json())
.then((res) => ({
total: res.info.results,
list: res.results,
}));
};
export default () => {
const [form] = Form.useForm();
const { tableProps, search, params } = useAntdTable(getTableData, {
defaultPageSize: 5,
form,
});
const { submit } = search;
const columns = [
{
title: 'name',
dataIndex: ['name', 'last'],
},
{
title: 'email',
dataIndex: 'email',
},
{
title: 'phone',
dataIndex: 'phone',
},
{
title: 'gender',
dataIndex: 'gender',
},
];
const searchForm = (
<div style={{ marginBottom: 16 }}>
<Form form={form} style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Form.Item name="gender" initialValue="male">
<Select style={{ width: 120, marginRight: 16 }} onChange={submit}>
<Option value="">all</Option>
<Option value="male">male</Option>
<Option value="female">female</Option>
</Select>
</Form.Item>
<Form.Item
name="name"
initialValue="jack"
rules={[{ required: true, message: 'name is required' }]}
>
<Input.Search placeholder="enter name" style={{ width: 240 }} onSearch={submit} />
</Form.Item>
</Form>
</div>
);
return (
<div>
{searchForm}
<Table columns={columns} rowKey="email" style={{ overflow: 'auto' }} {...tableProps} />
<div style={{ background: '#f5f5f5', padding: 8 }}>
<p>Current Table:</p>
<ReactJson src={params[0]!} collapsed={2} />
<p>Current Form:</p>
<ReactJson src={params[1]!} collapsed={2} />
</div>
</div>
);
};
================================================
FILE: packages/hooks/src/useAntdTable/index.en-US.md
================================================
---
nav:
path: /hooks
---
# useAntdTable
`useAntdTable` is implemented based on `useRequest` and encapsulates the commonly used [Ant Design Form](https://ant.design/components/form/) and [Ant Design Table](https://ant.design/components/table/) data binding logic, and supports both antd v3 and v4.
Before using it, you need to understand a few points that are different from `useRequest`:
1. `service` receives two parameters, the first parameter is the paging data `{ current, pageSize, sorter, filters, extra }`, and the second parameter is the form data.
2. The data structure returned by `service` must be `{ total: number, list: Item[] }`.
3. Additional `tableProps` and `search` fields will be returned to manage tables and forms.
4. When `refreshDeps` changes, it will reset `current` to the first page and re-initiate the request.
## Examples
The following demos are for antd v4. For v3, please refer to: https://ahooks-v2.js.org/hooks/table/use-antd-table
### Table management
`useAntdTable` will automatically manage the pagination data of `Table`, you only need to pass the returned `tableProps` to the `Table` component.
```tsx | pure
<Table columns={columns} rowKey="email" {...tableProps} />
```
<br />
<code src="./demo/table.tsx" />
### Form and Table data binding
When `useAntdTable` receives the `form` instance, it will return a search object to handle form related events.
- `search.type` supports switching between `simple` and `advance`
- `search.changeType`, switch form type
- `search.submit` submit form
- `search.reset` reset the current form
In the following example, you can try out the data binding between form and table.
<code src="./demo/form.tsx" />
### Default Params
`useAntdTable` sets the initial value through `defaultParams`, `defaultParams` is an array, the first item is paging related parameters, and the second item is form related data. If there is a second value, we will initialize the form for you!
It should be noted that the initial form data can be filled with all the form data of `simple` and `advance`, and we will help you select the form data of the currently activated type.
The following example sets paging data and form data during initialization.
<code src="./demo/init.tsx" />
### Form Validation
Before the form is submitted, we will call `form.validateFields` to validate the form data. If the verification fails, the request will not be initiated.
<code src="./demo/validate.tsx" />
### Data Caching
By setting `cacheKey`, we can apply the data caching for the `Form` and `Table`.
<code src="./demo/cache.tsx" />
## API
All parameters and returned results of `useRequest` are applicable to `useAntdTable`, so we won't repeat them here.
```typescript
type Data = { total: number; list: any[] };
type Params = [{ current: number; pageSize: number, filters?: any, sorter?: any, extra?: any }, { [key: string]: any }];
const {
...,
tableProps: {
dataSource: TData['list'];
loading: boolean;
onChange: (
pagination: any,
filters?: any,
sorter?: any,
extra?: any,
) => void;
pagination: {
current: number;
pageSize: number;
total: number;
};
};
search: {
type: 'simple' | 'advance';
changeType: () => void;
submit: () => void;
reset: () => void;
};
} = useAntdTable<TData extends Data, TParams extends Params>(
service: (...args: TParams) => Promise<TData>,
{
...,
form?: any;
defaultType?: 'simple' | 'advance';
defaultParams?: TParams,
defaultPageSize?: number;
refreshDeps?: any[];
}
);
```
### Result
| Property | Description | Type |
| ----------------- | ------------------------------------------ | --------------------- |
| tableProps | The data required by the `Table` component | - |
| search.type | Current form type | `simple` \| `advance` |
| search.changeType | Switch form type | `() => void` |
| search.submit | Submit form | `() => void` |
| search.reset | Reset the current form | `() => void` |
### Params
| Property | Description | Type | Default |
| --------------- | ------------------------------------------------------------------------------------------ | ------------------------ | -------- |
| form | `Form` instance | - | - |
| defaultType | Default form type | `simple` \| `advance` | `simple` |
| defaultParams | Default parameters, the first item is paging data, the second item is form data | `[pagination, formData]` | - |
| defaultPageSize | Default page size | `number` | `10` |
| refreshDeps | Changes in `refreshDeps` will reset current to the first page and re-initiate the request. | `React.DependencyList` | `[]` |
================================================
FILE: packages/hooks/src/useAntdTable/index.tsx
================================================
import { useEffect, useRef, useState } from 'react';
import useMemoizedFn from '../useMemoizedFn';
import usePagination from '../usePagination';
import useUpdateEffect from '../useUpdateEffect';
import type {
Antd4ValidateFields,
AntdTableOptions,
Data,
Params,
Service,
AntdTableResult,
} from './types';
const useAntdTable = <TData extends Data, TParams extends Params>(
service: Service<TData, TParams>,
options: AntdTableOptions<TData, TParams> = {},
) => {
const {
form,
defaultType = 'simple',
defaultParams,
manual = false,
refreshDeps = [],
ready = true,
...rest
} = options;
const result = usePagination<TData, TParams>(service, {
ready,
manual: true,
...rest,
onSuccess(...args) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
runSuccessRef.current = true;
rest.onSuccess?.(...args);
},
});
const { params = [], run } = result;
const cacheFormTableData = params[2] || ({} as any);
const [type, setType] = useState(cacheFormTableData?.type || defaultType);
const allFormDataRef = useRef<Record<string, any>>({});
const defaultDataSourceRef = useRef([]);
const runSuccessRef = useRef(false);
const isAntdV4 = !!form?.getInternalHooks;
// get current active field values
const getActiveFieldValues = () => {
if (!form) {
return {};
}
// antd 4
if (isAntdV4) {
return form.getFieldsValue(null, () => true);
}
// antd 3
const allFieldsValue = form.getFieldsValue();
const activeFieldsValue: Record<string | number, any> = {};
Object.keys(allFieldsValue).forEach((key: string) => {
if (form.getFieldInstance ? form.getFieldInstance(key) : true) {
activeFieldsValue[key] = allFieldsValue[key];
}
});
return activeFieldsValue;
};
const validateFields = (): Promise<Record<string, any>> => {
if (!form) {
return Promise.resolve({});
}
const activeFieldsValue = getActiveFieldValues();
const fields = Object.keys(activeFieldsValue);
// antd 4
if (isAntdV4) {
return (form.validateFields as Antd4ValidateFields)(fields);
}
// antd 3
return new Promise((resolve, reject) => {
form.validateFields(fields, (errors, values) => {
if (errors) {
reject(errors);
} else {
resolve(values);
}
});
});
};
const restoreForm = () => {
if (!form) {
return;
}
// antd v4
if (isAntdV4) {
return form.setFieldsValue(allFormDataRef.current);
}
// antd v3
const activeFieldsValue: Record<string | number, any> = {};
Object.keys(allFormDataRef.current).forEach((key) => {
if (form.getFieldInstance ? form.getFieldInstance(key) : true) {
activeFieldsValue[key] = allFormDataRef.current[key];
}
});
form.setFieldsValue(activeFieldsValue);
};
const changeType = () => {
const activeFieldsValue = getActiveFieldValues();
allFormDataRef.current = {
...allFormDataRef.current,
...activeFieldsValue,
};
setType((t: string) => (t === 'simple' ? 'advance' : 'simple'));
};
const _submit = (initPagination?: TParams[0]) => {
if (!ready) {
return;
}
setTimeout(() => {
validateFields()
.then((values = {}) => {
const pagination = initPagination || {
pageSize: options.defaultPageSize || 10,
...(params?.[0] || {}),
current: 1,
};
if (!form) {
// @ts-ignore
run(pagination);
return;
}
// record all form data
allFormDataRef.current = {
...allFormDataRef.current,
...values,
};
// @ts-ignore
run(pagination, values, {
allFormData: allFormDataRef.current,
type,
});
})
.catch((err) => err);
});
};
const reset = () => {
if (form) {
form.resetFields();
}
_submit({
...(defaultParams?.[0] || {}),
pageSize: options.defaultPageSize || options.defaultParams?.[0]?.pageSize || 10,
current: 1,
});
};
const submit = (e?: any) => {
e?.preventDefault?.();
_submit(
runSuccessRef.current
? undefined
: {
pageSize: options.defaultPageSize || options.defaultParams?.[0]?.pageSize || 10,
current: 1,
...(defaultParams?.[0] || {}),
},
);
};
const onTableChange = (pagination: any, filters: any, sorter: any, extra: any) => {
const [oldPaginationParams, ...restParams] = params || [];
run(
// @ts-ignore
{
...oldPaginationParams,
current: pagination.current,
pageSize: pagination.pageSize,
filters,
sorter,
extra,
},
...restParams,
);
};
// init
useEffect(() => {
// if has cache, use cached params. ignore manual and ready.
if (params.length > 0) {
allFormDataRef.current = cacheFormTableData?.allFormData || {};
restoreForm();
// @ts-ignore
run(...params);
return;
}
if (ready) {
allFormDataRef.current = defaultParams?.[1] || {};
restoreForm();
if (!manual) {
_submit(defaultParams?.[0]);
}
}
}, []);
// change search type, restore form data
useUpdateEffect(() => {
if (!ready) {
return;
}
restoreForm();
}, [type]);
// refresh & ready change on the same time
const hasAutoRun = useRef(false);
hasAutoRun.current = false;
useUpdateEffect(() => {
if (!manual && ready) {
hasAutoRun.current = true;
if (form) {
form.resetFields();
}
allFormDataRef.current = defaultParams?.[1] || {};
restoreForm();
_submit(defaultParams?.[0]);
}
}, [ready]);
useUpdateEffect(() => {
if (hasAutoRun.current) {
return;
}
if (!ready) {
return;
}
if (!manual) {
hasAutoRun.current = true;
if (options.refreshDepsAction) {
options.refreshDepsAction();
} else {
result.pagination.changeCurrent(1);
}
}
}, [...refreshDeps]);
return {
...result,
tableProps: {
dataSource: result.data?.list || defaultDataSourceRef.current,
loading: result.loading,
onChange: useMemoizedFn(onTableChange),
pagination: {
current: result.pagination.current,
pageSize: result.pagination.pageSize,
total: result.pagination.total,
},
},
search: {
submit: useMemoizedFn(submit),
type,
changeType: useMemoizedFn(changeType),
reset: useMemoizedFn(reset),
},
} as AntdTableResult<TData, TParams>;
};
export default useAntdTable;
================================================
FILE: packages/hooks/src/useAntdTable/index.zh-CN.md
================================================
---
nav:
title: Hooks
path: /hooks
---
# useAntdTable
`useAntdTable` 基于 `useRequest` 实现,封装了常用的 [Ant Design Form](https://ant.design/components/form-cn/) 与 [Ant Design Table](https://ant.design/components/table-cn/) 联动逻辑,并且同时支持 antd v3 和 v4。
在使用之前,你需要了解它与 `useRequest` 不同的几个点:
1. `service` 接收两个参数,第一个参数为分页数据 `{ current, pageSize, sorter, filters, extra }`,第二个参数为表单数据。
2. `service` 返回的数据结构为 `{ total: number, list: Item[] }`。
3. 会额外返回 `tableProps` 和 `search` 字段,管理表格和表单。
4. `refreshDeps` 变化,会重置 `current` 到第一页,并重新发起请求。
## 代码演示
以下展示的是 antd v4 的 demo,v3 请参考:https://ahooks-v2.js.org/hooks/table/use-antd-table
### Table 管理
`useAntdTable` 会自动管理 `Table` 分页数据,你只需要把返回的 `tableProps` 传递给 `Table` 组件就可以了。
```tsx | pure
<Table columns={columns} rowKey="email" {...tableProps} />
```
<br />
<code src="./demo/table.tsx" />
### Form 与 Table 联动
`useAntdTable` 接收 `form` 实例后,会返回 search 对象,用来处理表单相关事件。
- `search.type` 支持 `simple` 和 `advance` 两个表单切换
- `search.changeType`,切换表单类型
- `search.submit` 提交表单行为
- `search.reset` 重置当前表单
以下示例你可以体验表单与表格联动。
<code src="./demo/form.tsx" />
### 初始化数据
`useAntdTable` 通过 `defaultParams` 设置初始化值,`defaultParams` 是一个数组,第一项为分页相关参数,第二项为表单相关数据。如果有第二个值,我们会帮您初始化表单!
需要注意的是,初始化的表单数据可以填写 `simple` 和 `advance` 全量的表单数据,我们会帮您挑选当前激活的类型中的表单数据。
以下示例在初始化时设置了分页数据和表单数据。
<code src="./demo/init.tsx" />
### 表单验证
表单提交之前,我们会调用 `form.validateFields` 来校验表单数据,如果验证不通过,则不会发起请求。
<code src="./demo/validate.tsx" />
### 缓存
通过设置 `cacheKey`,我们可以实现 `Form` 与 `Table` 数据缓存。
<code src="./demo/cache.tsx" />
## API
`useRequest` 所有参数和返回结果均适用于 `useAntdTable`,此处不再赘述。
```typescript
type Data = { total: number; list: any[] };
type Params = [{ current: number; pageSize: number, filters?: any, sorter?: any, extra?: any }, { [key: string]: any }];
const {
...,
tableProps: {
dataSource: TData['list'];
loading: boolean;
onChange: (
pagination: any,
filters?: any,
sorter?: any,
extra?: any,
) => void;
pagination: {
current: number;
pageSize: number;
total: number;
};
};
search: {
type: 'simple' | 'advance';
changeType: () => void;
submit: () => void;
reset: () => void;
};
} = useAntdTable<TData extends Data, TParams extends Params>(
service: (...args: TParams) => Promise<TData>,
{
...,
form?: any;
defaultType?: 'simple' | 'advance';
defaultParams?: TParams,
defaultPageSize?: number;
refreshDeps?: any[];
}
);
```
### Result
| 参数 | 说明 | 类型 |
| ----------------- | --------------------------------------------------- | --------------------- |
| tableProps | `Table` 组件需要的数据,直接透传给 `Table` 组件即可 | - |
| search.type | 当前表单类型 | `simple` \| `advance` |
| search.changeType | 切换表单类型 | `() => void` |
| search.submit | 提交表单 | `() => void` |
| search.reset | 重置当前表单 | `() => void` |
### Params
| 参数 | 说明 | 类型 | 默认值 |
| --------------- | ------------------------------------------------------------- | ------------------------ | -------- |
| form | `Form` 实例 | - | - |
| defaultType | 默认表单类型 | `simple` \| `advance` | `simple` |
| defaultParams | 默认参数,第一项为分页数据,第二项为表单数据 | `[pagination, formData]` | - |
| defaultPageSize | 默认分页数量 | `number` | `10` |
| refreshDeps | `refreshDeps` 变化,会重置 current 到第一页,并重新发起请求。 | `React.DependencyList` | `[]` |
================================================
FILE: packages/hooks/src/useAntdTable/types.ts
================================================
import type { PaginationOptions, PaginationResult } from '../usePagination/types';
export type Data = { total: number; list: any[] };
export type Params = [
{
current: number;
pageSize: number;
sorter?: any;
filters?: any;
extra?: any;
[key: string]: any;
},
...any[],
];
export type Service<TData extends Data, TParams extends Params> = (
...args: TParams
) => Promise<TData>;
export type Antd3ValidateFields = (
fieldNames: string[],
callback: (errors: any, values: Record<string, any>) => void,
) => void;
export type Antd4ValidateFields = (fieldNames?: string[]) => Promise<Record<string, any>>;
export interface AntdFormUtils {
getFieldInstance?: (name: string) => Record<string, any>;
setFieldsValue: (value: Record<string, any>) => void;
getFieldsValue: (...args: any) => Record<string, any>;
resetFields: (...args: any) => void;
validateFields: Antd3ValidateFields | Antd4ValidateFields;
getInternalHooks?: any;
[key: string]: any;
}
export interface AntdTableResult<TData extends Data, TParams extends Params>
extends PaginationResult<TData, TParams> {
tableProps: {
dataSource: TData['list'];
loading: boolean;
onChange: (pagination: any, filters?: any, sorter?: any) => void;
pagination: any;
[key: string]: any;
};
search: {
type: 'simple' | 'advance';
changeType: () => void;
submit: () => void;
reset: () => void;
};
}
export interface AntdTableOptions<TData extends Data, TParams extends Params>
extends PaginationOptions<TData, TParams> {
form?: AntdFormUtils;
defaultType?: 'simple' | 'advance';
}
================================================
FILE: packages/hooks/src/useAsyncEffect/__tests__/index.spec.ts
================================================
import { act, renderHook } from '@testing-library/react';
import { useState } from 'react';
import { describe, expect, test } from 'vitest';
import { sleep } from '../../utils/testingHelpers';
import useAsyncEffect from '../index';
describe('useAsyncEffect', () => {
test('should work without clean up', async () => {
const hook = renderHook(() => {
const [x, setX] = useState(0);
useAsyncEffect(async () => {
await sleep(100);
setX(1);
}, []);
return x;
});
expect(hook.result.current).toBe(0);
await act(async () => {
await sleep(150);
});
expect(hook.result.current).toBe(1);
});
test('should work with yield break', async () => {
const hook = renderHook(() => {
const [x, setX] = useState(1);
const [y, setY] = useState(0);
useAsyncEffect(
async function* () {
await sleep(100);
yield;
setY(x);
},
[x],
);
return {
y,
setX,
};
});
expect(hook.result.current.y).toBe(0);
await act(async () => {
await sleep(50);
hook.result.current.setX(2);
});
expect(hook.result.current.y).toBe(0);
await act(async () => {
await sleep(20);
});
expect(hook.result.current.y).toBe(0);
await act(async () => {
await sleep(50);
hook.result.current.setX(3);
});
expect(hook.result.current.y).toBe(0);
await act(async () => {
await sleep(80);
});
expect(hook.result.current.y).toBe(0);
await act(async () => {
await sleep(50);
});
expect(hook.result.current.y).toBe(3);
});
});
================================================
FILE: packages/hooks/src/useAsyncEffect/demo/demo1.tsx
================================================
/**
* title: Default usage
* desc: Do async check when component is mounted.
*
* title.zh-CN: 基础用法
* desc.zh-CN: 组件加载时进行异步的检查
*/
import { useAsyncEffect } from 'ahooks';
import { useState } from 'react';
function mockCheck(): Promise<boolean> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, 3000);
});
}
export default () => {
const [pass, setPass] = useState<boolean>();
useAsyncEffect(async () => {
setPass(await mockCheck());
}, []);
return (
<div>
{pass === undefined && 'Checking...'}
{pass === true && 'Check passed.'}
</div>
);
};
================================================
FILE: packages/hooks/src/useAsyncEffect/demo/demo2.tsx
================================================
/**
* title: Break off
* desc: Use `yield` to stop the execution when effect has been cleaned up.
*
* title.zh-CN: 中断执行
* desc.zh-CN: 通过 `yield` 语句可以增加一些检查点,如果发现当前 effect 已经被清理,会停止继续往下执行。
*/
import { useState } from 'react';
import { useAsyncEffect } from 'ahooks';
function mockCheck(val: string): Promise<boolean> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(val.length > 0);
}, 1000);
});
}
export default () => {
const [value, setValue] = useState('');
const [pass, setPass] = useState<boolean>();
useAsyncEffect(
async function* () {
setPass(undefined);
const result = await mockCheck(value);
yield; // Check whether the effect is still valid, if it is has been cleaned up, stop at here.
setPass(result);
},
[value],
);
return (
<div>
<input
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
<p>
{pass === null && 'Checking...'}
{pass === false && 'Check failed.'}
{pass === true && 'Check passed.'}
</p>
</div>
);
};
================================================
FILE: packages/hooks/src/useAsyncEffect/index.en-US.md
================================================
---
nav:
path: /hooks
---
# useAsyncEffect
useEffect support async function.
## 代码演示
### Default usage
<code src="./demo/demo1.tsx" />
### Break off
<code src="./demo/demo2.tsx" />
## API
```typescript
function useAsyncEffect(
effect: () => AsyncGenerator | Promise,
deps: DependencyList
);
```
================================================
FILE: packages/hooks/src/useAsyncEffect/index.ts
================================================
import type { DependencyList } from 'react';
import { useEffect } from 'react';
import { isFunction } from '../utils';
function isAsyncGenerator(
val: AsyncGenerator<void, void, void> | Promise<void>,
): val is AsyncGenerator<void, void, void> {
return isFunction((val as any)[Symbol.asyncIterator]);
}
function useAsyncEffect(
effect: () => AsyncGenerator<void, void, void> | Promise<void>,
deps?: DependencyList,
) {
useEffect(() => {
const e = effect();
let cancelled = false;
async function execute() {
if (isAsyncGenerator(e)) {
while (true) {
const result = await e.next();
if (result.done || cancelled) {
break;
}
}
} else {
await e;
}
}
execute();
return () => {
cancelled = true;
};
}, deps);
}
export default useAsyncEffect;
================================================
FILE: packages/hooks/src/useAsyncEffect/index.zh-CN.md
================================================
---
nav:
path: /hooks
---
# useAsyncEffect
useEffect 支持异步函数。
## 代码演示
### 基础用法
<code src="./demo/demo1.tsx" />
### 中断执行
<code src="./demo/demo2.tsx" />
## API
```typescript
function useAsyncEffect(
effect: () => AsyncGenerator | Promise,
deps: DependencyList
);
```
================================================
FILE: packages/hooks/src/useBoolean/__tests__/index.spec.ts
================================================
import { act, renderHook } from '@testing-library/react';
import { describe, expect, test } from 'vitest';
import useBoolean from '../index';
const setUp = (defaultValue?: boolean) => renderHook(() => useBoolean(defaultValue));
describe('useBoolean', () => {
test('test on methods', async () => {
const { result } = setUp();
expect(result.current[0]).toBe(false);
act(() => {
result.current[1].setTrue();
});
expect(result.current[0]).toBe(true);
act(() => {
result.current[1].setFalse();
});
expect(result.current[0]).toBe(false);
act(() => {
result.current[1].toggle();
});
expect(result.current[0]).toBe(true);
act(() => {
result.current[1].toggle();
});
expect(result.current[0]).toBe(false);
act(() => {
result.current[1].set(false);
});
expect(result.current[0]).toBe(false);
act(() => {
result.current[1].set(true);
});
expect(result.current[0]).toBe(true);
act(() => {
// @ts-ignore
result.current[1].set(0);
});
expect(result.current[0]).toBe(false);
act(() => {
// @ts-ignore
result.current[1].set('a');
});
expect(result.current[0]).toBe(true);
});
test('test on default value', () => {
const hook1 = setUp(true);
expect(hook1.result.current[0]).toBe(true);
const hook2 = setUp();
expect(hook2.result.current[0]).toBe(false);
// @ts-ignore
const hook3 = setUp(0);
expect(hook3.result.current[0]).toBe(false);
// @ts-ignore
const hook4 = setUp('');
expect(hook4.result.current[0]).toBe(false);
// @ts-ignore
const hook5 = setUp('hello');
expect(hook5.result.current[0]).toBe(true);
});
});
================================================
FILE: packages/hooks/src/useBoolean/demo/demo1.tsx
================================================
/**
* title: Basic usage
* desc: Toggle boolean, default value can be set optionally.
*
* title.zh-CN: 基础用法
* desc.zh-CN: 切换 boolean,可以接收默认值。
*/
import { useBoolean } from 'ahooks';
export default () => {
const [state, { toggle, setTrue, setFalse }] = useBoolean(true);
return (
<div>
<p>Effects:{JSON.stringify(state)}</p>
<p>
<button type="button" onClick={toggle}>
Toggle
</button>
<button type="button" onClick={setFalse} style={{ margin: '0 16px' }}>
Set false
</button>
<button type="button" onClick={setTrue}>
Set true
</button>
</p>
</div>
);
};
================================================
FILE: packages/hooks/src/useBoolean/index.en-US.md
================================================
---
nav:
path: /hooks
---
# useBoolean
A hook that elegantly manages boolean state.
## Examples
### Default usage
<code src="./demo/demo1.tsx" />
## API
```typescript
const [state, { toggle, set, setTrue, setFalse }] = useBoolean(
defaultValue?: boolean,
);
```
### Params
| Property | Description | Type | Default |
| ------------ | ----------------------------------------- | --------- | ------- |
| defaultValue | The default value of the state. Optional. | `boolean` | `false` |
### Result
| Property | Description | Type |
| -------- | -------------------------------------- | --------- |
| state | Current value | `boolean` |
| actions | A set of methods to update state value | `Actions` |
### Actions
| Property | Description | Type |
| -------- | -------------------- | -------------------------- |
| toggle | Toggle state | `() => void` |
| set | Set state | `(value: boolean) => void` |
| setTrue | Set state to `true` | `() => void` |
| setFalse | Set state to `false` | `() => void` |
================================================
FILE: packages/hooks/src/useBoolean/index.ts
================================================
import { useMemo } from 'react';
import useToggle from '../useToggle';
export interface Actions {
setTrue: () => void;
setFalse: () => void;
set: (value: boolean) => void;
toggle: () => void;
}
export default function useBoolean(defaultValue = false): [boolean, Actions] {
const [state, { toggle, set }] = useToggle(!!defaultValue);
const actions: Actions = useMemo(() => {
const setTrue = () => set(true);
const setFalse = () => set(false);
return {
toggle,
set: (v) => set(!!v),
setTrue,
setFalse,
};
}, []);
return [state, actions];
}
================================================
FILE: packages/hooks/src/useBoolean/index.zh-CN.md
================================================
---
nav:
path: /hooks
---
# useBoolean
优雅的管理 boolean 状态的 Hook。
## 代码演示
### 基础用法
<code src="./demo/demo1.tsx" />
## API
```typescript
const [state, { toggle, set, setTrue, setFalse }] = useBoolean(
defaultValue?: boolean,
);
```
### Params
| 参数 | 说明 | 类型 | 默认值 |
| ------------ | ------------------------ | --------- | ------- |
| defaultValue | 可选项,传入默认的状态值 | `boolean` | `false` |
### Result
| 参数 | 说明 | 类型 |
| ------- | -------- | --------- |
| state | 状态值 | `boolean` |
| actions | 操作集合 | `Actions` |
### Actions
| 参数 | 说明 | 类型 |
| -------- | ------------ | -------------------------- |
| toggle | 切换 state | `() => void` |
| set | 设置 state | `(value: boolean) => void` |
| setTrue | 设置为 true | `() => void` |
| setFalse | 设置为 false | `() => void` |
================================================
FILE: packages/hooks/src/useClickAway/__tests__/index.spec.ts
================================================
import { renderHook } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import useClickAway from '../index';
describe('useClickAway', () => {
let container: HTMLDivElement;
let container1: HTMLDivElement;
beforeEach(() => {
container = document.createElement('div');
container1 = document.createElement('div');
container1.setAttribute('id', 'ele');
document.body.appendChild(container);
document.body.appendChild(container1);
});
afterEach(() => {
document.body.removeChild(container);
document.body.removeChild(container1);
});
test('test on dom optional', async () => {
let state: number = 0;
const { rerender, unmount } = renderHook((dom: any) =>
useClickAway(() => {
state++;
}, dom),
);
rerender(container);
container.click();
expect(state).toBe(0);
document.body.click();
expect(state).toBe(1);
rerender(container1);
container1.click();
expect(state).toBe(1);
document.body.click();
expect(state).toBe(2);
unmount();
document.body.click();
expect(state).toBe(2);
});
test('should works on multiple target', async () => {
let state: number = 0;
const { rerender, unmount } = renderHook((dom: any) =>
useClickAway(() => {
state++;
}, dom),
);
rerender([container, container1]);
container.click();
expect(state).toBe(0);
container1.click();
expect(state).toBe(0);
document.body.click();
expect(state).toBe(1);
unmount();
document.body.click();
expect(state).toBe(1);
});
});
================================================
FILE: packages/hooks/src/useClickAway/demo/demo1.tsx
================================================
/**
* title: Default usage
* desc: Please click button or outside of button to show effects.
*
* title.zh-CN: 基础用法
* desc.zh-CN: 请点击按钮或按钮外查看效果。
*/
import { useState, useRef } from 'react';
import { useClickAway } from 'ahooks';
export default () => {
const [counter, setCounter] = useState(0);
const ref = useRef<HTMLButtonElement>(null);
useClickAway(() => {
setCounter((s) => s + 1);
}, ref);
return (
<div>
<button ref={ref} type="button">
box
</button>
<p>counter: {counter}</p>
</div>
);
};
================================================
FILE: packages/hooks/src/useClickAway/demo/demo2.tsx
================================================
/**
* title: Support DOM
* desc: Support pass in a DOM element or function.
*
* title.zh-CN: 支持传入 DOM
* desc.zh-CN: 支持直接传入 DOM 对象或 function。
*/
import { useState } from 'react';
import { useClickAway } from 'ahooks';
export default () => {
const [counter, setCounter] = useState(0);
useClickAway(
() => {
setCounter((s) => s + 1);
},
() => document.getElementById('use-click-away-button'),
);
return (
<div>
<button type="button" id="use-click-away-button">
box
</button>
<p>counter: {counter}</p>
</div>
);
};
================================================
FILE: packages/hooks/src/useClickAway/demo/demo3.tsx
================================================
/**
* title: Support multiple DOM
* desc: Support pass multiple DOM elements.
*
* title.zh-CN: 支持多个 DOM 对象
* desc.zh-CN: 支持传入多个目标对象。
*/
import { useState, useRef } from 'react';
import { useClickAway } from 'ahooks';
export default () => {
const [counter, setCounter] = useState(0);
const ref1 = useRef(null);
const ref2 = useRef(null);
useClickAway(() => {
setCounter((s) => s + 1);
}, [ref1, ref2]);
return (
<div>
<button type="button" ref={ref1}>
box1
</button>
<button type="button" ref={ref2} style={{ marginLeft: 16 }}>
box2
</button>
<p>counter: {counter}</p>
</div>
);
};
================================================
FILE: packages/hooks/src/useClickAway/demo/demo4.tsx
================================================
/**
* title: Listen to other events
* desc: By setting eventName, you can specify the event to be listened, Try click the right mouse.
*
* title.zh-CN: 监听其它事件
* desc.zh-CN: 通过设置 eventName,可以指定需要监听的事件,试试点击鼠标右键。
*/
import { useState, useRef } from 'react';
import { useClickAway } from 'ahooks';
export default () => {
const [counter, setCounter] = useState(0);
const ref = useRef<HTMLButtonElement>(null);
useClickAway(
() => {
setCounter((s) => s + 1);
},
ref,
'contextmenu',
);
return (
<div>
<button ref={ref} type="button">
box
</button>
<p>counter: {counter}</p>
</div>
);
};
================================================
FILE: packages/hooks/src/useClickAway/demo/demo5.tsx
================================================
/**
* title: Support multiple events
* desc: Set up multiple events, you can try using the mouse click or right click.
*
* title.zh-CN: 支持传入多个事件名称
* desc.zh-CN: 设置了多个事件,你可以试试用鼠标左键或者右键。
*/
import { useState, useRef } from 'react';
import { useClickAway } from 'ahooks';
export default () => {
const [counter, setCounter] = useState(0);
const ref = useRef(null);
useClickAway(
() => {
setCounter((s) => s + 1);
},
ref,
['click', 'contextmenu'],
);
return (
<div>
<button type="button" ref={ref}>
box
</button>
<p>counter: {counter}</p>
</div>
);
};
================================================
FILE: packages/hooks/src/useClickAway/demo/demo6.tsx
================================================
/**
* title: Support shadow DOM
* desc: Add the addEventListener to shadow DOM root instead of the document
*
* title.zh-CN: 支持 shadow DOM
* desc.zh-CN: 将 addEventListener 添加到 shadow DOM root
*/
import { useState, useRef } from 'react';
import { useClickAway } from 'ahooks';
import root from 'react-shadow';
export default () => {
const [counter, setCounter] = useState(0);
const ref = useRef(null);
useClickAway(
() => {
setCounter((s) => s + 1);
},
ref,
['click', 'contextmenu'],
);
return (
<root.div>
<div>
<button type="button" ref={ref}>
box
</button>
<p>counter: {counter}</p>
</div>
</root.div>
);
};
================================================
FILE: packages/hooks/src/useClickAway/index.en-US.md
================================================
---
nav:
path: /hooks
---
# useClickAway
Listen for click events outside the target element.
## Examples
### Default usage
<code src="./demo/demo1.tsx" />
### Custom DOM
<code src="./demo/demo2.tsx" />
### Support multiple DOM
<code src="./demo/demo3.tsx" />
### Listen for other events
<code src="./demo/demo4.tsx" />
### Support multiple events
<code src="./demo/demo5.tsx"/>
### Support shadow DOM
<code src="./demo/demo6.tsx"/>
## API
```typescript
type Target = Element | (() => Element) | React.MutableRefObject<Element>;
type DocumentEventKey = keyof DocumentEventMap;
useClickAway<T extends Event = Event>(
onClickAway: (event: T) => void,
target: Target | Target[],
eventName?: DocumentEventKey | DocumentEventKey[]
);
```
### Params
| Property | Description | Type | Default |
| ----------- | ---------------------------------------------- | ------------------------------------------ | ------- |
| onClickAway | Trigger Function | `(event: T) => void` | - |
| target | DOM elements or Ref or Function, support array | `Target` \| `Target[]` | - |
| eventName | Set the event to be listened, support array | `DocumentEventKey` \| `DocumentEventKey[]` | `click` |
================================================
FILE: packages/hooks/src/useClickAway/index.ts
================================================
import useLatest from '../useLatest';
import type { BasicTarget } from '../utils/domTarget';
import { getTargetElement } from '../utils/domTarget';
import getDocumentOrShadow from '../utils/getDocumentOrShadow';
import useEffectWithTarget from '../utils/useEffectWithTarget';
type DocumentEventKey = keyof DocumentEventMap;
export default function useClickAway<T extends Event = Event>(
onClickAway: (event: T) => void,
target: BasicTarget | BasicTarget[],
eventName: DocumentEventKey | DocumentEventKey[] = 'click',
) {
const onClickAwayRef = useLatest(onClickAway);
useEffectWithTarget(
() => {
const handler = (event: any) => {
const targets = Array.isArray(target) ? target : [target];
if (
targets.some((item) => {
const targetElement = getTargetElement(item);
return !targetElement || targetElement.contains(event.target);
})
) {
return;
}
onClickAwayRef.current(event);
};
const documentOrShadow = getDocumentOrShadow(target);
const eventNames = Array.isArray(eventName) ? eventName : [eventName];
eventNames.forEach((event) => documentOrShadow.addEventListener(event, handler));
gitextract_8eg352yw/ ├── .babelrc ├── .coveralls.yml ├── .cursor/ │ └── rules/ │ ├── demo.mdc │ ├── docs.mdc │ ├── git.mdc │ ├── project.mdc │ ├── testing.mdc │ └── typescript.mdc ├── .editorconfig ├── .github/ │ ├── PULL_REQUEST_TEMPLATE/ │ │ └── pr_cn.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── comment-when-needs-more-info.yml │ ├── gitleaks.yml │ ├── issue-close-require.yml │ ├── issue-reply.yml │ ├── pkg.pr.new.yml │ ├── static.yml │ └── test.yml ├── .gitignore ├── .gitleaks.toml ├── .husky/ │ ├── commit-msg │ └── pre-commit ├── .npmrc ├── .travis.yml ├── CONTRIBUTING.MD ├── CONTRIBUTING.zh-CN.MD ├── LICENSE ├── README.md ├── README.zh-CN.md ├── SECURITY.md ├── biome.json ├── config/ │ ├── config.ts │ └── hooks.ts ├── docs/ │ ├── guide/ │ │ ├── blog/ │ │ │ ├── function.en-US.md │ │ │ ├── function.zh-CN.md │ │ │ ├── hmr.en-US.md │ │ │ ├── hmr.zh-CN.md │ │ │ ├── ssr.en-US.md │ │ │ ├── ssr.zh-CN.md │ │ │ ├── strict.en-US.md │ │ │ └── strict.zh-CN.md │ │ ├── dom.en-US.md │ │ ├── dom.zh-CN.md │ │ ├── index.en-US.md │ │ ├── index.zh-CN.md │ │ ├── upgrade.en-US.md │ │ └── upgrade.zh-CN.md │ ├── index.en-US.md │ └── index.zh-CN.md ├── example/ │ └── .gitkeep ├── gulpfile.js ├── package.json ├── packages/ │ ├── hooks/ │ │ ├── gulpfile.js │ │ ├── package.json │ │ ├── src/ │ │ │ ├── createDeepCompareEffect/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ └── index.ts │ │ │ ├── createUpdateEffect/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ └── index.ts │ │ │ ├── createUseStorageState/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ └── index.ts │ │ │ ├── global.d.ts │ │ │ ├── index.spec.ts │ │ │ ├── index.ts │ │ │ ├── useAntdTable/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── cache.tsx │ │ │ │ │ ├── form.tsx │ │ │ │ │ ├── init.tsx │ │ │ │ │ ├── ready.tsx │ │ │ │ │ ├── table.tsx │ │ │ │ │ └── validate.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.tsx │ │ │ │ ├── index.zh-CN.md │ │ │ │ └── types.ts │ │ │ ├── useAsyncEffect/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useBoolean/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useClickAway/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ ├── demo3.tsx │ │ │ │ │ ├── demo4.tsx │ │ │ │ │ ├── demo5.tsx │ │ │ │ │ └── demo6.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useControllableValue/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ └── demo3.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useCookieState/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.tsx │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ └── demo3.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useCountDown/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ └── demo3.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useCounter/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useCreation/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useDebounce/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── debounceOptions.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useDebounceEffect/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useDebounceFn/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useDeepCompareEffect/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.tsx │ │ │ │ └── index.zh-CN.md │ │ │ ├── useDeepCompareLayoutEffect/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.tsx │ │ │ │ └── index.zh-CN.md │ │ │ ├── useDocumentVisibility/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useDrag/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ └── index.ts │ │ │ ├── useDrop/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useDynamicList/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ ├── demo3.tsx │ │ │ │ │ └── demo4.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useEventEmitter/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useEventListener/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ └── demo3.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useEventTarget/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useExternal/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ └── demo3.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useFavicon/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.tsx │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useFocusWithin/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.tsx │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.tsx │ │ │ │ └── index.zh-CN.md │ │ │ ├── useFullscreen/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ ├── demo3.tsx │ │ │ │ │ └── demo4.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useFusionTable/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── cache.tsx │ │ │ │ │ ├── form.tsx │ │ │ │ │ ├── init.tsx │ │ │ │ │ ├── table.tsx │ │ │ │ │ └── validate.tsx │ │ │ │ ├── fusionAdapter.ts │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.tsx │ │ │ │ ├── index.zh-CN.md │ │ │ │ └── types.ts │ │ │ ├── useGetState/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useHistoryTravel/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ └── demo3.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useHover/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.tsx │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useInViewport/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ └── demo3.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useInfiniteScroll/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── default.tsx │ │ │ │ │ ├── mutate.tsx │ │ │ │ │ ├── pagination.tsx │ │ │ │ │ ├── reload.tsx │ │ │ │ │ ├── scroll.tsx │ │ │ │ │ └── scrollTop.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.tsx │ │ │ │ ├── index.zh-CN.md │ │ │ │ └── types.ts │ │ │ ├── useInterval/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useIsomorphicLayoutEffect/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useKeyPress/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.tsx │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ ├── demo3.tsx │ │ │ │ │ ├── demo4.tsx │ │ │ │ │ ├── demo5.tsx │ │ │ │ │ ├── demo6.tsx │ │ │ │ │ ├── demo7.tsx │ │ │ │ │ └── demo8.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useLatest/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useLocalStorageState/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ ├── demo3.tsx │ │ │ │ │ └── demo4.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useLockFn/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useLongPress/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ └── demo3.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useMap/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useMemoizedFn/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useMount/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useMouse/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useMutationObserver/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useNetwork/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── usePagination/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ ├── demo3.tsx │ │ │ │ │ └── demo4.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ ├── index.zh-CN.md │ │ │ │ └── types.ts │ │ │ ├── usePrevious/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useRafInterval/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── index.spec.ts │ │ │ │ │ └── node.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useRafState/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useRafTimeout/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── index.spec.ts │ │ │ │ │ └── node.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useReactive/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.tsx │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ ├── demo3.tsx │ │ │ │ │ ├── demo4.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useRequest/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── index.spec.ts │ │ │ │ │ ├── useAutoRunPlugin.spec.ts │ │ │ │ │ ├── useCachePlugin.spec.tsx │ │ │ │ │ ├── useDebouncePlugin.spec.ts │ │ │ │ │ ├── useLoadingDelayPlugin.spec.ts │ │ │ │ │ ├── usePollingPlugin.spec.ts │ │ │ │ │ ├── useRefreshOnWindowFocusPlugin.spec.ts │ │ │ │ │ ├── useRetryPlugin.spec.ts │ │ │ │ │ └── useThrottlePlugin.spec.ts │ │ │ │ ├── doc/ │ │ │ │ │ ├── basic/ │ │ │ │ │ │ ├── basic.en-US.md │ │ │ │ │ │ ├── basic.zh-CN.md │ │ │ │ │ │ └── demo/ │ │ │ │ │ │ ├── cancel.tsx │ │ │ │ │ │ ├── default.tsx │ │ │ │ │ │ ├── lifeCycle.tsx │ │ │ │ │ │ ├── manual-run.tsx │ │ │ │ │ │ ├── manual-runAsync.tsx │ │ │ │ │ │ ├── mutate.tsx │ │ │ │ │ │ ├── params.tsx │ │ │ │ │ │ └── refresh.tsx │ │ │ │ │ ├── cache/ │ │ │ │ │ │ ├── cache.en-US.md │ │ │ │ │ │ ├── cache.zh-CN.md │ │ │ │ │ │ └── demo/ │ │ │ │ │ │ ├── cacheKey.tsx │ │ │ │ │ │ ├── clearCache.tsx │ │ │ │ │ │ ├── params.tsx │ │ │ │ │ │ ├── setCache.tsx │ │ │ │ │ │ ├── share.tsx │ │ │ │ │ │ └── staleTime.tsx │ │ │ │ │ ├── debounce/ │ │ │ │ │ │ ├── debounce.en-US.md │ │ │ │ │ │ ├── debounce.zh-CN.md │ │ │ │ │ │ └── demo/ │ │ │ │ │ │ └── debounce.tsx │ │ │ │ │ ├── index/ │ │ │ │ │ │ ├── demo/ │ │ │ │ │ │ │ ├── default.tsx │ │ │ │ │ │ │ └── manual.tsx │ │ │ │ │ │ ├── index.en-US.md │ │ │ │ │ │ └── index.zh-CN.md │ │ │ │ │ ├── loadingDelay/ │ │ │ │ │ │ ├── demo/ │ │ │ │ │ │ │ └── loadingDelay.tsx │ │ │ │ │ │ ├── loadingDelay.en-US.md │ │ │ │ │ │ └── loadingDelay.zh-CN.md │ │ │ │ │ ├── polling/ │ │ │ │ │ │ ├── demo/ │ │ │ │ │ │ │ ├── polling.tsx │ │ │ │ │ │ │ └── pollingError.tsx │ │ │ │ │ │ ├── polling.en-US.md │ │ │ │ │ │ └── polling.zh-CN.md │ │ │ │ │ ├── ready/ │ │ │ │ │ │ ├── demo/ │ │ │ │ │ │ │ ├── manualReady.tsx │ │ │ │ │ │ │ └── ready.tsx │ │ │ │ │ │ ├── ready.en-US.md │ │ │ │ │ │ └── ready.zh-CN.md │ │ │ │ │ ├── refreshDeps/ │ │ │ │ │ │ ├── demo/ │ │ │ │ │ │ │ ├── refreshDeps.tsx │ │ │ │ │ │ │ └── refreshDepsAction.tsx │ │ │ │ │ │ ├── refresyDeps.en-US.md │ │ │ │ │ │ └── refresyDeps.zh-CN.md │ │ │ │ │ ├── refreshOnWindowFocus/ │ │ │ │ │ │ ├── demo/ │ │ │ │ │ │ │ └── refreshOnWindowFocus.tsx │ │ │ │ │ │ ├── refreshOnWindowFocus.en-US.md │ │ │ │ │ │ └── refreshOnWindowFocus.zh-CN.md │ │ │ │ │ ├── retry/ │ │ │ │ │ │ ├── demo/ │ │ │ │ │ │ │ └── retry.tsx │ │ │ │ │ │ ├── retry.en-US.md │ │ │ │ │ │ └── retry.zh-CN.md │ │ │ │ │ └── throttle/ │ │ │ │ │ ├── demo/ │ │ │ │ │ │ └── throttle.tsx │ │ │ │ │ ├── throttle.en-US.md │ │ │ │ │ └── throttle.zh-CN.md │ │ │ │ ├── index.ts │ │ │ │ └── src/ │ │ │ │ ├── Fetch.ts │ │ │ │ ├── plugins/ │ │ │ │ │ ├── useAutoRunPlugin.ts │ │ │ │ │ ├── useCachePlugin.ts │ │ │ │ │ ├── useDebouncePlugin.ts │ │ │ │ │ ├── useLoadingDelayPlugin.ts │ │ │ │ │ ├── usePollingPlugin.ts │ │ │ │ │ ├── useRefreshOnWindowFocusPlugin.ts │ │ │ │ │ ├── useRetryPlugin.ts │ │ │ │ │ └── useThrottlePlugin.ts │ │ │ │ ├── types.ts │ │ │ │ ├── useRequest.ts │ │ │ │ ├── useRequestImplement.ts │ │ │ │ └── utils/ │ │ │ │ ├── cache.ts │ │ │ │ ├── cachePromise.ts │ │ │ │ ├── cacheSubscribe.ts │ │ │ │ ├── isDocumentVisible.ts │ │ │ │ ├── isOnline.ts │ │ │ │ ├── limit.ts │ │ │ │ ├── subscribeFocus.ts │ │ │ │ └── subscribeReVisible.ts │ │ │ ├── useResetState/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useResponsive/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ └── index.spec.ts.snap │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useSafeState/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useScroll/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ └── demo3.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useSelections/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ └── demo3.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useSessionStorageState/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useSet/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useSetState/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useSize/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.tsx │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useTextSelection/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ ├── demo2.tsx │ │ │ │ │ └── demo3.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useTheme/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useThrottle/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ ├── index.zh-CN.md │ │ │ │ └── throttleOptions.ts │ │ │ ├── useThrottleEffect/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useThrottleFn/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useTimeout/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useTitle/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useToggle/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useTrackedEffect/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useUnmount/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useUnmountedRef/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.tsx │ │ │ │ └── index.zh-CN.md │ │ │ ├── useUpdate/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useUpdateEffect/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useUpdateLayoutEffect/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useVirtualList/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ ├── demo1.tsx │ │ │ │ │ └── demo2.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useWebSocket/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ ├── useWhyDidYouUpdate/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── index.spec.ts │ │ │ │ ├── demo/ │ │ │ │ │ └── demo1.tsx │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.ts │ │ │ │ └── index.zh-CN.md │ │ │ └── utils/ │ │ │ ├── __tests__/ │ │ │ │ └── index.spec.ts │ │ │ ├── createEffectWithTarget.ts │ │ │ ├── depsAreSame.ts │ │ │ ├── depsEqual.ts │ │ │ ├── domTarget.ts │ │ │ ├── getDocumentOrShadow.ts │ │ │ ├── index.ts │ │ │ ├── isAppleDevice.ts │ │ │ ├── isBrowser.ts │ │ │ ├── isDev.ts │ │ │ ├── lodash-polyfill.ts │ │ │ ├── noop.ts │ │ │ ├── rect.ts │ │ │ ├── testingHelpers.ts │ │ │ ├── tests.tsx │ │ │ ├── useDeepCompareWithTarget.ts │ │ │ ├── useEffectWithTarget.ts │ │ │ ├── useIsomorphicLayoutEffectWithTarget.ts │ │ │ └── useLayoutEffectWithTarget.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.pro.json │ │ ├── vitest.config.ts │ │ └── webpack.config.js │ └── use-url-state/ │ ├── README.md │ ├── __tests__/ │ │ ├── browser.spec.tsx │ │ ├── router.spec.tsx │ │ └── setup.tsx │ ├── demo/ │ │ ├── demo1.tsx │ │ ├── demo2.tsx │ │ ├── demo3.tsx │ │ └── demo4.tsx │ ├── gulpfile.js │ ├── package.json │ ├── src/ │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.pro.json │ ├── use-url-state.en-US.md │ ├── use-url-state.zh-CN.md │ ├── vitest.config.ts │ └── webpack.config.js ├── pnpm-workspace.yaml ├── public/ │ ├── style.css │ └── useExternal/ │ ├── bootstrap-badge.css │ └── test-external-script.js ├── scripts/ │ └── build-with-relative-paths.js ├── tsconfig.base.json ├── tsconfig.pro.json ├── umd.html ├── vitest.config.ts └── webpack.common.js
SYMBOL INDEX (362 symbols across 170 files)
FILE: packages/hooks/gulpfile.js
function camelToKebab (line 8) | function camelToKebab(str) {
function genDesc (line 12) | async function genDesc(mdPath) {
function genMetaData (line 26) | async function genMetaData() {
FILE: packages/hooks/src/createDeepCompareEffect/index.ts
type EffectHookType (line 5) | type EffectHookType = typeof useEffect | typeof useLayoutEffect;
type CreateUpdateEffect (line 7) | type CreateUpdateEffect = (hook: EffectHookType) => EffectHookType;
FILE: packages/hooks/src/createUpdateEffect/index.ts
type EffectHookType (line 4) | type EffectHookType = typeof useEffect | typeof useLayoutEffect;
FILE: packages/hooks/src/createUseStorageState/__tests__/index.spec.ts
class TestStorage (line 6) | class TestStorage implements Storage {
method clear (line 13) | clear(): void {
method getItem (line 18) | getItem(key: string): string | null {
method key (line 22) | key(index: number): string | null {
method removeItem (line 30) | removeItem(key: string): void {
method setItem (line 36) | setItem(key: string, value: string): void {
type StorageStateProps (line 45) | interface StorageStateProps<T> extends Pick<Options<T>, 'defaultValue'> {
FILE: packages/hooks/src/createUseStorageState/index.ts
constant SYNC_STORAGE_EVENT_NAME (line 7) | const SYNC_STORAGE_EVENT_NAME = 'AHOOKS_SYNC_STORAGE_EVENT_NAME';
type SetState (line 9) | type SetState<S> = S | ((prevState?: S) => S);
type Options (line 11) | interface Options<T> {
FILE: packages/hooks/src/global.d.ts
type Window (line 3) | interface Window {
FILE: packages/hooks/src/useAntdTable/__tests__/index.spec.ts
type Query (line 9) | interface Query {
method getFieldsValue (line 36) | getFieldsValue() {
method setFieldsValue (line 44) | setFieldsValue(values: object) {
method resetFields (line 50) | resetFields() {
method validateFields (line 53) | validateFields(fields: any[]) {
method getFieldInstance (line 195) | getFieldInstance(key: string) {
FILE: packages/hooks/src/useAntdTable/demo/cache.tsx
type Item (line 8) | interface Item {
type Result (line 17) | interface Result {
FILE: packages/hooks/src/useAntdTable/demo/form.tsx
type Item (line 7) | interface Item {
type Result (line 16) | interface Result {
FILE: packages/hooks/src/useAntdTable/demo/init.tsx
type Item (line 7) | interface Item {
type Result (line 16) | interface Result {
FILE: packages/hooks/src/useAntdTable/demo/ready.tsx
type Item (line 8) | interface Item {
type Result (line 17) | interface Result {
FILE: packages/hooks/src/useAntdTable/demo/table.tsx
type Item (line 4) | interface Item {
type Result (line 13) | interface Result {
FILE: packages/hooks/src/useAntdTable/demo/validate.tsx
type Item (line 7) | interface Item {
type Result (line 16) | interface Result {
FILE: packages/hooks/src/useAntdTable/index.tsx
method onSuccess (line 33) | onSuccess(...args) {
FILE: packages/hooks/src/useAntdTable/types.ts
type Data (line 3) | type Data = { total: number; list: any[] };
type Params (line 5) | type Params = [
type Service (line 17) | type Service<TData extends Data, TParams extends Params> = (
type Antd3ValidateFields (line 21) | type Antd3ValidateFields = (
type Antd4ValidateFields (line 25) | type Antd4ValidateFields = (fieldNames?: string[]) => Promise<Record<str...
type AntdFormUtils (line 27) | interface AntdFormUtils {
type AntdTableResult (line 37) | interface AntdTableResult<TData extends Data, TParams extends Params>
type AntdTableOptions (line 54) | interface AntdTableOptions<TData extends Data, TParams extends Params>
FILE: packages/hooks/src/useAsyncEffect/demo/demo1.tsx
function mockCheck (line 12) | function mockCheck(): Promise<boolean> {
FILE: packages/hooks/src/useAsyncEffect/demo/demo2.tsx
function mockCheck (line 12) | function mockCheck(val: string): Promise<boolean> {
FILE: packages/hooks/src/useAsyncEffect/index.ts
function isAsyncGenerator (line 5) | function isAsyncGenerator(
function useAsyncEffect (line 11) | function useAsyncEffect(
FILE: packages/hooks/src/useBoolean/index.ts
type Actions (line 4) | interface Actions {
function useBoolean (line 11) | function useBoolean(defaultValue = false): [boolean, Actions] {
FILE: packages/hooks/src/useClickAway/index.ts
type DocumentEventKey (line 7) | type DocumentEventKey = keyof DocumentEventMap;
function useClickAway (line 9) | function useClickAway<T extends Event = Event>(
FILE: packages/hooks/src/useControllableValue/__tests__/index.spec.ts
method onChange (line 29) | onChange(v: any, extra: any) {
type Value (line 75) | type Value = {
FILE: packages/hooks/src/useControllableValue/index.ts
type Options (line 7) | interface Options<T> {
type Props (line 14) | type Props = Record<string, any>;
type StandardProps (line 16) | interface StandardProps<T> {
function useControllableValue (line 29) | function useControllableValue<T = any>(defaultProps?: Props, options: Op...
FILE: packages/hooks/src/useCookieState/demo/demo2.tsx
function App (line 11) | function App() {
FILE: packages/hooks/src/useCookieState/demo/demo3.tsx
function App (line 11) | function App() {
FILE: packages/hooks/src/useCookieState/index.ts
type State (line 6) | type State = string | undefined;
type Options (line 8) | interface Options extends Cookies.CookieAttributes {
function useCookieState (line 12) | function useCookieState(cookieKey: string, options: Options = {}) {
FILE: packages/hooks/src/useCountDown/index.ts
type TDate (line 6) | type TDate = dayjs.ConfigType;
type Options (line 8) | interface Options {
type FormattedRes (line 15) | interface FormattedRes {
FILE: packages/hooks/src/useCounter/index.ts
type Options (line 5) | interface Options {
type Actions (line 10) | interface Actions {
type ValueParam (line 17) | type ValueParam = number | ((c: number) => number);
function getTargetValue (line 19) | function getTargetValue(val: number, options: Options = {}) {
function useCounter (line 31) | function useCounter(initialValue: number = 0, options: Options = {}) {
FILE: packages/hooks/src/useCreation/__tests__/index.spec.ts
class Foo (line 7) | class Foo {
method constructor (line 8) | constructor() {
FILE: packages/hooks/src/useCreation/demo/demo1.tsx
class Foo (line 12) | class Foo {
method constructor (line 13) | constructor() {
FILE: packages/hooks/src/useDebounce/debounceOptions.ts
type DebounceOptions (line 1) | interface DebounceOptions {
FILE: packages/hooks/src/useDebounce/index.ts
function useDebounce (line 5) | function useDebounce<T>(value: T, options?: DebounceOptions) {
FILE: packages/hooks/src/useDebounceEffect/index.ts
function useDebounceEffect (line 7) | function useDebounceEffect(
FILE: packages/hooks/src/useDebounceFn/__tests__/index.spec.ts
type ParamsObj (line 6) | interface ParamsObj {
FILE: packages/hooks/src/useDebounceFn/index.ts
type noop (line 9) | type noop = (...args: any[]) => any;
function useDebounceFn (line 11) | function useDebounceFn<T extends noop>(fn: T, options?: DebounceOptions) {
FILE: packages/hooks/src/useDocumentVisibility/__tests__/index.spec.ts
method default (line 11) | get default() {
FILE: packages/hooks/src/useDocumentVisibility/index.ts
type VisibilityState (line 5) | type VisibilityState = 'hidden' | 'visible' | 'prerender' | undefined;
function useDocumentVisibility (line 14) | function useDocumentVisibility(): VisibilityState {
FILE: packages/hooks/src/useDrag/__tests__/index.spec.ts
method get (line 64) | get() {
FILE: packages/hooks/src/useDrag/index.ts
type Options (line 9) | interface Options {
FILE: packages/hooks/src/useDrop/__tests__/index.spec.ts
method items (line 23) | get items() {
method files (line 26) | get files() {
method items (line 32) | get items() {
method files (line 35) | get files() {
FILE: packages/hooks/src/useDrop/demo/demo2.tsx
constant COMMON_STYLE (line 12) | const COMMON_STYLE: React.CSSProperties = {
FILE: packages/hooks/src/useDrop/index.ts
type Options (line 7) | interface Options {
FILE: packages/hooks/src/useDynamicList/demo/demo4.tsx
type Item (line 15) | interface Item {
FILE: packages/hooks/src/useEventEmitter/index.ts
type Subscription (line 3) | type Subscription<T> = (val: T) => void;
class EventEmitter (line 5) | class EventEmitter<T> {
method subscription (line 20) | function subscription(val: T) {
function useEventEmitter (line 33) | function useEventEmitter<T = void>() {
FILE: packages/hooks/src/useEventListener/index.ts
type noop (line 6) | type noop = (...p: any) => void;
type Target (line 8) | type Target = BasicTarget<HTMLElement | Element | Window | Document>;
type Options (line 10) | type Options<T extends Target = Target> = {
function useEventListener (line 45) | function useEventListener(eventName: string | string[], handler: noop, o...
FILE: packages/hooks/src/useEventTarget/index.ts
type EventTarget (line 5) | interface EventTarget<U> {
type Options (line 11) | interface Options<T, U> {
function useEventTarget (line 16) | function useEventTarget<T, U = T>(options?: Options<T, U>) {
FILE: packages/hooks/src/useExternal/index.ts
type JsOptions (line 3) | type JsOptions = {
type CssOptions (line 9) | type CssOptions = {
type DefaultOptions (line 15) | type DefaultOptions = {
type Options (line 22) | type Options = JsOptions | CssOptions | DefaultOptions;
constant EXTERNAL_USED_COUNT (line 26) | const EXTERNAL_USED_COUNT: Record<string, number> = {};
type Status (line 28) | type Status = 'unset' | 'loading' | 'ready' | 'error';
type LoadResult (line 30) | interface LoadResult {
type LoadExternal (line 35) | type LoadExternal = <T>(path: string, props?: Partial<T>) => LoadResult;
FILE: packages/hooks/src/useFavicon/demo/demo1.tsx
constant DEFAULT_FAVICON_URL (line 12) | const DEFAULT_FAVICON_URL = 'https://ahooks.js.org/simple-logo.svg';
constant GOOGLE_FAVICON_URL (line 14) | const GOOGLE_FAVICON_URL = 'https://www.google.com/favicon.ico';
FILE: packages/hooks/src/useFavicon/index.ts
type ImgTypes (line 10) | type ImgTypes = keyof typeof ImgTypeMap;
FILE: packages/hooks/src/useFocusWithin/index.tsx
type Options (line 5) | interface Options {
function useFocusWithin (line 11) | function useFocusWithin(target: BasicTarget, options?: Options) {
FILE: packages/hooks/src/useFullscreen/demo/demo4.tsx
function vanillaToggleFullscreen (line 12) | function vanillaToggleFullscreen(element: HTMLElement) {
FILE: packages/hooks/src/useFullscreen/index.ts
type PageFullscreenOptions (line 9) | interface PageFullscreenOptions {
type Options (line 14) | interface Options {
function getIsFullscreen (line 33) | function getIsFullscreen() {
FILE: packages/hooks/src/useFusionTable/__tests__/index.spec.ts
type Result (line 6) | type Result = {
method getNames (line 44) | getNames() {
method setValues (line 47) | setValues(v: any) {
method getValues (line 50) | getValues() {
method resetToDefault (line 53) | resetToDefault() {
method validate (line 56) | validate(names: any, callback: (err: any, values: any) => void) {
FILE: packages/hooks/src/useFusionTable/demo/cache.tsx
type Item (line 6) | interface Item {
type Result (line 15) | interface Result {
FILE: packages/hooks/src/useFusionTable/demo/form.tsx
type Item (line 5) | interface Item {
type Result (line 14) | interface Result {
FILE: packages/hooks/src/useFusionTable/demo/init.tsx
type Item (line 5) | interface Item {
type Result (line 14) | interface Result {
FILE: packages/hooks/src/useFusionTable/demo/table.tsx
type Item (line 4) | interface Item {
type Result (line 13) | interface Result {
FILE: packages/hooks/src/useFusionTable/demo/validate.tsx
type Item (line 5) | interface Item {
type Result (line 14) | interface Result {
FILE: packages/hooks/src/useFusionTable/types.ts
type Field (line 3) | interface Field {
type FusionTableResult (line 12) | interface FusionTableResult<TData extends Data, TParams extends Params>
type FusionTableOptions (line 35) | interface FusionTableOptions<TData extends Data, TParams extends Params>
FILE: packages/hooks/src/useGetState/index.ts
type GetStateAction (line 5) | type GetStateAction<S> = () => S;
function useGetState (line 15) | function useGetState<S>(initialState?: S) {
FILE: packages/hooks/src/useHistoryTravel/index.ts
type IData (line 5) | interface IData<T> {
function useHistoryTravel (line 34) | function useHistoryTravel<T>(initialValue?: T, maxLength: number = 0) {
FILE: packages/hooks/src/useHover/index.ts
type Options (line 5) | interface Options {
FILE: packages/hooks/src/useInViewport/index.ts
type CallbackType (line 7) | type CallbackType = (entry: IntersectionObserverEntry) => void;
type Options (line 9) | interface Options {
function useInViewport (line 16) | function useInViewport(target: BasicTarget | BasicTarget[], options?: Op...
FILE: packages/hooks/src/useInfiniteScroll/__tests__/index.spec.ts
function mockRequest (line 9) | async function mockRequest() {
function setTargetInfo (line 24) | function setTargetInfo(key: 'scrollTop', value: any) {
FILE: packages/hooks/src/useInfiniteScroll/demo/default.tsx
type Result (line 3) | interface Result {
function getLoadMoreList (line 10) | function getLoadMoreList(nextId: string | undefined, limit: number): Pro...
FILE: packages/hooks/src/useInfiniteScroll/demo/mutate.tsx
type Result (line 3) | interface Result {
function getLoadMoreList (line 10) | function getLoadMoreList(nextId: string | undefined, limit: number): Pro...
function deleteItem (line 28) | function deleteItem(id: string) {
FILE: packages/hooks/src/useInfiniteScroll/demo/pagination.tsx
type Result (line 3) | interface Result {
function getLoadMoreList (line 10) | function getLoadMoreList(page: number, pageSize: number): Promise<Result> {
constant PAGE_SIZE (line 24) | const PAGE_SIZE = 4;
FILE: packages/hooks/src/useInfiniteScroll/demo/reload.tsx
type Result (line 4) | interface Result {
function getLoadMoreList (line 11) | function getLoadMoreList(
FILE: packages/hooks/src/useInfiniteScroll/demo/scroll.tsx
type Result (line 4) | interface Result {
function getLoadMoreList (line 11) | function getLoadMoreList(nextId: string | undefined, limit: number): Pro...
FILE: packages/hooks/src/useInfiniteScroll/demo/scrollTop.tsx
type Result (line 4) | interface Result {
function getLoadMoreList (line 28) | function getLoadMoreList(nextId: string | undefined, limit: number): Pro...
method onSuccess (line 57) | onSuccess() {
FILE: packages/hooks/src/useInfiniteScroll/types.ts
type Data (line 4) | type Data = { list: any[]; [key: string]: any };
type Service (line 6) | type Service<TData extends Data> = (currentData?: TData) => Promise<TData>;
type InfiniteScrollResult (line 8) | interface InfiniteScrollResult<TData extends Data> {
type InfiniteScrollOptions (line 23) | interface InfiniteScrollOptions<TData extends Data> {
FILE: packages/hooks/src/useInterval/__tests__/index.spec.ts
type ParamsObj (line 5) | interface ParamsObj {
FILE: packages/hooks/src/useKeyPress/index.ts
type KeyType (line 8) | type KeyType = number | string;
type KeyPredicate (line 9) | type KeyPredicate = (event: KeyboardEvent) => KeyType | boolean | undefi...
type KeyFilter (line 10) | type KeyFilter = KeyType | KeyType[] | ((event: KeyboardEvent) => boolean);
type KeyEvent (line 11) | type KeyEvent = 'keydown' | 'keyup';
type Target (line 13) | type Target = BasicTarget<HTMLElement | Document | Window>;
type Options (line 15) | type Options = {
function isValidKeyType (line 155) | function isValidKeyType(value: unknown): value is string | number {
function countKeyByEvent (line 160) | function countKeyByEvent(event: KeyboardEvent) {
function genFilterKey (line 179) | function genFilterKey(event: KeyboardEvent, keyFilter: KeyType, exactMat...
function genKeyFormatter (line 222) | function genKeyFormatter(keyFilter: KeyFilter, exactMatch: boolean): Key...
function useKeyPress (line 238) | function useKeyPress(
FILE: packages/hooks/src/useLatest/index.ts
function useLatest (line 3) | function useLatest<T>(value: T) {
FILE: packages/hooks/src/useLocalStorageState/demo/demo4.tsx
function Counter (line 20) | function Counter() {
FILE: packages/hooks/src/useLockFn/demo/demo1.tsx
function mockApiRequest (line 13) | function mockApiRequest() {
FILE: packages/hooks/src/useLockFn/index.ts
function useLockFn (line 3) | function useLockFn<P extends any[] = any[], V = any>(fn: (...args: P) =>...
FILE: packages/hooks/src/useLongPress/__tests__/index.spec.ts
method get (line 104) | get() {
FILE: packages/hooks/src/useLongPress/index.ts
type EventType (line 7) | type EventType = MouseEvent | TouchEvent;
type Options (line 8) | interface Options {
function useLongPress (line 15) | function useLongPress(
FILE: packages/hooks/src/useMap/index.ts
function useMap (line 4) | function useMap<K, T>(initialValue?: Iterable<readonly [K, T]>) {
FILE: packages/hooks/src/useMemoizedFn/index.ts
type noop (line 5) | type noop = (this: any, ...args: any[]) => any;
type PickFunction (line 7) | type PickFunction<T extends noop> = (
FILE: packages/hooks/src/useMount/index.ts
type MountCallback (line 6) | type MountCallback = EffectCallback | (() => Promise<void | (() => void)>);
FILE: packages/hooks/src/useMouse/__tests__/index.spec.ts
function moveMouse (line 6) | function moveMouse(x: number, y: number) {
FILE: packages/hooks/src/useMouse/index.ts
type CursorState (line 6) | interface CursorState {
FILE: packages/hooks/src/useNetwork/index.ts
type NetworkState (line 4) | interface NetworkState {
type NetworkEventType (line 15) | enum NetworkEventType {
function getConnection (line 21) | function getConnection() {
function getConnectionProperty (line 29) | function getConnectionProperty(): NetworkState {
function useNetwork (line 44) | function useNetwork(): NetworkState {
FILE: packages/hooks/src/usePagination/demo/demo1.tsx
type UserListItem (line 4) | interface UserListItem {
function getUserList (line 26) | async function getUserList(params: {
FILE: packages/hooks/src/usePagination/demo/demo2.tsx
type UserListItem (line 6) | interface UserListItem {
function getUserList (line 28) | async function getUserList(params: {
FILE: packages/hooks/src/usePagination/demo/demo3.tsx
type UserListItem (line 6) | interface UserListItem {
function getUserList (line 28) | async function getUserList(params: {
FILE: packages/hooks/src/usePagination/demo/demo4.tsx
type UserListItem (line 7) | interface UserListItem {
function getUserList (line 29) | async function getUserList(params: {
FILE: packages/hooks/src/usePagination/types.ts
type Data (line 3) | type Data = { total: number; list: any[] };
type Params (line 5) | type Params = [{ current: number; pageSize: number; [key: string]: any }...
type Service (line 7) | type Service<TData extends Data, TParams extends Params> = (
type PaginationResult (line 11) | interface PaginationResult<TData extends Data, TParams extends Params>
type PaginationOptions (line 24) | interface PaginationOptions<TData extends Data, TParams extends Params>
FILE: packages/hooks/src/usePrevious/__tests__/index.spec.ts
function getHook (line 7) | function getHook<T>(initialValue?: T, compareFunction?: ShouldUpdateFunc...
type Obj (line 70) | type Obj = { label: string; value: string };
FILE: packages/hooks/src/usePrevious/demo/demo2.tsx
type Person (line 12) | interface Person {
FILE: packages/hooks/src/usePrevious/index.ts
type ShouldUpdateFunc (line 3) | type ShouldUpdateFunc<T> = (prev?: T, next?: T) => boolean;
function usePrevious (line 7) | function usePrevious<T>(
FILE: packages/hooks/src/useRafInterval/__tests__/index.spec.ts
type ParamsObj (line 6) | interface ParamsObj {
constant FRAME_TIME (line 15) | const FRAME_TIME = 16;
FILE: packages/hooks/src/useRafInterval/__tests__/node.spec.ts
type ParamsObj (line 5) | interface ParamsObj {
constant FRAME_TIME (line 14) | const FRAME_TIME = 16;
FILE: packages/hooks/src/useRafInterval/index.ts
type Handle (line 5) | interface Handle {
function useRafInterval (line 42) | function useRafInterval(
FILE: packages/hooks/src/useRafState/index.ts
function useRafState (line 8) | function useRafState<S>(initialState?: S | (() => S)) {
FILE: packages/hooks/src/useRafTimeout/__tests__/index.spec.ts
type ParamsObj (line 6) | interface ParamsObj {
constant FRAME_TIME (line 13) | const FRAME_TIME = 16.7;
FILE: packages/hooks/src/useRafTimeout/__tests__/node.spec.ts
type ParamsObj (line 5) | interface ParamsObj {
constant FRAME_TIME (line 12) | const FRAME_TIME = 16.7;
FILE: packages/hooks/src/useRafTimeout/index.ts
type Handle (line 5) | interface Handle {
function useRafTimeout (line 45) | function useRafTimeout(fn: () => void, delay: number | undefined) {
FILE: packages/hooks/src/useReactive/__tests__/index.spec.tsx
method value (line 260) | get value() {
FILE: packages/hooks/src/useReactive/demo/demo3.tsx
method addBug (line 7) | addBug(bug: any) {
method bugsCount (line 10) | get bugsCount() {
FILE: packages/hooks/src/useReactive/index.ts
function observer (line 11) | function observer<T extends Record<string, any>>(initialVal: T, cb: () =...
function useReactive (line 57) | function useReactive<S extends Record<string, any>>(initialState: S): S {
FILE: packages/hooks/src/useRequest/doc/basic/demo/cancel.tsx
function editUsername (line 5) | function editUsername(username: string): Promise<void> {
FILE: packages/hooks/src/useRequest/doc/basic/demo/default.tsx
function getUsername (line 9) | function getUsername(): Promise<string> {
FILE: packages/hooks/src/useRequest/doc/basic/demo/lifeCycle.tsx
function editUsername (line 5) | function editUsername(username: string): Promise<void> {
FILE: packages/hooks/src/useRequest/doc/basic/demo/manual-run.tsx
function editUsername (line 13) | function editUsername(username: string): Promise<void> {
FILE: packages/hooks/src/useRequest/doc/basic/demo/manual-runAsync.tsx
function editUsername (line 13) | function editUsername(username: string): Promise<void> {
FILE: packages/hooks/src/useRequest/doc/basic/demo/mutate.tsx
function getUsername (line 12) | function getUsername(): Promise<string> {
function editUsername (line 20) | function editUsername(username: string): Promise<void> {
FILE: packages/hooks/src/useRequest/doc/basic/demo/params.tsx
function getUsername (line 5) | function getUsername(id: string): Promise<string> {
FILE: packages/hooks/src/useRequest/doc/basic/demo/refresh.tsx
function getUsername (line 11) | function getUsername(id: number): Promise<string> {
FILE: packages/hooks/src/useRequest/doc/debounce/demo/debounce.tsx
function getEmail (line 3) | async function getEmail(search?: string): Promise<string[]> {
FILE: packages/hooks/src/useRequest/doc/index/demo/default.tsx
function getUsername (line 9) | function getUsername(): Promise<string> {
FILE: packages/hooks/src/useRequest/doc/index/demo/manual.tsx
function changeUsername (line 14) | function changeUsername(username: string): Promise<{ success: boolean }> {
FILE: packages/hooks/src/useRequest/doc/loadingDelay/demo/loadingDelay.tsx
function getUsername (line 4) | function getUsername(): Promise<string> {
FILE: packages/hooks/src/useRequest/doc/polling/demo/polling.tsx
function getUsername (line 4) | function getUsername() {
FILE: packages/hooks/src/useRequest/doc/polling/demo/pollingError.tsx
function getUsername (line 5) | function getUsername() {
FILE: packages/hooks/src/useRequest/doc/ready/demo/manualReady.tsx
function getUsername (line 3) | function getUsername() {
FILE: packages/hooks/src/useRequest/doc/ready/demo/ready.tsx
function getUsername (line 3) | function getUsername() {
FILE: packages/hooks/src/useRequest/doc/refreshDeps/demo/refreshDeps.tsx
function getUsername (line 14) | function getUsername(id: number): Promise<string> {
FILE: packages/hooks/src/useRequest/doc/refreshDeps/demo/refreshDepsAction.tsx
function getUsername (line 15) | function getUsername(id: number): Promise<string> {
FILE: packages/hooks/src/useRequest/doc/refreshOnWindowFocus/demo/refreshOnWindowFocus.tsx
function getUsername (line 4) | function getUsername() {
FILE: packages/hooks/src/useRequest/doc/retry/demo/retry.tsx
function editUsername (line 5) | function editUsername(username: string) {
FILE: packages/hooks/src/useRequest/doc/throttle/demo/throttle.tsx
function getEmail (line 3) | async function getEmail(search?: string): Promise<string[]> {
FILE: packages/hooks/src/useRequest/src/Fetch.ts
class Fetch (line 6) | class Fetch<TData, TParams extends any[]> {
method constructor (line 18) | constructor(
method setState (line 31) | setState(s: Partial<FetchState<TData, TParams>> = {}) {
method runPluginHandler (line 39) | runPluginHandler(event: keyof PluginReturn<TData, TParams>, ...rest: a...
method runAsync (line 45) | async runAsync(...params: TParams): Promise<TData> {
method run (line 130) | run(...params: TParams) {
method cancel (line 138) | cancel() {
method refresh (line 147) | refresh() {
method refreshAsync (line 152) | refreshAsync() {
method mutate (line 157) | mutate(data?: TData | ((oldData?: TData) => TData | undefined)) {
FILE: packages/hooks/src/useRequest/src/types.ts
type Service (line 5) | type Service<TData, TParams extends any[]> = (...args: TParams) => Promi...
type Subscribe (line 6) | type Subscribe = () => void;
type FetchState (line 10) | interface FetchState<TData, TParams extends any[]> {
type PluginReturn (line 17) | interface PluginReturn<TData, TParams extends any[]> {
type Options (line 41) | interface Options<TData, TParams extends any[]> {
type Plugin (line 96) | type Plugin<TData, TParams extends any[]> = {
type Result (line 111) | interface Result<TData, TParams extends any[]> {
type Timeout (line 124) | type Timeout = ReturnType<typeof setTimeout>;
FILE: packages/hooks/src/useRequest/src/useRequest.ts
function useRequest (line 22) | function useRequest<TData, TParams extends any[]>(
FILE: packages/hooks/src/useRequest/src/useRequestImplement.ts
function useRequestImplement (line 12) | function useRequestImplement<TData, TParams extends any[]>(
FILE: packages/hooks/src/useRequest/src/utils/cache.ts
type Timer (line 1) | type Timer = ReturnType<typeof setTimeout>;
type CachedKey (line 2) | type CachedKey = string | number;
type CachedData (line 4) | interface CachedData<TData = any, TParams = any> {
type RecordData (line 9) | interface RecordData extends CachedData {
FILE: packages/hooks/src/useRequest/src/utils/cachePromise.ts
type CachedKey (line 1) | type CachedKey = string | number;
FILE: packages/hooks/src/useRequest/src/utils/cacheSubscribe.ts
type Listener (line 1) | type Listener = (data: any) => void;
FILE: packages/hooks/src/useRequest/src/utils/isDocumentVisible.ts
function isDocumentVisible (line 3) | function isDocumentVisible(): boolean {
FILE: packages/hooks/src/useRequest/src/utils/limit.ts
function limit (line 1) | function limit(fn: any, timespan: number) {
FILE: packages/hooks/src/useRequest/src/utils/subscribeFocus.ts
type Listener (line 6) | type Listener = () => void;
function subscribe (line 10) | function subscribe(listener: Listener) {
FILE: packages/hooks/src/useRequest/src/utils/subscribeReVisible.ts
type Listener (line 4) | type Listener = () => void;
function subscribe (line 8) | function subscribe(listener: Listener) {
FILE: packages/hooks/src/useResetState/index.ts
type ResetState (line 7) | type ResetState = () => void;
FILE: packages/hooks/src/useResponsive/__tests__/index.spec.ts
function changeWidth (line 6) | function changeWidth(width: number) {
FILE: packages/hooks/src/useResponsive/index.ts
type Subscriber (line 4) | type Subscriber = () => void;
type ResponsiveConfig (line 8) | type ResponsiveConfig = Record<string, number>;
type ResponsiveInfo (line 9) | type ResponsiveInfo = Record<string, boolean>;
function handleResize (line 21) | function handleResize() {
function calculate (line 34) | function calculate() {
function configResponsive (line 49) | function configResponsive(config: ResponsiveConfig) {
function useResponsive (line 54) | function useResponsive() {
FILE: packages/hooks/src/useSafeState/index.ts
function useSafeState (line 9) | function useSafeState<S>(initialState?: S | (() => S)) {
FILE: packages/hooks/src/useScroll/index.ts
type Position (line 7) | type Position = { left: number; top: number };
type Target (line 9) | type Target = BasicTarget<Element | Document>;
type ScrollListenController (line 10) | type ScrollListenController = (val: Position) => boolean;
function useScroll (line 12) | function useScroll(
FILE: packages/hooks/src/useSelections/__tests__/index.spec.ts
type CaseCallback (line 19) | type CaseCallback<T = number | object> = (data: T[], selected: T[], sele...
FILE: packages/hooks/src/useSelections/demo/demo3.tsx
type DataType (line 13) | interface DataType {
type PaginationType (line 18) | interface PaginationType {
FILE: packages/hooks/src/useSelections/index.ts
type Options (line 6) | interface Options<T> {
function useSelections (line 11) | function useSelections<T>(items: T[], options?: T[] | Options<T>) {
FILE: packages/hooks/src/useSet/index.ts
function useSet (line 4) | function useSet<K>(initialValue?: Iterable<K>) {
FILE: packages/hooks/src/useSetState/demo/demo1.tsx
type State (line 11) | interface State {
FILE: packages/hooks/src/useSetState/demo/demo2.tsx
type State (line 11) | interface State {
FILE: packages/hooks/src/useSetState/index.ts
type SetState (line 5) | type SetState<S extends Record<string, any>> = <K extends keyof S>(
FILE: packages/hooks/src/useSize/__tests__/index.spec.tsx
function Setup (line 34) | function Setup() {
FILE: packages/hooks/src/useSize/index.ts
type Size (line 7) | type Size = { width: number; height: number };
function useSize (line 9) | function useSize(target: BasicTarget): Size | undefined {
FILE: packages/hooks/src/useTextSelection/__tests__/index.spec.ts
function downMouse (line 7) | function downMouse(x: number, y: number, options?: MouseEventInit) {
function upMouse (line 21) | function upMouse(x: number, y: number) {
function initGetSelection (line 34) | function initGetSelection({ top = 0, left = 0, height = 0, width = 0, te...
FILE: packages/hooks/src/useTextSelection/index.ts
type Rect (line 6) | interface Rect {
type State (line 14) | interface State extends Rect {
function getRectFromSelection (line 32) | function getRectFromSelection(selection: Selection | null): Rect {
function useTextSelection (line 52) | function useTextSelection(target?: BasicTarget<Document | Element>): Sta...
FILE: packages/hooks/src/useTheme/index.ts
type ThemeMode (line 5) | enum ThemeMode {
type ThemeModeType (line 11) | type ThemeModeType = `${ThemeMode}`;
type ThemeType (line 13) | type ThemeType = 'light' | 'dark';
type Options (line 44) | type Options = {
function useTheme (line 48) | function useTheme(options: Options = {}) {
FILE: packages/hooks/src/useThrottle/index.ts
function useThrottle (line 5) | function useThrottle<T>(value: T, options?: ThrottleOptions) {
FILE: packages/hooks/src/useThrottle/throttleOptions.ts
type ThrottleOptions (line 1) | interface ThrottleOptions {
FILE: packages/hooks/src/useThrottleEffect/index.ts
function useThrottleEffect (line 7) | function useThrottleEffect(
FILE: packages/hooks/src/useThrottleFn/__tests__/index.spec.ts
type ParamsObj (line 6) | interface ParamsObj {
FILE: packages/hooks/src/useThrottleFn/index.ts
type noop (line 9) | type noop = (...args: any[]) => any;
function useThrottleFn (line 11) | function useThrottleFn<T extends noop>(fn: T, options?: ThrottleOptions) {
FILE: packages/hooks/src/useTimeout/__tests__/index.spec.ts
type ParamsObj (line 5) | interface ParamsObj {
FILE: packages/hooks/src/useTitle/index.ts
type Options (line 5) | interface Options {
constant DEFAULT_OPTIONS (line 9) | const DEFAULT_OPTIONS: Options = {
function useTitle (line 13) | function useTitle(title: string, options: Options = DEFAULT_OPTIONS) {
FILE: packages/hooks/src/useToggle/index.ts
type Actions (line 3) | interface Actions<T> {
function useToggle (line 16) | function useToggle<D, R>(defaultValue: D = false as unknown as D, revers...
FILE: packages/hooks/src/useTrackedEffect/index.ts
type Effect (line 4) | type Effect<T extends DependencyList> = (
FILE: packages/hooks/src/useVirtualList/index.ts
type ItemHeight (line 12) | type ItemHeight<T> = (index: number, data: T) => number;
type Options (line 14) | interface Options<T> {
FILE: packages/hooks/src/useWebSocket/demo/demo1.tsx
type ReadyState (line 4) | enum ReadyState {
FILE: packages/hooks/src/useWebSocket/index.ts
type ReadyState (line 6) | enum ReadyState {
type Options (line 13) | interface Options {
type Result (line 25) | interface Result {
function useWebSocket (line 34) | function useWebSocket(socketUrl: string, options: Options = {}): Result {
FILE: packages/hooks/src/useWhyDidYouUpdate/index.ts
type IProps (line 3) | type IProps = Record<string, any>;
function useWhyDidYouUpdate (line 5) | function useWhyDidYouUpdate(componentName: string, props: IProps) {
FILE: packages/hooks/src/utils/depsAreSame.ts
function depsAreSame (line 3) | function depsAreSame(oldDeps: DependencyList, deps: DependencyList): boo...
FILE: packages/hooks/src/utils/domTarget.ts
type TargetValue (line 5) | type TargetValue<T> = T | undefined | null;
type TargetType (line 7) | type TargetType = HTMLElement | Element | Window | Document;
type BasicTarget (line 9) | type BasicTarget<T extends TargetType = Element> =
function getTargetElement (line 14) | function getTargetElement<T extends TargetType>(target: BasicTarget<T>, ...
FILE: packages/hooks/src/utils/getDocumentOrShadow.ts
type TargetValue (line 4) | type TargetValue<T> = T | undefined | null;
FILE: packages/hooks/src/utils/lodash-polyfill.ts
function isNodeOrWeb (line 3) | function isNodeOrWeb() {
FILE: packages/hooks/src/utils/testingHelpers.ts
function sleep (line 1) | function sleep(time: number) {
function request (line 9) | function request(req: any) {
FILE: packages/use-url-state/src/index.ts
type Options (line 11) | interface Options {
type UrlState (line 27) | type UrlState = Record<string, any>;
type State (line 33) | type State = Partial<{
Condensed preview — 657 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,144K chars).
[
{
"path": ".babelrc",
"chars": 100,
"preview": "{\n \"presets\": [[\"@babel/env\"], \"@babel/react\"],\n \"plugins\": [\"@babel/plugin-transform-runtime\"]\n}\n"
},
{
"path": ".coveralls.yml",
"chars": 71,
"preview": "service_name: travis-pro\nrepo_token: dO6l2UfWXIPwCzK3lmEUKQyQsfzXZUZml\n"
},
{
"path": ".cursor/rules/demo.mdc",
"chars": 636,
"preview": "---\ndescription:\nglobs: components/*/demo/**\nalwaysApply: false\n---\n\n# Demo 规范\n\n- demo 代码尽可能简洁\n- 避免冗余代码,方便用户复制到项目直接使用\n- "
},
{
"path": ".cursor/rules/docs.mdc",
"chars": 555,
"preview": "---\ndescription: 规范项目文档和 Changelog\nglobs: [\"**/CHANGELOG*.md\", \"components/**/index.*.md\"]\nalwaysApply: false\n---\n\n# Cha"
},
{
"path": ".cursor/rules/git.mdc",
"chars": 2032,
"preview": "---\ndescription:\nglobs:\nalwaysApply: true\n---\n# Git 规范\n\n## 开发流程\n\n1. 从保护分支(通常是 `master`)创建新的功能分支\n2. 在新分支上进行开发\n3. 提交 Pull "
},
{
"path": ".cursor/rules/project.mdc",
"chars": 344,
"preview": "---\ndescription:\nglobs:\nalwaysApply: true\n---\n # 项目背景\n\n这是由蚂蚁团队开发的一个高质量、可靠的 React Hooks 库。\n\n- 易学易用\n- 支持 SSR\n- 对输入输出函数做了特殊"
},
{
"path": ".cursor/rules/testing.mdc",
"chars": 214,
"preview": "---\ndescription:\nglobs: **/__tests__/**,**/*.test.tsx,**/*.test.ts\nalwaysApply: false\n---\n # 测试规范\n\n- 使用 vitest 和 @testin"
},
{
"path": ".cursor/rules/typescript.mdc",
"chars": 1208,
"preview": "# TypeScript 规范\n\n## 基本原则\n\n- 所有组件和函数必须提供准确的类型定义\n- 尽量避免使用 `any` 类型,尽可能精确地定义类型\n- 使用接口而非类型别名定义对象结构\n- 导出所有公共接口类型,方便用户使用\n- 严格遵"
},
{
"path": ".editorconfig",
"chars": 244,
"preview": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_tr"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE/pr_cn.md",
"chars": 819,
"preview": "<!--\n首先,感谢你的贡献!😄\n\n新特性请提交至 master 分支。\n在维护者审核通过后会合并。\n请确保填写以下 pull request 的信息,谢谢!~\n-->\n\n[[English Template / 英文模板](https:/"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 1629,
"preview": "<!--\nFirst of all, thank you for your contribution! 😄\n\nNew feature please send a pull request to master branch.\nPull req"
},
{
"path": ".github/workflows/comment-when-needs-more-info.yml",
"chars": 632,
"preview": "name: Comment When Needs More Info Label Added\n\non:\n issues:\n types: [labeled]\n\njobs:\n create-comment:\n runs-on:"
},
{
"path": ".github/workflows/gitleaks.yml",
"chars": 375,
"preview": "name: gitleaks\n\non: [push, pull_request]\n\njobs:\n gitleaks:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/"
},
{
"path": ".github/workflows/issue-close-require.yml",
"chars": 755,
"preview": "name: Issue Close Require\n\non:\n schedule:\n - cron: '0 0 * * *'\n\njobs:\n close-issues:\n runs-on: ubuntu-latest\n "
},
{
"path": ".github/workflows/issue-reply.yml",
"chars": 2320,
"preview": "name: Issue Reply\n\non:\n issues:\n types: [labeled]\n\njobs:\n reply-helper:\n runs-on: ubuntu-latest\n steps:\n "
},
{
"path": ".github/workflows/pkg.pr.new.yml",
"chars": 766,
"preview": "name: Publish Any Commit\n\non:\n push:\n branches:\n - master\n pull_request:\n types: [opened, synchronize, reop"
},
{
"path": ".github/workflows/static.yml",
"chars": 1847,
"preview": "name: Deploy static content to Pages\n\non:\n # Runs on pushes targeting the default branch\n push:\n branches: [\"master"
},
{
"path": ".github/workflows/test.yml",
"chars": 1380,
"preview": "name: Test CI\n\non: [push, pull_request]\n\njobs:\n lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/check"
},
{
"path": ".gitignore",
"chars": 226,
"preview": "dist\nes\nlib\n.docz\nnode_modules\n.history\n.idea\n.vscode\ncoverage\n.doc\n.DS_Store\n.umi\n.umi-production\npage\nlerna-debug.log\n"
},
{
"path": ".gitleaks.toml",
"chars": 4188,
"preview": "title = \"gitleaks config\"\n[[rules]]\n\tdescription = \"AWS Manager ID\"\n\tregex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|AN"
},
{
"path": ".husky/commit-msg",
"chars": 67,
"preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx commitlint --edit $1\n"
},
{
"path": ".husky/pre-commit",
"chars": 67,
"preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpm run pretty"
},
{
"path": ".npmrc",
"chars": 21,
"preview": "shamefully-hoist=true"
},
{
"path": ".travis.yml",
"chars": 275,
"preview": "language: node_js\nnode_js:\n - 'lts/*'\ninstall:\n - pnpm install\n - pnpm install -g surge\nscript:\n - pnpm run build:do"
},
{
"path": "CONTRIBUTING.MD",
"chars": 1337,
"preview": "# Contributing\n\nThe following is a set of guidelines for contributing to `ahooks`. Please spend several minutes reading "
},
{
"path": "CONTRIBUTING.zh-CN.MD",
"chars": 745,
"preview": "# 贡献指南\n\n这篇指南会指导你如何为 `ahooks` 贡献一份自己的力量,请在你要提 issue 或者 pull request 之前花几分钟来阅读一遍这篇指南。\n\n## 透明的开发\n\n我们所有的工作都会放在 [GitHub](http"
},
{
"path": "LICENSE",
"chars": 1071,
"preview": "MIT License\n\nCopyright (c) 2019-present ahooks\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "README.md",
"chars": 3266,
"preview": "<p align=\"center\">\n <a href=\"https://ahooks.js.org\">\n <img width=\"200\" src=\"https://ahooks.js.org/logo.svg\">\n </a>\n"
},
{
"path": "README.zh-CN.md",
"chars": 2795,
"preview": "<p align=\"center\">\n <a href=\"https://ahooks.js.org\">\n <img width=\"200\" src=\"https://ahooks.js.org/logo.svg\">\n </a>\n"
},
{
"path": "SECURITY.md",
"chars": 139,
"preview": "# Security Policy\n\n## Reporting a Vulnerability\n\nPlease report vulnerabilities to brickspert.fjl@antfin.com or guangbo.h"
},
{
"path": "biome.json",
"chars": 813,
"preview": "{\n \"$schema\": \"./node_modules/@biomejs/biome/configuration_schema.json\",\n \"files\": {\n \"ignoreUnknown\": true\n },\n "
},
{
"path": "config/config.ts",
"chars": 5063,
"preview": "import { menus } from './hooks';\n\nconst packages = require('../packages/hooks/package.json');\n\nexport default {\n // ssr"
},
{
"path": "config/hooks.ts",
"chars": 2524,
"preview": "export const menus = [\n {\n title: 'useRequest',\n children: [\n 'useRequest/doc/index',\n 'useRequest/doc/"
},
{
"path": "docs/guide/blog/function.en-US.md",
"chars": 2521,
"preview": "# ahooks function specification\n\nahooks tries its best to help everyone avoid the closure problem by specially processin"
},
{
"path": "docs/guide/blog/function.zh-CN.md",
"chars": 1561,
"preview": "# ahooks 函数处理规范\n\nahooks 通过对输入输出函数做特殊处理,尽力帮助大家避免闭包问题。\n\n**1. ahooks 所有的输出函数,地址都是不会变化的**\n\n```ts\nconst [state, setState] = R"
},
{
"path": "docs/guide/blog/hmr.en-US.md",
"chars": 10426,
"preview": "# React Hooks & react-refresh(HMR)\n\n## What is react-refresh?\n\n[react-refresh-webpack-plugin](https://github.com/pmmmwh/"
},
{
"path": "docs/guide/blog/hmr.zh-CN.md",
"chars": 8263,
"preview": "# React Hooks & react-refresh(HMR)\n\n## 什么是 react-refresh\n\n[react-refresh-webpack-plugin](https://github.com/pmmmwh/react"
},
{
"path": "docs/guide/blog/ssr.en-US.md",
"chars": 4423,
"preview": "# React Hooks & SSR\n\nServer-Side Rendering refers to the page processing technology where the HTML structure of the page"
},
{
"path": "docs/guide/blog/ssr.zh-CN.md",
"chars": 3549,
"preview": "# React Hooks & SSR\n\n服务端渲染(Server-Side Rendering),是指由服务侧完成页面的 HTML 结构拼接的页面处理技术。一般用于解决 SEO 问题和首屏加载速度问题。\n\n由于 SSR 是在非浏览器环境执"
},
{
"path": "docs/guide/blog/strict.en-US.md",
"chars": 3318,
"preview": "# React Hooks & strict mode\n\n## What is strict mode\n\nIn React, there are many historical APIs or writing methods that wi"
},
{
"path": "docs/guide/blog/strict.zh-CN.md",
"chars": 2127,
"preview": "# React Hooks & strict mode\n\n## 什么是严格模式\n\n在 React 中,有很多历史的 API 或写法,在未来版本中会被废弃,现在被标记为不建议使用。既然是不建议使用,那还是可以用的,比如 `componentW"
},
{
"path": "docs/guide/dom.en-US.md",
"chars": 1318,
"preview": "## Hooks of DOM specification\n\nMost of the DOM Hooks will receive the `target` parameter, which indicates the element to"
},
{
"path": "docs/guide/dom.zh-CN.md",
"chars": 1158,
"preview": "## DOM 类 Hooks 使用规范\n\nahooks 大部分 DOM 类 Hooks 都会接收 `target` 参数,表示要处理的元素。\n\n`target` 支持三种类型 `React.MutableRefObject`、`HTMLEl"
},
{
"path": "docs/guide/index.en-US.md",
"chars": 886,
"preview": "## Intro\n\nahooks, pronounced [eɪ hʊks], is a high-quality and reliable React Hooks library. In the current React project"
},
{
"path": "docs/guide/index.zh-CN.md",
"chars": 566,
"preview": "# 介绍\n\nahooks,发音 [eɪ hʊks],是一套高质量可靠的 React Hooks 库。在当前 React 项目研发过程中,一套好用的 React Hooks 库是必不可少的,希望 ahooks 能成为您的选择。\n\n## 特性\n"
},
{
"path": "docs/guide/upgrade.en-US.md",
"chars": 8108,
"preview": "## v2 to v3\n\nCompared with the ahooks v2 version, the changes in the ahooks v3 version mainly include:\n\n- New `useReques"
},
{
"path": "docs/guide/upgrade.zh-CN.md",
"chars": 4819,
"preview": "## v2 to v3\n\n相较于 ahooks v2 版本,ahooks v3 版本的变更主要包括:\n\n- 全新的 `useRequest`\n- 全面支持 SSR\n- 对输入输出函数做特殊处理,避免闭包问题\n- DOM 类 Hooks 支持"
},
{
"path": "docs/index.en-US.md",
"chars": 3207,
"preview": "---\ntitle: ahooks - React Hooks Library\nhero:\n image: /logo.svg\n desc: A high-quality & reliable React Hooks library\n "
},
{
"path": "docs/index.zh-CN.md",
"chars": 2814,
"preview": "---\ntitle: ahooks - React Hooks Library\nhero:\n image: /logo.svg\n desc: 一套高质量可靠的 React Hooks 库\n actions:\n - text: 指"
},
{
"path": "example/.gitkeep",
"chars": 421,
"preview": "import React from 'react';\r\nimport { useBoolean } from 'ahooks';\r\n\r\nexport default function Demo() {\r\n const [state, { "
},
{
"path": "gulpfile.js",
"chars": 1382,
"preview": "const gulp = require('gulp');\nconst babel = require('gulp-babel');\nconst ts = require('gulp-typescript');\nconst del = re"
},
{
"path": "package.json",
"chars": 3251,
"preview": "{\n \"name\": \"ahooks\",\n \"private\": true,\n \"packageManager\": \"pnpm@10.12.4\",\n \"engines\": {\n \"pnpm\": \">=7 <=10\"\n },\n"
},
{
"path": "packages/hooks/gulpfile.js",
"chars": 1621,
"preview": "const commonConfig = require('../../gulpfile');\nconst gulp = require('gulp');\nconst fs = require('fs');\nconst fse = requ"
},
{
"path": "packages/hooks/package.json",
"chars": 1389,
"preview": "{\n \"name\": \"ahooks\",\n \"version\": \"3.9.6\",\n \"description\": \"react hooks library\",\n \"keywords\": [\n \"ahooks\",\n \"u"
},
{
"path": "packages/hooks/src/createDeepCompareEffect/__tests__/index.spec.ts",
"chars": 2551,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { useEffect, useLayoutEffect, useState } from 'react';\n"
},
{
"path": "packages/hooks/src/createDeepCompareEffect/index.ts",
"chars": 621,
"preview": "import { useRef } from 'react';\nimport type { DependencyList, useEffect, useLayoutEffect } from 'react';\nimport { depsEq"
},
{
"path": "packages/hooks/src/createUpdateEffect/__tests__/index.spec.ts",
"chars": 934,
"preview": "import { renderHook } from '@testing-library/react';\nimport { useEffect, useLayoutEffect } from 'react';\nimport { descri"
},
{
"path": "packages/hooks/src/createUpdateEffect/index.ts",
"chars": 608,
"preview": "import { useRef } from 'react';\nimport type { useEffect, useLayoutEffect } from 'react';\n\ntype EffectHookType = typeof u"
},
{
"path": "packages/hooks/src/createUseStorageState/__tests__/index.spec.ts",
"chars": 2729,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { describe, expect, test } from 'vitest';\nimport type {"
},
{
"path": "packages/hooks/src/createUseStorageState/index.ts",
"chars": 4087,
"preview": "import { useRef, useState } from 'react';\nimport useEventListener from '../useEventListener';\nimport useMemoizedFn from "
},
{
"path": "packages/hooks/src/global.d.ts",
"chars": 67,
"preview": "declare module '*.jpg';\n\ninterface Window {\n TEST_SCRIPT?: any;\n}\n"
},
{
"path": "packages/hooks/src/index.spec.ts",
"chars": 266,
"preview": "import { describe, expect, test } from 'vitest';\nimport * as ahooks from '.';\n\ndescribe('ahooks', () => {\n test('export"
},
{
"path": "packages/hooks/src/index.ts",
"chars": 4922,
"preview": "import { createUpdateEffect } from './createUpdateEffect';\nimport useAntdTable from './useAntdTable';\nimport useAsyncEff"
},
{
"path": "packages/hooks/src/useAntdTable/__tests__/index.spec.ts",
"chars": 9574,
"preview": "import type { RenderHookResult } from '@testing-library/react';\nimport { act, renderHook, waitFor } from '@testing-libra"
},
{
"path": "packages/hooks/src/useAntdTable/demo/cache.tsx",
"chars": 4601,
"preview": "import { useState } from 'react';\nimport { Button, Col, Form, Input, Row, Table, Select } from 'antd';\nimport { useAntdT"
},
{
"path": "packages/hooks/src/useAntdTable/demo/form.tsx",
"chars": 3509,
"preview": "import { Button, Col, Form, Input, Row, Table, Select } from 'antd';\nimport { useAntdTable } from 'ahooks';\nimport React"
},
{
"path": "packages/hooks/src/useAntdTable/demo/init.tsx",
"chars": 3643,
"preview": "import { Button, Col, Form, Input, Row, Table, Select } from 'antd';\nimport { useAntdTable } from 'ahooks';\nimport React"
},
{
"path": "packages/hooks/src/useAntdTable/demo/ready.tsx",
"chars": 3859,
"preview": "import { useState } from 'react';\nimport { Button, Col, Form, Input, Row, Table, Select } from 'antd';\nimport { useAntdT"
},
{
"path": "packages/hooks/src/useAntdTable/demo/table.tsx",
"chars": 1054,
"preview": "import { Table } from 'antd';\nimport { useAntdTable } from 'ahooks';\n\ninterface Item {\n name: {\n last: string;\n };\n"
},
{
"path": "packages/hooks/src/useAntdTable/demo/validate.tsx",
"chars": 2457,
"preview": "import { Form, Input, Select, Table } from 'antd';\nimport { useAntdTable } from 'ahooks';\nimport ReactJson from 'react-j"
},
{
"path": "packages/hooks/src/useAntdTable/index.en-US.md",
"chars": 5328,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useAntdTable\n\n`useAntdTable` is implemented based on `useRequest` and encapsulates the co"
},
{
"path": "packages/hooks/src/useAntdTable/index.tsx",
"chars": 6837,
"preview": "import { useEffect, useRef, useState } from 'react';\nimport useMemoizedFn from '../useMemoizedFn';\nimport usePagination "
},
{
"path": "packages/hooks/src/useAntdTable/index.zh-CN.md",
"chars": 3927,
"preview": "---\nnav:\n title: Hooks\n path: /hooks\n---\n\n# useAntdTable\n\n`useAntdTable` 基于 `useRequest` 实现,封装了常用的 [Ant Design Form](h"
},
{
"path": "packages/hooks/src/useAntdTable/types.ts",
"chars": 1624,
"preview": "import type { PaginationOptions, PaginationResult } from '../usePagination/types';\n\nexport type Data = { total: number; "
},
{
"path": "packages/hooks/src/useAsyncEffect/__tests__/index.spec.ts",
"chars": 1665,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { useState } from 'react';\nimport { describe, expect, t"
},
{
"path": "packages/hooks/src/useAsyncEffect/demo/demo1.tsx",
"chars": 626,
"preview": "/**\n * title: Default usage\n * desc: Do async check when component is mounted.\n *\n * title.zh-CN: 基础用法\n * desc.zh-CN: 组件"
},
{
"path": "packages/hooks/src/useAsyncEffect/demo/demo2.tsx",
"chars": 1123,
"preview": "/**\n * title: Break off\n * desc: Use `yield` to stop the execution when effect has been cleaned up.\n *\n * title.zh-CN: 中"
},
{
"path": "packages/hooks/src/useAsyncEffect/index.en-US.md",
"chars": 310,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useAsyncEffect\n\nuseEffect support async function.\n\n## 代码演示\n\n### Default usage\n\n<code src="
},
{
"path": "packages/hooks/src/useAsyncEffect/index.ts",
"chars": 868,
"preview": "import type { DependencyList } from 'react';\nimport { useEffect } from 'react';\nimport { isFunction } from '../utils';\n\n"
},
{
"path": "packages/hooks/src/useAsyncEffect/index.zh-CN.md",
"chars": 280,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useAsyncEffect\n\nuseEffect 支持异步函数。\n\n## 代码演示\n\n### 基础用法\n\n<code src=\"./demo/demo1.tsx\" />\n\n##"
},
{
"path": "packages/hooks/src/useBoolean/__tests__/index.spec.ts",
"chars": 1727,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { describe, expect, test } from 'vitest';\nimport useBoo"
},
{
"path": "packages/hooks/src/useBoolean/demo/demo1.tsx",
"chars": 673,
"preview": "/**\n * title: Basic usage\n * desc: Toggle boolean, default value can be set optionally.\n *\n * title.zh-CN: 基础用法\n * desc."
},
{
"path": "packages/hooks/src/useBoolean/index.en-US.md",
"chars": 1216,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useBoolean\n\nA hook that elegantly manages boolean state.\n\n## Examples\n\n### Default usage\n"
},
{
"path": "packages/hooks/src/useBoolean/index.ts",
"chars": 598,
"preview": "import { useMemo } from 'react';\nimport useToggle from '../useToggle';\n\nexport interface Actions {\n setTrue: () => void"
},
{
"path": "packages/hooks/src/useBoolean/index.zh-CN.md",
"chars": 909,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useBoolean\n\n优雅的管理 boolean 状态的 Hook。\n\n## 代码演示\n\n### 基础用法\n\n<code src=\"./demo/demo1.tsx\" />\n\n"
},
{
"path": "packages/hooks/src/useClickAway/__tests__/index.spec.ts",
"chars": 1645,
"preview": "import { renderHook } from '@testing-library/react';\nimport { afterEach, beforeEach, describe, expect, test } from 'vite"
},
{
"path": "packages/hooks/src/useClickAway/demo/demo1.tsx",
"chars": 555,
"preview": "/**\n * title: Default usage\n * desc: Please click button or outside of button to show effects.\n *\n * title.zh-CN: 基础用法\n "
},
{
"path": "packages/hooks/src/useClickAway/demo/demo2.tsx",
"chars": 583,
"preview": "/**\n * title: Support DOM\n * desc: Support pass in a DOM element or function.\n *\n * title.zh-CN: 支持传入 DOM\n * desc.zh-CN:"
},
{
"path": "packages/hooks/src/useClickAway/demo/demo3.tsx",
"chars": 662,
"preview": "/**\n * title: Support multiple DOM\n * desc: Support pass multiple DOM elements.\n *\n * title.zh-CN: 支持多个 DOM 对象\n * desc.z"
},
{
"path": "packages/hooks/src/useClickAway/demo/demo4.tsx",
"chars": 657,
"preview": "/**\n * title: Listen to other events\n * desc: By setting eventName, you can specify the event to be listened, Try click "
},
{
"path": "packages/hooks/src/useClickAway/demo/demo5.tsx",
"chars": 624,
"preview": "/**\n * title: Support multiple events\n * desc: Set up multiple events, you can try using the mouse click or right click."
},
{
"path": "packages/hooks/src/useClickAway/demo/demo6.tsx",
"chars": 707,
"preview": "/**\n * title: Support shadow DOM\n * desc: Add the addEventListener to shadow DOM root instead of the document\n *\n * titl"
},
{
"path": "packages/hooks/src/useClickAway/index.en-US.md",
"chars": 1371,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useClickAway\n\nListen for click events outside the target element.\n\n## Examples\n\n### Defau"
},
{
"path": "packages/hooks/src/useClickAway/index.ts",
"chars": 1437,
"preview": "import useLatest from '../useLatest';\nimport type { BasicTarget } from '../utils/domTarget';\nimport { getTargetElement }"
},
{
"path": "packages/hooks/src/useClickAway/index.zh-CN.md",
"chars": 1176,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useClickAway\n\n监听目标元素外的点击事件。\n\n## 代码演示\n\n### 基础用法\n\n<code src=\"./demo/demo1.tsx\" />\n\n### 自定义 "
},
{
"path": "packages/hooks/src/useControllableValue/__tests__/index.spec.ts",
"chars": 2467,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { describe, expect, test } from 'vitest';\nimport type {"
},
{
"path": "packages/hooks/src/useControllableValue/demo/demo1.tsx",
"chars": 611,
"preview": "/**\n * title: Uncontrolled component\n * desc: If there is no value in props, the component manage state by self\n *\n * ti"
},
{
"path": "packages/hooks/src/useControllableValue/demo/demo2.tsx",
"chars": 842,
"preview": "/**\n * title: Controlled component\n * desc: If props has the value field, then the state is controlled by it's parent\n *"
},
{
"path": "packages/hooks/src/useControllableValue/demo/demo3.tsx",
"chars": 851,
"preview": "/**\n * title: No value, have onChange component\n * desc: If there is an onChange field in props, the onChange will be tr"
},
{
"path": "packages/hooks/src/useControllableValue/index.en-US.md",
"chars": 2010,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useControllableValue\n\nIn some components, we need the state to be managed by itself or co"
},
{
"path": "packages/hooks/src/useControllableValue/index.ts",
"chars": 1868,
"preview": "import { useMemo, useRef } from 'react';\nimport type { SetStateAction } from 'react';\nimport { isFunction } from '../uti"
},
{
"path": "packages/hooks/src/useControllableValue/index.zh-CN.md",
"chars": 1631,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useControllableValue\n\n在某些组件开发时,我们需要组件的状态既可以自己管理,也可以被外部控制,`useControllableValue` 就是帮你管理这种状"
},
{
"path": "packages/hooks/src/useCookieState/__tests__/index.spec.tsx",
"chars": 3315,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport Cookies from 'js-cookie';\nimport { describe, expect, te"
},
{
"path": "packages/hooks/src/useCookieState/demo/demo1.tsx",
"chars": 521,
"preview": "/**\n * title: Store state into Cookie\n * desc: Refresh this page and you will get the state from Cookie.\n *\n * title.zh-"
},
{
"path": "packages/hooks/src/useCookieState/demo/demo2.tsx",
"chars": 943,
"preview": "/**\n * title: SetState can receive function\n * desc: Function updater is also acceptable with useCookieState's setState,"
},
{
"path": "packages/hooks/src/useCookieState/demo/demo3.tsx",
"chars": 1196,
"preview": "/**\n * title: Use the option property to configure Cookie\n * desc: 'Available options: defaultValue、expires、path、domain、"
},
{
"path": "packages/hooks/src/useCookieState/index.en-US.md",
"chars": 2997,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useCookieState\n\nA Hook that store state into Cookie.\n\n## Examples\n\n### Store state into C"
},
{
"path": "packages/hooks/src/useCookieState/index.ts",
"chars": 1300,
"preview": "import Cookies from 'js-cookie';\nimport { useState } from 'react';\nimport useMemoizedFn from '../useMemoizedFn';\nimport "
},
{
"path": "packages/hooks/src/useCookieState/index.zh-CN.md",
"chars": 2416,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useCookieState\n\n一个可以将状态存储在 Cookie 中的 Hook 。\n\n## 代码演示\n\n### 将 state 存储在 Cookie 中\n\n<code src"
},
{
"path": "packages/hooks/src/useCountDown/__tests__/index.spec.ts",
"chars": 6846,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { afterAll, beforeAll, describe, expect, test, vi } fro"
},
{
"path": "packages/hooks/src/useCountDown/demo/demo1.tsx",
"chars": 563,
"preview": "/**\n * title: Basic Usage\n * desc: Basic countdown management.\n *\n * title.zh-CN: 基础用法\n * desc.zh-CN: 基础的倒计时管理。\n */\n\nimp"
},
{
"path": "packages/hooks/src/useCountDown/demo/demo2.tsx",
"chars": 915,
"preview": "/**\n * title: Adcanved Uasge\n * desc: Dynamic change targetDate, suitable for verification codes or similar scenarios.\n "
},
{
"path": "packages/hooks/src/useCountDown/demo/demo3.tsx",
"chars": 360,
"preview": "/**\n * title: The rest of time\n * desc: A countdown to the number of milliseconds remaining.\n *\n * title.zh-CN: 剩余时间\n * "
},
{
"path": "packages/hooks/src/useCountDown/index.en-US.md",
"chars": 2059,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useCountDown\n\nA hook for manage countdown.\n\n## Countdown to target time\n\n<code src=\"./dem"
},
{
"path": "packages/hooks/src/useCountDown/index.ts",
"chars": 2053,
"preview": "import dayjs from 'dayjs';\nimport { useEffect, useMemo, useState } from 'react';\nimport useLatest from '../useLatest';\ni"
},
{
"path": "packages/hooks/src/useCountDown/index.zh-CN.md",
"chars": 1441,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useCountDown\n\n一个用于管理倒计时的 Hook。\n\n## 到未来某一时间点的倒计时\n\n<code src=\"./demo/demo1.tsx\" />\n\n## 配置项动"
},
{
"path": "packages/hooks/src/useCounter/__tests__/index.spec.ts",
"chars": 1254,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { describe, expect, test } from 'vitest';\nimport type {"
},
{
"path": "packages/hooks/src/useCounter/demo/demo1.tsx",
"chars": 1040,
"preview": "/**\n * title: Basic usage\n * desc: Simple example of counter management.\n *\n * title.zh-CN: 基础用法\n * desc.zh-CN: 简单的 coun"
},
{
"path": "packages/hooks/src/useCounter/index.en-US.md",
"chars": 1314,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useCounter\n\nA hook that manage counter.\n\n## Examples\n\n### Default usage\n\n<code src=\"./dem"
},
{
"path": "packages/hooks/src/useCounter/index.ts",
"chars": 1607,
"preview": "import { useState } from 'react';\nimport useMemoizedFn from '../useMemoizedFn';\nimport { isNumber } from '../utils';\n\nex"
},
{
"path": "packages/hooks/src/useCounter/index.zh-CN.md",
"chars": 1026,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useCounter\n\n管理计数器的 Hook。\n\n## 代码演示\n\n### 基础用法\n\n<code src=\"./demo/demo1.tsx\" />\n\n## API\n\n```"
},
{
"path": "packages/hooks/src/useCreation/__tests__/index.spec.ts",
"chars": 921,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { useState } from 'react';\nimport { describe, expect, t"
},
{
"path": "packages/hooks/src/useCreation/demo/demo1.tsx",
"chars": 736,
"preview": "/**\n * title: Make sure only one instance is created\n * desc: You can click the \"Rerender\" button and trigger the update"
},
{
"path": "packages/hooks/src/useCreation/index.en-US.md",
"chars": 1443,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useCreation\n\n`useCreation` is the replacement for `useMemo` or `useRef`.\n\n`useMemo` can't"
},
{
"path": "packages/hooks/src/useCreation/index.ts",
"chars": 510,
"preview": "import type { DependencyList } from 'react';\nimport { useRef } from 'react';\nimport depsAreSame from '../utils/depsAreSa"
},
{
"path": "packages/hooks/src/useCreation/index.zh-CN.md",
"chars": 1162,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useCreation\n\n`useCreation` 是 `useMemo` 或 `useRef` 的替代品。\n\n因为 `useMemo` 不能保证被 memo 的值一定不会被重"
},
{
"path": "packages/hooks/src/useDebounce/__tests__/index.spec.ts",
"chars": 865,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { describe, expect, test } from 'vitest';\nimport { slee"
},
{
"path": "packages/hooks/src/useDebounce/debounceOptions.ts",
"chars": 117,
"preview": "export interface DebounceOptions {\n wait?: number;\n leading?: boolean;\n trailing?: boolean;\n maxWait?: number;\n}\n"
},
{
"path": "packages/hooks/src/useDebounce/demo/demo1.tsx",
"chars": 647,
"preview": "/**\n * title: Default usage\n * desc: DebouncedValue will change after the input ends 500ms.\n *\n * title.zh-CN: 基础用法\n * d"
},
{
"path": "packages/hooks/src/useDebounce/index.en-US.md",
"chars": 1197,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDebounce\n\nA hook that deal with the debounced value.\n\n## Examples\n\n### Default usage\n\n"
},
{
"path": "packages/hooks/src/useDebounce/index.ts",
"chars": 446,
"preview": "import { useEffect, useState } from 'react';\nimport useDebounceFn from '../useDebounceFn';\nimport type { DebounceOptions"
},
{
"path": "packages/hooks/src/useDebounce/index.zh-CN.md",
"chars": 733,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDebounce\n\n用来处理防抖值的 Hook。\n\n## 代码演示\n\n### 基础用法\n\n<code src=\"./demo/demo1.tsx\" />\n\n## API\n\n"
},
{
"path": "packages/hooks/src/useDebounceEffect/__tests__/index.spec.ts",
"chars": 2688,
"preview": "import { act, type RenderHookResult, renderHook } from '@testing-library/react';\nimport { describe, expect, test, vi } f"
},
{
"path": "packages/hooks/src/useDebounceEffect/demo/demo1.tsx",
"chars": 712,
"preview": "import { useDebounceEffect } from 'ahooks';\nimport { useState } from 'react';\n\nexport default () => {\n const [value, se"
},
{
"path": "packages/hooks/src/useDebounceEffect/index.en-US.md",
"chars": 1529,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDebounceEffect\n\nDebounce your `useEffect`.\n\n## Examples\n\n### Default usage\n\n<code src="
},
{
"path": "packages/hooks/src/useDebounceEffect/index.ts",
"chars": 623,
"preview": "import { useEffect, useState } from 'react';\nimport type { DependencyList, EffectCallback } from 'react';\nimport type { "
},
{
"path": "packages/hooks/src/useDebounceEffect/index.zh-CN.md",
"chars": 946,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDebounceEffect\n\n为 `useEffect` 增加防抖的能力。\n\n## 代码演示\n\n### 基础用法\n\n<code src=\"./demo/demo1.tsx"
},
{
"path": "packages/hooks/src/useDebounceFn/__tests__/index.spec.ts",
"chars": 1443,
"preview": "import { act, type RenderHookResult, renderHook } from '@testing-library/react';\nimport { describe, expect, test } from "
},
{
"path": "packages/hooks/src/useDebounceFn/demo/demo1.tsx",
"chars": 651,
"preview": "/**\n * title: Default usage\n * desc: Frequent calls run, but the function is executed only after all the clicks have com"
},
{
"path": "packages/hooks/src/useDebounceFn/index.en-US.md",
"chars": 1802,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDebounceFn\n\nA hook that deal with the debounced function.\n\n## Examples\n\n### Default us"
},
{
"path": "packages/hooks/src/useDebounceFn/index.ts",
"chars": 1030,
"preview": "import { debounce } from '../utils/lodash-polyfill';\nimport { useMemo } from 'react';\nimport type { DebounceOptions } fr"
},
{
"path": "packages/hooks/src/useDebounceFn/index.zh-CN.md",
"chars": 1203,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDebounceFn\n\n用来处理防抖函数的 Hook。\n\n## 代码演示\n\n### 基础用法\n\n<code src=\"./demo/demo1.tsx\" />\n\n## AP"
},
{
"path": "packages/hooks/src/useDeepCompareEffect/__tests__/index.spec.ts",
"chars": 678,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { useState } from 'react';\nimport { describe, expect, t"
},
{
"path": "packages/hooks/src/useDeepCompareEffect/demo/demo1.tsx",
"chars": 714,
"preview": "import { useDeepCompareEffect } from 'ahooks';\nimport { useEffect, useState, useRef } from 'react';\n\nexport default () ="
},
{
"path": "packages/hooks/src/useDeepCompareEffect/index.en-US.md",
"chars": 364,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDeepCompareEffect\n\nUsage is the same as `useEffect`, but deps are compared with [react"
},
{
"path": "packages/hooks/src/useDeepCompareEffect/index.tsx",
"chars": 157,
"preview": "import { useEffect } from 'react';\nimport { createDeepCompareEffect } from '../createDeepCompareEffect';\n\nexport default"
},
{
"path": "packages/hooks/src/useDeepCompareEffect/index.zh-CN.md",
"chars": 323,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDeepCompareEffect\n\n用法与 useEffect 一致,但 deps 通过 [react-fast-compare](https://www.npmjs.c"
},
{
"path": "packages/hooks/src/useDeepCompareLayoutEffect/__tests__/index.spec.ts",
"chars": 680,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { useState } from 'react';\nimport { describe, expect, t"
},
{
"path": "packages/hooks/src/useDeepCompareLayoutEffect/demo/demo1.tsx",
"chars": 738,
"preview": "import { useDeepCompareLayoutEffect } from 'ahooks';\nimport { useLayoutEffect, useState, useRef } from 'react';\n\nexport "
},
{
"path": "packages/hooks/src/useDeepCompareLayoutEffect/index.en-US.md",
"chars": 382,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDeepCompareLayoutEffect\n\nUsage is the same as `useLayoutEffect`, but deps are compared"
},
{
"path": "packages/hooks/src/useDeepCompareLayoutEffect/index.tsx",
"chars": 169,
"preview": "import { useLayoutEffect } from 'react';\nimport { createDeepCompareEffect } from '../createDeepCompareEffect';\n\nexport d"
},
{
"path": "packages/hooks/src/useDeepCompareLayoutEffect/index.zh-CN.md",
"chars": 341,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDeepCompareLayoutEffect\n\n用法与 useLayoutEffect 一致,但 deps 通过 [react-fast-compare](https:/"
},
{
"path": "packages/hooks/src/useDocumentVisibility/__tests__/index.spec.ts",
"chars": 1530,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { afterAll, describe, expect, test, vi } from 'vitest';"
},
{
"path": "packages/hooks/src/useDocumentVisibility/demo/demo1.tsx",
"chars": 505,
"preview": "/**\n * title: Default usage\n * desc: Listen to document visibility change.\n *\n * title.zh-CN: 基础用法\n * desc.zh-CN: 监听 doc"
},
{
"path": "packages/hooks/src/useDocumentVisibility/index.en-US.md",
"chars": 662,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDocumentVisibility\n\nA Hook can tell if the page is visible, refer to [visibilityState "
},
{
"path": "packages/hooks/src/useDocumentVisibility/index.ts",
"chars": 678,
"preview": "import { useState } from 'react';\nimport useEventListener from '../useEventListener';\nimport isBrowser from '../utils/is"
},
{
"path": "packages/hooks/src/useDocumentVisibility/index.zh-CN.md",
"chars": 612,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDocumentVisibility\n\n监听页面是否可见,参考 [visibilityState API](https://developer.mozilla.org/do"
},
{
"path": "packages/hooks/src/useDrag/__tests__/index.spec.ts",
"chars": 2285,
"preview": "import { renderHook } from '@testing-library/react';\nimport { beforeEach, describe, expect, test, vi } from 'vitest';\nim"
},
{
"path": "packages/hooks/src/useDrag/index.ts",
"chars": 2221,
"preview": "import { useRef } from 'react';\nimport useLatest from '../useLatest';\nimport useMount from '../useMount';\nimport { isStr"
},
{
"path": "packages/hooks/src/useDrop/__tests__/index.spec.ts",
"chars": 5322,
"preview": "import { renderHook } from '@testing-library/react';\nimport { describe, expect, test, vi } from 'vitest';\nimport type { "
},
{
"path": "packages/hooks/src/useDrop/demo/demo1.tsx",
"chars": 1818,
"preview": "/**\n * title: Basic usage\n * desc: The drop area can accept files, uri, text or one of the boxes below.\n *\n * title.zh-C"
},
{
"path": "packages/hooks/src/useDrop/demo/demo2.tsx",
"chars": 743,
"preview": "/**\n * title: Customize Image\n * desc: Customize image that follow the mouse pointer during dragging.\n *\n * title.zh-CN:"
},
{
"path": "packages/hooks/src/useDrop/index.en-US.md",
"chars": 5417,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDrop & useDrag\n\nA pair of hooks to help you manage data transfer between drag and drop"
},
{
"path": "packages/hooks/src/useDrop/index.ts",
"chars": 3954,
"preview": "import useLatest from '../useLatest';\nimport type { BasicTarget } from '../utils/domTarget';\nimport { getTargetElement }"
},
{
"path": "packages/hooks/src/useDrop/index.zh-CN.md",
"chars": 4430,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDrop & useDrag\n\n处理元素拖拽的 Hook。\n\n> useDrop 可以单独使用来接收文件、文字和网址的拖拽。\n>\n> useDrag 允许一个 DOM 节点"
},
{
"path": "packages/hooks/src/useDynamicList/__tests__/index.spec.ts",
"chars": 5468,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { afterAll, afterEach, describe, expect, test, vi } fro"
},
{
"path": "packages/hooks/src/useDynamicList/demo/demo1.tsx",
"chars": 1677,
"preview": "/**\n * title: Basic usage\n * desc: Dynamic list management\n *\n * title.zh-CN: 基础用法\n * desc.zh-CN: 管理动态列表\n */\n\nimport { M"
},
{
"path": "packages/hooks/src/useDynamicList/demo/demo2.tsx",
"chars": 2226,
"preview": "/**\n * title: Used in antd Form\n * desc: Used in antd Form, a component can be packaged independently, like DynamicInput"
},
{
"path": "packages/hooks/src/useDynamicList/demo/demo3.tsx",
"chars": 2092,
"preview": "/**\n * title: Used in antd Form\n * desc: Pay attention to the use of sortList. The data of antd Form is not sorted corre"
},
{
"path": "packages/hooks/src/useDynamicList/demo/demo4.tsx",
"chars": 3380,
"preview": "/**\n * title: Draggable dynamic table\n * desc: Using antd Table to build dynamic table form.\n *\n * title.zh-CN: 可拖拽的动态表格"
},
{
"path": "packages/hooks/src/useDynamicList/index.en-US.md",
"chars": 3875,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDynamicList\n\nA hook that helps you manage dynamic list and generate unique key for eac"
},
{
"path": "packages/hooks/src/useDynamicList/index.ts",
"chars": 4579,
"preview": "import { useCallback, useRef, useState } from 'react';\nimport isDev from '../utils/isDev';\n\nconst useDynamicList = <T>(i"
},
{
"path": "packages/hooks/src/useDynamicList/index.zh-CN.md",
"chars": 3134,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useDynamicList\n\n一个帮助你管理动态列表状态,并能生成唯一 key 的 Hook。\n\n## 代码演示\n\n### 基础用法\n\n<code src=\"./demo/de"
},
{
"path": "packages/hooks/src/useEventEmitter/__tests__/index.spec.ts",
"chars": 906,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { useState } from 'react';\nimport { describe, expect, t"
},
{
"path": "packages/hooks/src/useEventEmitter/demo/demo1.tsx",
"chars": 1306,
"preview": "/**\n * title: Parent component shares a event\n * desc: The parent component creates a `focus$` event emitter, and passes"
},
{
"path": "packages/hooks/src/useEventEmitter/index.en-US.md",
"chars": 1817,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useEventEmitter\n\nSometimes it is difficult to pass events between multiple components. By"
},
{
"path": "packages/hooks/src/useEventEmitter/index.ts",
"chars": 1054,
"preview": "import { useRef, useEffect } from 'react';\n\ntype Subscription<T> = (val: T) => void;\n\nexport class EventEmitter<T> {\n p"
},
{
"path": "packages/hooks/src/useEventEmitter/index.zh-CN.md",
"chars": 1190,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useEventEmitter\n\n在多个组件之间进行事件通知有时会让人非常头疼,借助 EventEmitter ,可以让这一过程变得更加简单。\n\n在组件中调用 `useEvent"
},
{
"path": "packages/hooks/src/useEventListener/__tests__/index.spec.ts",
"chars": 2135,
"preview": "import { renderHook } from '@testing-library/react';\nimport { afterEach, beforeEach, describe, expect, test } from 'vite"
},
{
"path": "packages/hooks/src/useEventListener/demo/demo1.tsx",
"chars": 505,
"preview": "/**\n * title: Default usage\n * desc: Click the button to preview.\n *\n * title.zh-CN: 基础用法\n * desc.zh-CN: 点击按钮查看效果。\n */\n\n"
},
{
"path": "packages/hooks/src/useEventListener/demo/demo2.tsx",
"chars": 387,
"preview": "/**\n * title: Listen keydown\n * desc: Press any key to preview.\n *\n * title.zh-CN: 监听 keydown 事件\n * desc.zh-CN: 按下键盘查看效果"
},
{
"path": "packages/hooks/src/useEventListener/demo/demo3.tsx",
"chars": 558,
"preview": "/**\n * title: Listen to multiple events.\n * desc: Mouse hover or over the button to preview.\n *\n * title.zh-CN: 监听多个事件\n "
},
{
"path": "packages/hooks/src/useEventListener/index.en-US.md",
"chars": 3329,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useEventListener\n\nUse addEventListener elegant by Hook.\n\n## Examples\n\n### Default usage\n\n"
},
{
"path": "packages/hooks/src/useEventListener/index.ts",
"chars": 2505,
"preview": "import useLatest from '../useLatest';\nimport type { BasicTarget } from '../utils/domTarget';\nimport { getTargetElement }"
},
{
"path": "packages/hooks/src/useEventListener/index.zh-CN.md",
"chars": 2372,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useEventListener\n\n优雅的使用 addEventListener。\n\n## 代码演示\n\n### 基础用法\n\n<code src=\"./demo/demo1.tsx"
},
{
"path": "packages/hooks/src/useEventTarget/__tests__/index.spec.ts",
"chars": 1709,
"preview": "import { act, renderHook } from '@testing-library/react';\nimport { describe, expect, test } from 'vitest';\nimport useEve"
},
{
"path": "packages/hooks/src/useEventTarget/demo/demo1.tsx",
"chars": 510,
"preview": "/**\n * title: Basic usage\n * desc: Controlled input component,support reset.\n *\n * title.zh-CN: 基础用法\n * desc.zh-CN: 受控的 "
},
{
"path": "packages/hooks/src/useEventTarget/demo/demo2.tsx",
"chars": 650,
"preview": "/**\n * title: Custom transformer function\n * desc: Controlled input component with number input only\n *\n * title.zh-CN: "
},
{
"path": "packages/hooks/src/useEventTarget/index.en-US.md",
"chars": 1351,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useEventTarget\n\nA hook that encapsulates `onChange` and `value` logic for form controls t"
},
{
"path": "packages/hooks/src/useEventTarget/index.ts",
"chars": 976,
"preview": "import { useCallback, useState } from 'react';\nimport useLatest from '../useLatest';\nimport { isFunction } from '../util"
},
{
"path": "packages/hooks/src/useEventTarget/index.zh-CN.md",
"chars": 1023,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useEventTarget\n\n常见表单控件(通过 e.target.value 获取表单值) 的 onChange 跟 value 逻辑封装,支持自定义值转换和重置功能。\n\n#"
},
{
"path": "packages/hooks/src/useExternal/__tests__/index.spec.ts",
"chars": 3691,
"preview": "import { act, fireEvent, renderHook } from '@testing-library/react';\nimport { beforeEach, describe, expect, test, vi } f"
},
{
"path": "packages/hooks/src/useExternal/demo/demo1.tsx",
"chars": 620,
"preview": "/**\n * title: Default usage\n * desc: Load js file, such as [test-external-script.js](/useExternal/test-external-script.j"
},
{
"path": "packages/hooks/src/useExternal/demo/demo2.tsx",
"chars": 1485,
"preview": "/**\n * title: Load style dynamically\n * desc: Load css file, such as [bootstrap-badge.css](/useExternal/bootstrap-badge."
},
{
"path": "packages/hooks/src/useExternal/demo/demo3.tsx",
"chars": 1896,
"preview": "/**\n * title: Load style dynamically\n * desc: Load css file, such as [bootstrap-badge.css](/useExternal/bootstrap-badge."
},
{
"path": "packages/hooks/src/useExternal/index.en-US.md",
"chars": 1946,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useExternal\n\nDynamically load JS or CSS, useExternal can ensure that the resource are glo"
},
{
"path": "packages/hooks/src/useExternal/index.ts",
"chars": 4062,
"preview": "import { useEffect, useRef, useState } from 'react';\n\ntype JsOptions = {\n type: 'js';\n js?: Partial<HTMLScriptElement>"
},
{
"path": "packages/hooks/src/useExternal/index.zh-CN.md",
"chars": 1388,
"preview": "---\nnav:\n path: /hooks\n---\n\n# useExternal\n\n动态注入 JS 或 CSS 资源,useExternal 可以保证资源全局唯一。\n\n## 代码演示\n\n### 基础用法\n\n<code src=\"./de"
},
{
"path": "packages/hooks/src/useFavicon/__tests__/index.spec.tsx",
"chars": 1105,
"preview": "import { renderHook } from '@testing-library/react';\nimport { describe, expect, test } from 'vitest';\nimport useFavicon "
},
{
"path": "packages/hooks/src/useFavicon/demo/demo1.tsx",
"chars": 855,
"preview": "/**\n * title: Basic usage\n * desc: Set favicon\n *\n * title.zh-CN: 基础用法\n * desc.zh-CN: 设置 favicon\n */\n\nimport { useState "
}
]
// ... and 457 more files (download for full content)
About this extraction
This page contains the full source code of the alibaba/hooks GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 657 files (1.0 MB), approximately 295.1k tokens, and a symbol index with 362 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.