Help us understand the problem. What is going on with this article?

react-primitivesってなにやってるんですか?! 〜ソースコードから要点を解説〜

More than 1 year has passed since last update.

はじめに

最近よく聞くフロントエンド周りで真のユニバーサルプラットフォームを目指すぜ!っていう文脈でreact-native-web, reactxpなどが出てきています。
今回は、その中でもreact-primitivesについてこのライブラリがなにをやっているのか、どうやって動いているのか要点を解説したいと思います。

react-primitivesの概要

そもそもreact-primitivesとはなんなのか。
react-primitivesは簡単に言うと、ReactNativeで書いたコードを、web, native, vr, sketch, windowsのプラットフォームを抽象化する層を挟むことで各プラットフォームで動作可能にし、reactのアプリケーションを開発する上で必要最低限のコンポーネントを集めたライブラリです。
primitivesという名前はこの共通の必要最低限のものと言った意味で命名されているようです。

react-native-webとの違いを最初は勘違いされる方が多いのですが、react-native-webはReactNativeの書き方でwebのdomをベースとしたreactに変換するものです。
つまり、webでは動きますが、nativeでは動きません。そのため、同じコードでどちらも動かそうとするためにはそれぞれのビルドを分ける必要があるのですが、それをreact-primitivesは、解消しています。
また、そのwebの部分はreact-native-webから一部のコンポーネントを取ってきて使用するように設計されているため、webにおいておおよその部分はreact-native-webが使用されています。

スクリーンショット 2018-09-13 16.29.37.png

使用できるコンポーネント・関数

このライブラリで公開されているコンポーネントは以下のようになっています。
- Animted
- View
- Text
- Image
- Touchable
- Easing
- Dimensions
- PixelRatio
- Platform

どうやってweb, native, vr, sketch等を分けているのか。

どうやって振り分けているのか、やっていることはかなり簡単で、プラットフォームごとに拡張子を変えて読み込むファイルを変えているだけです。先ほど、react-native-webでは、ビルドを分ける必要があったとしましたが、その部分をここでやってくれている。そんなところでしょうか。
実際に見てみますと、entryのファイルは以下のindex.jsで同じものの、

index.js
module.exports = require('./lib');

ここから読み込まれるlib以下のものが異なります。

$ ls lib/index*
lib/index.android.js lib/index.ios.js     lib/index.js         lib/index.sketch.js
lib/index.vr.js      lib/index.web.js     lib/index.windows.js

それぞれの実装はどうなっているのか。

それぞれのプラットホームごとの実装はどうなっているのか、まずsrc以下のディレクトリは以下のようになっています。

src
├── ReactPrimitives.js
├── index.android.js
├── index.ios.js
├── index.js
├── index.sketch.js
├── index.vr.js
├── index.web.js
├── index.windows.js
├── injection
│   ├── react-native-web.js
│   ├── react-native.js
│   ├── react-sketchapp.js
│   └── react-vr.js
├── modules
│   ├── PixelRatio.js
│   ├── Platform.js
│   └── Touchable.js
└── vr
    └── Touchable.js

その中でまずは、index.ios.jsを見てみましょう。
ここで登場するファイルについて追って行くと実装が見えてきます。

index.ios.js
require('./injection/react-native');

module.exports = require('./ReactPrimitives');

ReactPrimitives.jsのReactPrimitivesが外部から呼び出されるオブジェクトです。
ここでは、それぞれのコンポーネント・関数がkeyでvalueがnullのものとinjectという関数が見受けられます。
このinject関数では引数にそれぞれのプラットフォームごとにそれぞれのコンポーネントを定義したオブジェクトを受け取り、そのオブジェクトにそれぞれのコンポーネントのvalueが存在する時にnullだったものから受け取ったコンポーネントに書き換えています。

ReactPrimitives.js
const ReactPrimitives = {
  StyleSheet: null,
  View: null,
  Text: null,
  Image: null,
  Touchable: null,
  Easing: null,
  Animated: null,
  Dimensions: null,
  PixelRatio: require('./modules/PixelRatio'),
  Platform: require('./modules/Platform'),
  inject: (api) => {
    if (api.StyleSheet) {
      ReactPrimitives.StyleSheet = api.StyleSheet;
    }
    if (api.View) {
      ReactPrimitives.View = api.View;
    }
    if (api.Text) {
      ReactPrimitives.Text = api.Text;
    }
    if (api.Image) {
      ReactPrimitives.Image = api.Image;
    }
    if (api.Touchable) {
      ReactPrimitives.Touchable = api.Touchable;
    }
    if (api.Easing) {
      ReactPrimitives.Easing = api.Easing;
    }
    if (api.Animated) {
      ReactPrimitives.Animated = api.Animated;
    }
    if (api.Dimensions) {
      ReactPrimitives.Dimensions = api.Dimensions;
    }
    if (api.Platform) {
      ReactPrimitives.Platform.inject(api.Platform);
    }
  },
};

module.exports = ReactPrimitives;

実際に上のinject関数がどう使われているのかを見てみましょう。
ここではmodules以下のreact-native.jsを見てみます。
react-nativeをそのままほとんどinjectしているのがわかります。
また、Touchableコンポーネントのような自前のハックを行ったりするそのままではないコンポーネントをinjectしている場合があります。
特に、react-native-web.jsなどではそういうパターンがよくみられます。
ここは割愛しますが、Touchableではwebだとカーソルをつけるだったり、Touchable.Mixinなるものを使ってTouchableを拡張したりして、それぞれのプラットフォームへの対応などを行っています。(ここで1秒間に2回しかボタンが押せないように調整されているのですごくパフォーマンスが悪く感じます、、)

injections/react-native.js
const ReactPrimitives = require('../ReactPrimitives');
const {
  Animated,
  View,
  Text,
  Image,
  StyleSheet,
  Platform,
  Easing,
  Dimensions,
  Touchable,
} = require('react-native');

ReactPrimitives.inject({
  StyleSheet,
  View,
  Text,
  Image,
  Easing,
  Animated,
  Platform: {
    OS: Platform.OS,
    Version: Platform.Version,
  },
  Dimensions,
  Touchable: require('../modules/Touchable')(Animated, StyleSheet, Platform, Touchable.Mixin),
});

こうしてReactPrimitivesオブジェクトがそれぞれのプラットフォームごとに書き換えられエクスポートされます。
以上が主な実装の概要です。理解いただけたでしょうか?
今回説明していない部分のソースコードもそれほど難しくありませんので、要点以外にも読みたいと思う方は読んでみてください。

まとめ

実装を見て、かなり薄いライブラリであることがわかっていただけたと思います。
実際に実務で使ってみた感想としては、導入も容易で、フラットで簡単なデザインを実装するのは容易だったりするのですが、TextInputがまだ実装していなかったり、web特有の擬似クラスを自分たちで実装する必要があったり(react-primitivesというかreact-native-webの問題ですが)と、なかなか苦しいところはあります。
しかし、開発でreact-primitivesでもう無理となった場合でもそれぞれのプラットフォームごとにビルドシステムを構築する等すれば容易に捨てることができますし、統一性、メンテナンス性などの面ではかなりいいものがあるように感じました。

Shagamii
Tokyo University of Science, Faculty of Engineering, Dept. Information and Computer Technology 4th
https://shagamii.tech
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした