メインコンテンツへスキップ

React Native用の<InputAccessoryView>の構築

2018年3月22日 ·7分で読めます
Peter Argany
Facebook ソフトウェアエンジニア

動機

3年前、React Nativeからinput accessory viewをサポートするためのGitHub issueが立てられました。

その後数年間、この問題に関してRNには数え切れないほどの「+1」、様々な回避策がありましたが、今日まで具体的な変更はありませんでした。iOSから始めて、ネイティブのインプットアクセサリビューにアクセスするためのAPIを公開し、どのように構築したかを共有できることを楽しみにしています。

背景

インプットアクセサリビューとは正確には何ですか?Appleの開発者向けドキュメントを読むと、レシーバーが最初のレスポンダーになるときにシステムキーボードの上部に固定できるカスタムビューであることがわかります。UIResponderを継承するものはすべて、.inputAccessoryViewプロパティを読み書き可能として再宣言し、ここにカスタムビューを管理できます。レスポンダーインフラストラクチャはビューをマウントし、システムキーボードと同期させます。ドラッグやタップなど、キーボードを閉じるジェスチャーは、フレームワークレベルでインプットアクセサリビューに適用されます。これにより、iMessageやWhatsAppのようなトップティアのメッセージングアプリに不可欠な機能である、インタラクティブなキーボード非表示機能を備えたコンテンツを構築できます。

キーボードの上部にビューを固定する一般的なユースケースは2つあります。1つ目は、Facebookの投稿作成画面の背景ピッカーのようなキーボードツールバーを作成することです。

このシナリオでは、キーボードはテキスト入力フィールドにフォーカスされており、入力補助ビューは追加のキーボード機能を提供するために使用されます。この機能は入力フィールドの種類に固有です。マッピングアプリケーションでは住所の提案、テキストエディタではリッチテキストの書式設定ツールなどが考えられます。


このシナリオで<InputAccessoryView>を所有するObjective-CのUIResponderは明確であるべきです。<TextInput>がファーストレスポンダーになり、内部的にはUITextViewまたはUITextFieldのインスタンスになります。

2つ目の一般的なシナリオは、スティッキーテキスト入力(追従するテキスト入力欄)です。

ここでは、テキスト入力自体がinput accessory viewの一部になっています。これはメッセージングアプリケーションでよく使用され、過去のメッセージのスレッドをスクロールしながらメッセージを作成できます。


この例で<InputAccessoryView>を所有するのは誰ですか?またしてもUITextViewまたはUITextFieldでしょうか?テキスト入力はインプットアクセサリビューの**内側**にあり、これは循環依存のように聞こえます。この問題を解決するだけでも、それ自体が**別のブログ記事**になります。ネタバレ:所有者は汎用のUIViewサブクラスで、手動でbecomeFirstResponderを呼び出すように指示します。

API設計

これで<InputAccessoryView>が何であるか、そしてそれをどのように使いたいかがわかりました。次のステップは、両方のユースケースにとって意味があり、<TextInput>のような既存のReact Nativeコンポーネントとうまく機能するAPIを設計することです。

キーボードツールバーについては、考慮したい点がいくつかあります。

  1. 任意の汎用的なReact Nativeのビュー階層を<InputAccessoryView>にホイスティング(引き上げ)できるようにしたい。
  2. この汎用的で分離されたビュー階層が、タッチイベントを受け付け、アプリケーションの状態を操作できるようにしたい。
  3. 特定の<TextInput><InputAccessoryView>をリンクさせたい。
  4. コードを複製することなく、複数のテキスト入力で<InputAccessoryView>を共有できるようにしたい。

#1は、React portalsに似た概念を使用して達成できます。この設計では、React Nativeビューをレスポンダーインフラストラクチャによって管理されるUIView階層にポータルします。React NativeビューはUIViewsとしてレンダリングされるため、これは実際には非常に簡単です。オーバーライドするだけで済みます。

- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex

そして、すべてのサブビューを新しいUIView階層にパイプします。#2では、<InputAccessoryView>用に新しいRCTTouchHandlerを設定します。状態更新は、通常のイベントコールバックを使用して行われます。#3と#4では、<TextInput>コンポーネントの作成時に、nativeIDフィールドを使用して、ネイティブコードでアクセサリビューのUIView階層を特定します。この関数は、基礎となるネイティブテキスト入力の.inputAccessoryViewプロパティを使用します。これにより、ObjCの実装で<InputAccessoryView><TextInput>に効果的にリンクされます。

スティッキーテキスト入力(シナリオ2)のサポートには、さらにいくつかの制約が追加されます。この設計では、入力アクセサリビューにテキスト入力が子として含まれるため、nativeIDによるリンクはオプションではありません。代わりに、汎用のオフスクリーンUIView.inputAccessoryViewを、ネイティブの<InputAccessoryView>階層に設定します。この汎用のUIViewにファーストレスポンダーになるように手動で指示することで、階層はレスポンダーインフラストラクチャによってマウントされます。この概念は、前述のブログ投稿で詳しく説明されています。

落とし穴

もちろん、このAPIを構築する過程ですべてが順風満帆だったわけではありません。ここでは、私たちが遭遇したいくつかの落とし穴と、それらをどのように修正したかを紹介します。

このAPIを構築するための最初のアイデアは、UIKeyboardWill(Show/Hide/ChangeFrame)イベントのためにNSNotificationCenterをリッスンすることでした。このパターンは、いくつかのオープンソースライブラリや、Facebookアプリの内部の一部で使用されています。残念ながら、スワイプ時に<InputAccessoryView>フレームを更新するためにUIKeyboardDidChangeFrameイベントが間に合って呼び出されませんでした。また、キーボードの高さの変更はこれらのイベントによってキャプチャされません。これにより、次のようなバグが発生します。

iPhone Xでは、テキストキーボードと絵文字キーボードの高さが異なります。キーボードイベントを使用してテキスト入力フレームを操作するほとんどのアプリケーションは、上記のバグを修正する必要がありました。私たちの解決策は、.inputAccessoryViewプロパティを使用することにコミットすることでした。これにより、レスポンダーインフラストラクチャがこのようなフレームの更新を処理します。


iPhone Xのホームピルを避けるという、もう1つの厄介なバグに遭遇しました。「Appleは、まさにこの目的のためにsafeAreaLayoutGuideを開発しました。これは些細なことです!」と考えるかもしれません。私たちも同じくらい世間知らずでした。最初の問題は、ネイティブの<InputAccessoryView>実装には、表示されるまでアンカーするウィンドウがないことです。それは問題ありません。-(BOOL)becomeFirstResponderをオーバーライドし、そこでレイアウト制約を適用できます。これらの制約に従うとアクセサリビューが上に移動しますが、別のバグが発生します。

インプットアクセサリビューはホームピルを回避することに成功しましたが、安全でない領域の後ろのコンテンツが見えるようになりました。解決策は、このレーダーにあります。ネイティブの<InputAccessoryView>階層を、safeAreaLayoutGuide制約に準拠しないコンテナでラップしました。ネイティブコンテナは安全でない領域のコンテンツをカバーし、<InputAccessoryView>は安全な領域の境界内に留まります。


使用例

以下は、<TextInput>の状態をリセットするためのキーボードツールバーボタンを構築する例です。

class TextInputAccessoryViewExample extends React.Component<
{},
*,
> {
constructor(props) {
super(props);
this.state = {text: 'Placeholder Text'};
}

render() {
const inputAccessoryViewID = 'inputAccessoryView1';
return (
<View>
<TextInput
style={styles.default}
inputAccessoryViewID={inputAccessoryViewID}
onChangeText={text => this.setState({text})}
value={this.state.text}
/>
<InputAccessoryView nativeID={inputAccessoryViewID}>
<View style={{backgroundColor: 'white'}}>
<Button
onPress={() =>
this.setState({text: 'Placeholder Text'})
}
title="Reset Text"
/>
</View>
</InputAccessoryView>
</View>
);
}
}

スティッキーテキスト入力の別の例はリポジトリにあります

いつから利用できますか?

この機能実装の完全なコミットはこちらです。<InputAccessoryView>は、今後リリースされるv0.55.0で利用可能になります。

ハッピーキーボーディング :)

React NativeでAWSを使用する

10分で読めます
Richard Threlkeld
AWSモバイルのシニアテクニカルプロダクトマネージャー

AWSは、テクノロジー業界でクラウドサービスのプロバイダーとしてよく知られています。これには、コンピューティング、ストレージ、データベース技術のほか、完全に管理されたサーバーレスサービスも含まれます。AWS Mobileチームは、顧客やJavaScriptエコシステムのメンバーと緊密に連携し、クラウド接続されたモバイルおよびWebアプリケーションをより安全でスケーラブルにし、開発と展開を容易にすることに取り組んできました。私たちは完全なスターターキットから始めましたが、最近さらにいくつかの開発を進めています。

このブログ記事では、React および React Native 開発者にとって興味深いことについて説明します。

  • クラウドサービスを使用するJavaScriptアプリケーション向けの宣言型ライブラリ、**AWS Amplify**
  • オフラインおよびリアルタイム機能を備えたフルマネージドGraphQLサービス、**AWS AppSync**

