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

「engineering」のタグが付いた32件の投稿

すべてのタグを表示

新しいiOS WebViewの導入

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

Appleは長い間、UIWebViewの使用を推奨せず、WKWebViewを推奨してきました。数ヶ月以内にリリースされるiOS 12では、UIWebViewは正式に非推奨になります。React NativeのiOS WebView実装は、UIWebViewクラスに大きく依存しています。そのため、これらの進展を受けて、WKWebViewを使用するWebView React Nativeコンポーネント用の新しいネイティブiOSバックエンドを構築しました。

これらの変更の最終段階はこのコミットで取り込まれ、0.57リリースで利用可能になります。

この新しい実装を選択するには、useWebKitプロパティを使用してください。

<WebView
useWebKit={true}
source={{url: 'https://www.google.com'}}
/>

改善点

UIWebViewには、WebView内で実行されているJavaScriptとReact Native間の通信を促進する正当な方法がありませんでした。WebViewからメッセージが送信された場合、それらをReact Nativeに配信するためにハックに頼っていました。簡単に言えば、メッセージデータを特殊なスキームのURLにエンコードし、WebViewをそのURLに移動させました。ネイティブ側では、このナビゲーションを傍受してキャンセルし、URLからデータを解析し、最後にReact Nativeを呼び出していました。この実装はエラーが発生しやすく、安全ではありませんでした。WKWebViewの機能を活用してこれを完全に置き換えることができたことをお知らせします。

WKWebViewがUIWebViewよりも優れている点としては、JavaScriptの実行速度の向上や、マルチプロセスアーキテクチャなどがあります。詳細については、2014年のWWDCをご覧ください。

注意点

コンポーネントが以下のプロパティを使用している場合、WKWebViewに切り替えると問題が発生する可能性があります。当面の間、これらのプロパティの使用は避けることをお勧めします。

動作の不整合

automaticallyAdjustContentInsets および contentInsets (コミット)

WKWebViewにcontentInsetsを追加しても、WKWebViewのビューポートは変更されません。ビューポートはフレームと同じサイズのままです。UIWebViewでは、ビューポートのサイズが実際に変更されます(コンテンツインセットが正の場合、小さくなります)。

backgroundColor (コミット)

WebViewの新しいiOS実装では、このプロパティを使用すると背景色がちらつく可能性があります。さらに、WKWebViewUIWebviewとは異なる方法で透明な背景をレンダリングします。詳細については、コミットの説明を参照してください。

非サポート

scalesPageToFit (コミット)

WKWebViewはscalesPageToFitプロパティをサポートしていなかったため、WebView React Nativeコンポーネントではこれを実装できませんでした。

アクセシビリティAPIの更新

·7分で読めます
Ziqi Chen
カリフォルニア大学バークレー校 学生

動機

テクノロジーが進歩し、モバイルアプリが日常生活においてますます重要になるにつれて、アクセシブルなアプリケーションを作成する必要性も同様に重要性を増しています。

React Nativeの限定的なアクセシビリティAPIは、開発者にとって常に大きな悩みの種でした。そこで私たちは、インクルーシブなモバイルアプリケーションをより簡単に作成できるように、アクセシビリティAPIにいくつかのアップデートを行いました。

既存のAPIの問題点

問題1:2つの完全に異なるが類似したプロパティ - accessibilityComponentType(Android)とaccessibilityTraits(iOS)

accessibilityComponentTypeaccessibilityTraitsは、AndroidのTalkBackとiOSのVoiceOverに、ユーザーが操作しているUI要素の種類を伝えるために使用される2つのプロパティです。これらのプロパティに関する最大の2つの問題点は、

  1. **これらは異なる使用方法を持つ2つの異なるプロパティですが、目的は同じです。**以前のAPIでは、これらは2つの別々のプロパティ(プラットフォームごとに1つ)であり、不便であるだけでなく、多くの開発者にとって混乱を招くものでした。iOSのaccessibilityTraitsは17種類の異なる値を許可しますが、AndroidのaccessibilityComponentTypeは4種類の値しか許可しません。さらに、ほとんどの値に重複がありませんでした。これら2つのプロパティの入力型も異なります。accessibilityTraitsは特性の配列または単一の特性を渡すことができますが、accessibilityComponentTypeは単一の値のみを許可します。
  2. Androidでの機能が非常に限られています。 古いプロパティでは、Talkbackが認識できるUI要素は「button」、「radiobutton_checked」、「radiobutton_unchecked」のみでした。

