Skip to content

Commit

Permalink
Merge pull request #2 from softeerbootcamp4th/CC-88
Browse files Browse the repository at this point in the history
[Feat] 라우터 조건부 랜더링 기능 추가 &  Do를 추가
  • Loading branch information
minani-0621 authored Jul 29, 2024
2 parents 80483a9 + e84b65f commit f85c5d9
Show file tree
Hide file tree
Showing 16 changed files with 253 additions and 107 deletions.
14 changes: 14 additions & 0 deletions Caecae/src/Shared/Hooks/useChangeDependency.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useEffect, useRef } from "react";

const useChangeDependency = (cb: () => void, dependency: unknown[]) => {
const initRef = useRef(false);
useEffect(() => {
if (!initRef.current) {
initRef.current = true;
} else {
cb();
}
}, dependency);
};

export default useChangeDependency;
11 changes: 11 additions & 0 deletions Caecae/src/Shared/Hooks/useForceRendering.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useState } from "react";

const useForceRendering = () => {
const [state, setState] = useState(false);
return () => {
state;
setState((prev) => !prev);
};
};

export default useForceRendering;
10 changes: 10 additions & 0 deletions Caecae/src/Shared/Hooks/useIsShowing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useContext } from "react";
import { RouterContext } from "../Hyunouter/Router";

const useIsShowing = (): boolean => {
const { isFullScreen } = useContext(RouterContext);
console.log(isFullScreen);
return !isFullScreen;
};

export default useIsShowing;
13 changes: 9 additions & 4 deletions Caecae/src/Shared/Hyundux/Actions.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import State from "./State";
interface Action {
type: string;
actionName: string;
payload?: object;
type: string;
actionName: string;
payload?: object;
}
interface DoAction<T> {
type: string;
doing: (state: State<T>) => State<T>;
}

export default Action
export type { Action, DoAction };
2 changes: 1 addition & 1 deletion Caecae/src/Shared/Hyundux/Example_Counter/ConuntUI.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useWork from "../Hooks/useWork.tsx";
import { action, initCountState, countReducer } from "./CountWorkFlow.tsx";
import { action, initCountState, countReducer } from "./CountWork.tsx";
import store from "../Store.tsx";

const Counter = () => {
Expand Down
31 changes: 31 additions & 0 deletions Caecae/src/Shared/Hyundux/Example_Counter/CountDo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createState } from "../State";
import { DoAction } from "../Actions";
import State from "../State";
import { makePayLoad } from "../Util/StoreUtil";

const Do_NAME = "Count";

// state type
interface CountPayLoad {
count: number;
text: string;
}

const initCountState = createState<CountPayLoad>(Do_NAME, {
count: 0,
text: "helloWorld",
});

// actions
const doAction = {
countUp: (): DoAction<CountPayLoad> => {
return {
type: Do_NAME,
doing: (state: State<CountPayLoad>): State<CountPayLoad> => {
return makePayLoad(state, { count: state.payload.count + 1 });
},
};
},
};

export { doAction, initCountState };
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createState } from "../State";
import { makePayLoad } from "../Util/StoreUtil";
import Reducer from "../Reducer";
import Action from "../Actions";
import { Action } from "../Actions";

const WORKFLOW_NAME = "Count";

Expand Down
13 changes: 13 additions & 0 deletions Caecae/src/Shared/Hyundux/Hooks/useDo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useState } from "react";
import _State from "../State";
import store from "../Store";

function useDo<PayLoad>(initialState: _State<PayLoad>): PayLoad {
const [state, setState] = useState<_State<PayLoad>>(initialState);
store.subscribe(state, null, (newState) => {
setState(newState);
});
return state.payload;
}

export default useDo;
6 changes: 3 additions & 3 deletions Caecae/src/Shared/Hyundux/Reducer.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Action from "./Actions";
import { Action } from "./Actions";
import State from "./State";

interface Reducer<PayLoad> {
type: string;
reducer: (state: State<PayLoad>, action: Action) => Promise<State<PayLoad>>;
type: string;
reducer: (state: State<PayLoad>, action: Action) => Promise<State<PayLoad>>;
}

