본문 바로가기
프로젝트 답지

[Dap JI ] React-native-webView 적용해보기 ( 진행중)

by 띠리에이터 2024. 9. 4.
진행중 진행중 진행중 진행중 진행중 진행중 진행중 진행중 진행중 진행중

 

1. 왜 React-native-webView를 적용하는가! 

 

답지 프로젝트에 React-native-webView를 적용해 스토어 출시까지 목표로 해보기로 했다. 

보통 앱은 네이티브 앱이나 하이브리드 앱을 적용해서 만들어야 하는데  토스나 네이버 앱도 웹 앱을 적용한 경우라  답지도 React-native-webView를 사용해 네이티브 코드를 적용하기로 했다. 

 

단순히 앱 형식으로 보이게 하는거라면 pwa가 더 간단할 수 있을 것 같지만 pwa는 모바일 브라우저에 들어가서 설치해야 되기 때문에 내가 생각하는 웹앱과는 거리가 좀 먼 것 같은 느낌이 들었다. 사실React-native-webView 스토어에 배포할 수 있다는 것만 빼면 좀 비슷한 것 같지만..

 

 

2. 설치

https://docs.expo.dev/versions/latest/sdk/webview/

 

WebView

A library that provides a WebView component.

docs.expo.dev

 

  • react-native-webview를 직접 적용하는 대신 next.js 앱을 모바일 환경에서 실행할 수 있또록 네이티브 앱으로 패키징 하는 방식으로 진행, => 하이브리드 앱 또는 웹앱을 네이티브로 포장하는 것과 비슷하다. 
  • Next.js는 웹 애플리케이션이기 때문에, 이를 모바일 앱에서 실행하려면 WebView를 사용하여 감싸는 것이 가장 일반적인 방법입니다. 이 과정에서 Next.js 프로젝트는 그대로 유지하고, React Native 프로젝트에서 Next.js 앱을 react-native-webview를 통해 로드
  • Next.js 앱 배포:
    • Next.js 앱을 Vercel, Netlify, 또는 AWS와 같은 플랫폼에 배포하여 URL을 얻는다. 
  • React Native 프로젝트 생성:
    • React Native CLI나 Expo로 새 프로젝트를 생성한다. 
  • react-native-webview 설치:
    • React Native 프로젝트에서 react-native-webview를 설치하고, 배포된 Next.js 앱의 URL을 WebView에서 로드한다.

  • 하지만 기존 expo-cli 패키지는 더 이상 사용되지 않으며, Expo는 로컬 CLI를 사용하는 방식으로 변경되었다. . 즉, npm install -g expo-cli를 더 이상 사용하지 않고, 프로젝트 내에서 npx 명령어를 사용해야 한다. 
npx create-expo-app .

일단 expo app을 설치 

npx expo install react-native-webview

그 후 웹뷰를 적용할 react-native-webview설치

  • 방법1
    • next.js로 빌드된 url을 expo react-native-webview로 감싸기
    • 새 폴더 생성
    • npx create-expo-app . npx expo install react-native-webview .
    • npx login 입력 후 이메일 비밀번호 입력,
    • expo 페이지에서 프로젝트 생성
    • 프로젝트 생성 후 모달에서 뜨는 명령어 따라치기 (npm install --global eas-cli), 프로젝트 생성시 로컬폴더 이름이랑 같게 설정
     


기존에 webview 적용은 루트폴더에 app.js가 있었는데 최근에는 폴더구조가 바뀜!! 우엉우어우ㅜㅜㅜㅜㅜ

next.js의 페이지라우팅 방식과 비슷하게 변경된 것 같다. (파일 기반의 라우팅 방식)

  • app
    • layout파일은 보통 페이지 간의 공통 레이아웃을 관리한다. , 탭 내의 모든 화면에서 공통적으로 사용될 구조나 스타일을 정의하며 . Expo Router에서는 이런 레이아웃 파일을 통해 페이지 간의 전환을 관리할 수 있다. 
    • html파일은 앱의 HTML 구조를 설정하는 역할. 보통 웹에서 사용되는 메타 태그나 기본 HTML 템플릿 구조를 설정할 때 사용되며, Expo 프로젝트가 웹에서 렌더링될 때 활용될 수 있다.
    • index파일은 해당 경로의 초기 화면을 담당, tabs폴더에 index 파일이 있으니 처음엔 index의 내용을 보여주고 tabs내에서 화면전환으로 explore페이지를 보여준다. 
  • assets
    • Android와 ios에 사용되는 icon이미지와 스플래이미지가 들어간다
  • constants
    • 앱의 테마를 담당하는 컬러가 들어가있음 
  • scripts
    • reset.js가 있다. 브라우저 또는 모바일 플랫폼의 기본 스타일(CSS/JS)을 초기화하여 모든 페이지에서 동일한 스타일 기반을 갖도록 도와준다.

 