問題2:存在しないアクセシビリティヒント:

アクセシビリティヒントは、TalkBackやVoiceOverを使用しているユーザーが、アクセシビリティラベルだけでは分からない、アクセシビリティ要素に対するアクションを実行したときに何が起こるかを理解するのに役立ちます。これらのヒントは設定パネルでオンオフを切り替えることができます。以前は、React NativeのAPIはアクセシビリティヒントをまったくサポートしていませんでした。

問題点3:色の反転の無視

視覚障害のある一部のユーザーは、画面のコントラストを高めるために携帯電話で色反転を使用しています。AppleはiOS用のAPIを提供しており、開発者は特定のビューを無視することができます。これにより、ユーザーが色反転設定をオンにしていても、画像や動画が歪むことはありません。このAPIは現在、React Nativeではサポートされていません。

新しいAPIの設計

解決策1:accessibilityComponentType(Android)とaccessibilityTraits(iOS)の組み合わせ

accessibilityComponentTypeaccessibilityTraitsの間の混乱を解決するために、これらを単一のプロパティに統合することにしました。これらは技術的に同じ意図された機能を持っていたため、統合することで、開発者はアクセシビリティ機能を構築する際にプラットフォーム固有の複雑さを心配する必要がなくなりました。

背景

iOSでは、UIAccessibilityTraitsは任意のNSObjectに設定できるプロパティです。JavaScriptプロパティを介してネイティブに渡される17の特性のそれぞれは、Objective-CのUIAccessibilityTraits要素にマッピングされます。特性はそれぞれlong intで表現され、設定されたすべての特性はOR演算で結合されます。

しかしAndroidでは、AccessibilityComponentTypeはReact Nativeによって作られた概念であり、Androidのどのプロパティにも直接マッピングされません。アクセシビリティはアクセシビリティデリゲートによって処理されます。各ビューにはデフォルトのアクセシビリティデリゲートがあります。アクセシビリティアクションをカスタマイズしたい場合は、新しいアクセシビリティデリゲートを作成し、カスタマイズしたい特定のメソッドをオーバーライドし、そして処理するビューのアクセシビリティデリゲートを新しいデリゲートに関連付ける必要があります。開発者がAccessibilityComponentTypeを設定すると、ネイティブコードは渡されたコンポーネントに基づいて新しいデリゲートを作成し、ビューにそのアクセシビリティデリゲートを設定しました。

行われた変更

新しいプロパティについては、2つのプロパティのスーパーセットを作成したかったのです。accessibilityTraitsがはるかに多くの値を持っているため、新しいプロパティは既存のaccessibilityTraitsを主にモデル化することにしました。これらの特性に対するAndroidの機能は、アクセシビリティデリゲートを変更することによってポリフィルされます。

iOSのaccessibilityTraitsに設定できるUIAccessibilityTraitsの値は17種類あります。しかし、新しいプロパティの可能な値としてそれらすべてを含めたわけではありません。これは、これらの特性のいくつかを設定する効果があまりよく知られておらず、これらの値の多くは事実上まったく使用されていないためです。

UIAccessibilityTraitsの値は、一般的に2つの目的のいずれかを果たしていました。それは、UI要素が持っている役割を記述するか、UI要素が置かれている状態を記述するかでした。以前のプロパティのほとんどの使用例では、通常、役割を表す1つの値と、「状態が選択された」または「状態が無効になった」のいずれか、またはその両方を組み合わせて使用していました。したがって、私たちは2つの新しいアクセシビリティプロパティ、accessibilityRoleaccessibilityStateを作成することにしました。

accessibilityRole

新しいプロパティaccessibilityRoleは、TalkbackやVoiceoverにUI要素の役割を伝えるために使用されます。この新しいプロパティは、以下のいずれかの値を取ることができます。

  • なし
  • ボタン
  • リンク
  • 検索
  • 画像
  • キーボードキー
  • テキスト
  • 調整可能
  • ヘッダー
  • 要約
  • イメージボタン

このプロパティは、UI要素が論理的にこれらのうちの1つ以上を取ることが一般的にないため、1つの値のみを渡すことができます。例外はimageとbuttonなので、両方を組み合わせたimagebuttonという役割を追加しました。

accessibilityStates

