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

React Nativeでのより良いリストビュー

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

多くの方が、コミュニティグループでのティザー発表の後、すでに新しいリストコンポーネントの一部を使い始めていると思いますが、本日、正式に発表します!ListViewDataSource、古い行、無視されたバグ、過剰なメモリ消費はもうありません。最新のReact Native 2017年3月リリース候補版(0.43-rc.1)では、新しいコンポーネントスイートの中から、あなたのユースケースに最適なものを選択でき、優れたパフォーマンスと機能セットがすぐに利用できます。

<FlatList>

これはシンプルでパフォーマンスの高いリストのための主力コンポーネントです。データの配列とrenderItem関数を指定すれば、すぐに利用できます。

<FlatList
data={[{title: 'Title Text', key: 'item1'}, ...]}
renderItem={({item}) => <ListItem title={item.title} />}
/>

<SectionList>

データセットを論理的なセクションに分割して表示したい場合、セクションヘッダー(例:アルファベット順のアドレス帳)を付けたり、異種データとレンダリング(例:ボタンがいくつかあるプロフィールビューの後にコンポーザー、次にフォトグリッド、次にフレンドグリッド、最後にストーリーのリストが続くなど)を伴う可能性がある場合、これが最適な方法です。

<SectionList
renderItem={({item}) => <ListItem title={item.title} />}
renderSectionHeader={({section}) => <H1 title={section.key} />}
sections={[ // homogeneous rendering between sections
{data: [...], key: ...},
{data: [...], key: ...},
{data: [...], key: ...},
]}
/>

<SectionList
sections={[ // heterogeneous rendering between sections
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
]}
/>

<VirtualizedList>

より柔軟なAPIを備えた舞台裏の実装です。データが単純な配列ではない場合(イミュータブルなリストなど)に特に便利です。

機能

リストは多くの文脈で使用されるため、新しいコンポーネントには、ほとんどのユースケースをすぐに処理できる機能が満載されています。

  • スクロールによる読み込み(onEndReached)。
  • Pull to refresh(onRefresh / refreshing)。
  • 設定可能な可視性 (VPV) コールバック (onViewableItemsChanged / viewabilityConfig)。
  • 水平モード(horizontal)。
  • インテリジェントなアイテムとセクションのセパレータ。
  • 複数カラムのサポート(numColumns)。
  • scrollToEndscrollToIndex、およびscrollToItem
  • より良いFlowタイピング。

いくつかの注意点

  • コンテンツがレンダーウィンドウの外にスクロールされると、アイテムのサブツリーの内部状態は保持されません。すべてのデータがアイテムデータまたはFlux、Redux、Relayなどの外部ストアにキャプチャされていることを確認してください。

  • これらのコンポーネントはPureComponentに基づいているため、propsがシャロー比較で等しいままであれば再レンダリングされません。renderItem関数が直接依存するすべてのものが、更新後も===ではないプロップとして渡されていることを確認してください。そうでない場合、UIが変更されても更新されない可能性があります。これには、dataプロップと親コンポーネントの状態が含まれます。例えば、

    <FlatList
    data={this.state.data}
    renderItem={({item}) => (
    <MyItem
    item={item}
    onPress={() =>
    this.setState(oldState => ({
    selected: {
    // New instance breaks `===`
    ...oldState.selected, // copy old data
    [item.key]: !oldState.selected[item.key], // toggle
    },
    }))
    }
    selected={
    !!this.state.selected[item.key] // renderItem depends on state
    }
    />
    )}
    selected={
    // Can be any prop that doesn't collide with existing props
    this.state.selected // A change to selected should re-render FlatList
    }
    />
  • メモリを制約し、スムーズなスクロールを可能にするために、コンテンツは画面外で非同期にレンダリングされます。これは、フィルレートよりも速くスクロールすると、一時的に空白のコンテンツが表示される可能性があることを意味します。これは、各アプリケーションのニーズに合わせて調整できるトレードオフであり、舞台裏で改善に取り組んでいます。

  • デフォルトでは、これらの新しいリストは各アイテムのkeyプロップを探し、それをReactのキーとして使用します。代わりに、カスタムのkeyExtractorプロップを提供することもできます。

パフォーマンス

APIの簡素化に加えて、新しいリストコンポーネントはパフォーマンスが大幅に向上しており、主なものは、行数に関係なくメモリ使用量がほぼ一定であることです。これは、レンダリングウィンドウ外の要素をコンポーネント階層から完全にアンマウントし、ReactコンポーネントからのJSメモリと、シャドウツリーとUIビューからのネイティブメモリを再利用することで「仮想化」することによって行われます。これには、内部コンポーネントの状態が保持されないという注意点があるため、重要な状態は、コンポーネント自体以外の場所(例:Relay、Redux、Fluxストアなど)で追跡するようにしてください。

レンダリングウィンドウを制限することで、Reactやネイティブプラットフォームが行う必要がある作業量も削減されます。たとえば、ビューのトラバーサルなどです。たとえ100万個の要素の最後のものをレンダリングしている場合でも、これらの新しいリストを使用すれば、レンダリングのためにそれらすべての要素を繰り返し処理する必要はありません。scrollToIndexを使用すれば、過剰なレンダリングなしに真ん中にジャンプすることもできます。

スケジューリングにもいくつかの改善を加え、アプリケーションの応答性を向上させるはずです。レンダリングウィンドウの端にある項目は、アクティブなジェスチャー、アニメーション、その他のインタラクションが完了した後、低頻度で低い優先度でレンダリングされます。