npx expo start로 시작,  web으로 화면을 볼 경우 나오는 화면이다.

tabs에 home (index.tsx) explore( explore.tsx)가 푸터로 나오는 것을 볼 수 있다. 

(tabs)폴더에 index.ts와 explore.ts파일을 만들었 explore를 클릭하면 경로가 /explore가 된다.

tabs 레이아웃 파일을 살펴보면 tabs 컴포넌트를 볼 수 있다. 

태그는 Expo Router에서 제공하는 컴포넌트로, **탭 네비게이션(Tab Navigation)**을 구현하는 데 사용된다. 이 컴포넌트를 통해 앱 화면 하단에 탭을 배치하고, 각 탭을 누를 때마다 해당 화면으로 전환되는 기능을 쉽게 구현 가능!
Expo Router는 React Navigation과 통합되어, 이런 네비게이션 기능을 손쉽게 사용할 수 있다.

 <Tabs.Screen
        name="index"  // 탭이 가리킬 경로 또는 화면 이름
        options={{
          title: 'Home',  // 탭에 표시될 이름
          tabBarIcon: ({ color, focused }) => (
            <TabBarIcon name={focused ? 'home' : 'home-outline'} 
            color={color} />  // 탭 아이콘 설정
          ),
        }}
      />

 

그리고 app폴더 속해있는 최상위 루트 레이아웃을 확인하면 테마, 폰트, 스플래시 화면, 스택네비게이션이 설정된 것을 볼 수 있다. 

스택 네비게이션(Stack Navigation)**은 화면 간의 전환을 쌓는 방식으로 관리하는 네비게이션 구조.

화면을 스택처럼 쌓아 올리고, 뒤로 가기 버튼을 누르면 마지막에 쌓인 화면을 제거하는 방식으로 동작한다. React Navigation이나 Expo Router에서 이 네비게이션 방식을 사용하여, 사용자가 여러 화면을 탐색하고 자연스럽게 뒤로 이동할 수 있도록 도와준다.

스택은 last in first out으로 사용자가 '뒤로 가기'를 할 경우, 가장 위에 있는 화면이 제거되고, 그 아래에 있는 화면이 다시 표시된다.

 


이제 기본 보일러 플레이트를 확인했으니 프로젝트 생성을 위해 초기화 시키자

npm run reset-project

위 명렁어를 실행하면 기존에 app폴더에 있던 tabs와 파일들은 app-example폴더로 이동하고 app폴더는 초기화 된다.


폴더에 (tabs)를 만들고 그룹폴더 (home)을 생성, 그 후에 (tabs)레이아웃 파일에 Tabs 컴포넌트의 screen name옵션을 설정한다. 

그럼 현재 폴더 구조가 tabs => _layout.ts , setting.tsx, (home) 가 있고 Tabs로 setting와 (home)폴더로 설정하면 화면에는 푸터같은 느낌으로 settig와 heme가 적용된 것을 볼 수 있다. 

tabs에 폴더 이름이 그대로 들어간 (home)은 멋이 없으니 Tabs.Screen 의option title을 home으로 적용해주자 .

<Tabs.Screen name="(home)" options={{ title: "home" }} />

 

 


3. 이벤트 핸들러(앱을 개발하기)

 

   웹뷰를 개발함에 있어서 필수적으로 알아야 하는 개념 => 네비게이션, 네이티브 기기와 통신

 

1. 네비게이션 (화면 중첩과 보호) 

- 웹은 앵커태그
   웹의 URL이 브라우저 히스토리에 스택으로 푸시

 

- 앱은 네비게이터
   - 네비게이션은 정의한 화면을 렌더링하는 방법을 결정하는 리액트 컴포넌트

   - 앱이 화면을 전환하고 탐색 기록을 관리할 수 있는 방법

   - 스크린을 스택으로 쌓는다. => 메모리에서 스크린을 스택으로 관리

 

- 차이점은 스택이 변경될 때 제스처와 애니메이션을 제공한다는 점

 1 - 1 네비게이션을 구성하는 방법

- 네비게이션 컨테이너
   - 탐색 트리를 관리하고 상태를 포함하는 루트 컴포넌트

   - 모든 네비게이터 구조는 여기에 래핑

- 네비게이션 컨테이너 STACK

   - 새 화면이 스택 위에 배치

   - 화면 간 앱이 전환하는 방법 제공

   - ios, android, 기본 애니메이션이 적용되나 커스텀 가능

