LoginSignup
5
7

More than 3 years have passed since last update.

ReactNative(expo)でアプリとWeb(View)で値をやり取りする

Last updated at Posted at 2020-01-01

ReactNativeのWebView(Web)とアプリを連携する必要があったのでメモ。

忙しい人向け

  • アプリからWeb => WebViewのinjectedJavaScriptにJSを渡して値をインジェクトしてWeb側でよしなにする
  • Webからアプリ => window.ReactNativeWebView.postMessage("message")で送りonMessage()で受け取る

やりたいこと

例えば決済機能等の実装において、決済画面だけは決済サービス会社が提供するものを利用したいが、値はアプリ側で計算したものをデフォルト値として渡したいケースなど。

以下のような仕様。

スクリーンショット 2020-01-01 15.37.16.png

最近の決済APIはカード情報非保持・非通過とするためWeb画面でカード番号を入れさせるものが多い。さらに言えばexpoでEjectしないで利用できるコンポーネントもない。

Web側

Web側は普通のHTMLだとまだ簡単なのですがReactを使うことが多いのでReactを使ってみます。

準備

場所作って必要なモジュールをインストール。

create-react-app web-app
cd web-app

npm install --save bootstrap reactstrap formik yup

実装

続いて実装。

index.js

bootstrap cssの読み込みとServiceWorkerを削除している(キャッシュが効いちゃうので)。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import './index.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

App.js

App.jsに一旦すべて実装。

  • priceというidのinputを用意
  • cardというidのinputを用意
  • priceにinjectedJavaScriptから値を設定

ということを想定。私いつもFormikつかうので使ってます。

App.js
import React from 'react';
import { Form, FormGroup, Label, Input, Button, FormFeedback } from 'reactstrap';
import { Formik } from 'formik';
import * as Yup from 'yup';

class App extends React.Component {

    handlePayment = async (values) => {

        //1秒休む
        await this.sleep(1000);

        //終了したらアプリ側にメッセージを送る
        window.ReactNativeWebView.postMessage(values.price + "円の決済が完了しました。");
    }

    //おやすみ補助関数
    sleep = (msec) => {
        return new Promise((resolve) => {
            setTimeout(() => {
                return resolve();
            }, msec)
        })
    }

    render() {
        return (
            <div className="container">
                <h3 className="my-4 text-center">PaymentここはWeb</h3>
                <div className="col-10 mx-auto">
                    <Formik
                        initialValues={{ price: 0, card: '1111-2222-3333-4444' }}
                        onSubmit={(values) => this.handlePayment(values)}
                        validationSchema={Yup.object().shape({
                            price: Yup.number().min(1).max(1000),
                            card: Yup.string().required(),
                        })}
                    >
                        {
                            ({ handleSubmit, handleChange, handleBlur, values, errors, touched, setFieldValue }) => (
                                <Form>
                                    <FormGroup>
                                        <Label>金額</Label>
                                        <Input
                                            type="text"
                                            name="price"
                                            id="price" //idで強引に値をセット
                                            value={values.price}
                                            onChange={handleChange}
                                            onBlur={handleBlur}
                                            invalid={Boolean(touched.price && errors.price)}
                                            disabled
                                        />
                                        <FormFeedback>
                                            {errors.price}
                                        </FormFeedback>
                                    </FormGroup>
                                    <FormGroup>
                                        <Label>カード番号</Label>
                                        <Input
                                            type="text"
                                            name="card"
                                            id="card"
                                            value={values.card}
                                            onChange={handleChange}
                                            onBlur={handleBlur}
                                            invalid={Boolean(touched.card && errors.card)}
                                        />
                                        <FormFeedback>
                                            {errors.card}
                                        </FormFeedback>
                                    </FormGroup>
                                    <Button type="button" onClick={async () => {
                                        const price = document.getElementById("price");
                                        //Formik使ってるので値を明示的にセットしてやる(完了するうちにValidationが走らないようawait)
                                        await setFieldValue("price", price.value);
                                        handleSubmit();
                                    }}>購入</Button>
                                </Form>
                            )
                        }
                    </Formik>
                </div>
            </div>
        );
    }
}

export default App;

とりあえず完成。window.ReactNativeWebView.postMessage()なんていう関数は標準のブラウザにはないのでchrome等でデバッグするとエラー出ますが無視します。

アプリ側

次にアプリ側。
場所の準備と必要コンポーネントをインストール。WebViewは普通にインストールするとexpoに怒られるのでexpo installコマンドで適切なバージョンのものをインストール。

expo init app-web-integration
cd app-web-integration

expo install react-navigation react-native-gesture-handler react-native-reanimated react-native-screens
expo install react-navigation-stack react-navigation-tabs react-navigation-drawer
expo install react-native-webview

まずApp.js。基本的にStackNavigatorを設定しているだけ。
Home.jsとPayment.jsを利用しています。

App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Card, Input, Button } from 'react-native-elements';

import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';

import Home from './Home';
import Payment from './Payment';

//stack navigator
const HomeStack = createStackNavigator(
    {
        Home: {
            screen: Home,
        },
        Payment: {
            screen: Payment,
        }
    }
);

const AppContainer = createAppContainer(HomeStack);

class App extends React.Component {
    render() {
        return (
            <AppContainer />
        );
    }
}

export default App;

Home.js

ボタンを配置してPayment.jsに移動します。またその時金額をパラメーターとして渡しています。

Home.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Card, Input, Button } from 'react-native-elements';

class Home extends React.Component {
    render() {
        return (
            <View style={{ flex: 1, alignItems: 'center', marginTop: 50 }}>
                <Text style={{ fontSize: 24 }}>Homeここはアプリ</Text>
                <Button
                    title="100円コースを買う"
                    style={{ width: '80%', marginTop: 20 }}
                    onPress={() => this.props.navigation.navigate("Payment", { price: 100 })}
                />
                <Button
                    title="200円コースを買う"
                    style={{ width: '80%', marginTop: 20 }}
                    onPress={() => this.props.navigation.navigate("Payment", { price: 200 })}
                />
            </View>
        );
    }
}

export default Home;

Payment.js

このコンポーネントはWebViewになります。WebViewの、

  • injectedJavaScriptにWeb側に渡す値を設定
  • onMessageに戻りの処理を書く
Payment.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Card, Input, Button } from 'react-native-elements';
import { WebView } from 'react-native-webview';

class Payment extends React.Component {

    state = {
        js: '',
    }

    componentDidMount = async () => {
        //前のページからパラメータを受け取る(なければ0)
        const price = await this.props.navigation.state.params.price ? this.props.navigation.state.params.price : 0;
        //priceを設定するスクリプトを動的に生成
        const js = `
            const price = document.getElementById("price");
            price.value = ${price}
        `;
        //stateを通じて渡す
        this.setState({ js: js });
    }

    //Web側からのpostMessageに対応
    onMessage = (event) => {
        const message = event.nativeEvent.data;
        this.props.navigation.navigate("Home");
        alert(message);
    }

    render() {

        //js内の変数が処理されないうちにWebViewがレンダリングするのを防ぐ
        if (this.state.js === '') {
            return <Text>Loading...</Text>
        }

        //WebViewをレンダリング
        return (
            <WebView
                source={{ uri: 'http://localhost:3000/' }}
                injectedJavaScript={this.state.js}
                onMessage={this.onMessage}
            />
        )
    }
}

export default Payment;

かなり端折ってるけるけどとりあえず。

5
7
1

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
5
7