From efe0dbaf27d368d3616bd01cf140cbd7a345604b Mon Sep 17 00:00:00 2001 From: mannix-lei Date: Mon, 29 Jan 2024 17:09:14 +0800 Subject: [PATCH] feat: add useMediaQuery --- config/hooks.ts | 1 + packages/hooks/src/index.ts | 2 + .../src/useMediaQuery/__tests__/index.test.ts | 42 +++++++++++++++++++ .../hooks/src/useMediaQuery/demo/demo1.tsx | 25 +++++++++++ .../hooks/src/useMediaQuery/index.en-US.md | 25 +++++++++++ packages/hooks/src/useMediaQuery/index.ts | 32 ++++++++++++++ .../hooks/src/useMediaQuery/index.zh-CN.md | 25 +++++++++++ 7 files changed, 152 insertions(+) create mode 100644 packages/hooks/src/useMediaQuery/__tests__/index.test.ts create mode 100644 packages/hooks/src/useMediaQuery/demo/demo1.tsx create mode 100644 packages/hooks/src/useMediaQuery/index.en-US.md create mode 100644 packages/hooks/src/useMediaQuery/index.ts create mode 100644 packages/hooks/src/useMediaQuery/index.zh-CN.md diff --git a/config/hooks.ts b/config/hooks.ts index fa74274cf2..63a7c6594b 100644 --- a/config/hooks.ts +++ b/config/hooks.ts @@ -100,6 +100,7 @@ export const menus = [ 'useScroll', 'useSize', 'useFocusWithin', + 'useMediaQuery', ], }, { diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index 58cefd9c79..dfe9923918 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -75,6 +75,7 @@ import useVirtualList from './useVirtualList'; import useWebSocket from './useWebSocket'; import useWhyDidYouUpdate from './useWhyDidYouUpdate'; import useMutationObserver from './useMutationObserver'; +import useMediaQuery from './useMediaQuery'; export { useRequest, @@ -156,4 +157,5 @@ export { useRafTimeout, useResetState, useMutationObserver, + useMediaQuery, }; diff --git a/packages/hooks/src/useMediaQuery/__tests__/index.test.ts b/packages/hooks/src/useMediaQuery/__tests__/index.test.ts new file mode 100644 index 0000000000..5a59faf252 --- /dev/null +++ b/packages/hooks/src/useMediaQuery/__tests__/index.test.ts @@ -0,0 +1,42 @@ +import { renderHook, act } from '../../utils/tests'; +import useMediaQuery from '..'; + +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // deprecated + removeListener: jest.fn(), // deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); + +describe('useMediaQuery', () => { + function changeWidth(width: number) { + act(() => { + (global as any).innerWidth = width; + (global as any).dispatchEvent(new Event('resize')); + }); + } + changeWidth(1024); + + it('should be defined', () => { + expect(useMediaQuery).toBeDefined(); + }); + + it('should return the correct value', () => { + const { result } = renderHook(() => useMediaQuery(['(min-width: 1024px)'], [true], false)); + expect(result.current[0]).toBe(false); + }); + + it('should return the correct value when the media query changes', () => { + const { result } = renderHook(() => useMediaQuery(['(min-width: 1024px)'], [true], false)); + expect(result.current[0]).toBe(false); + changeWidth(768); + expect(result.current[0]).toBe(false); + }); +}); diff --git a/packages/hooks/src/useMediaQuery/demo/demo1.tsx b/packages/hooks/src/useMediaQuery/demo/demo1.tsx new file mode 100644 index 0000000000..d77742e18c --- /dev/null +++ b/packages/hooks/src/useMediaQuery/demo/demo1.tsx @@ -0,0 +1,25 @@ +/** + * title: Get responsive info in components + * desc: By calling useMediaQuery in components, you can retrieve the responsive infomation of the browser page and subscribe to it at the same time. + * + * title.zh-CN: 在组件中获取响应式信息 + * desc.zh-CN: 在组件中调用 useMediaQuery 可以获取并订阅浏览器窗口的响应式信息。 + */ + +import React from 'react'; +import { useMediaQuery } from 'ahooks'; + +export default function () { + const [value, currentQuery] = useMediaQuery( + ['(min-width: 1024px)', '(min-width: 768px)', '(min-width: 320px)'], + [3, 2, 1], + 0, + ); + + return ( +
+
currentValue: {value.toString()}
+
currentQuery: {currentQuery}
+
+ ); +} diff --git a/packages/hooks/src/useMediaQuery/index.en-US.md b/packages/hooks/src/useMediaQuery/index.en-US.md new file mode 100644 index 0000000000..1875835a49 --- /dev/null +++ b/packages/hooks/src/useMediaQuery/index.en-US.md @@ -0,0 +1,25 @@ +--- +nav: + path: /hooks +--- + +# useMediaQuery + +React Hook for getting responsive info. + +## Examples + +### Get responsive info in components + + + +## API + +```typescript +interface ResponsiveInfo { + value: number; + currentQuery: string; +} + +function useMediaQuery(queries: K[], values: T[], defaultValue: T): ResponsiveInfo; +``` diff --git a/packages/hooks/src/useMediaQuery/index.ts b/packages/hooks/src/useMediaQuery/index.ts new file mode 100644 index 0000000000..1cbf30560a --- /dev/null +++ b/packages/hooks/src/useMediaQuery/index.ts @@ -0,0 +1,32 @@ +import { useRef, useState } from 'react'; + +function useMediaQuery(queries: K[], values: T[], defaultValue: T) { + const mediaQueryLists = useRef([]); + const [value, setValue] = useState(defaultValue); + const [currentQuery, setCurrentQuery] = useState((queries as MediaQueryList[])[0].media); + + const getValue = () => { + const index = mediaQueryLists.current.findIndex((mql) => mql.matches); + setCurrentQuery(mediaQueryLists.current?.[index]?.media || ''); + return values?.[index] || defaultValue; + }; + + const handleQueryListener = () => setValue(getValue()); + + const handleMediaQueryLists = () => { + mediaQueryLists.current.forEach((mql) => + mql.removeEventListener('change', handleQueryListener), + ); + mediaQueryLists.current = queries.map((query) => query && window.matchMedia(query as string)); + mediaQueryLists.current.forEach((mql) => mql.addEventListener('change', handleQueryListener)); + setValue(getValue()); + }; + + useState(() => { + handleMediaQueryLists(); + }); + + return [value, currentQuery]; +} + +export default useMediaQuery; diff --git a/packages/hooks/src/useMediaQuery/index.zh-CN.md b/packages/hooks/src/useMediaQuery/index.zh-CN.md new file mode 100644 index 0000000000..2cd3291788 --- /dev/null +++ b/packages/hooks/src/useMediaQuery/index.zh-CN.md @@ -0,0 +1,25 @@ +--- +nav: + path: /hooks +--- + +# useMediaQuery + +获取响应式信息。 + +## 代码演示 + +### 在组件中获取响应式信息 + + + +## API + +```typescript +interface ResponsiveInfo { + value: number; + currentQuery: string; +} + +function useMediaQuery(queries: K[], values: T[], defaultValue: T): ResponsiveInfo; +```