- 네비게이션 컨테이너 Drawer

   - 제스처를 통해 열고 닫을 수 있는 화면을 렌더링 

- 네비게이션 컨테이너 BottonTab => 현재는 그냥 tab인

   - 다른 영역을 전환할 수 있는 화면 하단의 탭 모음

   - 영역은 천천히 초기화 되고 처음 열리기 전까지 마운트되지 않음

- 네비게이션들은 스크린의 컴포넌트 props로 네비게이션과 라우트를 주입하게 된다. 
   =>  웹뷰에서 넘어오는 코드를 효율적으로 핸들링 가능 

- 앱 구현과 기본적인 설계

   - 모바일 웹과 앱에서의 컴포넌트 구분
      => 웹,앱뷰인지 여부를 판단해서 조건문으로 컴포넌트를 렌더링

   - 페이지 라우팅 제어 방식

      => router 이벤트를 발생시키지 않고 postmessage로 웹뷰에 보내기

   - 로그인 여부에 따라 다른 화면 보여주기 ?

      => 쿠키, 로컬스토리지에 저장된 유저 정보를 전송하고 네비게이션 중첩

   - 유저 정보를 어떻게 저장할까 ? 

      => 리액트 네이티브 저장소 라이브러리 적용 

 

1 - 2 라우트 이벤트 제어 방법

 

- 웹뷰와 앱 모두 postMessage라는 메서드를 통해서 통신한다.

   웹의 경우 window.ReactNativeWebView.postMessage 를 통해서, 
   앱에서는 webView컴포넌트.postMessage 형태로 사용, ref를 활용해야한다.

- 이 통신에서 오갈 수 있는 것은 string의 형태의 데이터여서 객체를 보내기 위해서는 json.stringify를 통해 string으로 변환 해 넘겨야 하고 받을 때는 json,parse를 통해 객체로 변환해 주어야 한다.   

 

=> 웹코드에서  window.ReactNativeWebView.postMessage메서드로 json.stringify를 통해 객체를 보낸다. 

.  

=> 앱에선 onMessage를 통해 넘어온 string를 객체로 받는다

 

이런 형식으로 되는듯 싶다. 

 

ios 는 window 객체

android는 document객체로 받는다

 

 

1 - 3 정보 저장

 

- react-native-async-storage / async-storage 
   - 리액트 네이티브 커뮤니티에서 개선과 유지보수를 담당하는 라이브러리

   - 리액트 네이티브를 위한 암호화되지 않은 키-값 저장소로 비동기 저장

   - 직렬화를 통한 string 데이터 저장, localstorage와 같은 사용법

   - json.stringify, json,parse api 활용 가능  

 

 

1 - 4 푸시알림

 

- 알림에는 로컬알림 / 푸시알림 두가지가 있다. 

 

  - 로컬 알림 

     - 앱이 설치된기기에서 설치된 앱이 트리거 하는 알림 (리마인더, 알람 시계, 할 일 앱 등)

 

   - 푸시 알림

      - 내 기기에 설치된 앱에서 트리거된 알림을 다른 기기에 설치된 앱으로 전송   

      - http메서드로 요청

 

expo - notifications 

 

- app json 설정

- extra.eas.projectId추가

- expo로 생성한 프로젝트 id

- expo-contants 라이브러리로 호출가능  

expo - notifications 으로 푸시 알림 설정하기

 

- getPermissionsAsync => 권한 상세 정보를 가져오는 메서드 , 푸시 알림을 보내기 전에 권한이 있어야한다. 

- 권한을 요청하기 전에 권한이 있는지 확인하는 코드 

 

요청해서 권한이 다 있으면 db에 저장된 인스턴스 id를 가져올때 getExpoPushTokenAsync id로 제어한다. 

- getExpoPushTokenAsync => expo 프로젝트 아이디로 앱이 설치된 현재 기기의 토큰을 가져오는 메서드

 

 

- css는 프로퍼티를 이용한 인라인 스타일과 stylesheet 객체로 작성

- clikc이벤트 대신에 pressable 컴포넌트로 감사서 이벤트를 핸들링

   - touchableHightlight, TouchableNativeFeedback, TouchableOpacity 등 다양하게 존재

- dom 이벤트 객체가 없으므로 event.nativeEvent 객체를 활용하여 핸들링

- dom의 스크롤이 없으므로 scrollView컴포넌트로 스크롤을 핸들링 

 

- 빌드는 eas build 

 