export default Reducer;
60 changes: 40 additions & 20 deletions Caecae/src/Shared/Hyundux/Store.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Action from "./Actions";
import { Action, DoAction } from "./Actions";
import State from "./State";
import Reducer from "./Reducer";
import removeFirst from "./Util/RemoveFirst";
Expand All @@ -9,28 +9,38 @@ const store: {
reducers: Reducer<unknown>[];
subscribe: <PayLoad>(
initState: State<PayLoad>,
reducer: Reducer<PayLoad>,
reducer: Reducer<PayLoad> | null,
cb: (state: State<PayLoad>) => void
) => void;
dispatch: (action: Action) => void;
dispatch: <T>(action: Action | DoAction<T>) => void;
publish: <PayLoad>(state: State<PayLoad>) => void;
subscribeList: Map<string, <PayLoad>(state: State<PayLoad>) => void>;
} = {
states: [],
reducers: [],
subscribeList: new Map(),
dispatch: async function (action) {
const reducer = this.reducers.filter(
(reducer) => reducer.type == action.type
)[0].reducer;
const { removed, newArray } = removeFirst(
this.states,
(state) => state.type == action.type
);
const newState = await reducer(removed, action);
// 여기서 모든것을 바로 state를 적용하는것이 아니라 이게 다른 state도 propagation하는지도 확인해야함
this.states = [...newArray, newState];
this.publish(newState);
if (isAction(action)) {
const reducer = this.reducers.filter(
(reducer) => reducer.type == action.type
)[0].reducer;
const { removed, newArray } = removeFirst(
this.states,
(state) => state.type == action.type
);
const newState = await reducer(removed, action);
// 여기서 모든것을 바로 state를 적용하는것이 아니라 이게 다른 state도 propagation하는지도 확인해야함
this.states = [...newArray, newState];
this.publish(newState);
} else if (isDoAction(action)) {
const { removed, newArray } = removeFirst(
this.states,
(state) => state.type == action.type
);
const newState = action.doing(removed);
this.states = [...newArray, newState];
this.publish(newState);
}
},
publish: function (state) {
const publishedCallBack = this.subscribeList.get(state.type);
Expand All @@ -40,21 +50,31 @@ const store: {
},
subscribe: function <PayLoad>(
state: State<PayLoad>,
reducer: Reducer<PayLoad>,
reducer: Reducer<PayLoad> | null = null,
cb: (state: State<PayLoad>) => void
) {
this.states = replaceFirst(
this.states,
state,
(element) => element.type == state.type
);
this.reducers = replaceFirst(
this.reducers,
reducer as Reducer<unknown>,
(element) => element.type == state.type
);
if (reducer != null) {
this.reducers = replaceFirst(
this.reducers,
reducer as Reducer<unknown>,
(element) => element.type == state.type
);
}
this.subscribeList.set(state.type, cb as (state: State<unknown>) => void);
},
};

function isAction(action: unknown): action is Action {
return (action as Action).actionName !== undefined;
}

function isDoAction<T>(action: unknown): action is DoAction<T> {
return (action as DoAction<T>).doing !== undefined;
}

export default store;
24 changes: 24 additions & 0 deletions Caecae/src/Shared/Hyunouter/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { ReactNode, useContext, MouseEvent } from "react";
import { RouterContext } from "./Router";

interface LinkProps {
to: string;
children: ReactNode;
}

const Link: React.FC<LinkProps> = ({ to, children }) => {
const { changePath } = useContext(RouterContext);

const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
changePath(to);
};

return (
<a href={to} onClick={handleClick}>
{children}
</a>
);
};

export default Link;
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { ReactElement } from "react";
import { ReactElement } from "react";

interface RouteProps {
path: string;
isFullScreen: boolean;
element: ReactElement;
}

Expand Down
89 changes: 89 additions & 0 deletions Caecae/src/Shared/Hyunouter/Router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
useState,
createContext,
ReactNode,
FC,
useEffect,
useRef,
} from "react";
import useForceRendering from "../Hooks/useForceRendering";

interface RouterProps {
children: ReactNode;
}

interface RouterContextType {
path: string;
fullScreenPath: string[];
changePath: (path: string) => void;
addFullScreenPath: (path: string) => void;
isFullScreen: boolean;
}

const RouterContext = createContext<RouterContextType>({
path: "",
fullScreenPath: [],
changePath: () => undefined,
addFullScreenPath: () => undefined,
isFullScreen: false,
});
RouterContext.displayName = "RouterContext";

const Router: FC<RouterProps> = ({ children }) => {
const forceRerendering = useForceRendering();
const [path, setPath] = useState(window.location.pathname);
const fullScreenPaths = useRef<string[]>([]);
const isFullScreen = useRef<boolean>(false);
useEffect(() => {
if (fullScreenPaths.current.includes(window.location.pathname)) {
isFullScreen.current = true;
forceRerendering();
}
const handleLocationChange = () => {
isFullScreen.current = false;
if (fullScreenPaths.current.includes(window.location.pathname)) {
isFullScreen.current = true;
}
setPath(window.location.pathname);
};

window.addEventListener("popstate", handleLocationChange);

return () => {
window.removeEventListener("popstate", handleLocationChange);
};
}, []);

const changePath = (newPath: string) => {
if (path !== newPath) {
window.history.pushState({}, "", newPath);
isFullScreen.current = false;
if (fullScreenPaths.current.includes(newPath)) {
isFullScreen.current = true;
}
setPath(newPath);
}
};

const addFullScreenPath = (path: string) => {
const newPaths = fullScreenPaths.current;
if (newPaths.includes(path)) return;
newPaths.push(path);
fullScreenPaths.current = newPaths;
};

const contextValue = {
path: path,
changePath: changePath,
fullScreenPath: fullScreenPaths.current,
addFullScreenPath: addFullScreenPath,
isFullScreen: isFullScreen.current,
};
return (
<RouterContext.Provider value={contextValue}>
{children}
</RouterContext.Provider>
);
};

export { Router, RouterContext };
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ interface RoutesProps {
}

const Routes: React.FC<RoutesProps> = ({ children }) => {
const { path } = useContext(RouterContext);
const { path, addFullScreenPath } = useContext(RouterContext);

let element: ReactElement | null = null;

React.Children.forEach(children, (child) => {
if (!isValidElement(child)) {
return;
}
if (child.props.isFullScreen) {
addFullScreenPath(child.props.path);
}
if (child.type === React.Fragment) {
return;
}
Expand All @@ -28,6 +31,7 @@ const Routes: React.FC<RoutesProps> = ({ children }) => {
if (child.props.path !== path) {
return;
}

element = child.props.element;
});

Expand Down
Loading

0 comments on commit f85c5d9

Please sign in to comment.