AWS Amplify

Create React Native AppやExpoのようなツールを使用すると、React Nativeアプリケーションを非常に簡単に起動できます。しかし、ユースケースをインフラストラクチャサービスに合わせようとすると、それらをクラウドに接続することは困難な場合があります。たとえば、React Nativeアプリで写真をアップロードする必要があるかもしれません。これらはユーザーごとに保護されるべきでしょうか?それはおそらく、何らかの登録またはサインインプロセスが必要であることを意味します。独自のユーザーディレクトリが必要ですか、それともソーシャルメディアプロバイダーを使用していますか?おそらく、アプリはユーザーがログインした後にカスタムビジネスロジックでAPIを呼び出す必要もあります。

JavaScript開発者がこれらの問題に対処できるよう、AWS Amplifyというライブラリをリリースしました。この設計は、AWS固有の実装ではなく、タスクの「カテゴリ」に分割されています。たとえば、ユーザーに登録、ログイン、そしてプライベート写真のアップロードをさせたい場合、アプリケーションにAuthStorageカテゴリを組み込むだけで済みます。

import { Auth } from 'aws-amplify';

Auth.signIn(username, password)
.then(user => console.log(user))
.catch(err => console.log(err));

Auth.confirmSignIn(user, code)
.then(data => console.log(data))
.catch(err => console.log(err));

上記のコードでは、Amplifyが多要素認証 (MFA) コードをEメールまたはSMSで利用するなど、一般的なタスクをどのようにサポートしているかを示す例を見ることができます。現在サポートされているカテゴリは次のとおりです。

  • Auth: 認証情報の自動化を提供します。既成の実装では、AWS認証情報とAmazon CognitoからのOIDC JWTトークンを使用します。MFA機能などの一般的な機能がサポートされています。
  • Analytics: 1行のコードで、Amazon Pinpointで認証済みまたは未認証のユーザーのトラッキングを取得します。必要に応じて、カスタムメトリクスまたは属性に拡張できます。
  • API: AWS Signature Version 4を活用し、安全な方法でRESTful APIとの対話を提供します。APIモジュールはAmazon API Gatewayと連携するサーバーレスインフラストラクチャで非常に優れています。
  • Storage: Amazon S3へのコンテンツのアップロード、ダウンロード、リスト表示を簡素化するコマンド。データはユーザーごとに公開または非公開のコンテンツに簡単にグループ化できます。
  • Caching: WebアプリとReact Nativeを横断するLRUキャッシュインターフェースで、実装固有の永続化を使用します。
  • i18n およびロギング: 国際化およびローカライズ機能、ならびにデバッグおよびロギング機能を提供します。

Amplifyの優れた点の1つは、特定のプログラミング環境向けに「ベストプラクティス」を設計に組み込んでいることです。たとえば、お客様やReact Native開発者と協力する中で、開発中に物事を迅速に機能させるために採用されたショートカットが、本番環境のスタックまで到達してしまうことがありました。これらはスケーラビリティやセキュリティを損ない、インフラストラクチャの再設計やコードのリファクタリングを強制する可能性があります。

開発者がこれを回避するのに役立つ例の1つは、AWS Lambdaによるサーバーレス参照アーキテクチャです。これらは、バックエンドを構築する際にAmazon API GatewayとAWS Lambdaを連携させるためのベストプラクティスを示しています。このパターンは、AmplifyのAPIカテゴリに組み込まれています。このパターンを使用して、複数の異なるRESTエンドポイントと対話し、カスタムビジネスロジックのためにLambda関数にヘッダーを渡すことができます。また、これらの機能を使用して新規または既存のReact NativeプロジェクトをブートストラップするためのAWS Mobile CLIもリリースしました。開始するには、**npm**経由でインストールし、構成プロンプトに従ってください。

npm install --global awsmobile-cli
awsmobile configure

モバイルエコシステムに特化した、エンコードされたベストプラクティスのもう1つの例は、パスワードのセキュリティです。デフォルトのAuthカテゴリの実装は、ユーザー登録とサインインのためにAmazon Cognitoユーザープールを活用します。このサービスは、認証試行中にユーザーを保護する方法としてSecure Remote Passwordプロトコルを実装しています。プロトコルの数学を読み進めていくと、原始根上でパスワード検証を計算する際に、グループを生成するために大きな素数を使用する必要があることに気付くでしょう。React Native環境では、JITは無効です。これにより、このようなセキュリティ操作のためのBigInteger計算のパフォーマンスが低下します。これを考慮して、プロジェクト内にリンクできるAndroidとiOSのネイティブブリッジをリリースしました。

npm install --save aws-amplify-react-native
react-native link amazon-cognito-identity-js

Expoチームが、この機能を最新のSDKに含めてくれたことにも興奮しています。これにより、Amplifyをイジェクトすることなく使用できます。

最後に、React Native (およびReact) 開発に特化したものとして、Amplifyには、アプリへのサインアップやサインインなどの機能を簡単にラップするための高階コンポーネント (HOC)が含まれています。

import Amplify, { withAuthenticator } from 'aws-amplify-react-native';
import aws_exports from './aws-exports';

Amplify.configure(aws_exports);

class App extends React.Component {
...
}

export default withAuthenticator(App);

基盤となるコンポーネントは<Authenticator />としても提供されており、UIを完全にカスタマイズできます。また、ユーザーがサインイン済みかMFA確認待ちかなど、ユーザーの状態を管理するためのプロパティや、状態が変更されたときにトリガーできるコールバックも提供されます。

同様に、さまざまなユースケースに利用できる一般的なReactコンポーネントも提供されています。これらは、たとえばStorageモジュールからAmazon S3のすべてのプライベート画像を表示するなど、ニーズに合わせてカスタマイズできます。

<S3Album
level="private"
path={path}
filter={(item) => /jpg/i.test(item.path)}/>

前述の通り、公開/非公開ストレージオプションなど、多くのコンポーネント機能をプロパティで制御できます。ユーザーが特定のUIコンポーネントと対話したときに、自動的にアナリティクスを収集する機能さえあります。

return <S3Album track/>

AWS Amplifyは、グローバルな初期化ルーチンまたはカテゴリレベルでの初期化により、設定よりも規約を優先する開発スタイルを採用しています。最も迅速に開始する方法は、aws-exportsファイルを使用することです。ただし、開発者は既存のリソースでライブラリを独立して使用することもできます。

その哲学を深く掘り下げてデモ全体を見るには、AWS re:Inventのビデオをご覧ください。

AWS AppSync

AWS Amplifyのローンチ直後、AWS AppSyncもリリースしました。これは、オフラインとリアルタイムの両方の機能を備えた、フルマネージドのGraphQLサービスです。GraphQLはさまざまなクライアントプログラミング言語(ネイティブのAndroidとiOSを含む)で使用できますが、React Native開発者の間で非常に人気があります。これは、データモデルが単方向データフローとコンポーネント階層にうまく適合するためです。

AWS AppSyncを使用すると、独自のAWSアカウント内のリソースに接続でき、つまりデータを所有および管理できます。これはデータソースを使用することで行われ、サービスはAmazon DynamoDBAmazon ElasticsearchAWS Lambdaをサポートしています。これにより、NoSQLや全文検索などの機能を単一のGraphQL APIでスキーマとして組み合わせることができます。これにより、データソースを組み合わせることができます。AppSyncサービスはスキーマからプロビジョニングすることもできるため、AWSサービスに詳しくなくても、GraphQL SDLを記述し、ボタンをクリックするだけで自動的に稼働できます。

AWS AppSyncのリアルタイム機能は、GraphQLサブスクリプションとよく知られたイベントベースのパターンによって制御されます。AWS AppSyncのサブスクリプションは、GraphQLディレクティブを使用してスキーマ上で制御されるため、スキーマは任意のデータソースを使用できるため、Amazon DynamoDBおよびAmazon Elasticsearch Serviceによるデータベース操作、またはAWS Lambdaによるインフラストラクチャの他の部分から通知をトリガーできます。

AWS Amplifyと同様に、AWS AppSyncではGraphQL APIでエンタープライズセキュリティ機能を使用できます。このサービスでは、APIキーですぐに始めることができます。ただし、本番環境に移行する際には、AWS Identity and Access Management (IAM) またはAmazon CognitoユーザープールからのOIDCトークンを使用するように移行できます。型に対するポリシーを使用して、リゾルバーレベルでアクセスを制御できます。きめ細かなアクセス制御の実行時チェックに論理チェックを使用することもできます。たとえば、ユーザーが特定のデータベースリソースの所有者であるかどうかを検出するなどです。リゾルバーの実行や個々のデータベースレコードへのアクセスに対するグループメンバーシップのチェックに関する機能もあります。

React Native開発者がこれらのテクノロジーについてさらに学ぶのを助けるために、AWS AppSyncコンソールホームページから起動できる組み込みのGraphQLサンプルスキーマがあります。このサンプルは、GraphQLスキーマをデプロイし、データベーステーブルをプロビジョニングし、クエリ、ミューテーション、およびサブスクリプションを自動的に接続します。この組み込みスキーマを活用する機能的なAWS AppSync用React Nativeの例(およびReactの例)もあり、クライアントとクラウドの両方のコンポーネントを数分で実行できます。

