Nx를 사용하여 React Web과 React Native Mobile 간에 코드 공유

내가 해결하고자 하는 문제 중 하나는 웹 응용 프로그램을 만들고 싶을 뿐만 아니라 모바일 응용 프로그램도 만들고 싶다는 아주 좋은 생각이 있다는 것이다.일반적으로 웹과 모바일 응용 프로그램을 만드는 데는 완전히 다른 기술 창고가 필요하고 코드를 공유하기 어렵다.본고는 내가 Nx을 어떻게 사용하여 같은 monorepo에 React 웹 응용 프로그램과 React 원본 모바일 응용 프로그램을 추가하고 이들의 코드 공유를 최적화하는지 보여준다.
저는 주로 인터넷 개발자이기 때문에 먼저 인터넷 응용 프로그램부터 시작하겠습니다. https://xiongemi.github.io/studio-ghibli-search-engine.그것은 지브리 스튜디오의 영화와 캐릭터 검색엔진이다.

Github 재구매: https://github.com/xiongemi/studio-ghibli-search-engine
Github 페이지: https://xiongemi.github.io/studio-ghibli-search-engine
이제 이 프로그램의 모바일 버전을 만듭니다.

기술 스택
  • Monorepo:Nx
  • 네트워크 프런트엔드: React
  • API:https://ghibliapi.herokuapp.com/
  • 현재, 우리 Nx 작업 영역에는 React 웹 응용 프로그램이 하나밖에 없습니다.nx dep-graph을 실행하는 경우 종속성 다이어그램은 다음과 같습니다.


    리액션 네이티브 설정
    시작하려면 Nx 작업공간에 React 네이티브 지원을 추가해야 합니다.
    # npm 
    npm install @nrwl/react-native --save-dev
    
    # yarn
    yarn add @nrwl/react-native --dev
    
    그런 다음 다음 다음 프로그램을 실행하여 새로운 React 기본 응용 프로그램을 생성할 수 있습니다.
    npx nx generate @nrwl/react-native:app studio-ghibli-search-engine-mobile
    

    Note, if you’re using VSCode you might want to try Nx Console for a more visual experience of running such commands.


    위 명령을 실행하면 현재 apps 디렉토리에 studio-ghibli-search-engine-mobilestudio-ghibli-search-engine-mobile-e2e 두 개의 새 폴더가 있어야 합니다.

    현재 nx dep-graph을 다시 실행하면 의존 관계도는 다음과 같습니다.
    studio-ghibli-search-engine-mobilestudio-ghibli-search-engine-web 사이에 공유 코드가 없습니다.그러나, 우리의 목표는 웹 버전을 위한 새로운 React 본체 버전의 응용 프로그램에서 웹 버전을 위한 일부 기능을 다시 사용하는 것이다.

    공유할 수 없는 코드
    비록 우리의 목표는 React 웹 응용 프로그램과 React 원본 응용 프로그램 사이에서 가능한 한 많이 공유하는 것이지만, 일부 부분은 근본적으로 공유할 수 없다.

    사용자 인터페이스
    모바일 애플리케이션의 모든 UI 구성 요소를 다시 작성해야 합니다.React Native는 Cordova 또는 Ionic과 달리 네트워크 뷰가 아닙니다.작성한 JavaScript는 로컬 요소를 이동하는 것으로 해석되고 변환됩니다.따라서 React 웹 응용 프로그램에 작성된 UI HTML 요소를 간단하게 재사용할 수 없습니다.
    다음은 React 웹 응용 프로그램에 사용할 라이브러리의 빠른 목록과 우리가 사용할 수 있는 해당 React 본체 대응 라이브러리입니다.
    경로
  • react-router-dom
  • @react-navigation/native 모바일 장치용
  • 재료 설계 라이브러리
  • @mui/material
  • react-native-paper 모바일 장치용
  • 위의 React 라이브러리 외에 몇 가지 핵심 유틸리티 라이브러리를 설치해야 합니다.
  • 현지인 부활
  • 리액션 네이티브 제스처
  • 기본 화면
  • 반응 로컬 안전구역 상하문
  • @react native community/masked view
  • 기본 벡터 아이콘
  • 적절한 설치 명령은 다음과 같습니다.
    # npm
    npm install @react-navigation/native @react-navigation/native-stack react-native-paper react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view --save
    
    # yarn
    yarn add @react-navigation/native @react-navigation/native-stack react-native-paper react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
    

    보관부
    React 웹 응용 프로그램에 대해 우리는 redux-persist을 사용합니다. 이것은 Redux 상점을 localstorage에 저장합니다.그러나 React Native는 localstorage을 지원하지 않습니다.
    웹의 경우 redux persist에서 persistStore로 전달되는 변수 persistConfig은 다음과 같습니다.
    import storage from 'redux-persist/lib/storage';
    
      const persistConfig = {  
        key: 'root',  
        storage: storage,  
        whitelist: ['search', 'films', 'people'],  
        transforms: [transformEntityStateToPersist],  
      };
    
    하지만 모바일 장치의 경우 @react-native-async-storage/async-storage 라이브러리를 설치해야 합니다.
    # npm
    npm install @react-native-async-storage/async-storage --save-dev
    
    # yarn  
    yarn add @react-native-async-storage/async-storage --dev
    
    따라서 Redux persist에서 persistStore로 전달되는 persistConfig은 다음과 같습니다.
    import AsyncStorage from '@react-native-async-storage/async-storage';
    
      const persistConfig = {  
        key: 'root',  
        storage: AsyncStorage,  
        whitelist: ['search', 'films', 'people'],  
        transforms: [transformEntityStateToPersist],  
      };
    

    역사.
    React 웹 응용 프로그램에서, 우리는 connected-react-router을 사용하여 공유기 상태를 Redux 상점에 넣었다.그러나 React Native는 History API (windows.history)을 지원하지 않습니다.대안으로 createMemoryHistory을 사용할 수 있습니다.
    웹 응용 프로그램의 사용 내역은 다음과 같습니다.
    import { createHashHistory, History } from 'history';
    
    const history: History = createHashHistory();
    
    모바일 애플리케이션의 경우 사용 내역은 다음과 같습니다.
    import { createMemoryHistory, History } from 'history';
    
    const history: History = createMemoryHistory();
    
    코드를 다시 사용하기 위해서 우리는 connected-react-router을 사용하여 루트 축소기의 생성을 약간 재구성할 수 있다. 그러면 history의 대상을 매개 변수로 할 수 있다.
    import { combineReducers } from '@reduxjs/toolkit';  
    import { connectRouter } from 'connected-react-router';  
    import { History } from 'history';
    
    import { filmsSlice } from '../films/films.slice';  
    import { peopleSlice } from '../people/people.slice';  
    import { searchSlice } from '../search/search.slice';
    
    import { RootState } from './root-state.interface';
    
    export const createRootReducer = (history: History) =>  
      combineReducers<RootState>({  
        films: filmsSlice.reducer,  
        router: connectRouter(history) as any, 
        search: searchSlice.reducer,  
        people: peopleSlice.reducer,  
      });
    

    질의 매개변수
    웹에서 개발할 때 상태나 정보를 전달하는 가장 간단한 방법은 보통 URL을 이용하여 파라미터를 조회하는 것이다.우리의 검색 프로그램 예시에서 우리는 ?search=searchText과 비슷한 것을 간단하게 사용할 수 있다.
    우리는 react-router-dom을 사용하여 새로운 역사 항목을 전달할 수 있다.
    import { useHistory } from 'react-router-dom';
    
    const history = useHistory();
    
    const submitSearchForm = (text: string) => {  
      history.push(`${AppRoutes.results}?search=${text}`);  
    };
    
    현재 질의 매개변수 search을 읽고 분석하려면 다음과 같이 하십시오.
    import { useLocation } from 'react-router-dom';
    
    const params = new URLSearchParams(useLocation().search);  
    const searchParam = params.get('search');
    
    URL은 모바일 애플리케이션에 표시되지 않지만 매개 변수를 전달할 수 있습니다.다른 패키지인 @react-navigation/native을 사용해야 합니다.
    import { useNavigation } from '@react-navigation/native';
    
    const navigation = useNavigation();
    
    const submitSearchForm = () => {  
      navigation.navigate(AppRoutes.results, { search: text });  
    };
    
    매개변수를 읽고 분석하려면 다음과 같이 하십시오.
    import { RouteProp, useRoute } from '@react-navigation/native';
    
    const route = useRoute<RouteProp<{ params: { search: string } }>>();  
    const searchParam = route.params?.search;
    
    typescript를 사용하여react 내비게이션의 유형 검사를 진행하려면, 루트 이름을 루트에 비추는 파라미터인 RootStackParamList 형식을 만들어야 합니다.
    export type RootStackParamList = {  
      [AppRoutes.search]: undefined;  
      [AppRoutes.results]: { search: string };  
    };
    
    또한 루트 탐색기의 글로벌 유형을 지정해야 합니다.
    declare global {  
      // eslint-disable-next-line @typescript-eslint/no-namespace  
      namespace ReactNavigation {  
        // eslint-disable-next-line @typescript-eslint/no-empty-interface  
        interface RootParamList extends RootStackParamList {}  
      }  
    }
    
    So we create the stack navigator, we need to pass the above `RootStackParamList` type:
    
    import { createNativeStackNavigator } from '@react-navigation/native-stack';
    
    const Stack = createNativeStackNavigator<RootStackParamList\>();
    

    환경 변수
    Nx는 handling environment variables의 다양한 옵션을 제공합니다.작업공간 루트 디렉토리에는 간단한 .env 파일이 있습니다.
    NX_REQUEST_BASE_URL=://ghibliapi.herokuapp.com
    
    이것은 우리의React 웹 구축에 매우 효과적이지만, 우리의React 본체 응용 프로그램에는 그렇지 않다.React Native와 React 애플리케이션이 서로 다른 Javascript 바인딩기를 사용하기 때문입니다.React Native는 Metro을, React는 Webpack을 사용합니다.그래서 우리가 process.env.NX_REQUEST_BASE_URL을 방문하려고 시도했을 때 우리는 undefined을 얻었다.
    이 문제를 해결하기 위해 react-native-config 라이브러리를 사용할 수 있습니다.
    # npm
    npm install react-native-config --save-dev
    
    # yarn
    yarn add react-native-config --dev
    
    다음은 react-native-config을 설정하는 방법의 예: https://github.com/luggit/react-native-config#setup.
    이후에 우리는 간단한 실용 함수를 사용하여 응용 프로그램의 환경 변수를 검색할 수 있다.
    import Config from 'react-native-config';
    
    export function getEnv(envName: string) {  
      return process.env[envName] || Config[envName];  
    }
    
    환경 변수 NX_REQUEST_BASE_URL에 접근하려면 위의 함수: getEnv(‘NX_REQUEST_BASE_URL’)을 사용하십시오.

    HTTP를 사용하여 가져오기
    인터넷상에서, 당신은 fetch API에 의존하여 네트워크 요청을 할 가능성이 매우 높다.그러나 iOS에서 당신은 TypeError: Network request failed의 오류를 얻게 될 것입니다.
    React Native는 기본적으로 HTTP 요청을 허용하지 않습니다. https://stackoverflow.com/questions/38418998/react-native-fetch-network-request-failed.
    이 문제를 해결하려면 iOS의 경우 apps/studio-ghibli-search-engine-mobile/ios/StudioGhibliSearchEngineApp/Info.plist을 열고 요청 URL을 NSExceptionDomainsNSAppTransportSecurity에 추가합니다.
    <key>NSAppTransportSecurity</key>  
     <dict>  
      <key>NSExceptionDomains</key>  
      <dict>  
       <key>localhost</key>  
       <dict>  
        <key>NSExceptionAllowsInsecureHTTPLoads</key>  
        <true/>  
       </dict>  
       <key>ghibliapi.herokuapp.com</key>  
       <dict>  
        <key>NSExceptionAllowsInsecureHTTPLoads</key>  
        <true/>  
       </dict>
      </dict>  
     </dict>
    
    마찬가지로 Android의 경우 apps/studio-ghibli-search-engine-mobile/android/app/src/main/res/xml/network_security_config.xml을 열고 요청 URL을 이 구성 파일에 추가합니다.
    <?xml version="1.0" encoding="utf-8"?>  
    <network-security-config>  
        <domain-config cleartextTrafficPermitted="true">  
            <domain includeSubdomains="true">10.0.2.2</domain>  
            <domain includeSubdomains="true">localhost</domain>  
            <domain includeSubdomains="true">herokuapp.com</domain>  
        </domain-config>  
    </network-security-config>
    
    그러면 네트워크 오류가 제거됩니다.
    React 네이티브 애플리케이션의 경우 많은 사용자 정의가 필요할 것 같습니다.그러나 대부분의 비 UI 코드는 재사용할 수 있습니다.

    공유 가능한 코드
    UI가 아닌 모든 비즈니스 논리 코드를 공유할 수 있습니다.이 예에서 나는 monorepo에서 3개의 라이브러리를 얻었는데 모두 공유할 수 있다.
  • 모델: 유형 및 인터페이스 정의
  • 서비스: API
  • 과 상호 작용하는 서비스
  • 상점:redux상점
  • Nx를 사용할 때 위의 라이브러리 코드를 공유하려면 0 구성이 필요합니다.비록 내가 웹 응용 프로그램을 위해 이 라이브러리를 만들 때, 나는 nx generate @nrwl/react:lib store 같은 명령을 사용했지만, 나는react 원본 모바일 응용 프로그램에서 그것들을 직접 사용할 수 있다.
    예를 들어, 필름의 세부내용을 표시하고 필름 id를 매개 변수로 전달할 필름 페이지를 만들어야 합니다.

    스토어 라이브러리에서 직접 가져오기:
    import {  
      filmsActions,  
      filmsSelectors,  
      RootState,  
    } from '@studio-ghibli-search-engine/store';
    
    영화의 구성 부분은 다음과 같다.

    참고: @studio-ghibli-search-engine/models, @studio-ghibli-search-engine/services@studio-ghibli-search-engine/store에서 직접 가져올 수 있습니다


    현재 내가 nx dep-graph을 실행할 때 의존 관계도를 나타낸다. 아래와 같이 이 세 라이브러리는 웹과 모바일 기기 사이에서 공유된다.



    이 예시 항목에 대해 모바일 프로그램을 만들기 위해 UI 전체를 다시 쓰는 데 시간이 좀 걸렸습니다.하지만 위의 라이브러리를 너무 많이 변경할 필요는 없습니다





    결론


    본고에서 우리는 최종적으로 Nx을 사용하여 같은 메모리 라이브러리에서 React 기반의 웹 응용 프로그램과 상응하는 React 본체 응용 프로그램을 구축하였다


    Nx의 구조는 관심점의 분리를 추진하고 사물을 apps(기술에 특정)과 libs(기술에 특정하거나 기술에 독립)으로 나눈다.이것은 우리로 하여금 기술과 독립된 라이브러리에서 우리의 일반적인 업무 논리를 쉽게 가지게 하고, 이 라이브러리(Nx의 설정 덕분)는 우리의React 웹과 React 원본 모바일 응용 프로그램에 쉽게 연결할 수 있게 한다.p>

    UI의 구체적인 차이를 고려해야 하지만 하나는 웹 기술 창고이고 다른 하나는 로컬 응용 프로그램이지만, 우리는 여전히 응용 프로그램에서 기술과 무관한 대량의 업무 논리를 공유할 수 있다.이것은 결국 서로 다른 플랫폼 간의 유지보수와 기능의 피어에 도움이 된다


    (본문 코드를 포함하는 저장소 링크가 맨 위에 있음 주의)

    좋은 웹페이지 즐겨찾기