新しいプロパティaccessibilityStatesは、TalkbackやVoiceoverにUI要素がどのような状態にあるかを伝えるために使用されます。このプロパティは、以下の値のいずれかまたは両方を含む配列を取ります。

  • 選択済み
  • 無効

解決策2:アクセシビリティヒントの追加

このために、新しいプロパティaccessibilityHintを追加しました。このプロパティを設定すると、TalkbackやVoiceoverがユーザーにヒントを読み上げることができるようになります。

accessibilityHint

このプロパティは、読み上げられるアクセシビリティヒントを文字列の形式で受け取ります。

iOSでは、このプロパティを設定すると、ビューに対応するネイティブプロパティAccessibilityHintが設定されます。iPhoneでアクセシビリティヒントがオンになっている場合、ヒントはVoiceoverによって読み上げられます。

Androidでは、このプロパティを設定すると、ヒントの値がアクセシビリティラベルの末尾に追加されます。この実装の利点は、iOSのヒントの動作を模倣していることですが、欠点は、iOSのようにAndroidの設定でこれらのヒントをオフにできないことです。

Androidでこの決定を下した理由は、通常、アクセシビリティヒントは特定のアクション(例:クリック)に対応しており、プラットフォーム間で動作の一貫性を保ちたかったためです。

問題点3への解決策

accessibilityIgnoresInvertColors

私たちはAppleのAPIであるAccessibilityIgnoresInvertColorsをJavaScriptに公開しました。これにより、色を反転させたくないビュー(例:画像)がある場合、このプロパティをtrueに設定すると、反転されなくなります。

新しい使い方

これらの新しいプロパティは、React Native 0.57リリースで利用可能になります。

アップグレード方法

現在accessibilityComponentTypeaccessibilityTraitsを使用している場合は、以下の手順で新しいプロパティにアップグレードできます。

1. jscodeshiftの使用

最も単純なユースケースは、jscodeshiftスクリプトを実行することで置き換えることができます。

このスクリプトは、以下のインスタンスを置き換えます。

accessibilityTraits=“trait”
accessibilityTraits={[“trait”]}

以下のように

accessibilityRole= “trait”

このスクリプトはまた、AccessibilityComponentTypeのインスタンスを削除します(AccessibilityComponentTypeを設定するすべての場所でAccessibilityTraitsも設定すると仮定しています)。

2. 手動でのcodemodの使用

AccessibilityTraitsを使用したケースで、AccessibilityRoleに対応する値がない場合、および複数の特性がAccessibilityTraitsに渡されたケースでは、手動でコードモッドを行う必要がありました。

一般的に、

accessibilityTraits= {[“button”, “selected”]}

は手動で以下のように置き換えられます。

accessibilityRole=“button”
accessibilityStates={[“selected”]}

これらのプロパティは、すでにFacebookのコードベースで使用されています。Facebookのコードモッドは驚くほどシンプルでした。jscodeshiftスクリプトが約半分のインスタンスを修正し、残りの半分は手動で修正されました。全体として、プロセス全体にかかった時間は数時間未満でした。

更新されたAPIが役立つことを願っています!そして、引き続きアクセシブルなアプリを作り続けてください!#inclusion

React Nativeの現状 2018

·5分で読めます
Sophie Alpert
Facebook React エンジニアリングマネージャー

React Nativeの現状について最後のアップデートを公開してから、しばらく時間が経ちました。

Facebookでは、かつてないほどReact Nativeを多くの重要なプロジェクトで使用しています。当社の最も人気のある製品の1つはMarketplaceで、これは当社のアプリのトップレベルタブの1つであり、毎月8億人ものユーザーに利用されています。2015年の立ち上げ以来、MarketplaceのすべてがReact Nativeで構築されており、アプリのさまざまな部分にわたって100以上の全画面ビューが含まれています。

また、アプリの多くの新しい部分にもReact Nativeを使用しています。先月のF8基調講演をご覧になった方は、献血、危機対応、プライバシーショートカット、ウェルネスチェックを認識されるでしょう。これらはすべてReact Nativeで構築された最近の機能です。そして、主要なFacebookアプリ以外のプロジェクトでもReact Nativeが使用されています。新しいOculus Go VRヘッドセットには、完全にReact Nativeで構築されたコンパニオンモバイルアプリが含まれており、ヘッドセット自体で多くの体験を支えるReact VRは言うまでもありません。