AWSAppSyncClientを使用すると簡単に開始できます。これはApollo Clientに接続します。AWSAppSyncClientは、GraphQL APIのセキュリティと署名、オフライン機能、およびサブスクリプションのハンドシェイクとネゴシエーションプロセスを処理します。

import AWSAppSyncClient from "aws-appsync";
import { Rehydrated } from 'aws-appsync-react';
import { AUTH_TYPE } from "aws-appsync/lib/link/auth-link";

const client = new AWSAppSyncClient({
url: awsconfig.graphqlEndpoint,
region: awsconfig.region,
auth: {type: AUTH_TYPE.API_KEY, apiKey: awsconfig.apiKey}
});

AppSyncコンソールは、GraphQLエンドポイント、AWSリージョン、APIキーを含む設定ファイルをダウンロードできます。その後、React Apolloとクライアントを使用できます。

const WithProvider = () => (
<ApolloProvider client={client}>
<Rehydrated>
<App />
</Rehydrated>
</ApolloProvider>
);

この時点で、標準のGraphQLクエリを使用できます。

query ListEvents {
listEvents{
items{
__typename
id
name
where
when
description
comments{
__typename
items{
__typename
eventId
commentId
content
createdAt
}
nextToken
}
}
}
}

上記の例は、AppSyncによってプロビジョニングされたサンプルアプリスキーマを使用したクエリを示しています。DynamoDBとのインタラクションだけでなく、データのページネーション(暗号化されたトークンを含む)と、EventsComments間の型リレーションも含まれています。アプリはAWSAppSyncClientで構成されているため、データは自動的にオフラインで永続化され、デバイスが再接続したときに同期されます。

これとReact Nativeのデモの背後にあるクライアント技術の詳しい説明は、このビデオで見ることができます。

フィードバック

ライブラリを開発しているチームは、これらのライブラリやサービスがあなたにとってどのように役立っているかを知りたいと熱望しています。また、クラウドサービスを使用してReactとReact Nativeの開発をより簡単にするために、他に何ができるかについても聞きたいと思っています。AWS AmplifyまたはAWS AppSyncについては、GitHubでAWS Mobileチームにご連絡ください。

React NativeでTwitterのアプリ起動アニメーションを実装する

·12分で読めます
Eli White
イーライ・ホワイト
ソフトウェアエンジニア @ Meta

TwitterのiOSアプリには、私がとても気に入っているローディングアニメーションがあります。

アプリの準備が整うと、Twitterのロゴが楽しく拡大し、アプリが現れます。

このローディングアニメーションをReact Nativeで再現する方法を考え出したいと思いました。


それをどのように作るかを理解するためには、まずローディングアニメーションの異なる部分を理解する必要がありました。その微妙な違いを見る一番簡単な方法は、スロー再生することです。

これを構築するためには、いくつか主要な要素を理解する必要があります。

  1. 鳥を拡大する。
  2. 鳥が大きくなるにつれて、その下のアプリが表示される
  3. 最後にアプリをわずかに縮小する

このアニメーションの作り方を理解するのに、かなりの時間がかかりました。

青い背景とTwitterの鳥はアプリの**上に**レイヤーとして存在し、鳥が大きくなるにつれて透明になり、下のアプリが見えるようになるという**間違った**仮定から始めました。このアプローチはうまくいきません。なぜなら、Twitterの鳥が透明になると、下のアプリではなく青いレイヤーが見えてしまうからです!

幸運なことに、読者の皆さんは私と同じようなフラストレーションを経験する必要はありません。この素敵なチュートリアルで、良いところだけをすぐに学べます!


正しい方法

コードに入る前に、これをどのように分解するかを理解することが重要です。この効果を視覚化するために、CodePen(数段落に埋め込まれています)で再現しましたので、さまざまなレイヤーをインタラクティブに見ることができます。

このエフェクトには主に3つのレイヤーがあります。1つ目は青い背景レイヤーです。これはアプリの上に表示されているように見えますが、実際には背面にあります。

次に、真っ白なレイヤーがあります。そして最後に、一番手前に私たちのアプリがあります。


このアニメーションの主なコツは、Twitterロゴをmaskとして使用し、アプリと白いレイヤーの両方をマスクすることです。マスキングの詳細については深く掘り下げません。豊富なリソースオンラインにあります。

この文脈でのマスキングの基本は、マスクの不透明なピクセルがマスキング対象のコンテンツを表示し、透明なピクセルがマスキング対象のコンテンツを隠す画像を持つことです。

私たちはTwitterのロゴをマスクとして使用し、真っ白なレイヤーとアプリレイヤーの2つのレイヤーをマスキングします。

アプリを表示させるために、マスクを画面全体より大きくなるまで拡大します。

マスクが拡大するにつれて、アプリレイヤーの不透明度を徐々にフェードインさせ、アプリを表示し、その背後にある白いレイヤーを非表示にします。効果を完成させるために、アプリレイヤーをスケール > 1で開始し、アニメーションが終了するにつれてスケール1に縮小します。その後、非アプリレイヤーは二度と表示されないため、非表示にします。

「百聞は一見にしかず」と言いますが、インタラクティブな視覚化はどれほどの価値があるでしょうか。「次のステップ」ボタンでアニメーションをクリックして進んでください。レイヤーを表示すると、横からの視点が得られます。グリッドは透明なレイヤーを視覚化するのに役立ちます。

さて、React Nativeへ

さてと。何を構築するのか、そしてアニメーションがどのように機能するのかがわかったので、コードに取り掛かりましょう――皆さんが本当にここにいる理由です。

このパズルの主要なピースは、React NativeのコアコンポーネントであるMaskedViewIOSです。

import {MaskedViewIOS} from 'react-native';

<MaskedViewIOS maskElement={<Text>Basic Mask</Text>}>
<View style={{backgroundColor: 'blue'}} />
</MaskedViewIOS>;

MaskedViewIOSは、maskElementchildrenというプロパティを受け取ります。子要素はmaskElementによってマスクされます。マスクは画像である必要はなく、任意のビューで構いません。上記の例の動作は、青いビューをレンダリングしますが、そのビューはmaskElementの「Basic Mask」という単語がある場所でのみ表示されます。私たちは複雑な青いテキストを作成したことになります。

私たちがやりたいことは、青いレイヤーをレンダリングし、その上にTwitterロゴでマスクされたアプリと白いレイヤーをレンダリングすることです。

{
fullScreenBlueLayer;
}
<MaskedViewIOS
style={{flex: 1}}
maskElement={
<View style={styles.centeredFullScreen}>
<Image source={twitterLogo} />
</View>
}>
{fullScreenWhiteLayer}
<View style={{flex: 1}}>
<MyApp />
</View>
</MaskedViewIOS>;

これにより、以下に見られるレイヤーが得られます。

さて、Animatedの部分へ

これを機能させるために必要なすべてのピースが揃いました。次のステップはそれらをアニメーション化することです。このアニメーションを良い感じにするために、React NativeのAnimated APIを活用します。

Animatedを使用すると、JavaScriptでアニメーションを宣言的に定義できます。デフォルトでは、これらのアニメーションはJavaScriptで実行され、すべてのフレームでネイティブレイヤーにどのような変更を行うかを指示します。JavaScriptはすべてのフレームでアニメーションを更新しようとしますが、十分な速さで更新できない可能性が高く、フレームがドロップされる(ジャンプ)原因となります。これは望ましくありません!

Animatedには、このジャンプなしでアニメーションを実現するための特別な動作があります。AnimatedにはuseNativeDriverというフラグがあり、アニメーションの開始時にJavaScriptからネイティブにアニメーション定義を送信し、ネイティブ側がJavaScriptと毎フレーム行き来することなくアニメーションの更新を処理できるようにします。useNativeDriverの欠点は、特定のプロパティセット、主にtransformopacityしか更新できないことです。useNativeDriverで背景色のようなものをアニメーション化することはできません。少なくともまだはできません。時間の経過とともに追加していきますし、もちろん、プロジェクトに必要なプロパティについてはいつでもPRを提出して、コミュニティ全体に貢献できます😀。

このアニメーションをスムーズにしたいので、これらの制約の中で作業します。useNativeDriverが内部でどのように機能するかをさらに詳しく知るには、発表時のブログ記事をご覧ください。

アニメーションの分解

私たちのアニメーションには4つの構成要素があります。

  1. 鳥を拡大し、アプリと真っ白なレイヤーを表示する
  2. アプリをフェードインさせる
  3. アプリを縮小する
  4. 完了したら、白いレイヤーと青いレイヤーを非表示にする

Animatedには、アニメーションを定義する主な方法が2つあります。1つ目は、Animated.timingを使用する方法で、アニメーションが実行される時間と、動きを滑らかにするためのイージングカーブを正確に指定できます。もう1つは、Animated.springなどの物理ベースのAPIを使用する方法です。Animated.springを使用すると、バネの摩擦量や張力などのパラメーターを指定し、物理エンジンにアニメーションを実行させることができます。

