[RN] ๐Ÿงถ RN ํ”„๋กœ์ ํŠธ๋ฅผ Web์œผ๋กœ ๋นŒ๋“œํ•˜๊ธฐ

TATAยท2024๋…„ 7์›” 19์ผ
0

React-Native

๋ชฉ๋ก ๋ณด๊ธฐ
9/9

โ–ท react-native-web ์‚ฌ์šฉ๋ฒ•

react-native-web์€ React Native์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์›น ํ™˜๊ฒฝ์— ๋งž๊ฒŒ ๋ณ€ํ™˜ํ•˜์—ฌ ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง์ ‘ ์‹คํ–‰๋˜๋„๋ก ํ•ด์ค€๋‹ค.

React Native๋กœ ๋ชจ๋ฐ”์ผ ์•ฑ์„ ๊ฐœ๋ฐœํ•˜๋‹ค๊ฐ€ ์›น์œผ๋กœ๋„ ๋งŒ๋“ค์–ด๋ณด๊ณ  ์‹ถ์–ด์„œ ์‚ฌ์šฉํ•ด ๋ดค๋‹ค.

# ์„ค์น˜
yarn add react-dom react-native-web
yarn add url-loader
yarn add -D @types/react-dom

# webpack ์„ค์น˜
yarn add -D webpack webpack-cli webpack-dev-server html-webpack-plugin babel-loader @babel/preset-react

์ด์ œ ์ด ๋ธ”๋กœ๊ทธ ๋ณด๊ณ  ๊ธฐ๋ณธ ์„ค์ • ํ•ด์ฃผ๋ฉด ๋จ


โœš ์ถ”๊ฐ€์ ์ธ ์„ค์ •

๐Ÿงถ tailwindcss

# ์•„๋ž˜ ๋‘๊ฐœ๋Š” ์ด๋ฏธ ์„ค์น˜ ํ–ˆ์—ˆ์Œ
# yarn nativewind
# yarn add -D tailwindcss

yarn add -D postcss autoprefixer postcss-loader css-loader style-loader

tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './App.{js,jsx,ts,tsx}',
    './src/**/*.{js,jsx,ts,tsx}',
    './public/**/*.{js,jsx,ts,tsx,html}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

postcss.config.js

module.exports = {
  plugins: [require('tailwindcss'), require('autoprefixer')],
};

src/global.css

@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  background-color: rgb(233, 236, 239);
}
.root-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}
.root-wrapper {
  max-width: 540px;
  width: 100%;
  height: 100vh;
  background-color: #fff;
}

๐Ÿงถ index.web.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from '/App';
import './src/global.css'; // โญ๏ธ ์ถ”๊ฐ€

const rootNode = document.getElementById('root');
ReactDOM.createRoot(rootNode).render(<App />);

๐Ÿงถ webpack.config.js

...
// css ํŒŒ์ผ ๋กœ๋” ์ถ”๊ฐ€
const cssLoader = {
  test: /\.css$/,
  use: ['style-loader', 'css-loader', 'postcss-loader'],
  include: path.resolve(__dirname, 'src'),
};
module.exports = {
  resolve: {
    ...
    extensions: ['.web.js', '.js', '.ts', '.tsx', 'json', '.css'],
  },
  module: {
    rules: [tsxLoader, imgLoader, svgLoader, cssLoader],
  },
  ...
};

๐Ÿงถ babel.config.js

module.exports = {
  presets: ['module:@react-native/babel-preset', '@babel/preset-react'],
  ...
}

๐Ÿงถ package.json

"scripts": {
  "start:web": "webpack serve --config ./webpack.config.js --mode development",
  "build:web": "webpack --mode production",
  ...
},

๐Ÿงถ ์›น์œผ๋กœ ์‹คํ–‰

yarn run start:web

๐Ÿงถ ๊ฒฝ๋กœ ์„ค์ •

์›น๊ณผ ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ๊ฒฝ๋กœ ์„ค์ •์„ ๋‹ค๋ฅด๊ฒŒ ํ•ด์•ผ ํ•œ๋‹ค.
๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์›น์—์„œ๋Š” ๊ฒฝ๋กœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด๋„ URL์ด ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š๋Š”๋‹ค.

react-router-dom์€ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์—์„œ๋งŒ ์ž‘๋™ํ•˜๋ฏ€๋กœ ๋ชจ๋ฐ”์ผ ์•ฑ์—์„œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.
(๋ชจ๋ฐ”์ผ์—์„œ ์‚ฌ์šฉํ•˜๋ฉด document ๊ฐ์ฒด์™€ ๊ด€๋ จ๋œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•จ)

# ์„ค์น˜
yarn add react-router-dom

App.tsx

/* App.tsx */
import React from 'react';
import {Platform} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom';

import HomeScreen from 'screens/HomeScreen';
import DetailMenuScreen from 'screens/DetailMenuScreen';

function App() {
  const Stack = createNativeStackNavigator();
  const isWeb = Platform.OS === 'web';

  return (
    <>
      {!isWeb && (
        <NavigationContainer>
          <Stack.Navigator>
            <Stack.Screen
              name="Home"
              options={{headerShown: false}}
              component={HomeScreen}
            />
            <Stack.Screen
              name="DetailMenu"
              options={{headerShown: false}}
              component={DetailMenuScreen}
            />
          </Stack.Navigator>
        </NavigationContainer>
      )}
      {isWeb && (
        <Router>
          <Routes>
            <Route path="/" element={<HomeScreen />} />
            <Route path="/menu/detail" element={<DetailMenuScreen />} />
          </Routes>
        </Router>
      )}
    </>
  );
}

export default App;

src/screen/HomeScreen.tsx

/* src/screen/HomeScreen.tsx */
import React from 'react';
import {Platform, Pressable, SafeAreaView, StatusBar, Text} from 'react-native';
import {useNavigation} from '@react-navigation/native';
import {NativeStackNavigationProp} from '@react-navigation/native-stack';
import {useNavigate} from 'react-router-dom';

const HomeScreen = () => {
  const isWeb = Platform.OS === 'web';
  const navigate = isWeb ? useNavigate() : undefined;
  const navigation = isWeb
    ? undefined
    : useNavigation<NativeStackNavigationProp<any>>();

  const goToDetailMenu = () => {
    if (isWeb && navigate) {
      navigate('/menu/detail');
      return;
    }
    if (navigation) {
      navigation.navigate('DetailMenu');
    }
  };

  return (
    <SafeAreaView className="bg-white">
      <StatusBar barStyle={'light-content'} backgroundColor={'#fff'} />
      <Pressable
        onPress={goToDetailMenu}
        className="bg-green-300 p-4 text-green-900 m-10 border border-solid border-green-900 rounded">
        <Text>Home</Text>
      </Pressable>
    </SafeAreaView>
  );
};

export default HomeScreen;

ํ›…์œผ๋กœ ๋ถ„๋ฆฌ

hooks/useNavi.ts

import { Platform } from 'react-native';
import { useNavigate } from 'react-router-dom';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';

const useNavi = () => {
  const isWeb = Platform.OS === 'web';

  const navigate = isWeb ? useNavigate() : undefined;
  const navigation = isWeb ? undefined : useNavigation<NativeStackNavigationProp<any>>();

  return {
    navigate, // ์›น
    navigation, // ์•ฑ
  };
};

export default useNavi;
profile
๐ŸŒฟ https://www.tatahyeonv.com

0๊ฐœ์˜ ๋Œ“๊ธ€