React Native for Windowsで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 関数を呼ばないと動かないのはどうなのかと思ったのですがこれしか方法が見つけられませんでした・・・
参考にしたサイト
RN4W の公式ドキュメントです。わかんないことがあればここを見るのがベスト。
UWP の公式ドキュメントです。ここを見るのがいいというかここにしか情報がないです。
C#で作る記事です。上に書いた通り自分はうまくいかなかったのですがもしかしたらうまくいくのかもしれません。
この記事は https://note.nazo6.dev/blog/43e24b6b170a40 とのクロスポストです。
Discussion