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

#705 @taxi/app init #704

Merged
merged 12 commits into from
Jan 30, 2024
Merged

#705 @taxi/app init #704

merged 12 commits into from
Jan 30, 2024

Conversation

SnowSuno
Copy link
Member

@SnowSuno SnowSuno commented Jan 18, 2024

Summary

It closes #705

@taxi/app 패키지를 초기화하고 웹뷰 스크린을 local navigation과 연결합니다.

Images or Screenshots

Video in Slack

Further Work

Copy link

netlify bot commented Jan 18, 2024

Deploy Preview for taxi-dev-preview ready!

Name Link
🔨 Latest commit 036ac16
🔍 Latest deploy log https://app.netlify.com/sites/taxi-dev-preview/deploys/65b863797fbdc40008cead84
😎 Deploy Preview https://deploy-preview-704--taxi-dev-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@SnowSuno SnowSuno force-pushed the #702-app-core-package-init branch from 41896d0 to ab623cd Compare January 18, 2024 08:15
@SnowSuno SnowSuno changed the title @taxi/app, @taxi/core init #702 @taxi/app, @taxi/core init Jan 18, 2024
Comment on lines +36 to +38
SERVER_URL: isDev
? `http://${ip.v4.sync()}:3000`
: "https://taxi.sparcs.org",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개발 환경에서는 로컬 네트워크의 택시 웹이 뜨도록 하였습니다.
모바일 기기(expo go)를 이용하여 개발하는 경우 localhost:3000으로는 접속하지 못하기 때문에 local ip를 가져오도록 하였습니다.

"web": "pnpm -F @taxi/web",
"app": "pnpm --filter @taxi/app",
"web": "pnpm --filter @taxi/web",
"start:app": "pnpm -r --stream start",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pnpm start:app을 할 경우 web과 app이 동시에 시작됩니다.

Comment on lines +22 to +32
const onNavigationStateChange = useCallback(
(event: WebViewNavigation) => {
const screen = mapWebRoutes(event.url);
if (!isFocused || isSameScreen(currentScreen, screen)) return;

navigation.navigate(...screen);
ref.current?.stopLoading();
ref.current?.goBack();
},
[isFocused, currentScreen, navigation]
);
Copy link
Member Author

@SnowSuno SnowSuno Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onNavigationStateChange 리스너를 이용하여 새로운 route로 이동하는 경우 그 페이지에 해당하는 native app에서의 navigating을 진행해 줍니다.

나중에 앱과 웹의 코드베이스가 공유되는 경우에는 다른 접근을 취하는게 좋을 것 같습니다.

Copy link
Member Author

@SnowSuno SnowSuno Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Web route를 native screen으로 매핑하기 위해 필요한 유틸 함수들이 정의되어 있습니다.

현재는 web route와 native screen을 각각 정의하고, 둘을 매핑해주는 방식을 사용하고 있지만 추후에는 둘을 동시에 정의할 수 있는 공통 인터페이스를 구현하는 것이 유지보수 상 용이할 것 같습니다.

Comment on lines +6 to +19
export const screen = <ScreenName extends keyof RootStackParamList>(
...args: // this first condition allows us to iterate over a union type
// This is to avoid getting a union of all the params from `ParamList[RouteName]`,
// which will get our types all mixed up if a union RouteName is passed in.
ScreenName extends unknown
? // This condition checks if the params are optional,
// which means it's either undefined or a union with undefined
undefined extends RootStackParamList[ScreenName]
?
| [screen: ScreenName] // if the params are optional, we don't have to provide it
| [screen: ScreenName, params: RootStackParamList[ScreenName]]
: [screen: ScreenName, params: RootStackParamList[ScreenName]]
: never
) => args;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

screen을 정의하는 함수입니다. @/navigation/types에 정의된 스크린을 type-safe하게 사용할 수 있도록 react-navigation에 정의되어 있는 navigation.navigate()의 타입 정의를 그대로 사용하였습니다.

screen("Chatting", { roomId })와 같이 스크린을 표현할 수 있습니다.