私たちは、互いに密接に関連する複数のアニメーションを同時に実行したいと考えています。たとえば、マスクが明らかになる途中でアプリのフェードインを開始したいと考えています。これらのアニメーションは密接に関連しているため、単一のAnimated.ValueAnimated.timingを使用します。

Animated.Valueは、Animatedがアニメーションの状態を知るために使用するネイティブ値のラッパーです。通常、完全なアニメーションに対してはこれを1つだけ持つのが望ましいです。Animatedを使用するほとんどのコンポーネントは、この値をstateに保存します。

私はこのアニメーションを、完全なアニメーションの進行に沿って異なる時点で発生するステップとして考えているので、Animated.Valueを0(0%完了を表す)から始め、値を100(100%完了を表す)で終了させます。

コンポーネントの初期stateは次のようになります。

state = {
loadingProgress: new Animated.Value(0),
};

アニメーションを開始する準備ができたら、Animatedにこの値を100にアニメーションするように指示します。

Animated.timing(this.state.loadingProgress, {
toValue: 100,
duration: 1000,
useNativeDriver: true, // This is important!
}).start();

それから、アニメーションのさまざまな部分と、全体のアニメーションのさまざまな段階でそれらが持つべき値のざっくりとした見積もりを出そうとします。以下は、アニメーションのさまざまな部分と、時間の経過とともにそれらの値がどのように変化すべきかを示した表です。

Twitterの鳥のマスクはスケール1から始まり、サイズが急上昇する前に小さくなります。したがって、アニメーションの10%時点では、スケール値は0.8になり、その後最後にスケール70まで急上昇します。正直なところ、70を選んだのはかなり恣意的でした。鳥が画面全体を完全に明らかにするのに十分な大きさが必要で、60では不十分でした😀。ただし、この部分で興味深いのは、数字が大きいほど、同じ時間でそこに到達する必要があるため、成長が速く見えることです。この数字は、このロゴで見た目を良くするために試行錯誤を要しました。異なるサイズのロゴ/デバイスでは、画面全体が明らかになるように、この最終スケールが異なる必要があります。

アプリはしばらくの間、少なくともTwitterロゴが小さくなるまでは不透明であるべきです。公式アニメーションに基づいて、鳥が拡大する途中で表示を開始し、かなり早く完全に表示したいと考えています。そのため、15%で表示を開始し、全体のアニメーションの30%で完全に表示されます。

アプリのスケールは1.1から始まり、アニメーションの終わりまでに通常のスケールに縮小します。

そして、コードへ。

ここで私たちが行ったことは、アニメーションの進行状況の割合の値を、個々の部分の値に本質的にマッピングすることです。それをAnimatedで.interpolateを使って行います。this.state.loadingProgressに基づいて補間された値を使用して、アニメーションの各部分に対して3つの異なるスタイルオブジェクトを作成します。

const loadingProgress = this.state.loadingProgress;

const opacityClearToVisible = {
opacity: loadingProgress.interpolate({
inputRange: [0, 15, 30],
outputRange: [0, 0, 1],
extrapolate: 'clamp',
// clamp means when the input is 30-100, output should stay at 1
}),
};

const imageScale = {
transform: [
{
scale: loadingProgress.interpolate({
inputRange: [0, 10, 100],
outputRange: [1, 0.8, 70],
}),
},
],
};

const appScale = {
transform: [
{
scale: loadingProgress.interpolate({
inputRange: [0, 100],
outputRange: [1.1, 1],
}),
},
],
};

これらのスタイルオブジェクトができたので、投稿の冒頭で紹介したビューのコードスニペットをレンダリングする際にそれらを使用できます。Animated.ViewAnimated.Text、およびAnimated.ImageのみがAnimated.Valueを使用するスタイルオブジェクトを使用できることに注意してください。

const fullScreenBlueLayer = (
<View style={styles.fullScreenBlueLayer} />
);
const fullScreenWhiteLayer = (
<View style={styles.fullScreenWhiteLayer} />
);

return (
<View style={styles.fullScreen}>
{fullScreenBlueLayer}
<MaskedViewIOS
style={{flex: 1}}
maskElement={
<View style={styles.centeredFullScreen}>
<Animated.Image
style={[styles.maskImageStyle, imageScale]}
source={twitterLogo}
/>
</View>
}>
{fullScreenWhiteLayer}
<Animated.View
style={[opacityClearToVisible, appScale, {flex: 1}]}>
{this.props.children}
</Animated.View>
</MaskedViewIOS>
</View>
);

やった!これでアニメーションの各部分が望み通りに見えるようになりました。あとは、二度と表示されない青と白のレイヤーをクリーンアップするだけです。

いつクリーンアップできるかを知るためには、アニメーションがいつ完了したかを知る必要があります。幸いなことに、Animated.timingを呼び出す場所で、.startはアニメーション完了時に実行されるオプションのコールバックを受け取ります。

Animated.timing(this.state.loadingProgress, {
toValue: 100,
duration: 1000,
useNativeDriver: true,
}).start(() => {
this.setState({
animationDone: true,
});
});

これで、アニメーションが完了したかどうかを知るための値がstateに入ったので、青と白のレイヤーを修正してそれを使用できます。

const fullScreenBlueLayer = this.state.animationDone ? null : (
<View style={[styles.fullScreenBlueLayer]} />
);
const fullScreenWhiteLayer = this.state.animationDone ? null : (
<View style={[styles.fullScreenWhiteLayer]} />
);

できました!これでアニメーションが機能し、アニメーションが完了したら不要なレイヤーをクリーンアップします。Twitterアプリのローディングアニメーションを構築しました!

でも待って、私のものは動かない!

心配しないでください、読者の皆さん。私も、ガイドがコードの一部しか提供せず、完成したソースコードをくれないときは嫌なものです。

このコンポーネントはnpmに公開されており、GitHubではreact-native-mask-loaderとして公開されています。お使いの携帯電話でこれを試すには、Expoで利用可能です。

さらなる読み物 / 発展課題

  1. React Nativeのドキュメントを読んだ後、Animatedについてさらに学ぶにはこのgitbookが素晴らしいリソースです。
  2. 実際のTwitterアニメーションは、最後に近づくにつれてマスクの表示が速くなるようです。その動作によりよく一致するように、異なるイージング関数(またはspring!)を使用するようにローダーを修正してみてください。
  3. 現在のマスクの最終スケールはハードコードされており、タブレットではアプリ全体を表示できない可能性があります。画面サイズと画像サイズに基づいて最終スケールを計算することは、素晴らしいPRになるでしょう。

React Nativeマンスリー #6

·4分で読めます

React Nativeの月例ミーティングは、今も盛況です!次回のセッションについては、この記事の最後のお知らせをぜひチェックしてください。

Expo

  • Devin AbbottHoussein Djirdehによる「Full Stack React Native」書籍の先行リリース、おめでとうございます!この本では、いくつかの小さなアプリを構築しながらReact Nativeを学ぶことができます。
  • reason-react-native-scriptsの最初の(実験的)バージョンをリリースしました。これは、ReasonMLを簡単に試せるようにするためのものです。
  • Expo SDK 24がリリースされました!React Native 0.51を使用しており、スタンドアロンアプリでの画像のバンドル(初回ロード時のキャッシュ不要!)、画像操作API(切り抜き、サイズ変更、回転、反転)、顔検出API、新しいリリースチャネル機能(特定のチャネルのアクティブリリースを設定およびロールバック)、スタンドアロンアプリのビルドを追跡するウェブダッシュボード、OpenGL Android実装とAndroidマルチタスカーの長年のバグ修正など、多くの新機能と改善が含まれています。
  • この1月からReact Navigationにさらに多くのリソースを割り当てています。ReactコンポーネントとAnimatedやreact-native-gesture-handlerのようなプリミティブだけでReact Nativeのナビゲーションを構築することは可能であり、望ましいことだと強く信じており、計画しているいくつかの改善点について非常に興奮しています。コミュニティに貢献したい場合は、react-native-mapsreact-native-svgをチェックしてみてください。どちらも助けを必要としています!

インフィニットレッド