- 앱 출시를 위한 계정 준비

   - 구글 개발자 계정, 애플 개발자 계정에 각각 등록

   - 구글 개발자 계정은 수수료 결제 시 바로 사용 가능

   - 애플 계발자 계정은 최대 3주 시간 소요 

 

- 앱 출시를 위한 asset준비

   - 아이콘 : 1024 * 1024 사이즈의 png파일 필요

   - 스크린샷 : 기기 해상도별로 준비

   - 안내용 동영상 : 500메가 이하로 동영상 업로드가 가능

   - 인앱 구매 상품이 있다면 640 * 920 크기의 인앱 심사용 스크린샷 필요

   - 모든 이미지에는 타 채널 정보가 포함되어 있으면 안된다. 

   - 앱 사용 가능 연령을 표기하기 위한 등급설정 필요

   - 사용자를 지원할 수 있는 링크된 웹페이지가 반드시 필요

   - 스토어 게시글을 작성하고 개인정보 처리 방침을 작 

 

- 디테일 잡기

   - 스플래시 이미지, 앱 아이콘을 등록할 수 있는 app.json

   - statusBar 컴포넌트

   -  기기의 모서리, 상단 노치 등의 간격을 조절해주는 safeAreaView 컴포넌트

   - 앱의 테마를 설정할 수 있는 여러가지 수단들 : app.json, useTheme, styleSheet 등

   - 유저 인터렉션 ui에 대한 핸들링

    - 웹뷰 url을 노출을 숨길 수 있는 방법

   - 네비게이션 중첩을 활용하여 어떻게 사용성을 증가시킬 수 있는지 고민 

 

하지만 앱을 확장하고 싶으면 네이티브 개발이 필수

 

 


네이티브 postmessage첫 시도

 

const OauthBtnForm = () => {
  const router = useRouter();

  const loginClick = () => {
    router.push("/(tabs)");
  };
  const dapjiSignInClick = () => {
    router.push("/signin");
    console.log("Login button clicked");
  };
  return (
    <View>
      <OauthBtnStyle
        title="답지  아이디  로그인"
        onPress={dapjiSignInClick}
        color="white"
        icon={
          <Image
            source={require("@/assets/images/dapji.png")}
            style={{ width: 47, height: 47 }}
          />
        }
      />
    </View>
  );
};

export default OauthBtnForm;

 

react-native  루트 index페이지에 next.js 웹코드로 작성한 signin 페이지로 가는 click함수를 만들어준다. 

import { WebView } from "react-native-webview";
import { StyleSheet, Alert } from "react-native";
import Constants from "expo-constants";

const SigninScreen = () => {
  
  const handleMessage = (event: any) => {
    // 웹뷰로부터 받은 메시지를 처리
    Alert.alert("Message from WebView", event.nativeEvent.data);
  };

  return (
    <WebView
      style={styles.container}
      source={{ uri: "http://" }}
      javaScriptEnabled={true}
      domStorageEnabled={true}
      onMessage={handleMessage} // 메시지를 받을 때 처리할 함수
    />
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: Constants.statusBarHeight,
  },
});

 

네이티브 코드와 웹코드는 postMessage로 데이터를 주고받는다.

네이티브쪽의 siginin 페이지 hand에 설정한 handleMessage에 onMessage 이벤트를 받을 수 있도록 설정한다. 
event 는 이벤트가 발생했을 때 웹뷰에서 보낸 데이터를 담고 있는 객체로정해주고  .
event.nativeEvent.data는 웹뷰로부터 전송된 데이터로, 이는 보통 문자열 형식으로 전달된다. 웹 페이지에서 window.ReactNativeWebView.postMessage()로 보낸 메시지를 이 속성으로 받을 수 있다. 

 

그리고 웹뷰에서 

interface Window {
  ReactNativeWebView: {
    postMessage: (message: string) => void;
  };
}

 

window.ReactNativeWebView 속성에 대한 타입을 선언해서 TypeScript가 인식할 수 있도록 해줘야 해. 이를 위해 전역 객체에 ReactNativeWebView 타입을 추가

 

웹뷰쪽의 sign in 페이지의 onSubmit에 formData 객체를 string형식으로 바꿔서 postMessage로 전송한다. 

야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호 야호

 

 

 

 

 

 

 

 

 

 

 

- 화면 구성을 정의하기 위한 스크린 컴포넌트를 자식으로 포함

 

 

 

 

- 자료참고 : 인프콘 2023 웹뷰를 이용해 웹 서비스를 앱으로 빠르게 구현하기

https://www.youtube.com/watch?v=hsh8BS7gyrY&t=910s


완벽한 앱으로서의 기능을 위해 expo로 다시 만들기로 결정,