Skip to content

Commit 9dcd79c

Browse files
committed
新增 useReducer useMethods
1 parent b2bcb3a commit 9dcd79c

File tree

8 files changed

+217
-2
lines changed

8 files changed

+217
-2
lines changed

docs/useMediatedState.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# `useMediatedState`
2+
3+
A lot like the standard `useState`, but with mediation process.
4+
5+
## Usage
6+
```jsx
7+
import { useMediatedState } from 'vue-next-use';
8+
9+
const inputMediator = s => s.replace(/[\s]+/g, ' ');
10+
const Demo = () => {
11+
const [state, setState] = useMediatedState(inputMediator, '');
12+
13+
return () => (
14+
<div>
15+
<div>You will not be able to enter more than one space</div>
16+
<input type="text" min="0" max="10"
17+
value={state}
18+
onChange={(ev) => {
19+
setState(ev.target.value);
20+
}}
21+
/>
22+
</div>
23+
);
24+
};
25+
```
26+
27+
## Reference
28+
```ts
29+
const [state, setState] = useMediatedState<S=any>(
30+
mediator: StateMediator<S>,
31+
initialState?: S
32+
);
33+
```
34+
35+
> Initial state will be set as-is.
36+
37+
In case mediator expects 2 arguments it will receive the `setState` function as second argument, it is useful for async mediators.
38+
>This hook will not cancel previous mediation when new one been invoked, you have to handle it yourself._

docs/useReducer.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# `useReducer`
2+
3+
A lot like the standardreact `useReducer`, but with vue implementation.
4+
5+
## Usage
6+
```jsx
7+
import { useReducer } from 'vue-next-use';
8+
9+
const Demo = {
10+
setup() {
11+
12+
const initializer = (initialState) => {
13+
return isNaN(initialState) ? 0 : initialState;
14+
};
15+
16+
const [state, dispatch] = useReducer((preState, action) => {
17+
switch (action) {
18+
case "increment":
19+
return preState + 1;
20+
case "decrement":
21+
return preState - 1;
22+
}
23+
return preState;
24+
}, undefined, initializer);
25+
26+
return () => (
27+
<div>
28+
<button onClick={() => dispatch('increment')}>increment</button>
29+
<button onClick={() => dispatch('decrement')}>decrement</button>
30+
<span style={{margin: "0 5px"}}>count: {state.value}</span>
31+
</div>
32+
);
33+
},
34+
};
35+
```
36+
37+
## Reference
38+
```ts
39+
const [state, dispatch] = useReducer<R extends (Reducer<any, any> | ReducerWithoutAction<any>), I>(
40+
reducer: R,
41+
initialState: I | ReducerState<R>,
42+
initializer?: (arg: I | ReducerState<R>) => ReducerState<R>
43+
): [ComputedRef<I>, Dispatch<ReducerAction<R>> | DispatchWithoutAction]
44+
```

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,5 @@ export {default as useLongPress} from './useLongPress';
4949
export {default as useBattery} from './useBattery';
5050
export {default as useReactive} from './useReactive';
5151
export {default as useReadonly} from './useReadonly';
52-
export {default as useMediatedState} from './useMediatedState';
52+
export {default as useMediatedState} from './useMediatedState';
53+
export {default as useReducer} from './useReducer';

src/misc/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,23 @@ export type PromiseType<P extends Promise<any>> = P extends Promise<infer T> ? T
44

55
export type FunctionReturningPromise = (...args: any[]) => Promise<any>;
66

7+
// Unlike the class component setState, the updates are not allowed to be partial
78
export type SetStateAction<S> = S | ((prevState: S) => S);
9+
// this technically does accept a second argument, but it's already under a deprecation warning
10+
// and it's not even released so probably better to not define it.
11+
export type Dispatch<A> = (value: A) => void;
12+
// Since action _can_ be undefined, dispatch may be called without any parameters.
13+
export type DispatchWithoutAction = () => void;
14+
// Unlike redux, the actions _can_ be anything
15+
export type Reducer<S, A> = (prevState: S, action: A) => S;
16+
// If useReducer accepts a reducer without action, dispatch may be called without any parameters.
17+
export type ReducerWithoutAction<S> = (prevState: S) => S;
18+
// types used to try and prevent the compiler from reducing S
19+
// to a supertype common with the second argument to useReducer()
20+
export type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never;
21+
export type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;
22+
// The identity check is done with the SameValue algorithm (Object.is), which is stricter than ===
23+
export type ReducerStateWithoutAction<R extends ReducerWithoutAction<any>> = R extends ReducerWithoutAction<infer S> ? S : never;
824

925
// NOTE: callbacks are _only_ allowed to return either void, or a destructor.
1026
// The destructor is itself only allowed to return void.

src/useMethods.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {ComputedRef, ref} from 'vue';
2+
import {useReducer} from "./index";
3+
import {Reducer} from './misc/types';
4+
5+
type Action = {
6+
type: string;
7+
payload?: any;
8+
};
9+
10+
type CreateMethods<M, T> = (
11+
state: T
12+
) => {
13+
[P in keyof M]: (payload?: any) => T;
14+
};
15+
16+
type WrappedMethods<M> = {
17+
[P in keyof M]: (...payload: any) => void;
18+
};
19+
20+
const useMethods = <M, T>(
21+
createMethods: CreateMethods<M, T>,
22+
initialState: T
23+
): [ComputedRef<T>, WrappedMethods<M>] => {
24+
25+
const reducer: Reducer<T, Action> = (reducerState: T, action: Action) => {
26+
return createMethods(reducerState)[action.type](...action.payload);
27+
};
28+
29+
const [state, dispatch] = useReducer<Reducer<T, Action>>(reducer, initialState);
30+
31+
const wrappedMethods: WrappedMethods<M> = (() => {
32+
const actionTypes = Object.keys(createMethods(initialState));
33+
34+
return actionTypes.reduce((acc, type) => {
35+
acc[type] = (...payload) => dispatch({type, payload});
36+
return acc;
37+
}, {} as WrappedMethods<M>);
38+
})();
39+
40+
return [state, wrappedMethods];
41+
};
42+
43+
export default useMethods;

src/useReducer.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {ComputedRef} from "vue";
2+
import {useComputedState} from "./index";
3+
import {Dispatch, Reducer, ReducerAction, ReducerState, ReducerStateWithoutAction, DispatchWithoutAction, ReducerWithoutAction} from "./misc/types";
4+
import {resolveHookState} from './misc/hookState'
5+
6+
function useReducer<R extends (Reducer<any, any> | ReducerWithoutAction<any>)>(
7+
reducer: R,
8+
initialState: ReducerState<R>,
9+
initializer?: (arg: ReducerState<R>) => ReducerState<R>
10+
): [ComputedRef<ReducerState<R>>, Dispatch<ReducerAction<R>> | DispatchWithoutAction];
11+
12+
function useReducer<R extends (Reducer<any, any> | ReducerWithoutAction<any>), I>(
13+
reducer: R,
14+
initialState: I | ReducerState<R>,
15+
initializer?: (arg: I | ReducerState<R>) => ReducerState<R>
16+
): [ComputedRef<I>, Dispatch<ReducerAction<R>> | DispatchWithoutAction] {
17+
18+
if (initializer) {
19+
initialState = initializer(resolveHookState(initialState));
20+
}
21+
22+
const [state, setState] = useComputedState(initialState);
23+
24+
const dispatch: Dispatch<R> = (action) => {
25+
setState(prevState => reducer(prevState, action))
26+
};
27+
28+
return [state, dispatch];
29+
};
30+
31+
export default useReducer;

stories/useMediatedState.stories.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default {
77
argTypes: {},
88
};
99

10-
// export const Docs = ShowDocs(require('../docs/useIdle.md'));
10+
export const Docs = ShowDocs(require('../docs/useMediatedState.md'));
1111

1212
export const Demo = ShowDemo({
1313
setup() {

stories/useReducer.stories.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {ShowDemo, ShowDocs} from './util/index';
2+
import {useReducer} from "../src/index";
3+
import {Reducer} from "../src/misc/types";
4+
5+
export default {
6+
title: 'State/useReducer',
7+
argTypes: {},
8+
};
9+
10+
export const Docs = ShowDocs(require('../docs/useReducer.md'));
11+
12+
export const Demo = ShowDemo({
13+
setup() {
14+
15+
// https://zh-hans.reactjs.org/docs/hooks-reference.html#usereducer
16+
const initializer = (initialState) => {
17+
return isNaN(initialState) ? 0 : initialState;
18+
};
19+
20+
const [state, dispatch] = useReducer<Reducer<any, any>>((preState, action) => {
21+
switch (action) {
22+
case "increment":
23+
return preState + 1;
24+
case "decrement":
25+
return preState - 1;
26+
}
27+
return preState;
28+
}, 0, initializer);
29+
30+
return () => (
31+
<div>
32+
<button onClick={() => dispatch('increment')}>increment</button>
33+
<button onClick={() => dispatch('decrement')}>decrement</button>
34+
<span style={{margin: "0 5px"}}>count: {state.value}</span>
35+
</div>
36+
);
37+
},
38+
});
39+
40+
41+
42+

0 commit comments

Comments
 (0)