マイクロソフト

  • コアのReact Native Windowsブリッジを.NET Standardに移行するプルリクエストが開始され、事実上OSに依存しないものになりました。これにより、他の多くの.NET Coreプラットフォームが独自のスレッドモデル、JavaScriptランタイム、UIManagers(JavaScriptCore、Xamarin.Mac、Linux Gtk#、Samsung Tizenオプションなど)でブリッジを拡張できるようになることが期待されています。

ウィックス

  • Detox
    • E2Eテストをスケールさせるため、CIに費やす時間を最小限に抑えたいと考えており、Detoxの並列化サポートに取り組んでいます。
    • E2Eでのモックをより良くサポートするため、カスタムフレーバービルドを有効にするプルリクエストを提出しました。
  • DetoxInstruments
    • DetoxInstrumentsのキラー機能に取り組むことは非常に困難な作業であることが判明しました。任意の時点でJavaScriptのバックトレースを取得するには、JSスレッドの停止をサポートするためにカスタムJSCoreの実装が必要です。Wixのアプリでプロファイラを内部的にテストしたところ、JSスレッドに関する興味深い洞察が明らかになりました。
    • このプロジェクトはまだ一般利用には十分安定していませんが、活発に開発が進められており、近いうちに発表できることを願っています。
  • React Native Navigation
    • V2の開発ペースが大幅に上がりました。これまでは開発者1名が20%の時間で作業していましたが、現在では3名の開発者がフルタイムで取り組んでいます!
  • Androidのパフォーマンス
    • 古いJSCoreをRNにバンドルされている最新バージョン(webkitGTKプロジェクトの先端、カスタムJIT構成)に置き換えたところ、JSスレッドのパフォーマンスが40%向上しました。次は、その64ビット版をコンパイルする予定です。この取り組みは、Android用JSCビルドスクリプトに基づいています。現在の状況はこちらで確認してください。

次回のセッション

この会議を、単一の特定のトピック(例:ナビゲーション、React Nativeモジュールの別リポジトリへの移行、ドキュメンテーションなど)について議論するために再利用することについて、いくつか議論がありました。そうすることで、React Nativeコミュニティに最大限貢献できると感じています。次回の会議セッションで開催されるかもしれません。取り上げてほしいトピックがあれば、遠慮なくツイートしてください。

React Nativeマンスリー #5

·4分で読めます

React Nativeの月例ミーティングは続きます!各チームの取り組みを見てみましょう。

コールスタック

  • 私たちはReact NativeのCIに取り組んできました。最も重要なこととして、TravisからCircleに移行し、React NativeのCIパイプラインを1つに統一しました。
  • 私たちはHacktoberfest - React Native editionを開催し、参加者と一緒にオープンソースプロジェクトに多くのプルリクエストを送信しようとしました。
  • Haulの開発を続けています。先月、webpack 3のサポートを含む2つの新しいリリースを提出しました。私たちはCRNAExpoのサポートを追加し、より良いHMRに取り組む予定です。私たちのロードマップはissueトラッカーで公開されています。改善の提案やフィードバックがありましたら、お知らせください!

Expo

  • Expo SDK 22(React Native 0.49を使用)をリリースし、それに合わせてCRNAを更新しました。
    • 改善されたスプラッシュスクリーンAPI、基本的なARKitサポート、「DeviceMotion」API、iOS11でのSFAuthenticationSessionサポート、その他が含まれています。
  • あなたのSnackで複数のJavaScriptファイルを持てるようになり、画像やその他のアセットをエディタにドラッグ&ドロップするだけでアップロードできるようになりました。
  • iPhone Xのサポートを追加するためにreact-navigationにコントリビュートしてください。
  • Expoで大規模なアプリケーションを構築する際の課題に注力しています。例えば、
    • 複数の環境(ステージング、本番、任意のチャネル)へのデプロイをファーストクラスでサポート。チャネルは、特定のチャネルのアクティブリリースをロールバックおよび設定することをサポートします。早期テスターになりたい場合は、@expo_ioまでお知らせください。
    • また、スタンドアロンアプリのビルドインフラを改善し、OTA(Over The Air)でのアセット更新機能を維持しつつ、スタンドアロンアプリのビルドに画像やその他の非コードアセットをバンドルするサポートを追加する作業にも取り組んでいます。

Facebook

  • RTLサポートの改善
    • 方向を意識したスタイルを多数導入しています。
      • 位置
        • (左|右) → (開始|終了)
      • 余白
        • margin(Left|Right) → margin(Start|End)
      • パディング
        • padding(Left|Right) → padding(Start|End)
      • 枠線
        • borderTop(Left|Right)Radius → borderTop(Start|End)Radius
        • borderBottom(Left|Right)Radius → borderBottom(Start|End)Radius
        • border(Left|Right)Width → border(Start|End)Width
        • border(Left|Right)Color → border(Start|End)Color
    • RTLでは、位置、マージン、パディング、ボーダースタイルで「左」と「右」の意味が入れ替わっていました。数か月以内に、この動作を削除し、「左」は常に「左」を、「右」は常に「右」を意味するようにします。互換性のない変更はフラグの下に隠されています。React NativeコンポーネントでI18nManager.swapLeftAndRightInRTL(false)を使用して、それらの変更をオプトインしてください。
  • 内部ネイティブモジュールのFlow型付けに取り組んでおり、それらを使用して、ネイティブ実装が実装する必要があるJavaのインターフェースとObjCのプロトコルを生成しています。このコード生成が来年、早ければオープンソースになることを願っています。

インフィニットレッド

  • React Nativeや他のプロジェクトを支援するための新しいOSSツールです。詳細はこちら
  • 新しいボイラープレートリリース(コードネーム:Bowser)のためにIgniteを刷新しています。

Shoutem

  • Shoutemの開発フローを改善しています。アプリの作成から最初のカスタム画面までのプロセスを合理化し、非常に簡単にすることで、新しいReact Native開発者にとっての障壁を下げたいと考えています。新しい機能をテストするためにいくつかのワークショップを準備しました。また、新しいフローをサポートするためにShoutem CLIも改善しました。
  • Shoutem UIはいくつかのコンポーネントの改善とバグ修正が行われました。また、最新のReact Nativeバージョンとの互換性も確認しました。
  • Shoutemプラットフォームはいくつかの注目すべきアップデートを受け、新しい統合はオープンソース拡張プロジェクトの一部として利用可能です。他の開発者からのShoutem拡張機能での活発な開発を非常に嬉しく思っています。私たちは積極的に連絡を取り、彼らの拡張機能についてアドバイスやガイダンスを提供しています。

次回のセッション

次回のセッションは2017年12月6日水曜日に予定されています。会議の成果をどのように改善すべきかについて提案があれば、お気軽にTwitterで私にご連絡ください。

React Nativeマンスリー #4

·3分で読めます
Mike Grabowski
マイク・グラボウスキー
Callstack CTO兼共同創設者

React Nativeの月例ミーティングは続きます!各チームからの議事録はこちらです。

コールスタック

  • React Native EUは終了しました。33カ国から300人以上の参加者がヴロツワフを訪れました。講演はYouTubeで視聴できます。
  • カンファレンス後、ゆっくりとオープンソースのスケジュールに戻っています。特筆すべきは、既存のほとんどの問題を修正するreact-native-opentokの次期リリースに取り組んでいることです。

ギーキーアンツ

以下の取り組みにより、React Nativeを採用する開発者の参入障壁を下げようとしています。

  • React Native EUBuilderX.ioを発表しました。BuilderXは、JavaScriptファイル(現時点ではReact Nativeのみサポート)と直接連携して、美しく、読みやすく、編集可能なコードを生成するデザインツールです。
  • ReactNativeSeed.comをローンチしました。これは、次のReact Nativeプロジェクト向けのボイラープレートのセットを提供します。データ型にはTypeScriptとFlow、状態管理にはMobX、Redux、mobx-state-tree、スタックにはCRNAとプレーンなReact-Nativeといった様々なオプションが用意されています。

Expo

  • 間もなくSDK 21をリリースします。これには、react-native 0.48.3のサポートと、ビデオ録画、新しいスプラッシュスクリーンAPI、react-native-gesture-handlerのサポート、改善されたエラー処理など、Expo SDKにおける多くのバグ修正/信頼性向上/新機能が含まれています。
  • react-native-gesture-handlerについて、Software MansionKrzysztof Magieraが引き続き推進しており、私たちは彼のテストと開発時間の一部を資金援助しています。SDK21でこれをExpoに統合することで、Snackで簡単に試せるようになるため、人々がどんなものを作り出すのか楽しみです。
  • エラーロギング/処理の改善について - ロギング(特に「問題2」)の詳細については、Expoの内部PRに関するこのgistを、npm標準ライブラリモジュールのインポート失敗を処理する変更についてはこのコミットをご覧ください。このようにReact Nativeのアップストリームのエラーメッセージを改善する機会はたくさんあり、私たちは今後、アップストリームのPRに取り組んでいきます。コミュニティも参加してくれると嬉しいです。
  • native.directoryは成長を続けており、GitHubリポジトリからプロジェクトを追加できます。
  • 北米各地のハッカソンに参加します。PennAppsHack The NorthHackMIT、そしてまもなくMHacks

Facebook

  • Androidでの<Text>および<TextInput>コンポーネントの改善に取り組んでいます。(<TextInput>のネイティブ自動拡張、深くネストされた<Text>コンポーネントのレイアウト問題、より良いコード構造、パフォーマンス最適化)。
  • 私たちは引き続き、issueやプルリクエストのトリアージを手伝ってくれるコントリビューターを探しています。

マイクロソフト

  • CodePushのコード署名機能をリリースしました。React Native開発者は、CodePushでアプリケーションバンドルに署名できるようになりました。発表はこちらで確認できます。
  • CodePushのMobile Centerへの統合完了に取り組んでいます。テストやクラッシュレポートの統合も検討中です。

次回のセッション

次回のセッションは2017年10月10日水曜日に予定されています。今回でまだ4回目のミーティングなので、これらのメモがReact Nativeコミュニティにどのように役立っているかを知りたいと思っています。ミーティングの成果をどのように改善すべきかについて提案があれば、お気軽にTwitterで私にご連絡ください。

React Nativeマンスリー #3

·5分で読めます
Mike Grabowski
マイク・グラボウスキー
Callstack CTO兼共同創設者

React Native月例会議は継続中!今月の会議は、ほとんどのチームがリリースで忙しかったため、少し短くなりました。来月は、ポーランドのヴロツワフで開催されるReact Native EUカンファレンスに参加します。ぜひチケットを手に入れて、会場でお会いしましょう!さて、チームの活動を見ていきましょう。

参加チーム

この3回目の会議には、5つのチームが参加しました

議事録

以下は各チームからの議事録です。

コールスタック

  • 最近react-native-material-paletteをオープンソース化しました。これは画像から主要な色を抽出し、視覚的に魅力的なアプリを作成するのに役立ちます。現時点ではAndroidのみですが、将来的にはiOSのサポートも検討しています。
  • haul に HMR サポートとその他のクールな機能が追加されました!最新リリースをチェックしてください。
  • React Native EU 2017 が近づいています!来月は React Native とポーランドがテーマです!残りのチケットは こちら でお早めにお求めください。

Expo

  • Snackでのnpmパッケージインストールサポートをリリースしました。通常のExpoの制限が適用されます。つまり、パッケージはExpoにまだ含まれていないカスタムネイティブAPIに依存することはできません。また、Snackでの複数ファイルとアセットのアップロードのサポートにも取り組んでいます。SatyajitReact Native EuropeでSnackについて講演します。
  • カメラ、決済、安全なストレージ、磁力計、fsダウンロードの一時停止/再開、スプラッシュ/ローディング画面の改善を含むSDK20をリリースしました。
  • Krzysztofと引き続きreact-native-gesture-handlerに取り組んでいます。ぜひ試してみて、以前PanResponderやネイティブのジェスチャー認識器を使って構築したジェスチャーを再構築し、遭遇した問題をお知らせください。
  • JSCデバッグプロトコルを試したり、Cannyで多数の機能リクエストに対応しています。

Facebook

  • 先月、GitHub のイシュートラッカーの管理について議論し、プロジェクトの保守性を向上させるための改善を試みることを決定しました。
  • 現在、未解決のissueの数は約600件で安定しており、しばらくはこの状態が続くと思われます。過去1ヶ月で、活動がない(過去60日間にコメントがないと定義される)ため、690件のissueをクローズしました。その690件のissueのうち、58件はさまざまな理由で再オープンされました(メンテナーが修正を提供することを約束したり、貢献者がissueを開いたままにする説得力のある主張をしたりしました)。
  • 今後も、古い問題の自動クローズを継続する予定です。私たちは、トラッカーで開かれたすべての影響力のある問題に対応できる状態になりたいと願っていますが、まだそこには到達していません。メンテナーからのすべての助けが必要です。問題のトリアージを行い、特に新しく作成されたプロジェクトに影響を与える回帰や破壊的な変更を導入する問題を見逃さないようにする必要があります。協力に興味がある人は、Facebook GitHub Botを使用して問題やプルリクエストをトリアージできます。新しいメンテナーガイドには、トリアージとGitHub Botの使用に関する詳細情報が含まれています。ぜひ問題タスクフォースに自分自身を追加し、他の活発なコミュニティメンバーにも同じようにするよう奨励してください!

マイクロソフト

  • 新しい Skype アプリは、可能な限り多くのコードをプラットフォーム間で共有できるように、React Native をベースに構築されています。React Native ベースの Skype アプリは現在、Android および iOS のアプリストアで利用可能です。
  • SkypeアプリをReact Nativeで構築する際、遭遇するバグや不足している機能に対処するために、React Nativeにプルリクエストを送信しています。これまでに、約70件のプルリクエストがマージされました。
  • React Nativeを使用することで、AndroidとiOS両方のSkypeアプリを同じコードベースで動かすことができました。また、そのコードベースをSkype Webアプリの動力源としても使用したいと考えています。この目標を達成するために、React/React Nativeの上にReactXPという薄いレイヤーを構築し、オープンソース化しました。ReactXPは、iOS/Androidをターゲットにする場合はReact Nativeに、Webをターゲットにする場合はreact-domにマッピングされるクロスプラットフォームコンポーネントのセットを提供します。ReactXPの目標は、React Native for Webという別のオープンソースライブラリと似ています。これらのライブラリのアプローチがどのように異なるかについての簡単な説明は、ReactXP FAQにあります。

Shoutem

  • Shoutem を使用してアプリを構築する際の開発者体験を改善および簡素化するための取り組みを続けています。
  • すべてのアプリを react-navigation に移行し始めましたが、より安定したバージョンがリリースされるか、ネイティブナビゲーションソリューションのいずれかが安定するまで延期することにしました。
  • すべての拡張機能とほとんどのオープンソースライブラリ(animationthemeui)をReact Native 0.47.1にアップデートしました。

次回のセッション

次回のセッションは2017年9月13日水曜日に予定されています。今回でまだ3回目のミーティングなので、これらのメモがReact Nativeコミュニティにどのように役立っているかを知りたいと思っています。ミーティングの成果をどのように改善すべきかについて提案があれば、お気軽にTwitterで私にご連絡ください。

MarketplaceでのReact Nativeパフォーマンス

·6分で読めます
Facebook ソフトウェアエンジニア

React Nativeは、Facebookファミリーの複数のアプリの複数の場所で利用されており、メインのFacebookアプリのトップレベルタブも含まれます。この投稿では、注目度の高い製品であるMarketplaceに焦点を当てます。これは数十カ国で利用可能で、ユーザーが他のユーザーによって提供される製品やサービスを発見することを可能にします。

2017年前半、Relayチーム、Marketplaceチーム、Mobile JSプラットフォームチーム、React Nativeチームの共同努力により、AndroidのYear Class 2010-11デバイスにおけるMarketplaceのTime to Interaction (TTI) を半分に短縮しました。Facebookはこれまで、これらのデバイスを低性能なAndroidデバイスと見なしており、あらゆるプラットフォームやデバイスタイプで最も遅いTTIを示していました。

典型的なReact Nativeの起動処理は次のようになります。

免責事項:比率は代表的なものではなく、React Nativeの設定や使用方法によって異なります。

まず、React Nativeコア(別名「ブリッジ」)を初期化し、その後プロダクト固有のJavaScriptを実行します。このJavaScriptが、ネイティブ処理時間内にReact Nativeがどのネイティブビューをレンダリングするかを決定します。

異なるアプローチ

我々が初期に犯した間違いの1つは、SystraceとCTScanにパフォーマンスへの取り組みを任せてしまったことです。これらのツールは2016年に多くの簡単に達成できる成果を見つけるのに役立ちましたが、SystraceもCTScanも**本番環境のシナリオを代表するものではない**ことが判明し、実際に起こることを模倣することはできません。内訳に費やされた時間の比率はしばしば不正確で、時には大きく外れています。極端な場合、数ミリ秒かかると予想したものが実際には数百または数千ミリ秒かかります。とはいえ、CTScanは有用であり、本番環境に到達する前にリグレッションの3分の1を捉えることがわかっています。

Androidでは、これらのツールの欠点を1) React Nativeがマルチスレッドフレームワークであること、2) MarketplaceがNewsfeedや他のトップレベルタブのような多数の複雑なビューと同居していること、3) 計算時間が大きく変動することに起因すると考えています。したがって、この半年間は、本番環境の測定と内訳がほぼすべての意思決定と優先順位付けを推進しました。

