LoginSignup
2
0

react-native-web対応ノウハウまとめ

Last updated at Posted at 2024-04-05

セットアップ

  • yarn add react-dom react-native-web
  • yarn add -D babel-plugin-react-native-web
  • publicディレクトリを作り、中にファイルを追加
    • index.htmlを作る
    • index.web.jsを作る
    • webpack.config.jsを作る
  • package.jsonにスクリプトを追加
    • "web": "webpack serve --mode=development --config ./public/webpack.config.js”
public/webpack.config.jsのサンプル
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const appDirectory = path.resolve(__dirname, '../');
const babelConfig = require(`${appDirectory}/babel.config.js`);

const compileNodeModules = [
  '@firebase',
  'react-native-vector-icons',
  'react-native-animatable',
  'react-native-gesture-handler',
].map(moduleName => path.resolve(appDirectory, `node_modules/${moduleName}`));

module.exports = {
  entry: path.join(__dirname, 'index.web.js'),
  output: {
    path: path.resolve(appDirectory, 'dist'),
    publicPath: '/',
    filename: 'rnw.bundle.js',
  },
  resolve: {
    extensions: ['.web.tsx', '.web.ts', '.tsx', '.ts', '.web.js', '.js'],
    alias: {
      'react-native$': 'react-native-web',
      'react-native-maps': 'react-native-web-maps',
    },
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx|js|jsx)$/,
        include: [
          path.resolve(__dirname, 'index.web.js'),
          path.resolve(appDirectory, 'src'),
          ...compileNodeModules,
        ],
        use: {
          loader: 'babel-loader',
          options: {
            ...babelConfig,
            cacheDirectory: true,
          },
        },
      },
      {
        test: /\.svg$/,
        use: ['@svgr/webpack'],
      },
      {
        test: /\.(gif|jpe?g|png)$/,
        type: 'asset/resource',
        parser: {
          dataUrlCondition: {
            maxSize: 8192, // 8KB
          },
        },
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, 'index.html'),
    }),
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(appDirectory, 'public/Fonts/'),
          to: path.resolve(appDirectory, 'dist/Fonts/'),
        },
      ],
    }),
  ],
};
public/index.web.jsのサンプル
import {AppRegistry} from 'react-native';
import {App} from '../src/App';
if (module.hot) {
  module.hot.accept();
}
AppRegistry.registerComponent('React Native Web', () => App);
AppRegistry.runApplication('React Native Web', {
  initialProps: {},
  rootTag: document.getElementById('app-root'),
});
public/index.htmlのサンプル
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>RN Web</title>
    <style>
      #app-root {
        display: flex;
        flex: 1 1 100%;
        height: 100vh;
      }
    </style>
    <link rel="stylesheet" href="./font-style.css" />
  </head>
  <body>
    <div id="app-root"></div>
  </body>
</html>

webとそれ以外でコードを分ける方法

  • Platform.OS を使って分ける方法
    • import { Platform } from 'react-native';
  • Platform.selectを使って分ける方法
    • Platform.select({web: ..., default: ...})
  • ファイル名で分ける方法
    • *.web.js のように.webがついていれば、webではそれが読み込まれる
    • MyComponent.tsxなら、MyComponent フォルダを作り、MyComponent/index.tsx MyComponent/index.web.tsx とファイルを分ける
    • index.android.tsx index.ios.tsx のようにAndroidとiOS専用にすることもできる

Alertが使えない

  • alertコンポーネントを作り、それを使うように変更する
  • ネイティブはexport const alert = Alert.alert のようにそのままでOK
  • web用alert
    • window.confirm を使う
    • Modalを使って実装する

react-native-firebaseが使えない

  • 対応していないので、普通のfirebaseをインストールする
  • Uncaught ReferenceError: require is not defined
    • webpack.configの、js,jsx,ts,tsxのincludeに、@firebaseを追加
  • webとネイティブで書き方が異なる
    • 共通のインターフェースを用意してfirebaseの機能はそれを使うようにするか、webは名前空間方式(compat)を使うようにする
    • phoneAuth
      • <View id='recaptcha' /> などを置いて、 invisible reCAPTCHAを使う new RecaptchaVerifier(auth, 'recaptcha', {'size': 'invisible'})
      • PhoneAuthProvider.credentialverificationId と認証コードを渡してcredentialを作り、signInWithCredential でサインインできる
        • もちろん confirmationResult.confirm でもよい

react-native-mapsが使えない

  • react-native-web-mapsを入れ、aliasを設定し、index.htmlにスクリプトを追加
  • Markerを使おうとすると、Element type is invalid
    • import {Marker} ではなく、import MapViewからのMapView.Markerという古い形式で使わないと表示されない

react-native-vector-iconsが使えない

  • webpackのコンパイルする対象にincludeする
  • node_moduels/react-native-vector-icons/Fontsをpublicフォルダにコピー
  • index.htmlのstyleなどで、@font-face { src: url(Fonts/Ionicons.ttf); font-family: “Ionicons”; }
    • <link rel="stylesheet" href="./font-style.css" />のように外出しすると見やすい

react-navigationがうまく表示されない

  • 戻るボタンが表示されない
    • Stack.NavigatorのscreenOptionsを設定する
      • headerBackTitleVisible: true,
      • ...(Platform.OS === 'ios' && {headerBackImage: HeaderBackImage}),
        • このHeaderBackImageは自作コンポーネント。vector-iconで「chevron-left」などを指定する

ローカルパッケージが使えない

  • package.jsonで対象パッケージをnohoistにする
  • 対象のパッケージを、webpackのコンパイルする対象にincludeする

レスポンシブ(メディアクエリ)に対応する

  • デバイス幅を取得する
    • useWindowDimensions()
    • Dimensions.get('window').width
  • ライブラリを使う
responsive.tsxのサンプル
import {useWindowDimensions} from 'react-native';

const mobileWidth = 520;

/**
 * モバイル端末の場合のみ表示する
 * @example
 * ```jsx
 * <Mobile>モバイル端末のみ表示される</Mobile>
 * ```
 */
export const Mobile = ({children}: {children: React.ReactNode}) => {
  const {width} = useWindowDimensions();
  const isMobile = width < mobileWidth;
  return isMobile ? children : null;
};
/**
 * タブレット端末の場合のみ表示する
 * @example
 * ```jsx
 * <Tablet>タブレット端末のみ表示される</Tablet>
 * ```
 */
export const Tablet = ({children}: {children: React.ReactNode}) => {
  const {width} = useWindowDimensions();
  const isMobile = width < mobileWidth;
  return isMobile ? null : children;
};

type Media = 'mobile' | 'tablet';
/**
 * 現在のメディアを取得する
 * @returns {currentMedia: Media} 現在のメディア
 */
export const useCurrentMedia = (): {
  currentMedia: Media;
} => {
  const {width} = useWindowDimensions();
  return {
    currentMedia:
      width < mobileWidth ? 'mobile' : 'tablet',
  };
};
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0