Comment on lines +23 to +29
type ExtractRouteParams<T extends string> = string extends T
? Record<string, string>
: T extends `${infer Start}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractRouteParams<Rest>]: string }
: T extends `${infer Start}:${infer Param}`
? { [K in Param]: string }
: {};
Copy link
Member Author

@SnowSuno SnowSuno Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"/chatting/:chatId/message/:messageId" 등의 string에서 path parameter 타입 { chatId: string, messageId: string }을 추출해내기 위한 유틸리티 타입입니다.

Comment on lines +38 to +41
export const route = <Pattern extends string>(
routePattern: Pattern,
getScreen: (params: ExtractRouteParams<Pattern>) => Screen
): Route => {
Copy link
Member Author

@SnowSuno SnowSuno Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

route(<웹 라우트 패턴>, (파라미터) => <스크린>)으로의 단일 매핑을 정의하는 유틸 함수입니다.

<웹 라우트 패턴>에 정의된 :pathParameterparams로 그대로 전달되며 타입도 infer됩니다.

? { [K in Param]: string }
: {};

type Route = (route: string) => Screen | null;
Copy link
Member Author

@SnowSuno SnowSuno Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

route 함수의 return type 입니다.

route가 string으로 주어졌을 때 매칭되는 스크린이 있다면 해당 Screen을, 아닌 경우에는 null을 반환하는 매칭 함수를 반환합니다.

Comment on lines +42 to +45
const pattern = new URLPattern(routePattern);

return (pathname: string) => {
const params = pattern.match(pathname);
Copy link
Member Author

@SnowSuno SnowSuno Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

url pattern matching은 url-pattern 라이브러리를 이용하였습니다. 다만 해당 라이브러리의 타입 지원은 대부분 any로 지원되고 있어 위에서 정의한 커스텀 타입들을 덮어씌워 이용합니다.

Comment on lines +51 to +54
export const getPath = (route: string) => {
const match = route.match(new RegExp(`^${env.SERVER_URL}([\\w\\/]+)\\??`));
return match?.[1];
};
Copy link
Member Author

@SnowSuno SnowSuno Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

택시 웹의 주소 https://taxi.sparcs.org/chatting/123이 주어졌을 때 path /chatting/123을 반환합니다. (로컬 개발 환경일 경우에는 taxi.sparcs.org 대신 로컬 주소)

외부 주소(SPARCS SSO 등)일 경우에는 null을 반환합니다.

return match?.[1];
};

export const routes = (routes: Route[]) => (uri: string) => {
Copy link
Member Author

@SnowSuno SnowSuno Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여러 route 매핑 정의(매칭 함수)를 array로 받아 단일 매칭 함수로 합쳐 줍니다.

uri를 받아 해당하는 Screen을 반환하는 매칭 함수를 반환합니다.
해당 매칭 함수는 uri가 외부 uri이거나, 매칭되는 스크린이 없는 경우 screen("Web", { uri }) 스크린을 반환합니다. 해당 스크린은 모든 기타 웹 주소가 렌더링되는 스크린입니다.


const onNavigationStateChange = useCallback(
(event: WebViewNavigation) => {
const screen = mapWebRoutes(event.url);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uri가 바뀔 때 그 uri가 매칭되는 스크린을 받아옵니다.

const onNavigationStateChange = useCallback(
(event: WebViewNavigation) => {
const screen = mapWebRoutes(event.url);
if (!isFocused || isSameScreen(currentScreen, screen)) return;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

매칭된 스크린이 현재 스크린과 동일한 경우, 새로운 route navigation을 진행하지 않습니다.

Comment on lines +27 to +29
navigation.navigate(...screen);
ref.current?.stopLoading();
ref.current?.goBack();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 스크린으로 이동하는 경우, 현재 WebView에서의 routing은 취소하고, native에서 해당하는 스크린으로 navigation합니다. (tab navigation, stack navigation 모두 가능)

Comment on lines +67 to +75
export const isSameScreen = (
screen1: Screen,
screen2: Screen
): screen1 is Screen => {
const [screenName1, params1] = screen1;
const [screenName2, params2] = screen2;
if (screenName1 === "Web" && screenName2 === "Web") return true;
return screenName1 === screenName2 && shallowEqualObjects(params1, params2);
};
Copy link
Member Author

@SnowSuno SnowSuno Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

두 개의 스크린이 동일한 스크린인지 확인하기 위한 equality 유틸 함수입니다.

Comment on lines +60 to +63
for (const route of routes) {
const screen = route(path);
if (screen) return screen;
}
Copy link
Member Author

@SnowSuno SnowSuno Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주어진 route array 중 먼저 정의된 route부터 검사합니다.
즉, 한 개 이상의 route가 매칭되는 경우, 더 위에 정의된 screen으로 이동합니다.

@@ -4,7 +4,7 @@ const env = { ...import.meta.env, ...window["env"] };

// 환경변수
export const isDev = env.DEV; // automatically provided
export const backServer = env.REACT_APP_BACK_URL; // required
export const backServer = isDev ? "/api" : env.REACT_APP_BACK_URL; // use proxy in dev mode
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

local network(앱)에서도 backend에 접근할 수 있도록 하기 위해 local dev proxy를 이용합니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

local dev proxy를 설정합니다.
아직 socket 쪽은 작동하고 있지 않아 확인해 보아야 할 것 같습니다.
다만 rewrite 규칙을 사용하는 것보다 �sparcs-kaist/taxi-back#446 와 같은 접근을 취하는 것이 더 나아보입니다.

@SnowSuno SnowSuno changed the title #702 @taxi/app, @taxi/core init #705 @taxi/app init Jan 24, 2024
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://reactnavigation.org/docs/typescript/

react navigation docs에서 권장하는 방식의 navigiation screen 타입 정의입니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

웹에서 각각의 path와 path param이 어떤 스크린과 그 param에 대응되는지 정의되어 있습니다.
현재에 각각의 screen은 다시 WebView를 띄우고 있지만 그대로 저 param을 이용하는 native component로도 대체 가능합니다.

route, routes, screen 등 사용하는 util 함수들은 모두 type-safe 하게 작성했으며 구현은 @/utils/navigation에서 찾아보실 수 있습니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재에는 모든 스크린에서 WebView를 다시 리턴하고 있습니다.
그러나 추후 params.roomId 등의 screen parameter를 직접 사용하여 native component도 구현할 수 있습니다.

WebView -> Native Component 으로 이동한 경우에도 screen parameter가 자동으로 채워집니다.

@SnowSuno SnowSuno marked this pull request as ready for review January 30, 2024 02:49
Copy link
Contributor

@happycastle114 happycastle114 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@SnowSuno SnowSuno merged commit fcf0786 into dev Jan 30, 2024
4 checks passed
@SnowSuno SnowSuno deleted the #702-app-core-package-init branch January 30, 2024 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

@taxi/app init
2 participants