本番環境計測の道筋

本番環境を計測することは一見単純に聞こえるかもしれませんが、実際にはかなり複雑なプロセスであることが判明しました。マスターへのコミットからアプリをPlayストアにプッシュし、十分な本番環境サンプルを収集して作業に自信を持つまでに、2〜3週間のイテレーションサイクルが複数回必要でした。各イテレーションサイクルでは、内訳が正確か、適切な粒度か、全体的な期間に適切に合計されているかを調査しました。アルファ版やベータ版は一般の人々を代表するものではないため、それらに頼ることはできませんでした。本質的に、何百万ものサンプルの集計に基づいて、非常に正確な本番環境のトレースを非常に綿密に構築しました。

内訳のミリ秒単位のすべてが親メトリクスに適切に加算されることを綿密に検証した理由の1つは、計測にギャップがあることに早期に気づいたためです。最初の内訳では、スレッドジャンプによる停止が考慮されていないことが判明しました。スレッドジャンプ自体は高価ではありませんが、すでに作業を行っているビジースレッドへのスレッドジャンプは非常に高価です。最終的に、適切なタイミングでThread.sleep()呼び出しを散りばめることで、これらのブロックをローカルで再現し、次の方法で修正することに成功しました。

  1. AsyncTaskへの依存を削除
  2. UIスレッドでのReactContextとNativeModulesの強制的な初期化を取りやめ
  3. 初期化時にReactRootViewを測定する依存関係を削除

これらのスレッドブロッキングの問題を合わせて取り除くことで、起動時間が25%以上短縮されました。