当然のことながら、私たちはアプリを構築するために他の多くのテクノロジーも使用しています。LithoComponentKitは、アプリで広く使用している2つのライブラリです。どちらもネイティブ画面を構築するためのReactのようなコンポーネントAPIを提供しています。React Nativeが他のすべてのテクノロジーを置き換えることが目標であったことは一度もありません。私たちはReact Native自体をより良くすることに注力していますが、他のチームがReact Nativeからアイデアを借用して、非JavaScriptコードにもインスタントリロードをもたらすのを見るのが大好きです。

アーキテクチャ

2013年にReact Nativeプロジェクトを開始した際、JavaScriptとネイティブの間に非同期でシリアル化可能、そしてバッチ処理される単一の「ブリッジ」を持つように設計しました。React DOMがReactの状態更新をdocument.createElement(attrs).appendChild()のようなDOM APIへの命令的で可変的な呼び出しに変換するのと同様に、React Nativeは[["createView", attrs], ["manageChildren", ...]]のような、実行する変更をリストした単一のJSONメッセージを返すように設計されました。私たちは、このシステム全体を同期的な応答に依存しないように設計し、そのリスト内のすべてがJSONと逆方向に完全にシリアル化できることを保証しました。これは、柔軟性をもたらしたためです。このアーキテクチャの上に、Chromeデバッグのようなツールを構築することができました。これにより、すべてのJavaScriptコードがWebSocket接続を介して非同期に実行されます。

過去5年間で、これらの初期の原則が一部の機能の構築を困難にしていることが判明しました。非同期ブリッジは、JavaScriptロジックを多くのネイティブAPIに直接統合できないことを意味します。ネイティブ呼び出しをキューに入れるバッチ処理されたブリッジは、React Nativeアプリがネイティブに実装された関数を呼び出すのをより困難にします。そして、シリアル化可能なブリッジは、2つの世界間でメモリを直接共有するのではなく、不必要なコピーを意味します。React Nativeで完全に構築されたアプリの場合、これらの制限は通常許容できます。しかし、React Nativeと既存のアプリコードの間で複雑な統合が行われているアプリの場合、これらは不満です。

我々は、React Nativeをより柔軟にし、ハイブリッドJavaScript/ネイティブアプリのネイティブインフラストラクチャとの統合を改善するために、React Nativeの大規模な再アーキテクチャに取り組んでいます。このプロジェクトでは、過去5年間で学んだことを適用し、アーキテクチャをよりモダンなものに段階的に進化させます。React Nativeの内部の多くを書き直していますが、ほとんどの変更は内部にあります。既存のReact Nativeアプリは、変更なし、またはほとんど変更なしで引き続き動作します。

React Nativeをより軽量にし、既存のネイティブアプリに適合させるために、この再アーキテクチャには3つの主要な内部変更があります。まず、スレッドモデルを変更しています。各UI更新が3つの異なるスレッドで作業を行う必要があった代わりに、高優先度の更新では任意のシングルスレッドでJavaScriptに同期的に呼び出すことが可能になり、同時に低優先度の作業はメインスレッドから切り離して応答性を維持します。次に、複数のレンダリング優先度を可能にし、非同期データ処理を簡素化するために、React Nativeに非同期レンダリング機能を取り入れています。最後に、ブリッジを簡素化して、より高速かつ軽量にします。ネイティブとJavaScript間の直接呼び出しはより効率的になり、クロス言語のスタックトレースなどのデバッグツールを構築しやすくなります。

これらの変更が完了すると、より密接な統合が可能になります。現在、複雑なハックなしに、ネイティブナビゲーション、ジェスチャー処理、またはUICollectionViewやRecyclerViewのようなネイティブコンポーネントを組み込むことはできません。スレッドモデルの変更後、このような機能の構築は簡単になります。

この作業が完了に近づくにつれて、今年後半に詳細を公開する予定です。

コミュニティ

Facebook内のコミュニティとともに、Facebookの外でも活発なReact Nativeユーザーと協力者のコミュニティがあることを嬉しく思います。React Nativeユーザーへのより良いサービス提供と、プロジェクトへの貢献を容易にすることの両方で、React Nativeコミュニティをよりサポートしたいと考えています。