高度な使い方

ListViewとは異なり、レンダリングウィンドウ内のすべての項目は、プロパティが変更されるたびに再レンダリングされます。通常、これはウィンドウ処理によって項目の数が一定に減少するため問題ありませんが、項目が複雑な場合は、Reactのパフォーマンスに関するベストプラクティスに従い、React.PureComponentおよび/またはshouldComponentUpdateをコンポーネント内で適切に使用して、再帰的なサブツリーの再レンダリングを制限するようにしてください。

行の高さがレンダリングせずに計算できる場合は、getItemLayoutプロパティを提供することで、ユーザーエクスペリエンスを向上させることができます。これにより、scrollToIndexなどで特定の項目にスクロールする際に格段にスムーズになり、コンテンツの高さがレンダリングせずに決定できるため、スクロールインジケータUIが改善されます。

イミュータブルなリストのような代替データ型がある場合は、<VirtualizedList>が最適です。これはgetItemプロップを受け取り、任意のインデックスのアイテムデータを返すことができ、より緩やかなFlowタイピングを持っています。

また、特殊なユースケースがある場合に調整できるパラメーターも多数あります。たとえば、windowSizeを使用してメモリ使用量とユーザーエクスペリエンスのバランスを取ったり、maxToRenderPerBatchを使用してフィルレートと応答性のバランスを調整したり、onEndReachedThresholdを使用してスクロールロードが発生するタイミングを制御したりできます。

今後の作業

  • 既存のサーフェスの移行(最終的にはListViewの非推奨化)。
  • 必要性を見聞きするにつれて、より多くの機能を追加(ぜひお知らせください!)。
  • スティッキーなセクションヘッダーのサポート。
  • さらなるパフォーマンスの最適化。
  • 状態を持つ関数型アイテムコンポーネントのサポート。

idx: 存在関数

·2分で読めます
Timothy Yung
Facebook エンジニアリングマネージャー

Facebookでは、GraphQLで取得したデータ構造の深くネストされた値にアクセスする必要があることがよくあります。これらの深くネストされた値にアクセスする途中では、1つ以上の中間フィールドがnull許容であることが一般的です。これらのP中間フィールドがnullである理由はさまざまで、プライバシーチェックの失敗から、単にnullが致命的でないエラーを表す最も柔軟な方法であるという事実まであります。

残念ながら、これらの深くネストされた値へのアクセスは、現在、面倒で冗長です。

props.user &&
props.user.friends &&
props.user.friends[0] &&
props.user.friends[0].friends;

ECMAScriptでは存在演算子を導入する提案があり、これによって格段に便利になります。しかし、その提案が最終決定されるまでの間、私たちは生活の質を向上させ、既存の言語セマンティクスを維持し、Flowによる型安全性を奨励するソリューションを求めています。

私たちは idx と呼ぶ存在**関数**を考案しました。

idx(props, _ => _.user.friends[0].friends);

このコードスニペットの呼び出しは、上記のコードスニペットのブール式と同様に動作しますが、繰り返しが大幅に少なくなっています。`idx` 関数は正確に2つの引数を取ります。

  • 任意の値。通常は、ネストされた値にアクセスしたいオブジェクトまたは配列。
  • 最初の引数を受け取り、その中のネストされた値にアクセスする関数。

理論的には、idx関数は、nullまたは未定義のプロパティにアクセスした結果として発生するエラーをtry-catchします。そのようなエラーがキャッチされた場合、nullまたは未定義を返します。(そして、これがidx.jsでどのように実装されているかを見ることができます。)

実際には、ネストされたプロパティアクセスごとにtry-catchすることは遅く、特定の種類のTypeErrorを区別することは脆弱です。これらの欠点に対処するために、上記のidx呼び出しを次の式に変換するBabelプラグインを作成しました。

props.user == null
? props.user
: props.user.friends == null
? props.user.friends
: props.user.friends[0] == null
? props.user.friends[0]
: props.user.friends[0].friends;

最後に、idx のカスタム Flow 型宣言を追加しました。これにより、2番目の引数内の走査が適切に型チェックされつつ、ヌル許容プロパティへのネストされたアクセスが許可されます。

関数、Babelプラグイン、およびFlow宣言は、現在GitHubで公開されています。これらは、idxbabel-plugin-idxのnpmパッケージをインストールし、.babelrcファイルのプラグインリストに「idx」を追加することで使用できます。

Create React Native Appの紹介

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

本日、Create React Native Appを発表します。これは、React Nativeプロジェクトを始めるのを格段に簡単にする新しいツールです!これはCreate React Appのデザインから強くインスピレーションを受けており、FacebookExpo(旧Exponent)のコラボレーションの成果です。

多くの開発者は、React Nativeの現在のネイティブビルド依存関係のインストールと設定、特にAndroidで苦労しています。Create React Native Appを使用すると、XcodeやAndroid Studioを使用する必要がなく、LinuxやWindowsを使用してiOSデバイス向けに開発できます。これは、ネイティブコードをコンパイルせずに純粋なJavaScriptで書かれたCRNAプロジェクトを読み込み、実行するExpoアプリを使用して実現されます。

新しいプロジェクトを作成してみてください(yarnがインストールされている場合は、適切なyarnコマンドに置き換えてください)。

$ npm i -g create-react-native-app
$ create-react-native-app my-project
$ cd my-project
$ npm start

