Skip to content

Commit

Permalink
feat: add useUrlState hook
Browse files Browse the repository at this point in the history
  • Loading branch information
zuofenghua committed Jul 28, 2021
1 parent 1092ac7 commit a64371b
Show file tree
Hide file tree
Showing 10 changed files with 358 additions and 47 deletions.
1 change: 1 addition & 0 deletions docs/.vitepress/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const Router = {
'useInViewport',
],
State: [
'useUrlState',
'useToggle',
'useLocalStorageState',
// 'useLocalforage',
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
}
},
"scripts": {
"dev": "vitepress-fc dev docs",
"dev": "vitepress-fc dev docs --host",
"build": "vite build && yarn type",
"type": "tsc -d",
"test": "jest",
Expand All @@ -45,6 +45,7 @@
"devDependencies": {
"@types/jest": "^25.1.3",
"@types/lodash": "^4.14.159",
"@types/qs": "^6.9.7",
"@types/whatwg-fetch": "^0.0.33",
"@typescript-eslint/eslint-plugin": "^4.14.1",
"@typescript-eslint/parser": "^4.14.1",
Expand All @@ -68,13 +69,14 @@
"screenfull": "^5.1.0",
"ts-jest": "^25.2.1",
"typescript": "^4.1.5",
"vitepress-for-component": "^0.14.4",
"vitepress-for-component": "^0.14.8",
"vue": "^3.0.1",
"whatwg-fetch": "^3.5.0"
},
"homepage": "https://dewfall123.github.io/ahooks-vue/",
"dependencies": {
"lodash": "^4.17.20",
"qs": "^6.10.1",
"vue-demi": "^0.6.0"
},
"peerDependencies": {
Expand Down
24 changes: 13 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
export * from './useToggle';
export * from './useFullscreen';
export * from './useLocalStorageState';
export * from './useAxios';
export * from './useDebounce';
export * from './useDocumentVisibility';
export * from './useFullscreen';
export * from './useHover';
export * from './useInViewport';
export * from './useSize';
export * from './useWorkerFunction';
export * from './useThrottle';
export * from './useDebounce';
export * from './useTable';
export * from './useKeyPress';
export * from './useLocalforage';
export * from './useLocalStorageState';
export * from './useOLAP';
export * from './useRequest';
export * from './usePaginatedRequest';
export * from './useAxios';
export * from './useLocalforage';
export * from './useRequest';
export * from './useSize';
export * from './useTable';
export * from './useThrottle';
export * from './useToggle';
export * from './useUrlState';
export * from './useWorkerFunction';

2 changes: 1 addition & 1 deletion src/useToggle/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The API is different from [ahooks](https://ahooks.js.org/hooks/state/use-toggle)
<demo src="./demo/demo.vue"
language="vue"
title="Basic usage"
desc="click the button, look at the value.">
desc="Accept two parameters, switch between them.">
</demo>

<code src="./demo/demo1.tsx" />
Expand Down
22 changes: 22 additions & 0 deletions src/useUrlState/demo/demo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template>
<div>
<p>{{ state }}</p>
<button @click="addCount()">count++</button>
<button @click="subCount()">count--</button>
</div>
</template>

<script lang="ts" setup>
import { useUrlState } from 'ahooks-vue';
const routerPushFn = (s: string) => (location.hash = s);
const DefaultState = { count: 0, page: 1 };
const state = useUrlState(routerPushFn, DefaultState, {
localStorageKey: 'localStorageKey',
});
const addCount = () => state.value.count++;
const subCount = () => state.value.count--;
</script>
1 change: 1 addition & 0 deletions src/useUrlState/demo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './demo.vue';
48 changes: 48 additions & 0 deletions src/useUrlState/index.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
map:
path: /use-url-state
---

# useUrlState

一个同步组件内部状态和 query 参数的 hook。

::: warning
API 有改动,见[ahooks](https://ahooks.js.org/zh-CN/hooks/state)
:::

## Examples

### Basic usage

<demo src="./demo/demo.vue"
language="vue"
title="Basic usage"
desc="同步条件到url,在需要通过url分享复杂参数场景十分有用">
</demo>

## API

```typescript
const state = useUrlState(routerPush, DefaultState, {
localStorageKey: 'localStorageKey',
});

interface UseUrlStateOptions {
localStorageKey?: string;
}
```

### Params

| Property | Description | Type | Default |
| ------------ | --------------------------------------------------------------------------------------------------------------------- | --------------- | ------- |
| routerPushFn | Pass `router.push` | `function` | - |
| initialState | defaultValue | `S | (() => S)` | - |
| options | If set the `options.localStorageKey` is set, state will use the state saved in localStorage when url params is empty. | UseUrlStateOptions | - |

### Result

| Property | Description | Type |
| -------- | ----------- | ---- |
| state | state ref | - |
114 changes: 114 additions & 0 deletions src/useUrlState/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import qs from 'qs';
import { Ref, ref, watch } from 'vue-demi';
import { useLocalStorageState } from '../useLocalStorageState';

export interface UseUrlStateOptions {
localStorageKey?: string;
}

interface UrlState {
[key: string]: any;
}

function encodeParams(value: UrlState) {
return qs.stringify(value);
}

function decodeParams(valueStr: string) {
// return JSON.parse(decodeURIComponent(atob(valueStr)));
return qs.parse(valueStr, {
decoder(str, decoder, charset) {
const strWithoutPlus = str.replace(/\+/g, ' ');
if (charset === 'iso-8859-1') {
// unescape never throws, no try...catch needed:
return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape);
}

if (/^[+-]?\d+(\.\d+)?$/.test(str)) {
return parseFloat(str);
}

const keywords: Record<string, any> = {
true: true,
false: false,
null: null,
undefined,
};
if (str in keywords) {
return keywords[str];
}

// utf-8
try {
return decodeURIComponent(strWithoutPlus);
} catch (e) {
return strWithoutPlus;
}
},
});
}

export function useUrlState<S extends UrlState = UrlState>(
routerPushFn: (path: string) => void,
initialState?: S | (() => S),
options?: UseUrlStateOptions,
): Ref<S> {
const { localStorageKey } = options ?? {};

const [path, paramsStr] = location.hash.slice(1).split('?');

const defaultState =
(typeof initialState === 'function'
? (initialState as () => S)()
: initialState) ?? ({} as S);
let state = ref(defaultState) as Ref<S>;

if (localStorageKey) {
state = useLocalStorageState(localStorageKey, defaultState) as Ref<S>;
}

// 初始状态 url > localstorage
if (paramsStr) {
try {
const paramsValue = decodeParams(paramsStr);
console.log('解析url结果:');
console.log(paramsValue);
state.value = {
...defaultState,
...state.value,
...paramsValue,
};
} catch {
state.value = defaultState;
}
}

// 去掉多余的key
if (initialState && Object.keys(initialState).length) {
let newState = { ...initialState } as any;
for (const key in newState) {
if (key in state.value) {
newState[key] = state.value[key];
}
}
state.value = newState;
}

// 把params写到url
watch(
state,
() => {
const newParamsStr = encodeParams(state.value);

routerPushFn(`${path}?${newParamsStr}`);
console.log('写url');
console.log(`${path}?${newParamsStr}`);
},
{
deep: true,
immediate: true,
},
);

return state;
}
48 changes: 48 additions & 0 deletions src/useUrlState/index.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
map:
path: /use-url-state
---

# useUrlState

一个同步组件内部状态和 query 参数的 hook。

::: warning
API 有改动,见[ahooks](https://ahooks.js.org/zh-CN/hooks/state)
:::

## Examples

### 基础用法

<demo src="./demo/demo.vue"
language="vue"
title="基础用法"
desc="同步条件到url,在需要通过url分享复杂参数场景十分有用">
</demo>

## API

```typescript
const state = useUrlState(routerPush, DefaultState, {
localStorageKey: 'localStorageKey',
});

interface UseUrlStateOptions {
localStorageKey?: string;
}
```

### Params

| 参数 | 说明 | 类型 | 默认值 |
| ------------ | ------------------------------------------------------------------------ | ------------------ | ------ |
| routerPushFn | 一般来说,传 vue-router 的 `router.push`方法就行 | `function` | - |
| initialState | 默认值 | `S | (() => S)` | - |
| options | 设置`localStorageKey`的话,若 url 没有参数,会使用存在 localStorage 的值 | UseUrlStateOptions | - |

### Result

| 参数 | 说明 | 类型 |
| ----- | ------ | ---- |
| state | 状态值 | - |
Loading

0 comments on commit a64371b

Please sign in to comment.