私たちのアーキテクチャ変更がReact Nativeと他のネイティブインフラストラクチャとの連携をよりクリーンにするのに役立つように、React NativeはJavaScriptエコシステムに適合するためにJavaScript側でスリムになるべきです。これにはVMとバンドラーの交換可能性が含まれます。破壊的な変更のペースについていくのが難しいことは承知していますので、主要なリリースを減らす方法を見つけたいと考えています。最後に、一部のチームがスタートアップの最適化のようなトピックでより徹底したドキュメントを求めていることは承知しています。そこでは私たちの専門知識はまだ文書化されていません。今後1年間でこれらの変更の一部が期待されます。

あなたがReact Nativeを使用しているなら、あなたは私たちのコミュニティの一員です。React Nativeをあなたにとってより良くするために、引き続きご意見をお聞かせください。

React Nativeはモバイル開発者の道具箱の中の一つのツールに過ぎませんが、私たちが強く信じているツールであり、過去1年間で500人以上のコントリビューターから2500以上のコミットを得て、日々改善しています。

React NativeでTypeScriptを使用する

·8分で読めます
Ash Furrow
Artsy ソフトウェアエンジニア

JavaScript!私たちは皆、JavaScriptが大好きです。しかし、私たちの中にはも好きな人もいます。幸いなことに、JavaScriptに強力な型を追加するオプションは存在します。私のお気に入りはTypeScriptですが、React NativeはFlowをすぐにサポートしています。どちらを好むかは好みによる問題であり、それぞれJavaScriptに型の魔法を追加する方法について独自のアプローチを持っています。今日は、React NativeアプリでTypeScriptを使用する方法を見ていきましょう。

この記事では、MicrosoftのTypeScript-React-Native-Starterリポジトリをガイドとして使用します。

更新: このブログ記事が書かれてから、さらに簡単になりました。このブログ記事で説明されているセットアップはすべて、たった1つのコマンドを実行するだけで置き換えられます。

npx react-native init MyAwesomeProject --template react-native-template-typescript

しかし、上記のブログ記事で詳細に説明されているように、BabelのTypeScriptサポートにはいくつかの制限があります。_この_投稿で概説されている手順は依然として機能し、Artsyは引き続き本番環境でreact-native-typescript-transformerを使用していますが、React NativeとTypeScriptをすぐに使い始めるための最速の方法は、上記のコマンドを使用することです。必要に応じて、後でいつでも切り替えることができます。

いずれにせよ、楽しんでください!元のブログ記事は以下に続きます。

前提条件

複数の異なるプラットフォームで開発している可能性があり、いくつかの異なる種類のデバイスをターゲットとしているため、基本的なセットアップには手間がかかる場合があります。最初に、TypeScriptなしで通常のReact Nativeアプリを実行できることを確認する必要があります。React Nativeのウェブサイトの指示に従って始めましょう。デバイスまたはエミュレーターにデプロイできるようになったら、TypeScript React Nativeアプリを開始する準備が整います。

Node.jsnpmYarnも必要です。

初期化

通常のReact Nativeプロジェクトのひな形を作成してみたら、TypeScriptを追加する準備が整います。さあ、やってみましょう。

react-native init MyAwesomeProject
cd MyAwesomeProject

TypeScriptの追加

次のステップは、プロジェクトにTypeScriptを追加することです。以下のコマンドは、

  • プロジェクトにTypeScriptを追加します
  • プロジェクトにReact Native TypeScript Transformerを追加します
  • 空のTypeScript設定ファイルを初期化します。これは次に設定します
  • 空のReact Native TypeScript Transformer設定ファイルを追加します。これは次に設定します
  • ReactとReact Nativeの型定義を追加します

では、これらを実行してみましょう。

yarn add --dev typescript
yarn add --dev react-native-typescript-transformer
yarn tsc --init --pretty --jsx react
touch rn-cli.config.js
yarn add --dev @types/react @types/react-native

tsconfig.jsonファイルにはTypeScriptコンパイラのすべての設定が含まれています。上記のコマンドで作成されたデフォルトはほとんど問題ありませんが、ファイルを開いて次の行のコメントを解除してください。

