React Native for Windowsでネイティブ機能を呼び出す
この記事でやること
- React Native for Windows(以下RN4W)のプロジェクトを作成してそこにC++を使ってWindowsネイティブの機能を追加します。
- 今回はサンプルとしてボタンがクリックされた時にイベントが発火するようなものを作っていきます。
前提
こちらに書いてある通りなのですが一応書いておくと
- nodejs
- Visual Studio 2019
- Universal Windows Platform development
- Desktop development with C++
またそれに加えてこの記事では
- VSCode
- VSCodeのReact Native Tools拡張機能
も使うのでこれらをインストールしておいてください。
1. プロジェクトを作成
それではこちらに従って新しいプロジェクトを作成していきます。
> npx react-native init rn4wSampleApp --template react-native-template-typescript@6.5.*
新しいReact Nativeのプロジェクトを作ります。今回はTypeScriptを使います。
> cd .\rn4wSampleApp\
> npx react-native-windows-init --overwrite
RN4Wを入れます。
2. VSCodeを設定してReact Nativeパッケージャとデバッガを起動
VSCodeにReact Native Toolsを入れて起動設定を作るだけでJS側はほぼ設定いらずでデバッグ等ができてとても便利です。
- まずVSCodeで
rn4wSampleApp
のフォルダを開きます。 -
.vscode/launch.json
を作成し以下のように書き換えてください。
{
"configurations": [
{
"name": "Launch UWP",
"cwd": "${workspaceFolder}",
"type": "reactnative",
"request": "launch",
"platform": "windows"
},
{
"name": "Attach",
"cwd": "${workspaceFolder}",
"type": "reactnative",
"request": "attach"
},
{
"name": "Launch Android",
"cwd": "${workspaceFolder}",
"type": "reactnativedirect",
"request": "launch",
"platform": "android"
}
]
}
これでVSCode左部からデバッグができるようになります。Launch UWP
を選んで実行すれば起動するのですが今回はそれではなくAttach
を選択して実行しておいてください。
するとVSCodeがアタッチ待ちの状態になり、React Nativeパッケージャーが起動するのでそのまま次に進んでください。
3. アプリを起動
-
windows/rn4wSampleApp.sln
をVisual Studioで開きます。 - 上部のコンボボックスから
x86
かx64
を選び実行します。初回は結構時間がかかるので気長に待ちます。 - ウインドウが表示され、先ほど起動しておいたVSCodeのデバッガにアタッチされるはずです。その後しばらく待ち、「Welcome to React」の画面が表示されたら成功です。
4. ネイティブコードを書く
ここまでの手順だけでも普通にRN4Wを使うことができますが、やはりネイティブ機能を使いたいときが出てくるでしょう。RN4WではUWPの機能を呼び出すことができます。その機能を使って今回はマウスボタンがクリックされたときにイベントをJS側に送るようにしたいと思います。
- Visual Studioでソリューション内にある
rn4wSampleApp
プロジェクトを右クリックし、「追加」->「新しい項目」と選択します。 - 出てきた画面で「ヘッダーファイル (.h)」を選択し、ここでは「MouseHandler.h」と名づけることにしてファイルを新規作成します。
-
rn4wSampleApp
内にあるReactPackageProvider.cpp
に今作ったファイルをincludeさせます。includeの末尾に次の行を加えてください。windows/rn4wSampleApp/ReactPackageProvider.cpp#include "MouseHandler.h"
-
MouseHandler.h
を編集します。以下のように書き換えてください。実行してエラーが出なければ成功です。
#pragma once
#include "pch.h"
#include <functional>
#include "NativeModules.h"
#include <winrt/Windows.UI.Core.h>
#include <winrt/Windows.UI.Input.h>
#include <winrt/Windows.UI.Xaml.h>
#include <winrt/Windows.UI.Xaml.Input.h>
#include <winrt/Windows.System.h>
namespace winrt
{
using namespace Windows::UI::Input;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::System;
}
namespace MouseHandler
{
REACT_MODULE(MouseHandler);
struct MouseHandler
{
REACT_METHOD(Init);
void Init() noexcept {
winrt::Window::Current().CoreWindow().PointerPressed(
[=](winrt::CoreWindow const&, winrt::PointerEventArgs const args) {
winrt::PointerPointProperties pointerProps = args.CurrentPoint().Properties();
if (pointerProps.IsXButton1Pressed()) {
XButton1Click();
}
else if (pointerProps.IsXButton2Pressed()) {
XButton2Click();
}
else if (pointerProps.IsLeftButtonPressed()) {
LeftButtonClick();
}
else if (pointerProps.IsRightButtonPressed()) {
RightButtonClick();
}
else if (pointerProps.IsMiddleButtonPressed()) {
MiddleButtonClick();
}
return;
});
}
REACT_EVENT(XButton1Click);
std::function<void()> XButton1Click;
REACT_EVENT(XButton2Click);
std::function<void()> XButton2Click;
REACT_EVENT(RightButtonClick);
std::function<void()> RightButtonClick;
REACT_EVENT(LeftButtonClick);
std::function<void()> LeftButtonClick;
REACT_EVENT(MiddleButtonClick);
std::function<void()> MiddleButtonClick;
};
}
簡単に解説します。といってもこちらにある通りなのですが、マクロが用意されていて、それを使うことでうまい具合に関数をJS側から実行させたりできます。
まずREACT_MODULE
でモジュールを指定します。そして指定した構造体の中にいろいろ書いていきます。
今回はREACT_METHOD
とREACT_EVENT
を使っていますが、REACT_METHOD
は関数をJSから実行できるものでREACT_EVENT
はJS側で登録しでJS側のイベントを発火させることができます。
また基本的にC++でつけた名前がそのままJSでも使われますがマクロの第二引数に文字列を入れるとJS側から別名で呼び出せたりします。
5. JS側から実行する
さて、このままではうまくいってるんだかわからないので今度はJS側のコードを書き換えていきます。App.tsx
を次のように書き換えます。
import * as React from 'react';
import {useEffect} from 'react';
import {Text, View} from 'react-native';
import {NativeModules, NativeEventEmitter} from 'react-native';
const MouseHandlerEventEmitter = new NativeEventEmitter(
NativeModules.MouseHandler,
);
NativeModules.MouseHandler.Init();
const MainPage: React.FC = () => {
const [message, setMessage] = React.useState('');
useEffect(() => {
console.log(NativeModules);
MouseHandlerEventEmitter.addListener('XButton1Click', () => {
setMessage('x1 button clicked!');
});
MouseHandlerEventEmitter.addListener('XButton2Click', () => {
setMessage('x2 button clicked!');
});
MouseHandlerEventEmitter.addListener('RightButtonClick', () => {
setMessage('Right button clicked!');
});
MouseHandlerEventEmitter.addListener('LeftButtonClick', () => {
setMessage('Left button clicked!');
});
MouseHandlerEventEmitter.addListener('MiddleButtonClick', () => {
setMessage('Middle button clicked!');
});
return () => {
MouseHandlerEventEmitter.removeAllListeners('XButton1Click');
MouseHandlerEventEmitter.removeAllListeners('XButton2Click');
MouseHandlerEventEmitter.removeAllListeners('RightButtonClick');
MouseHandlerEventEmitter.removeAllListeners('LeftButtonClick');
MouseHandlerEventEmitter.removeAllListeners('MiddleButtonClick');
};
}, []);
return (
<View
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text>{message}</Text>
</View>
);
};
export default MainPage;
このコードを見たら大体わかっていただけると思いますがNativeModules.上のREACT_MODULEで指定した名前.REACT_METHODで指定した名前
の形でネイティブの関数を呼び出すことができます。そしてNativeEventEmitterのインスタンスを作りaddListenerの第一引数にREACT_EVENTで指定した名前
を入れることでネイティブからのイベントを実行することができるようになります。
というかNativeModulesの下はanyになってるのでTypeScriptにした意味あんまりありませんでしたね・・・
実際に使うときはちゃんと型定義を書いた方が良いでしょう。
さて、コードを保存したらアプリ上でCtrl+Shift+D
を押して「Reload Javascript」を選択してください。
アプリ上でなにかマウスのボタンをクリックするととても地味ですが表示が切り替わるはずです!
感想
- React NativeよりもUWPの情報を調べるのと自分が普段Web系の言語しか書いてないのもあってC++を書くのにも苦労しました。C++の説明で頓珍漢なこと言ってたら申し訳ないです。
- 一番きつかったのはUWPのAPIを調べることでした。最初はどのマウスボタンが押されたのか判別するAPIを見つけられなくてあきらめかけていました。公式のドキュメントを見ながら頑張りましょう。
- C++については調べてもC++/CXという似て非なるもののコードが載ってたりしてコピペしても動かなかったりしてつらかったのとそもそもC++でUWPを使うコード例がほとんど見当たりませんでした。C#では普通にプロパティでアクセスしているものがC++だと関数になっていたりとかいろいろつらかったです。
その他
- 一応C#で作ることも可能なようなのですがうまくいきませんでした。RN4WがC#で作られていたころはうまくいったのかも・・・?
- UWPアプリ以外は作ることができません。ただこれからWinUI3をサポートして古いバージョンのWin10もサポートするみたいなことが書いてあったので将来的には他の形式にもできるかもしれません。
- 最初にinit関数を呼ばないと動かないのはどうなのかと思ったのですがこれしか方法が見つけられませんでした・・・