Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 라우터 조건부 랜더링 기능 추가 & Do를 추가 #2

Merged
merged 16 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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