{
/* Search the config file for the following line and uncomment it. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
}

rn-cli.config.jsにはReact Native TypeScript Transformerの設定が含まれています。それを開いて以下を追加してください。

module.exports = {
getTransformModulePath() {
return require.resolve('react-native-typescript-transformer');
},
getSourceExts() {
return ['ts', 'tsx'];
},
};

TypeScriptへの移行

生成されたApp.jsおよび__tests_/App.jsファイルをApp.tsxに名前変更します。index.js.js拡張子を使用する必要があります。すべての新しいファイルは.tsx拡張子を使用する必要があります(ファイルにJSXが含まれていない場合は.ts)。

今すぐアプリを実行しようとすると、object prototype may only be an object or nullのようなエラーが発生します。これは、Reactからのデフォルトのエクスポートと、同じ行での名前付きエクスポートのインポートの失敗によって引き起こされます。App.tsxを開き、ファイルの上部にあるインポートを変更します。

-import React, { Component } from 'react';
+import React from 'react'
+import { Component } from 'react';

これの一部は、BabelとTypeScriptがCommonJSモジュールと相互運用する方法の違いに関係しています。将来的には、両者は同じ挙動に安定するでしょう。

この時点で、React Nativeアプリを実行できるはずです。

TypeScriptテストインフラの追加

React NativeにはJestが同梱されているため、TypeScriptでReact Nativeアプリをテストするには、devDependenciests-jestを追加します。

yarn add --dev ts-jest

次に、package.jsonを開き、jestフィールドを以下に置き換えます。

{
"jest": {
"preset": "react-native",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"transform": {
"^.+\\.(js)$": "<rootDir>/node_modules/babel-jest",
"\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"testPathIgnorePatterns": [
"\\.snap$",
"<rootDir>/node_modules/"
],
"cacheDirectory": ".jest/cache"
}
}

これにより、Jestは.tsおよび.tsxファイルをts-jestで実行するように設定されます。

依存関係の型定義ファイルのインストール

TypeScriptで最高の体験を得るには、型チェッカーが依存関係の形状とAPIを理解する必要があります。一部のライブラリは、基盤となるJavaScriptの形状を記述できる.d.tsファイル(型宣言/型定義ファイル)を含むパッケージを公開します。他のライブラリについては、@types/ npmスコープに適切なパッケージを明示的にインストールする必要があります。

たとえば、ここではJest、React、React Native、およびReact Test Rendererの型が必要になります。

yarn add --dev @types/jest @types/react @types/react-native @types/react-test-renderer

これらの宣言ファイルパッケージを_開発_依存関係に保存したのは、これがReact Native _アプリ_であり、開発中にのみこれらの依存関係を使用し、実行時には使用しないためです。ライブラリをNPMに公開する場合、これらの型の依存関係の一部を通常の依存関係として追加する必要があるかもしれません。

.d.tsファイルの入手方法についてはこちらで詳しく読むことができます。

無視するファイルの追加

ソース管理では、.jestフォルダを無視するように設定する必要があります。gitを使用している場合は、.gitignoreファイルにエントリを追加するだけです。

# Jest
#
.jest/

チェックポイントとして、ファイルをバージョン管理にコミットすることを検討してください。

git init
git add .gitignore # import to do this first, to ignore our files
git add .
git commit -am "Initial commit."

コンポーネントの追加

アプリにコンポーネントを追加しましょう。Hello.tsxコンポーネントを作成します。これは教育的なコンポーネントであり、実際にアプリで書くようなものではありませんが、React NativeでTypeScriptを使用する方法を示すための、ある程度複雑なものです。

componentsディレクトリを作成し、以下の例を追加してください。

// components/Hello.tsx
import React from 'react';
import {Button, StyleSheet, Text, View} from 'react-native';

export interface Props {
name: string;
enthusiasmLevel?: number;
}

interface State {
enthusiasmLevel: number;
}

export class Hello extends React.Component<Props, State> {
constructor(props: Props) {
super(props);

if ((props.enthusiasmLevel || 0) <= 0) {
throw new Error(
'You could be a little more enthusiastic. :D',
);
}

this.state = {
enthusiasmLevel: props.enthusiasmLevel || 1,
};
}

onIncrement = () =>
this.setState({
enthusiasmLevel: this.state.enthusiasmLevel + 1,
});
onDecrement = () =>
this.setState({
enthusiasmLevel: this.state.enthusiasmLevel - 1,
});
getExclamationMarks = (numChars: number) =>
Array(numChars + 1).join('!');

render() {
return (
<View style={styles.root}>
<Text style={styles.greeting}>
Hello{' '}
{this.props.name +
this.getExclamationMarks(this.state.enthusiasmLevel)}
</Text>

<View style={styles.buttons}>
<View style={styles.button}>
<Button
title="-"
onPress={this.onDecrement}
accessibilityLabel="decrement"
color="red"
/>
</View>

<View style={styles.button}>
<Button
title="+"
onPress={this.onIncrement}
accessibilityLabel="increment"
color="blue"
/>
</View>
</View>
</View>
);
}
}

// styles
const styles = StyleSheet.create({
root: {
alignItems: 'center',
alignSelf: 'center',
},
buttons: {
flexDirection: 'row',
minHeight: 70,
alignItems: 'stretch',
alignSelf: 'center',
borderWidth: 5,
},
button: {
flex: 1,
paddingVertical: 0,
},
greeting: {
color: '#999',
fontWeight: 'bold',
},
});

おお!たくさんありますが、分解してみましょう。

  • divspanh1などのHTML要素をレンダリングする代わりに、ViewButtonなどのコンポーネントをレンダリングしています。これらは、異なるプラットフォームで動作するネイティブコンポーネントです。
  • スタイリングは、React Nativeが提供するStyleSheet.create関数を使用して指定されます。Reactのスタイルシートを使用すると、Flexboxを使用してレイアウトを制御したり、CSSの他の構成要素に似たものを使用してスタイルを設定したりできます。

コンポーネントテストの追加

コンポーネントができたので、テストしてみましょう。

テストランナーとしてJestはすでにインストールされています。コンポーネントのスナップショットテストを記述するので、スナップショットテストに必要なアドオンを追加しましょう。

yarn add --dev react-addons-test-utils

それでは、componentsディレクトリに__tests__フォルダを作成し、Hello.tsxのテストを追加しましょう。

// components/__tests__/Hello.tsx
import React from 'react';
import renderer from 'react-test-renderer';

import {Hello} from '../Hello';

it('renders correctly with defaults', () => {
const button = renderer
.create(<Hello name="World" enthusiasmLevel={1} />)
.toJSON();
expect(button).toMatchSnapshot();
});

テストが最初に実行されると、レンダリングされたコンポーネントのスナップショットが作成され、components/__tests__/__snapshots__/Hello.tsx.snapファイルに保存されます。コンポーネントを変更すると、スナップショットを更新し、意図しない変更がないかレビューする必要があります。React Nativeコンポーネントのテストの詳細については、こちらをご覧ください。

次のステップ

公式のReactチュートリアルと状態管理ライブラリReduxをご覧ください。これらのリソースは、React Nativeアプリを作成する際に役立ちます。さらに、Web上のReactとReact Nativeの両方をサポートする、完全にTypeScriptで記述されたコンポーネントライブラリReactXPも参照することをお勧めします。

より型安全なReact Native開発環境で楽しんでください!

React Native用の<InputAccessoryView>の構築

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

動機

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

それ以来、この問題に関して数え切れないほどの「+1」、さまざまな回避策がありましたが、今日までRNへの具体的な変更はゼロでした。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ポータルと同様の概念を使用して達成できます。この設計では、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にbecomeFirstResponderを手動で指示することで、階層はレスポンダーインフラストラクチャによってマウントされます。この概念は、前述のブログ記事で徹底的に説明されています。

落とし穴

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

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

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


遭遇したもう1つの厄介なバグは、iPhone Xのホームピルを回避することでした。あなたは「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 Mobile シニアテクニカルプロダクトマネージャー

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

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

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

AWS Amplify

React Nativeアプリケーションは、Create React Native AppやExpoのようなツールを使って非常に簡単に起動できます。しかし、ユースケースをインフラストラクチャサービスに合わせようとすると、それらをクラウドに接続するのは難しい場合があります。例えば、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)コードをメールまたはSMSで使用するなど、一般的なタスクをどのように支援するかの例を見ることができます。現在サポートされているカテゴリは次のとおりです。

  • Auth: 認証情報の自動化を提供します。すぐに使える実装は、署名にAWS認証情報、Amazon CognitoからのOIDC JWTトークンを使用します。MFA機能などの一般的な機能がサポートされています。
  • Analytics: たった一行のコードで、認証済みまたは未認証のユーザーのトラッキングをAmazon Pinpointで取得できます。必要に応じて、カスタムメトリクスや属性のためにこれを拡張してください。
  • API: AWS Signature Version 4を活用し、安全な方法でRESTful APIとのやり取りを提供します。APIモジュールは、Amazon API Gatewayを使用したサーバーレスインフラストラクチャに最適です。
  • Storage: Amazon S3へのコンテンツのアップロード、ダウンロード、リスト表示を簡素化するコマンドです。ユーザーごとにデータをパブリックまたはプライベートコンテンツに簡単にグループ化することもできます。
  • キャッシング (Caching): ウェブアプリとReact Native全体で、実装固有の永続性を使用するLRUキャッシュインターフェースです。
  • i18nとロギング (i18n and Logging): 国際化とローカライズ機能、およびデバッグとロギング機能を提供します。

Amplifyの優れた点の1つは、特定のプログラミング環境向けに「ベストプラクティス」を設計に組み込んでいることです。たとえば、お客様やReact Native開発者との連携でわかったことの1つは、開発中に素早く動作させるためにとられたショートカットが、本番環境のスタックにも残ってしまうということです。これらはスケーラビリティやセキュリティを損なう可能性があり、インフラストラクチャの再アーキテクチャやコードのリファクタリングを余儀なくされます。

開発者がこれを避けるのを助ける方法の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

モバイルエコシステムに特化した、エンコードされたベストプラクティスのもう一つの例は、パスワードのセキュリティです。デフォルトの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)}/>

前述のとおり、公開またはプライベートストレージオプションを使用して、多くのコンポーネント機能をprops経由で制御できます。ユーザーが特定の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 Elasticsearch、およびAWS 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のデモをご覧いただけます。

フィードバック

ライブラリのチームは、これらのライブラリとサービスがどのように機能するかについて、お客様からのご意見を eagerly 伺いたいと考えています。また、クラウドサービスでの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のpropsを取ります。子要素は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で背景色のようなものをアニメーション化することはできません。少なくとも今のところはできませんが、時間の経過とともにさらに追加していきます。もちろん、プロジェクトに必要なプロパティについてはいつでもプルリクエストを送信して、コミュニティ全体に貢献することができます 😀。

このアニメーションをスムーズにしたいので、これらの制約の中で作業します。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.TextAnimated.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の月例ミーティングは、今も盛況です!次回のセッションについては、この記事の最後のお知らせをぜひチェックしてください。

博覧会

  • Devin AbbottHoussein Djirdehによる「Full Stack React Native」書籍のプレリリース、おめでとうございます!この本では、いくつかの小さなアプリを構築することでReact Nativeを学ぶことができます。
  • ReasonMLを簡単に試せるように、reason-react-native-scriptsの最初の(実験的な)バージョンをリリースしました。
  • 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オプションなど)でブリッジを拡張できるようになることを期待しています。

ウィックス

  • デトックス
    • E2Eテストをスケールさせるため、CIに費やす時間を最小限に抑えたいと考えており、Detoxの並列化サポートに取り組んでいます。
    • E2Eでのモックをより良くサポートするため、カスタムフレーバービルドを有効にするプルリクエストを提出しました。
  • デトックス器具
    • DetoxInstrumentsのキラー機能に取り組むことは非常に困難な作業であることが判明しており、特定の時点でのJavaScriptバックトレースを取得するには、JSスレッドのサスペンションをサポートするためのカスタムJSCore実装が必要です。Wixのアプリでプロファイラーを内部的にテストしたところ、JSスレッドに関する興味深い洞察が得られました。
    • このプロジェクトはまだ一般利用には十分安定していませんが、活発に開発が進められており、近いうちに発表できることを願っています。
  • React Native Navigation
    • V2の開発ペースが大幅に上がりました。これまでは開発者1名が20%の時間で作業していましたが、現在では3名の開発者がフルタイムで取り組んでいます!
  • Androidのパフォーマンス
    • RNにバンドルされている古いJSCoreを最新バージョン(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 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)でのアセット更新機能を維持しつつ、スタンドアロンアプリのビルドに画像やその他の非コードアセットをバンドルするサポートを追加する作業にも取り組んでいます。

フェイスブック

  • RTLサポートの改善
    • 方向を意識したスタイルを多数導入しています。
      • ポジション
        • (left|right) → (start|end)
      • マージン
        • 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で視聴できます。
  • 会議後、ゆっくりとオープンソースのスケジュールに戻っています。言及すべき点の1つは、既存のほとんどの問題を修正する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など、さまざまなオプションが用意されています。

博覧会

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

フェイスブック

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

マイクロソフト

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

次回のセッション

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