プロダクションメトリクスは、私たちの以前の仮定のいくつかにも異議を唱えました。たとえば、以前は、複数のモジュールを1つのバンドルに配置することで初期化コストが削減されるという仮定のもと、起動パスで多くのJavaScriptモジュールを事前ロードしていました。しかし、これらのモジュールを事前ロードして共存させるコストは、メリットをはるかに上回りました。インラインrequireブラックリストを再構成し、起動パスからJavaScriptモジュールを削除することで、Relay Classic(Relay Modernのみが必要な場合)のような不要なモジュールのロードを回避することができました。今日、私たちのRUN_JS_BUNDLEの内訳は75%以上高速化されています。

また、製品固有のネイティブモジュールを調査することでも成果がありました。例えば、ネイティブモジュールの依存関係を遅延注入することで、そのネイティブモジュールのコストを98%削減しました。Marketplaceの起動と他の製品との競合を排除することで、起動時間を同等の期間短縮しました。

最も素晴らしい点は、これらの改善の多くがReact Nativeで構築されたすべての画面に広く適用可能であることです。

結論

React Nativeの起動パフォーマンスの問題は、JavaScriptが遅いか、ネットワーク時間が非常に長いためだと多くの人が考えます。JavaScriptのような速度向上はTTIをかなり削減しますが、これらの各要素がTTIに貢献する割合は、以前考えられていたよりもはるかに小さいことがわかりました。

これまでの教訓は、「**計測、計測、計測!**」です。Relay ModernやLazy NativeModulesのように、実行時コストをビルド時コストに移動することから得られる成果もあります。また、コードの並列化をより賢く行ったり、デッドコードを削除したりして作業を回避することから得られる成果もあります。さらに、スレッドのブロックを解消するなど、React Nativeに対する大規模なアーキテクチャ変更から得られる成果もあります。パフォーマンスに特効薬はなく、長期的なパフォーマンス向上は、段階的な計測と改善から生まれるでしょう。認知バイアスが意思決定に影響を与えないようにしてください。代わりに、慎重に本番データ収集し、解釈して、将来の作業を導きましょう。

今後の計画

長期的には、MarketplaceのTTIをNativeで構築された同様の製品に匹敵するレベルにし、一般的にReact Nativeのパフォーマンスをネイティブのパフォーマンスと同等にすることを目指しています。さらに、今期はブリッジの起動コストを約80%大幅に削減しましたが、Prepackやビルド時処理の増加などのプロジェクトを通じて、React Nativeブリッジのコストをゼロに近づける計画です。

React Nativeマンスリー #2

·9分で読めます
Tomislav Tenodi
Shoutem プロダクトマネージャー

React Native月例会議は継続中!このセッションでは、React NativeカンファレンスであるChain Reactの背後にいる素晴らしい頭脳、Infinite Redが参加しました。ここにいるほとんどの人がChain Reactで講演していたため、会議を1週間後に延期しました。カンファレンスの講演はオンラインで公開されていますので、ぜひチェックしてみてください。それでは、各チームの活動を見ていきましょう。

参加チーム

第2回となるこのミーティングには、9つのチームが参加しました。

議事録

以下は各チームからの議事録です。

Airbnb

コールスタック

  • マイク・グラボウスキーは、いつものようにReact Nativeの月次リリースを管理しており、いくつかのベータ版も公開しました。特に、Windowsユーザーのブロックを解除するv0.43.5ビルドをnpmに公開することに取り組んでいます!
  • Haulはゆっくりと着実に開発が進んでいます。HMRを追加するプルリクエストがあり、その他の改善も実装されています。最近、いくつかの業界リーダーに採用されました。おそらく、この分野でフルタイムの有給仕事を開始することを計画しています。
  • 今月、JestチームのMichał PierzchałaがCallstackに加わりました。彼はHaulのメンテナンスを支援し、おそらくMetro BundlerJestにも取り組むでしょう。
  • Satyajit Sahooが私たちのチームに加わりました!イェイ!
  • 私たちのOSS部門から、たくさんのクールなものが登場予定です。特に、Material Palette APIをReact Nativeに導入する作業に取り組んでいます。ネイティブコンポーネントと1:1のルック&フィールを提供することを目的とした、ネイティブiOSキットを最終的にリリースする予定です。

Expo

  • React Nativeエコシステムにおけるライブラリの発見と評価を支援するため、Native Directoryを最近ローンチしました。問題点:多くのライブラリがあり、テストが困難で、手動でヒューリスティックを適用する必要があり、どれが最適かすぐにわかりません。また、CRNA/Expoと互換性があるかどうかを知るのも困難です。そこで、Native Directoryはこれらの問題を解決しようとします。ぜひチェックして、あなたのライブラリを追加してください。ライブラリのリストはこちらにあります。これはまだ最初の段階であり、Expoの人々だけでなく、コミュニティが所有・運営することを望んでいます。ですから、これが価値があると感じ、より良くしたいと思うのであれば、ぜひ参加してください!
  • Expo SDK 19でSnackへのnpmパッケージインストール機能を初期サポートしました。まだいくつかのバグに取り組んでいる最中ですので、問題が発生した場合はお知らせください。Native Directoryと合わせて、JSのみの依存関係を持つライブラリやExpo SDKに含まれる依存関係を持つライブラリのテストが簡単になるはずです。試してみてください。
  • Expo SDK19をリリースしました。全体的に多くの改善が施され、更新されたAndroid JSCを使用しています。
  • Alexander Kotliarskyiと共同で、アプリのユーザーエクスペリエンスを改善するためのヒントをまとめたドキュメントのガイドを作成しています。ぜひ参加してリストに追加したり、執筆を手伝ったりしてください!
  • 引き続き取り組んでいるのは、オーディオ/ビデオ、カメラ、ジェスチャー(Software Mansionと共同でreact-native-gesture-handler)、GLカメラ統合で、これらのいくつかをSDK20(8月)で初めてリリースし、他のものについても大幅な改善を目指しています。バックグラウンド作業(位置情報、オーディオ、通知処理など)のためのExpoクライアントのインフラストラクチャ構築は始まったばかりです。
  • アダム・ミスキエヴィッチが、UINavigationControllerのトランジションをreact-navigationで模倣する上で素晴らしい進歩を遂げました。彼のツイートで以前のバージョンをチェックしてください。近いうちにリリースされるでしょう。また、彼がアップストリームに統合したMaskedViewIOSもチェックしてください。Android版のMaskedViewを実装するスキルと意欲があるなら、素晴らしいことです!

Facebook

  • Facebookは、ネイティブのComponentKitおよびLithoコンポーネントをReact Native内に埋め込む可能性を社内で検討しています。
  • React Nativeへの貢献は大歓迎です!貢献方法について疑問がある場合は、「貢献方法」ガイドで開発プロセスが説明され、最初のプルリクエストを送信する手順が示されています。コードを書く必要のない貢献方法もあります。たとえば、問題のトリアージやドキュメントの更新などです。
    • 執筆時点では、React Nativeには**635**件の未解決のissueと**249**件の未解決のプルリクエストがあります。これはメンテナーにとって圧倒的な量であり、内部で修正された場合でも、関連するタスクが更新されていることを確認することは困難です。
    • コミュニティを満足させながら、これをどのように処理するのが最善か、私たちには分かりません。いくつかの(すべてではありませんが!)選択肢には、古い問題を閉じること、はるかに多くの人に問題を管理する権限を与えること、問題テンプレートに従わない問題を自動的に閉じることなどがあります。私たちは、期待を設定し、サプライズを避けるために「メンテナーに何を期待するか」というガイドを作成しました。メンテナーの体験を向上させ、問題を提起したりプルリクエストを作成したりする人々が意見を聞かれ、評価されていると感じられるようにする方法についてアイデアがあれば、ぜひお知らせください!

ギーキーアンツ

  • Chain ReactでReact Nativeファイルに対応したデザイナーツールのデモを行いました。多くの参加者がウェイティングリストに登録してくれました。
  • また、Google Flutter(主要な比較が間もなく行われる)、Kotlin NativeApache Weexなどの他のクロスプラットフォームソリューションも検討し、アーキテクチャの違いと、それらからReact Native全体のパフォーマンスを向上させるために何を学べるかを理解しようとしています。
  • ほとんどのアプリでreact-navigationに切り替え、全体的なパフォーマンスが向上しました。
  • また、NativeBase Marketを発表しました。これはReact Nativeのコンポーネントとアプリのマーケットプレイスです(開発者のための、開発者による)。

インフィニットレッド

マイクロソフト

  • CodePushMobile Centerに統合されました。既存ユーザーのワークフローに変更はありません。
    • 一部の方から、アプリが重複しているという問題が報告されています。彼らはすでにMobile Centerにアプリを持っていました。これらの問題を解決するために取り組んでいますが、もし2つのアプリがある場合はお知らせください。こちらでマージすることができます。
  • Mobile Centerは現在、CodePushのプッシュ通知をサポートしています。また、通知とCodePushを組み合わせてアプリのA/Bテストを行う方法も紹介しました。これはReactNativeアーキテクチャに特有のものです。
  • VS CodeにはReactNativeに関する既知のデバッグの問題があります。数日中にリリースされる拡張機能の次のバージョンで、この問題が修正される予定です。
  • Microsoft内にはReact Nativeに取り組んでいる他の多くのチームもあるため、次回のミーティングではすべてのグループからより良い代表者を得られるよう努めます。