これにより、React Nativeのパッケージャが起動し、QRコードが表示されます。Expoアプリで開き、JavaScriptをロードします。console.logへの呼び出しはターミナルに転送されます。標準のReact Native APIだけでなく、Expo SDKも利用できます。

ネイティブコードはどうなりますか?

多くのReact Nativeプロジェクトには、コンパイルする必要があるJavaまたはObjective-C/Swiftの依存関係があります。Expoアプリには、カメラ、ビデオ、連絡先などのAPIが含まれており、Airbnbのreact-native-mapsFacebook認証などの人気ライブラリもバンドルされています。ただし、Expoがバンドルしていないネイティブコードの依存関係が必要な場合は、おそらく独自のビルド設定が必要になるでしょう。Create React Appと同様に、CRNAでも「Eject」がサポートされています。

npm run ejectを実行すると、react-native initが生成するものと非常によく似たプロジェクトが作成されます。その時点で、react-native initで開始した場合と同様にXcodeやAndroid Studioが必要になり、react-native linkでライブラリを追加できるようになり、ネイティブコードのコンパイルプロセスを完全に制御できるようになります。

質問やフィードバックはありますか?

Create React Native Appは一般的に利用できるほど十分に安定しており、皆様からの利用経験のフィードバックを心待ちにしています!私に連絡するにはTwitterか、GitHubリポジトリでIssueを開いてください。プルリクエストも大歓迎です!

Animatedでネイティブドライバーを使用する

·6分で読めます
Janic Duplessis
App & Flow のソフトウェアエンジニア

この1年間、私たちはAnimatedライブラリを使用するアニメーションのパフォーマンス向上に取り組んできました。アニメーションは美しいユーザーエクスペリエンスを生み出す上で非常に重要ですが、正しく実装するのは難しい場合もあります。開発者がコードの一部が遅延を引き起こすことを心配することなく、パフォーマンスの高いアニメーションを簡単に作成できるようにしたいと考えています。

これは何ですか?

Animated APIは、非常に重要な制約、つまりシリアル化可能であることを念頭に置いて設計されました。これは、アニメーションが開始される前にすべてをネイティブに送信できることを意味し、ネイティブコードが各フレームでブリッジを通過することなくUIスレッドでアニメーションを実行できるようにします。これは非常に便利です。なぜなら、アニメーションが開始されると、JSスレッドがブロックされてもアニメーションはスムーズに実行されるからです。実際には、ユーザーコードがJSスレッドで実行され、ReactのレンダリングもJSを長時間ロックする可能性があるため、これはよく起こります。

少し歴史を振り返ると…

このプロジェクトは、およそ1年前、ExpoがAndroidでli.stアプリを構築したときに始まりました。Krzysztof MagieraがAndroidでの初期実装を構築する契約を結びました。それはうまくいき、li.stはAnimatedを使用したネイティブ駆動型アニメーションを搭載した最初のアプリとなりました。数ヶ月後、Brandon WithrowがiOSでの初期実装を構築しました。その後、Ryan Gombaと私は、Animated.eventのサポートなどの不足している機能を追加し、本番アプリで使用しているときに発見したバグを修正しました。これは本当にコミュニティ全体の努力であり、関わったすべての人々と、開発の大部分を後援してくれたExpoに感謝したいと思います。これは現在、React NativeのTouchableコンポーネントや、新しくリリースされたReact Navigationライブラリのナビゲーションアニメーションで使用されています。

仕組み

まず、JSドライバーを使用してアニメーションが現在どのように機能するかをAnimatedで見てみましょう。Animatedを使用すると、実行したいアニメーションを表すノードのグラフを宣言し、次に事前定義されたカーブを使用してAnimated値を更新するドライバーを使用します。Animated.eventを使用して、Animated値をViewのイベントに接続して更新することもできます。

以下に、アニメーションの手順とそれがどこで発生するかを示します。

  • JS: アニメーションドライバーは、requestAnimationFrameを使用して毎フレーム実行され、アニメーションカーブに基づいて計算された新しい値を使用して、駆動する値を更新します。
  • JS: 中間値が計算され、Viewにアタッチされたプロップノードに渡されます。
  • JS: setNativePropsを使用してViewが更新されます。
  • JSからネイティブブリッジ。
  • ネイティブ: UIViewまたはandroid.Viewが更新されます。

ご覧の通り、ほとんどの作業はJSスレッドで行われます。JSスレッドがブロックされると、アニメーションはフレームをスキップします。また、ネイティブビューを更新するために、毎フレームJSからネイティブブリッジを通過する必要があります。

ネイティブドライバーが行うのは、これらのすべてのステップをネイティブに移動することです。Animatedはアニメーションノードのグラフを生成するため、アニメーションが開始するときに一度だけシリアル化してネイティブに送信でき、JSスレッドへのコールバックの必要がなくなります。ネイティブコードは、各フレームでUIスレッドでビューを直接更新することができます。

アニメーション値と補間ノードをシリアライズする方法の例を次に示します(厳密な実装ではなく、単なる例です)。

ネイティブ値ノードを作成します。これがアニメーションされる値です。

NativeAnimatedModule.createNode({
id: 1,
type: 'value',
initialValue: 0,
});

ネイティブ補間ノードを作成します。これは、ネイティブドライバーに値を補間する方法を指示します。

NativeAnimatedModule.createNode({
id: 2,
type: 'interpolation',
inputRange: [0, 10],
outputRange: [10, 0],
extrapolate: 'clamp',
});

ネイティブプロップスノードを作成します。これは、ネイティブドライバーに、接続されているビューのどのプロップを対象とするかを指示します。

NativeAnimatedModule.createNode({
id: 3,
type: 'props',
properties: ['style.opacity'],
});

ノードを接続する

NativeAnimatedModule.connectNodes(1, 2);
NativeAnimatedModule.connectNodes(2, 3);

プロップノードをビューに接続する

NativeAnimatedModule.connectToView(3, ReactNative.findNodeHandle(viewRef));

これで、ネイティブのアニメーションモジュールは、JSに値を計算させることなく、ネイティブビューを直接更新するために必要なすべての情報を持っています。

あとは、実際にアニメーションを開始するだけです。どのようなタイプのアニメーションカーブを使用したいか、どのAnimated値を更新するかを指定します。タイミングアニメーションは、ネイティブ実装を小さくするために、JSでアニメーションのすべてのフレームを事前に計算することで簡素化することもできます。

NativeAnimatedModule.startAnimation({
type: 'timing',
frames: [0, 0.1, 0.2, 0.4, 0.65, ...],
animatedValueId: 1,
});

そして、アニメーション実行時に何が起こるかの内訳を次に示します。

  • ネイティブ: ネイティブアニメーションドライバーは、CADisplayLinkまたはandroid.view.Choreographerを使用して毎フレーム実行され、アニメーションカーブに基づいて計算された新しい値を使用して、駆動する値を更新します。
  • ネイティブ: 中間値が計算され、ネイティブビューにアタッチされたプロップノードに渡されます。
  • ネイティブ: UIViewまたはandroid.Viewが更新されます。

ご覧のとおり、JSスレッドもブリッジも必要なくなり、アニメーションがより速くなります!🎉🎉

アプリでこれを使用するにはどうすればよいですか?

通常のアニメーションの場合、答えは簡単です。アニメーションを開始するときに、アニメーション設定にuseNativeDriver: trueを追加するだけです。

変換前

Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
}).start();

変換後

Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
useNativeDriver: true, // <-- Add this
}).start();

アニメーション値は1つのドライバーとのみ互換性があるため、ある値のアニメーションを開始する際にネイティブドライバーを使用した場合、その値に対するすべてのアニメーションでもネイティブドライバーを使用するようにしてください。

Animated.eventとも連携します。これは、React Nativeの非同期的な性質のため、ネイティブドライバーがないとジェスチャーに1フレーム遅れて実行されるため、スクロール位置に追従するアニメーションがある場合に非常に便利です。

変換前

<ScrollView
scrollEventThrottle={16}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }]
)}
>
{content}
</ScrollView>

変換後

<Animated.ScrollView // <-- Use the Animated ScrollView wrapper
scrollEventThrottle={1} // <-- Use 1 here to make sure no events are ever missed
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }],
{ useNativeDriver: true } // <-- Add this
)}
>
{content}
</Animated.ScrollView>

注意点

現在、AnimatedでできることのすべてがNative Animatedでサポートされているわけではありません。主な制限は、レイアウト以外のプロパティ、つまりtransformopacityのようなものは機能しますが、Flexboxや位置プロパティは機能しないことです。もう1つはAnimated.eventで、ダイレクトイベントのみで機能し、バブリングイベントでは機能しません。つまり、PanResponderでは機能しませんが、ScrollView#onScrollのようなものでは機能します。

Native AnimatedはReact Nativeの一部としてかなり前から存在していましたが、実験的とみなされていたため、文書化されていませんでした。そのため、この機能を使用したい場合は、React Nativeの新しいバージョン(0.40以降)を使用していることを確認してください。

リソース

アニメーションについて詳しく知りたい場合は、Christopher Chedeauによるこの講演をご覧になることをお勧めします。

アニメーションの深掘りと、それらをネイティブにオフロードすることでユーザー体験を向上させる方法については、Krzysztof Magieraによるこの講演もおすすめです。

月次リリースサイクル: 12月と1月のRCをリリース

·3分で読めます
Eric Vicenti
Facebook エンジニア

React Nativeが導入された直後、コミュニティが新しい機能を導入しやすくするために、2週間ごとにリリースを開始しました。同時に、本番環境でのバージョンを安定させました。Facebookでは、本番用iOSアプリのリリースに向けて2週間ごとにコードベースを安定させる必要があったため、オープンソース版も同じペースでリリースすることにしました。現在、Facebookの多くのアプリは週に1回、特にAndroidでリリースされています。毎週マスターからリリースするため、かなり安定した状態を保つ必要があります。したがって、2週間ごとのリリースサイクルはもはや内部貢献者にとってもメリットがありません。

リリース頻度について、コミュニティから追いつくのが難しいというフィードバックをよく耳にします。Expoのようなツールは、バージョンの急速な変更に対応するため、隔週のリリースをスキップせざるを得ませんでした。したがって、隔週のリリースがコミュニティにうまく機能していなかったことは明らかです。

これからは月次リリースへ

新しい月次リリースサイクルと、先月ずっと安定化され採用準備が整った2016年12月リリース、v0.40を発表できることを嬉しく思います。(iOSのネイティブモジュールでヘッダーを更新することを忘れないでください)。

週末を避けたり、予期せぬ問題に対応するために数日ずれることはあるかもしれませんが、これからは各リリースが月の初めに利用可能になり、月末に正式リリースされると期待していただいて構いません。

最善のサポートを得るために当月版を使用する

1月のリリース候補版(RC)が試せるようになっています。新機能はこちらで確認できます。

今後の変更点を確認し、React Nativeの貢献者により良いフィードバックを提供するために、可能であれば常にその月のリリース候補版を使用してください。各バージョンが月末にリリースされる頃には、そのバージョンに含まれる変更は2週間以上本番のFacebookアプリでリリースされています。

新しいreact-native-git-upgradeコマンドを使えば、アプリを簡単にアップグレードできます。

npm install -g react-native-git-upgrade
react-native-git-upgrade 0.41.0-rc.0

このよりシンプルなアプローチによって、コミュニティがReact Nativeの変更を追いやすくなり、新しいバージョンをできるだけ早く導入しやすくなることを願っています!

(この計画を考案したMartin Konicekと、それを実現したMike Grabowskiに感謝します)

Gitによるアップグレードの簡素化

·5分で読めます
Nicolas Cuillery
ZenikaのJavaScriptコンサルタント兼トレーナー

React Nativeの新しいバージョンへのアップグレードは困難でした。以前、このような画面を見たことがあるかもしれません。

これらの選択肢はどれも理想的ではありません。ファイルを上書きするとローカルの変更が失われます。上書きしないと最新のアップデートが適用されません。

本日、この問題を解決する新しいツールを紹介できることを誇りに思います。このツールは react-native-git-upgrade と呼ばれ、裏側でGitを使用して、可能な限り自動的にコンフリクトを解決します。

使用方法

要件:Gitが PATH で利用可能である必要があります。プロジェクトがGitで管理されている必要はありません。

react-native-git-upgrade をグローバルにインストールします。

$ npm install -g react-native-git-upgrade

または、Yarn を使用する場合

$ yarn global add react-native-git-upgrade

その後、プロジェクトディレクトリ内で実行します。

$ cd MyProject
$ react-native-git-upgrade 0.38.0

注:react-nativeの新しいバージョンをインストールするために「npm install」を実行してはいけません。このツールは、古いプロジェクトテンプレートと新しいプロジェクトテンプレートを比較して正しく動作する必要があります。古いバージョンのまま、上記の通りアプリフォルダ内で実行するだけです。

出力例

引数なしで react-native-git-upgrade を実行して、React Nativeの最新バージョンにアップグレードすることもできます。

AndroidとiOSのビルドファイルにおける変更を保持しようと試みるため、アップグレード後に react-native link を実行する必要はありません。

私たちは、可能な限り侵襲性の低い実装を設計しました。それは、一時ディレクトリにその場で作成されるローカルGitリポジトリに完全に依存しています。プロジェクトのリポジトリ(Git、SVN、Mercurialなど、使用しているVCSに関係なく)と干渉することはありません。予期せぬエラーが発生した場合でも、ソースは復元されます。

仕組み

重要なステップはGitパッチの生成です。このパッチには、アプリが使用しているバージョンと新しいバージョンの間でReact Nativeテンプレートに加えられたすべての変更が含まれています。

このパッチを取得するには、node_modulesディレクトリ内のreact-nativeパッケージに埋め込まれたテンプレート(react-native initコマンドが使用するのと同じテンプレート)からアプリを生成する必要があります。そして、現在のバージョンと新しいバージョンの両方でテンプレートからネイティブアプリが生成された後、Gitはプロジェクトに適応したパッチ(つまり、アプリ名を含む)を生成できます。

[...]

diff --git a/ios/MyAwesomeApp/Info.plist b/ios/MyAwesomeApp/Info.plist
index e98ebb0..2fb6a11 100644
--- a/ios/MyAwesomeApp/Info.plist
+++ b/ios/MyAwesomeApp/Info.plist
@@ -45,7 +45,7 @@
<dict>
<key>localhost</key>
<dict>
- <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
+ <key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
[...]

次に、このパッチをソースファイルに適用するだけです。以前のreact-native upgradeプロセスでは、わずかな違いでもプロンプトが表示されましたが、Gitは3者マージアルゴリズムを使用してほとんどの変更を自動的にマージでき、最終的にはおなじみの競合デリミタを残します。

    13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
