Skip to content

Commit

Permalink
chore(useSlice.ts): 🤖options naming, example
Browse files Browse the repository at this point in the history
  • Loading branch information
ldu1020 committed Oct 22, 2023
1 parent 39dd6ee commit fa4c9d8
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 145 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,38 @@ import {

import ContextColorPicker from './components/ContextColorPicker';
import StatefulColorPicker from './components/StatefulColorPicker';
import { exampleSlice } from './context/hooks/useExampleSlice';
import { useExamplePageContext } from './context/useExamplePageContext';
import withExamplePageProvider from './hocs/withExamplePageProvider';

const increaseFromOutOfComponent = () => {
exampleSlice.dispatch({
type: 'INCREASE_VALUE',
payload: 1,
});
};

const decreaseFromOutOfComponent = () => {
exampleSlice.dispatch({
type: 'DECREASE_VALUE',
payload: 1,
});
};

const ContextSection = () => {
const value = useExamplePageContext((ctx) => ctx.state.value);
const decreaseValue = useExamplePageContext(
(ctx) => ctx.handler.decreaseValue,
);
const increaseValue = useExamplePageContext(
(ctx) => ctx.handler.increaseValue,
const dispatch = useExamplePageContext((ctx) => ctx.dispatch);
const accessAbleValue = useExamplePageContext(
(ctx) => ctx.accessAbleState.value,
);

const decreaseValue = () => {
dispatch({ type: 'DECREASE_VALUE', payload: 1 });
};
const increaseValue = () => {
dispatch({ type: 'INCREASE_VALUE', payload: 1 });
};

return (
<Grid
h={'100%'}
Expand Down Expand Up @@ -73,6 +93,33 @@ const ContextSection = () => {
</HStack>
</VStack>
</GridItem>
<GridItem rowSpan={2} colSpan={4} alignSelf={'center'}>
<VStack>
<Text as={'h2'} textStyle={'title-lg'}>
ExamplePage (컴포넌트 바깥에서 접근하기)
</Text>
<Text textStyle={'title'}>
state 를 만들때 useSlice 훅의 access 옵션의 값이 global 일 경우
컴포넌트 바깥에서 접근이 가능합니다. <br />
단, 지역 컨텍스트나 지역 상태에서는 컴포넌트 언마운트시 state 가
초기화가 되지 않을 수 있으므로 전역상태에서만 사용하는 것 을
권장합니다.
</Text>
<Text>{`Current Context Value is`}</Text>
<HStack>
<Button
isDisabled={accessAbleValue === 0}
onClick={decreaseFromOutOfComponent}
>
-
</Button>
<Text w={'100px'} textAlign={'center'}>
{accessAbleValue}
</Text>
<Button onClick={increaseFromOutOfComponent}>+</Button>
</HStack>
</VStack>
</GridItem>
<GridItem
rowSpan={1}
colSpan={4}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useSlice } from '@/hooks/useSlice';

import { createSlice } from '@/utils/react/create-slice';

type GlobalStateType = {
value: number;
};

const initialState: GlobalStateType = {
value: 0,
};

export const exampleSlice = createSlice({
initialState,
reducers: {
RESET: () => initialState,
SET_VALUE: (state, payload: number) => {
state.value = payload;
},
INCREASE_VALUE: (state, payload: number) => {
state.value += payload;
},
DECREASE_VALUE: (state, payload: number) => {
state.value -= payload;
},
},
});

/**
* 지역 컨텍스트에선 isGlobalSlice 옵션을 사용하지 않는것을 권장합니다.
*/
export const useExampleAccessAbleSlice = () => {
return useSlice(exampleSlice, { access: 'global' });
};

export const useExampleSlice = () => {
return useSlice(exampleSlice);
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { createContextSelector } from '@/utils/react/create-context-selector';

import { useExamplePageHandlers } from './hooks/useExamplePageHandlers';
import { useExamplePageState } from './hooks/useExamplePageState';
import {
useExampleAccessAbleSlice,
useExampleSlice,
} from './hooks/useExampleSlice';

const useExamplePage = () => {
const { state, dispatch } = useExamplePageState();
const handler = useExamplePageHandlers({ state, dispatch });
const [state, dispatch] = useExampleSlice();
const [accessAbleState, accessAbleDispatch] = useExampleAccessAbleSlice();

return { dispatch, state, handler };
return {
state,
dispatch,
accessAbleState,
accessAbleDispatch,
};
};

export const {
Expand Down
44 changes: 1 addition & 43 deletions src/containers/Home/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,10 @@
import { Box, Button, Text } from '@chakra-ui/react';

import { homeSlice } from '@/contexts/pages/home/hooks/useHomePageState';
import { useHomePageContext } from '@/contexts/pages/home/useHomePageContext';
import { Box, Text } from '@chakra-ui/react';

function Home() {
console.log('hi');
return (
<Box>
<Text>HomePage</Text>
<Count />
<Updator />
</Box>
);
}
export default Home;

const Count = () => {
const count = useHomePageContext((ctx) => ctx.state.value);
return (
<Box>
<Text>{count}</Text>
</Box>
);
};

const Updator = () => {
const dispatch = useHomePageContext((ctx) => ctx.dispatch);

return (
<Box>
<Button
onClick={() => {
homeSlice.setState((prev) => ({ ...prev, value: prev.value + 1 }));
}}
>
add
</Button>

<Button
onClick={() =>
dispatch({
type: 'SET_VALUE',
payload: 4,
})
}
>
add2
</Button>
</Box>
);
};
2 changes: 1 addition & 1 deletion src/contexts/global/hooks/useGlobalState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const globalSlice = createSlice({
});

export const useGlobalState = () => {
const [state, dispatch] = useSlice(globalSlice, { isGlobalSlice: true });
const [state, dispatch] = useSlice(globalSlice, { access: 'global' });

return { state, dispatch };
};
9 changes: 7 additions & 2 deletions src/hooks/useSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ import { useEffect, useReducer } from 'react';
import { createSlice } from '@/utils/react/create-slice';
import { ReducerMap } from '@/utils/react/types/reducer';

/**
* @param slice createSlice로 생성한 slice 객체
* @param options.access access 값이 global 일 경우, 외부 slice 상태 변경을 감지하여 리랜더링 할수 있습니다. 지역 컨텍스트에서는 컴포넌트의 언마운트가 될시, 상태가 초기화 되지 않을 수 있으므로 access 옵션을 사용하지 않는것을 권장합니다.
* @returns [state, dispatch]
*/
export const useSlice = <S, R extends ReducerMap<S, any>>(
slice: ReturnType<typeof createSlice<S, R>>,
options?: { isGlobalSlice: boolean },
options?: { access?: 'local' | 'global' },
) => {
const _slice = options?.isGlobalSlice ? slice : slice.copy();
const _slice = options?.access === 'global' ? slice : slice.copy();
const [state, dispatch] = useReducer(_slice.reducer, _slice.getState());

useEffect(() => {
Expand Down
56 changes: 19 additions & 37 deletions src/utils/react/create-slice.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
import { produce } from 'immer';

import { ActionsByMap, ReducerMap } from './types/reducer';

type CreateReducerParams<S, R extends ReducerMap<S, any>> = {
initialState: S;
reducers: R;
};

type CreateSliceReturn<S, R extends ReducerMap<S, any>> = {
initialState: S;
reducer: (
state: S,
action: ActionsByMap<S, R> | { type: 'RESET'; payload?: S },
) => S;
reducers: R;
getState: () => S;
setState: (next: S | ((prev: S) => S)) => void;
subscribe: (listener: () => void) => () => void;
copy: () => CreateSliceReturn<S, R>;
};
import { runIfFn } from '../validate/run-if-fn';
import {
ActionWithReset,
CreateReducerParams,
CreateSliceReturn,
ReducerMap,
ReducerWithResetAction,
} from './types/reducer';

export const createSlice = <S, R extends ReducerMap<S, any>>({
initialState,
Expand All @@ -28,28 +17,16 @@ export const createSlice = <S, R extends ReducerMap<S, any>>({

const listeners = new Set<() => void>();

const reducer = produce(
(
state: S,
action: ActionsByMap<S, R> | { type: 'RESET'; payload?: S },
): S => {
if (action.type === 'RESET') return (action.payload || initialState) as S;
const reducer = reducers[action.type];
return reducer(state, action.payload) as S;
},
) as (
state: S,
action: ActionsByMap<S, R> | { type: 'RESET'; payload?: S },
) => S;
const reducer: ReducerWithResetAction<S, R> = produce((state, action) => {
if (action.type === 'RESET') return (action.payload || initialState) as S;
const reducer = reducers[action.type];
return reducer(state as S, action.payload);
}) as ReducerWithResetAction<S, R>;

const getState = () => state;

const setState = (next: S | ((prev: S) => S)) => {
if (typeof next === 'function') {
state = (next as Function)(state);
} else {
state = next;
}
state = runIfFn(next, state);
listeners.forEach((listener) => listener());
};

Expand All @@ -60,10 +37,15 @@ export const createSlice = <S, R extends ReducerMap<S, any>>({

const copy = () => createSlice({ initialState, reducers });

const dispatch = (action: ActionWithReset<S, R>) => {
setState(reducer(state, action));
};

return {
initialState,
reducers,
reducer,
dispatch,
getState,
setState,
subscribe,
Expand Down
Loading

0 comments on commit fa4c9d8

Please sign in to comment.