Shoutem

  • ShoutemでのReact Native開発を容易にするための作業を完了しました。Shoutemでアプリを開発する際には、すべての標準的なreact-nativeコマンドを使用できます。
  • React Nativeのプロファイリングを最適化する方法を見つけるために多くの作業を行いました。大量のドキュメントが古くなっており、公式ドキュメントにプルリクエストを作成するか、少なくとも私たちの結論をブログ投稿にまとめるために最善を尽くします。
  • ナビゲーションソリューションをreact-navigationに切り替えているので、近々フィードバックがあるかもしれません。
  • ツールキットに新しいHTMLコンポーネントをリリースしました。これは生のHTMLをReact Nativeコンポーネントツリーに変換するものです。

ウィックス

  • 私たちは、react-native-repackagerの機能を備えたMetro Bundlerへのプルリクエストの作業を開始しました。私たちはreact-native-repackagerを更新し、RN 44 (本番環境で使用しています) をサポートするようにしました。detoxのモックインフラストラクチャに使用しています。
  • この3週間、Wixアプリのdetoxテストを実施してきました。40人以上のエンジニアが関わるこの規模のアプリで、手動QAを減らす方法を学ぶ素晴らしい経験になっています。その結果、detoxのいくつかの問題を解決し、新しいバージョンが公開されたばかりです。これまでのところ、「ゼロ不具合ポリシー」を順守しており、テストは一貫して合格していることを喜んでご報告します。
  • Detox for Androidは順調に進んでいます。コミュニティから大きな助けを得ています。約2週間で初期バージョンが期待されます。
  • 私たちのパフォーマンス・テスト・ツールであるDetoxInstrumentsは、当初の意図よりも少し大きくなっています。現在、detoxと密接に結合しないスタンドアロン・ツールにすることを計画しています。これにより、一般的なiOSアプリのパフォーマンスを調査できるようになります。また、detoxと統合して、パフォーマンス指標に関する自動テストを実行できるようになります。

次回のセッション

次回のセッションは2017年8月16日に予定されています。今回が2回目のミーティングであったため、これらのメモがReact Nativeコミュニティにどのような利益をもたらすかを知りたいと考えています。ミーティングの成果をどのように改善すべきかについて提案があれば、お気軽にTwitterで私に連絡してください。

React Nativeマンスリー #1

·6分で読めます
Tomislav Tenodi
Shoutem プロダクトマネージャー

Shoutemでは、React Nativeの最初期から関わることができ、幸運でした。私たちは最初からこの素晴らしいコミュニティの一員になりたいと決意しました。すぐに、コミュニティの成長と改善のペースに追いつくことはほとんど不可能だと気づきました。だからこそ、主要なReact Native貢献者全員が、それぞれの取り組みと計画を簡潔に発表できる月例会議を組織することにしました。

月例ミーティング

2017年6月14日に月例会議の初回セッションを開催しました。React Native Monthlyのミッションはシンプルかつ明確です: React Nativeコミュニティを改善することです。チームの取り組みを発表することで、オフラインで行われるチーム間の連携を容易にします。

参加チーム

第1回ミーティングには、8つのチームが参加しました。

今後のセッションでは、より多くのコアコントリビューターが参加してくれることを期待しています!

議事録

各チームの計画は、より広い層の関心事となる可能性があるため、ここReact Nativeブログで共有します。以下がその内容です。

Airbnb

  • ViewAccessibilityInfoネイティブモジュールにA11y (アクセシビリティ) APIを追加する予定です。
  • Androidのネイティブモジュールに、実行スレッドを指定できるAPIを追加することを検討します。
  • 初期化パフォーマンスを改善する可能性について調査を進めています。
  • 「unbundle」の上に、さらに洗練されたバンドル戦略をいくつか検討しています。

コールスタック

  • E2EテストにDetoxを使用することで、リリースプロセスの改善を検討しています。プルリクエストは間もなく提出される予定です。
  • 取り組んできたBlobのプルリクエストがマージされ、後続のプルリクエストも準備中です。
  • Metro Bundlerと比較してどのように機能するかを見るために、社内プロジェクト全体でHaulの採用を増やしています。webpackチームと協力して、より良いマルチスレッドパフォーマンスに取り組んでいます。
  • 社内で、オープンソースプロジェクトを管理するためのより良いインフラを実装しました。今後数週間で、より多くのものを公開する予定です。
  • React Native Europeカンファレンスが近づいていますが、まだ特筆すべきことはありません。皆さん、ぜひご参加ください!
  • 代替案(特にネイティブナビゲーション)を調査するため、react-navigationから一時的に離れています。

Expo

  • Snackでnpmモジュールをインストールできるようにする作業を進めています。これはライブラリがドキュメントにサンプルを追加する際に役立つでしょう。
  • Software MansionKrzysztofや他の人々と協力して、AndroidでのJSCアップデートとジェスチャーハンドリングライブラリに取り組んでいます。
  • Adam Miskiewiczは、react-navigationに注力するようになりました。
  • Create React Native AppはドキュメントのGetting Startedガイドに掲載されています。Expoは、ライブラリ作者に対し、自身のライブラリがCRNAで動作するかどうか、動作する場合どのようにセットアップするかを明確に説明することを奨励しています。

Facebook

  • React Nativeのパッケージャーは現在、独立したリポジトリにあるMetro Bundlerです。ロンドンのMetro Bundlerチームは、コミュニティのニーズに対応し、React Native以外の追加のユースケースのためにモジュール性を改善し、問題やPRへの応答性を高めることに意欲的です。
  • 今後数ヶ月で、React NativeチームはプリミティブコンポーネントのAPIを洗練させる作業に取り組みます。レイアウトの癖、アクセシビリティ、Flowの型付けに関する改善が期待されます。
  • React Nativeチームは今年、WindowsやmacOSといったサードパーティプラットフォームを完全にサポートするためのリファクタリングを行い、コアのモジュール性を向上させる計画です。

ギーキーアンツ

  • チームは、.jsファイルを直接操作するUI/UXデザインアプリ(コードネーム:Builder)を開発中です。現在はReact Nativeのみをサポートしており、Adobe XDやSketchに似ています。
  • 既存のReact Nativeアプリをエディタで読み込み、(デザイナーとして視覚的に)変更を加え、その変更を直接JSファイルに保存できるようにするため、チームは懸命に取り組んでいます。
  • デザイナーと開発者の間の溝を埋め、同じリポジトリで作業できるようにすることを目指しています。
  • また、NativeBaseは最近、GitHubスターが5,000に達しました。

マイクロソフト

  • CodePushMobile Centerに統合されました。これは、配布、分析、その他のサービスとより統合されたエクスペリエンスを提供する最初のステップです。その発表はこちらをご覧ください。
  • VS Codeにはデバッグに関するバグがあり、現在その修正に取り組んでおり、新しいビルドが提供される予定です。
  • 統合テストのためにDetoxを調査しており、クラッシュレポートと共に変数を取得するためにJSC Contextに注目しています。

Shoutem

  • React Nativeコミュニティのツールを使ってShoutemアプリをより簡単に開発できるようにします。Shoutemで作成されたアプリを、すべてのReact Nativeコマンドを使って実行できるようになります。
  • React Nativeのプロファイリングツールを調査しています。セットアップに多くの問題があったため、その過程で得られた知見をいくつか書き出す予定です。
  • Shoutemは、React Nativeを既存のネイティブアプリと統合しやすくする作業に取り組んでいます。コミュニティからのフィードバックを得るため、社内で開発したコンセプトをドキュメント化する予定です。

ウィックス

  • Wixアプリの大部分を「手動QAゼロ」に移行するために、社内でDetoxの導入を進めています。その結果、Detoxは数十人の開発者によって本番環境で extensively 使用され、急速に成熟しています。
  • ビルド中に任意のファイル拡張子をオーバーライドするために、Metro Bundlerのサポートを追加する作業を行っています。「ios」と「android」だけでなく、「e2e」や「detox」のような任意のカスタム拡張子もサポートします。E2Eモックに使用する計画です。react-native-repackagerというライブラリがすでに公開されており、現在はPRの作成に取り組んでいます。
  • パフォーマンステストの自動化を調査しています。これはDetoxInstrumentsという新しいリポジトリです。オープンソースで開発されているので、ぜひご覧ください。
  • KPNのコントリビューターと協力して、Android版Detoxと実機でのサポートに取り組んでいます。
  • シミュレーター/デバイスを自動化する必要がある他のツールを構築できるように、「プラットフォームとしてのDetox」について考えています。例としては、React Native用のStorybookや、Ramの統合テストのアイデアが挙げられます。

次回のセッション

ミーティングは4週間ごとに開催されます。次回のセッションは2017年7月12日に予定されています。このミーティングを開始したばかりですので、これらのメモがReact Nativeコミュニティにどのような利益をもたらすかを知りたいと考えています。今後のセッションで取り上げるべき内容や、ミーティングの成果をどのように改善すべきかについて提案があれば、お気軽にTwitterで私に連絡してください。