<<<<<<< ours
CODE_SIGN_IDENTITY = "iPhone Developer";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/HockeySDK.embeddedframework",
"$(PROJECT_DIR)/HockeySDK-iOS/HockeySDK.embeddedframework",
);
=======
CURRENT_PROJECT_VERSION = 1;
>>>>>>> theirs
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-code-push/ios/CodePush/**",
);

これらのコンフリクトは通常、理解しやすいものです。区切り文字 ours は「あなたのチーム」を表し、theirs は「React Nativeチーム」と見なすことができます。

なぜ新しいグローバルパッケージを導入するのか?

React Nativeには、グローバルCLI(react-native-cliパッケージ)が付属しており、コマンドをnode_modules/react-native/local-cliディレクトリに埋め込まれたローカルCLIに委譲します。

前述の通り、このプロセスは現在のReact Nativeバージョンから開始する必要があります。もし実装をlocal-cliに組み込んでいたとしたら、古いバージョンのReact Nativeを使用している場合、この機能を利用できませんでした。例えば、この新しいアップグレードコードが0.38.0でしかリリースされていなかった場合、0.29.2から0.38.0へアップグレードすることはできませんでした。

Gitに基づくアップグレードは、開発者エクスペリエンスの大きな改善であり、すべての人が利用できるようにすることが重要です。別途react-native-git-upgradeパッケージをグローバルにインストールすることで、プロジェクトがどのようなReact Nativeバージョンを使用していても、この新しいコードを今日から使用できます。

もう一つの理由は、Martin Konicekによる最近のYeomanの抹消です。パッチを作成するために古いテンプレートを評価できるよう、これらのYeomanの依存関係をreact-nativeパッケージに戻したくありませんでした。

試してみてフィードバックをお願いします

結論として、この機能を楽しんで、改善を提案したり、問題を報告したり、特にプルリクエストを送ったりすることをためらわないでください。各環境は少しずつ異なり、各React Nativeプロジェクトも異なります。この機能をすべての人にとってうまく機能させるためには、皆様からのフィードバックが必要です。

謝辞

素晴らしい企業であるZenikaM6 Web (アーカイブ)に感謝します。彼らがいなければ、このどれも実現しなかったでしょう!

Buttonの導入、Yarnによる高速インストール、そして公開ロードマップ

·3分で読めます
Héctor Ramos
ヘクター・ラモス
元Facebook デベロッパーアドボケイト

React Nativeでは多くの作業が行われているため、何が起こっているかを把握するのが難しいという声を多くの人から聞いています。現在進行中の作業を伝えるために、React Nativeのロードマップを公開しています。大まかに言うと、この作業は3つの優先事項に分けられます。

  • コアライブラリ。最も有用なコンポーネントとAPIに機能を追加します。
  • 安定性。基盤となるインフラストラクチャを改善し、バグを減らし、コード品質を向上させます。
  • 開発者エクスペリエンス。React Native開発者がより速く作業できるようにします。

ロードマップに価値があると思われる機能の提案がある場合は、Cannyをチェックしてください。Cannyでは、新しい機能を提案したり、既存の提案について議論したりできます。

React Nativeの最新情報

本日リリースされたReact Nativeのバージョン0.37では、どのアプリにもタッチ可能なボタンを簡単に追加できる新しいコアコンポーネントが導入されました。また、新しいパッケージマネージャーYarnのサポートも導入され、アプリの依存関係を更新するプロセス全体が大幅に高速化されるはずです。

ボタンの導入

本日、すべてのプラットフォームで見栄えの良い基本的な<Button />コンポーネントを導入しました。これは、私たちが受ける最も一般的なフィードバックの1つに対処するものです。React Nativeは、すぐに使えるボタンが付属していない唯一のモバイル開発ツールキットの1つです。

Simple Button on Android, iOS

<Button
onPress={onPressMe}
title="Press Me"
accessibilityLabel="Learn more about this Simple Button"
/>

経験豊富なReact Native開発者はボタンの作り方を知っています。iOSのデフォルトの外観にはTouchableOpacityを使用し、AndroidのリプルエフェクトにはTouchableNativeFeedbackを使用し、いくつかのスタイルを適用します。カスタムボタンの構築やインストールは特に難しいことではありませんが、私たちはReact Nativeを非常に簡単に学習できるようにすることを目指しています。基本的なボタンをコアに追加することで、初心者はボタンの書式設定やTouchableのニュアンスを学ぶ時間を費やすことなく、初日から素晴らしいものを開発できるようになります。

ボタンはどのプラットフォームでも優れた動作とネイティブな外観を実現することを意図しているため、カスタムボタンが持つすべての機能はサポートしていません。優れた出発点ですが、既存のすべてのボタンを置き換えることを意図したものではありません。詳細については、実行可能な例を含む新しいボタンのドキュメントを確認してください!

Yarnを使用してreact-native initを高速化する

JavaScriptの新しいパッケージマネージャーであるYarnを使用することで、react-native initの速度を大幅に向上させることができます。速度向上を確認するには、yarnをインストールし、react-native-cliを1.2.0にアップグレードしてください。

$ npm install -g react-native-cli

新しいアプリを設定する際に「Using yarn」と表示されるはずです。

Using yarn

簡単なローカルテストでは、react-native init良好なネットワーク環境で約1分で完了しました(npm 3.10.8を使用した場合の約3分と比較して)。Yarnのインストールは任意ですが、強く推奨されます。

謝辞

今回のリリースに貢献してくださった皆様に感謝いたします。完全なリリースノートはGitHubで公開されています。20以上のバグ修正と新機能により、皆様のおかげでReact Nativeは常に改善を続けています。

0.36: Headless JS、Keyboard API、その他

·3分で読めます
Héctor Ramos
ヘクター・ラモス
元Facebook デベロッパーアドボケイト

本日、React Native 0.36 をリリースします。新機能の詳細については、こちらをお読みください。

Headless JS

Headless JSは、アプリがバックグラウンドにある間にJavaScriptでタスクを実行する方法です。たとえば、最新データの同期、プッシュ通知の処理、音楽の再生などに使用できます。現在のところ、Androidでのみ利用可能です。

まず、非同期タスクを専用のファイル(例: SomeTaskName.js)で定義します。

module.exports = async taskData => {
// Perform your task here.
};

次に、AppRegistry にタスクを登録します。

AppRegistry.registerHeadlessTask('SomeTaskName', () =>
require('SomeTaskName'),
);

Headless JSを使用するには、必要な時にサービスを開始できるように、いくつかのネイティブJavaコードを記述する必要があります。詳細については、新しいHeadless JSのドキュメントをご覧ください!

Keyboard API

画面上のキーボードの操作が、Keyboardでより簡単になりました。ネイティブのキーボードイベントをリッスンして、それらに反応できるようになりました。たとえば、アクティブなキーボードを非表示にするには、Keyboard.dismiss()を呼び出すだけです。

import {Keyboard} from 'react-native';

// Hide that keyboard!
Keyboard.dismiss();

アニメーション除算

2つのアニメーション値を加算、乗算、モジュロで結合することは、React Nativeですでにサポートされています。バージョン0.36では、2つのアニメーション値を除算で結合することが可能になりました。計算のためにあるアニメーション値を他のアニメーション値と反転させる必要があるケースがいくつかあります。例としてスケールを反転させる場合(2倍 → 0.5倍)が挙げられます。

const a = Animated.Value(1);
const b = Animated.divide(1, a);

Animated.spring(a, {
toValue: 2,
}).start();

baのスプリングアニメーションに従い、1 / aの値を生成します。

基本的な使用法は次のようになります。

<Animated.View style={{transform: [{scale: a}]}}>
<Animated.Image style={{transform: [{scale: b}]}} />
<Animated.View>

この例では、親の拡大・縮小が相殺されるため、内側の画像はまったく引き伸ばされません。さらに詳しく知りたい場合は、アニメーションガイドをご覧ください。

ダークステータスバー

新しいbarStyle値がStatusBarに追加されました:dark-content。この追加により、AndroidとiOSの両方でbarStyleを使用できるようになりました。動作は以下のようになります。

  • default: プラットフォームのデフォルトを使用します(iOSではライト、Androidではダーク)。
  • light-content: 黒いテキストとアイコンを持つライトステータスバーを使用します。
  • dark-content: 白いテキストとアイコンを持つダークステータスバーを使用します。

その他

上記は0.36で変更された内容のほんの一部です。GitHubのリリースノートで、新機能、バグ修正、破壊的変更の完全なリストを確認してください。

ターミナルで次のコマンドを実行することで、0.36にアップグレードできます。

$ npm install --save react-native@0.36
$ react-native upgrade

React Nativeアプリの右から左へのレイアウトサポート

·8分で読めます
Mengjue (Mandy) Wang
Facebook ソフトウェアエンジニアインターン

アプリをアプリストアに公開した後、次のステップは国際化によってユーザー層を拡大することです。世界中で20か国以上、多数の人々が右横書き (RTL) 言語を使用しています。したがって、彼らのためにアプリがRTLをサポートするようにすることは不可欠です。

React NativeがRTLレイアウトをサポートするように改善されたことをお知らせできることを嬉しく思います。これは本日、react-nativeのマスターブランチで利用可能になり、次のRCであるv0.33.0-rcで利用可能になります。

これには、RNで使われているコアレイアウトエンジンであるcss-layoutやRNのコア実装、そして特定のOSS JSコンポーネントをRTLに対応させるための変更が含まれています。

RTLサポートを本番環境でテストするために、最新バージョンのFacebook Ads Managerアプリ(初のクロスプラットフォーム100%RNアプリ)が、iOSAndroidの両方でアラビア語とヘブライ語のRTLレイアウトで利用可能になりました。これらのRTL言語での表示は次のとおりです。

RNにおけるRTLサポートの変更点概要

css-layoutには、レイアウトのstartendという概念が既にあります。左から右 (LTR) レイアウトでは、startleftを意味し、endrightを意味します。しかし、RTLでは、startrightを意味し、endleftを意味します。これは、RNがpositionpaddingmarginを含む正しいレイアウトを計算するために、startendの計算に依存できることを意味します。

さらに、css-layoutは、各コンポーネントの方向が親から継承されるように設計されています。つまり、ルートコンポーネントの方向をRTLに設定するだけで、アプリ全体が反転します。

以下の図は、変更点の概要を示しています。

これらには以下のものが含まれます。

このアップデートにより、アプリでRTLレイアウトを許可すると、

  • すべてのコンポーネントのレイアウトが水平方向に反転します
  • RTL対応のOSSコンポーネントを使用している場合、一部のジェスチャーやアニメーションも自動的にRTLレイアウトになります
  • アプリを完全にRTL対応にするために、最小限の追加作業が必要になる場合があります

アプリをRTL対応にする

  1. RTLをサポートするには、まずRTL言語のバンドルをアプリに追加する必要があります。

    • iOSおよびAndroidからの一般的なガイドを参照してください。
  2. ネイティブコードの冒頭でallowRTL()関数を呼び出すことで、アプリのRTLレイアウトを許可します。このユーティリティは、アプリの準備ができたときにのみRTLレイアウトを適用するために提供されています。以下に例を示します。

    iOS

    // in AppDelegate.m
    [[RCTI18nUtil sharedInstance] allowRTL:YES];

    Android

    // in MainActivity.java
    I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
    sharedI18nUtilInstance.allowRTL(context, true);
  3. Androidの場合、AndroidManifest.xmlファイル内の<application>要素にandroid:supportsRtl="true"を追加する必要があります。

これで、アプリを再コンパイルし、デバイスの言語をRTL言語(例:アラビア語やヘブライ語)に変更すると、アプリのレイアウトは自動的にRTLに変わるはずです。

RTL対応コンポーネントの作成

一般的に、ほとんどのコンポーネントはすでにRTL対応です。例えば、

  • 左横書きレイアウト
  • 右横書きレイアウト

ただし、注意すべきいくつかのケースがあり、それらにはI18nManagerが必要になります。I18nManagerには、アプリのレイアウトがRTLかどうかを示す定数isRTLがあり、レイアウトに応じて必要な変更を加えることができます。

方向性を持つアイコン

コンポーネントにアイコンや画像がある場合、RNはソース画像を反転させないため、LTRレイアウトとRTLレイアウトで同じように表示されます。したがって、レイアウトスタイルに応じてこれらを反転させる必要があります。

  • 左横書きレイアウト
  • 右横書きレイアウト

方向に応じてアイコンを反転させる方法は2つあります。

  • 画像コンポーネントにtransformスタイルを追加する

    <Image
    source={...}
    style={{transform: [{scaleX: I18nManager.isRTL ? -1 : 1}]}}
    />
  • または、方向に応じて画像ソースを変更する

    let imageSource = require('./back.png');
    if (I18nManager.isRTL) {
    imageSource = require('./forward.png');
    }
    return <Image source={imageSource} />;

ジェスチャーとアニメーション

AndroidとiOSの開発では、RTLレイアウトに変更すると、ジェスチャーとアニメーションはLTRレイアウトと反対になります。現在、RNでは、ジェスチャーとアニメーションはRNコアコードレベルではなく、コンポーネントレベルでサポートされています。良いニュースは、SwipeableRowNavigationExperimentalなどの一部のコンポーネントはすでにRTLをサポートしているということです。しかし、ジェスチャーを持つ他のコンポーネントは、RTLを手動でサポートする必要があります。

ジェスチャーのRTLサポートを説明する良い例がSwipeableRowです。

ジェスチャーの例
// SwipeableRow.js
_isSwipingExcessivelyRightFromClosedPosition(gestureState: Object): boolean {
// ...
const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
return (
this._isSwipingRightFromClosed(gestureState) &&
gestureStateDx > RIGHT_SWIPE_THRESHOLD
);
},
アニメーションの例
// SwipeableRow.js
_animateBounceBack(duration: number): void {
// ...
const swipeBounceBackDistance = IS_RTL ?
-RIGHT_SWIPE_BOUNCE_BACK_DISTANCE :
RIGHT_SWIPE_BOUNCE_BACK_DISTANCE;
this._animateTo(
-swipeBounceBackDistance,
duration,
this._animateToClosedPositionDuringBounce,
);
},

RTL対応アプリのメンテナンス

最初のRTL対応アプリリリース後も、新しい機能を繰り返し開発する必要があるでしょう。開発効率を向上させるために、I18nManagerは、テストデバイスの言語を変更することなく高速にRTLテストを行うためのforceRTL()関数を提供しています。アプリにこのための簡単なスイッチを用意すると良いでしょう。RNTesterのRTL例からの例を次に示します。

<RNTesterBlock title={'Quickly Test RTL Layout'}>
<View style={styles.flexDirectionRow}>
<Text style={styles.switchRowTextView}>forceRTL</Text>
<View style={styles.switchRowSwitchView}>
<Switch
onValueChange={this._onDirectionChange}
style={styles.rightAlignStyle}
value={this.state.isRTL}
/>
</View>
</View>
</RNTesterBlock>;

_onDirectionChange = () => {
I18nManager.forceRTL(!this.state.isRTL);
this.setState({isRTL: !this.state.isRTL});
Alert.alert(
'Reload this page',
'Please reload this page to change the UI direction! ' +
'All examples in this app will be affected. ' +
'Check them out to see what they look like in RTL layout.',
);
};

新機能の開発中は、このボタンを簡単に切り替えてアプリをリロードすることで、RTLレイアウトを確認できます。これにより、テストのために言語設定を変更する必要がなくなりますが、次のセクションで説明するように、一部のテキストの配置は変更されません。したがって、リリース前にRTL言語でアプリをテストすることは常に良いアイデアです。

制限事項と今後の計画

RTLサポートはアプリのUXのほとんどをカバーするはずですが、現時点ではいくつかの制限があります。

  • テキストの配置の挙動がAndroidとiOSで異なる
    • iOSでは、デフォルトのテキスト配置はアクティブな言語バンドルに依存し、常に片側に揃えられます。Androidでは、デフォルトのテキスト配置はテキストコンテンツの言語に依存します。つまり、英語は左揃えになり、アラビア語は右揃えになります。
    • 理論的には、これはプラットフォーム間で一貫性を保つべきですが、アプリを使用する人によっては、一方の挙動を好む場合があります。テキスト配置のベストプラクティスを見つけるためには、さらなるユーザーエクスペリエンス調査が必要かもしれません。
  • 「真の」左/右が存在しない

    以前に議論したように、JS側のleft/rightスタイルをstart/endにマッピングします。RTLレイアウトの場合、コード内のすべてのleftは画面上で「right」になり、コード内のrightは画面上で「left」になります。これは、製品コードをそれほど変更する必要がないため便利ですが、コード内で「真の左」または「真の右」を指定する方法がないことを意味します。将来的には、言語に関係なくコンポーネントがその方向を制御できる必要があるかもしれません。

  • ジェスチャーとアニメーションのRTLサポートをより開発者フレンドリーにする

    現在、ジェスチャーとアニメーションをRTL互換にするには、まだプログラミングの労力が必要です。将来的には、ジェスチャーとアニメーションのRTLサポートをより開発者フレンドリーにする方法を見つけることが理想的です。

試してみましょう!

RTLサポートの詳細については、RNTesterRTLExampleを確認し、ご意見をお聞かせください!

最後までお読みいただきありがとうございます!React NativeのRTLサポートが、国際的なオーディエンス向けにアプリを成長させる助けとなることを願っています!