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

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

すべてのタグを表示

ホットリローディングの導入

·9分で読めます
Martín Bigio
Instagram ソフトウェアエンジニア

React Nativeの目標は、可能な限り最高の開発者体験を提供することです。その大部分を占めるのが、ファイルを保存してから変更を確認できるまでの時間です。私たちの目標は、アプリが成長してもこのフィードバックループを1秒未満にすることです。

私たちは3つの主要な機能を通じてこの理想に近づきました。

  • 言語としてJavaScriptを使用することで、長いコンパイルサイクルタイムがなくなります。
  • Packagerと呼ばれるツールを実装しました。これはes6/flow/jsxファイルを、VMが理解できる通常のJavaScriptに変換します。これはサーバーとして設計されており、中間状態をメモリに保持することで高速な差分変更を可能にし、複数のコアを使用します。
  • 保存時にアプリをリロードするLive Reloadという機能を構築しました。

現時点では、開発者のボトルネックはアプリのリロードにかかる時間ではなく、アプリの状態を失うことです。一般的なシナリオとして、起動画面から複数の画面離れた機能に取り組む場合があります。リロードするたびに、同じパスを何度もクリックして機能に戻る必要があり、サイクルが数秒長くなってしまいます。

ホットリロード

ホットリロードの背後にある考え方は、アプリを実行し続け、編集したファイルの新しいバージョンを実行時に注入することです。これにより、アプリの状態が失われず、特にUIを微調整している場合に便利です。

動画は百聞に一見に如かずです。Live Reload(現在)とHot Reload(新規)の違いをご覧ください。

よく見ると、レッドボックスから回復できること、また、以前は存在しなかったモジュールをフルリロードなしでインポートし始めることができることにお気づきでしょう。

注意:JavaScript は非常に状態を持つ言語であるため、ホットリロードを完全に実装することはできません。実際には、現在のセットアップは多くの一般的なユースケースでうまく機能しており、何か問題が発生した場合には常に完全なリロードが利用可能であることが分かりました。

ホットリロードは0.22から利用可能です。有効にするには

  • 開発者メニューを開きます
  • 「Enable Hot Reloading」をタップします

実装の概要

なぜそれが必要で、どのように使用するのかを見てきましたが、ここからが楽しい部分です:実際にどのように機能するのか。

ホットリロードは、ホットモジュールリプレースメント (HMR) と呼ばれる機能の上に構築されています。これは webpack によって初めて導入され、React Native Packager 内に実装されました。HMR により、Packager はファイルの変更を監視し、アプリに含まれる薄い HMR ランタイムに HMR 更新を送信します。

要するに、HMRアップデートには変更されたJSモジュールの新しいコードが含まれています。ランタイムがそれらを受け取ると、古いモジュールのコードを新しいものに置き換えます。

HMR 更新には、変更したいモジュールのコードだけでなく、もう少し多くのものが含まれています。なぜなら、単に置き換えるだけではランタイムが変更を認識するのに十分ではないからです。問題は、モジュールシステムがすでに更新したいモジュールの_エクスポート_をキャッシュしている可能性があることです。例えば、以下の2つのモジュールで構成されるアプリがあるとします。

// log.js
function log(message) {
const time = require('./time');
console.log(`[${time()}] ${message}`);
}

module.exports = log;
// time.js
function time() {
return new Date().getTime();
}

module.exports = time;

モジュールlogは、モジュールtimeから提供される現在の日付を含んだメッセージを出力します。

アプリがバンドルされると、React Nativeは各モジュールを__d関数を使用してモジュールシステムに登録します。このアプリでは、多くの__d定義の中にlogのためのものがあります。

__d('log', function() {
... // module's code
});

この呼び出しは、各モジュールのコードを一般的にファクトリ関数と呼ばれる匿名関数でラップします。モジュールシステムランタイムは、各モジュールのファクトリ関数、それがすでに実行されたかどうか、およびその実行結果(エクスポート)を追跡します。モジュールが要求されると、モジュールシステムはすでにキャッシュされたエクスポートを提供するか、モジュールファクトリ関数を初めて実行して結果を保存します。

では、アプリを起動して`log`を要求したとしましょう。この時点では、`log`も`time`のファクトリ関数も実行されていないため、エクスポートはキャッシュされていません。その後、ユーザーが`time`を`MM/DD`形式で日付を返すように変更します。

// time.js
function bar() {
const date = new Date();
return `${date.getMonth() + 1}/${date.getDate()}`;
}

module.exports = bar;

Packagerはtimeの新しいコードをランタイムに送信し(ステップ1)、最終的にlogがrequireされると、エクスポートされた関数が実行され、timeの変更が反映されます(ステップ2)。

次に、logのコードがトップレベルのrequireとしてtimeを必要とするとします。

const time = require('./time'); // top level require

// log.js
function log(message) {
console.log(`[${time()}] ${message}`);
}

module.exports = log;

`log`が要求されると、ランタイムは`log`と`time`のエクスポートをキャッシュします(ステップ1)。次に、`time`が変更されても、HMRプロセスは単に`time`のコードを置き換えた後では終了できません。もしそうすると、`log`が実行されたときに、`time`のキャッシュされたコピー(古いコード)で実行されてしまいます。

`log`が`time`の変更を認識するには、依存するモジュールの1つがホットスワップされたため、キャッシュされたエクスポートをクリアする必要があります(ステップ3)。最後に、`log`が再度要求されると、そのファクトリ関数が実行され、`time`が要求されて新しいコードが取得されます。

HMR API

React Native の HMR は、`hot` オブジェクトを導入することでモジュールシステムを拡張します。この API は webpack のものに基づいています。`hot` オブジェクトは `accept` と呼ばれる関数を公開しており、モジュールがホットスワップされるときに実行されるコールバックを定義できます。たとえば、`time` のコードを次のように変更すると、`time` を保存するたびにコンソールに「time changed」と表示されます。

// time.js
function time() {
... // new code
}

module.hot.accept(() => {
console.log('time changed');
});

module.exports = time;

このAPIを手動で使用する必要があるのはまれなケースのみであることに注意してください。ホットリロードは、最も一般的なユースケースではそのまま機能するはずです。

HMRランタイム

これまで見てきたように、ホットスワップされているモジュールを使用するモジュールがすでに実行され、そのインポートがキャッシュされている場合があるため、HMR の更新を受け入れるだけでは不十分な場合があります。例えば、映画アプリの例の依存関係ツリーに、`MovieSearch` と `MovieScreen` ビューに依存するトップレベルの `MovieRouter` があり、これらが以前の例の `log` と `time` モジュールに依存していたとします。

ユーザーがムービー検索ビューにアクセスしたが、他のビューにはアクセスしなかった場合、`MovieScreen` を除くすべてのモジュールがエクスポートをキャッシュしています。`time` モジュールに変更が加えられた場合、ランタイムは `log` のエクスポートをクリアして、`time` の変更を認識させる必要があります。プロセスはそこで終わりません。ランタイムは、すべての親が受け入れられるまで、このプロセスを再帰的に繰り返します。つまり、`log` に依存するモジュールを取得し、それらを受け入れようとします。`MovieScreen` はまだ要求されていないため、スキップできます。`MovieSearch` の場合、そのエクスポートをクリアし、その親を再帰的に処理する必要があります。最後に、`MovieRouter` に対して同じことを行い、`MovieRouter` に依存するモジュールがないため、そこで終了します。

依存関係ツリーをたどるために、ランタイムはHMRアップデートでPackagerから逆依存関係ツリーを受け取ります。この例では、ランタイムは次のようなJSONオブジェクトを受け取ります。

{
modules: [
{
name: 'time',
code: /* time's new code */
}
],
inverseDependencies: {
MovieRouter: [],
MovieScreen: ['MovieRouter'],
MovieSearch: ['MovieRouter'],
log: ['MovieScreen', 'MovieSearch'],
time: ['log'],
}
}

Reactコンポーネント

React コンポーネントは、ホットリロードで動作させるのが少し難しいです。問題は、コンポーネントの状態を失ってしまうため、単に古いコードを新しいコードに置き換えることができないことです。React Web アプリケーションの場合、Dan Abramov は、webpack の HMR API を使用してこの問題を解決する Babel の トランスフォーム を実装しました。要するに、彼の解決策は、_トランスフォーム時_にすべての React コンポーネントのプロキシを作成することで機能します。プロキシはコンポーネントの状態を保持し、ライフサイクルメソッドを実際のコンポーネントに委譲します。これがホットリロードされるものです。

プロキシコンポーネントを作成するだけでなく、このtransformはReactにコンポーネントの再レンダリングを強制するコードでaccept関数も定義します。これにより、アプリの状態を失うことなくレンダリングコードをホットリロードできます。

React Native に付属するデフォルトのトランスフォーマーは、`babel-preset-react-native` を使用しています。これは、webpack を使用する React Web プロジェクトで `react-transform` を使用するのと同じ方法で `react-transform` を使用するように設定されています。

Reduxストア

Reduxストアでホットリロードを有効にするには、webpackを使用するWebプロジェクトで行うのと同様にHMR APIを使用するだけです。

// configureStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducer from '../reducers';

export default function configureStore(initialState) {
const store = createStore(
reducer,
initialState,
applyMiddleware(thunk),
);

if (module.hot) {
module.hot.accept(() => {
const nextRootReducer = require('../reducers/index').default;
store.replaceReducer(nextRootReducer);
});
}

return store;
};

レデューサーを変更すると、そのレデューサーを受け入れるコードがクライアントに送信されます。するとクライアントは、レデューサーが自分自身を受け入れる方法を知らないことに気づき、参照しているすべてのモジュールを検索して、それらを受け入れようとします。最終的に、フローは単一のストアである `configureStore` モジュールに到達し、これが HMR の更新を受け入れます。

結論

ホットリロードの改善に興味がある方は、Dan Abramov 氏のホットリロードの将来に関する記事を読んで貢献することを強くお勧めします。例えば、Johny Days は複数の接続されたクライアントで動作するようにする予定です。この機能の維持と改善は皆さんに頼っています。

React Nativeを使えば、アプリの構築方法を再考し、素晴らしい開発者体験を実現する機会があります。ホットリロードはパズルのピースの一つにすぎません。それをより良くするために、他にどんなクレイジーなハックができるでしょうか?

React Nativeアプリをアクセシブルにする

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

React for Web と React Native for Mobile の最近のリリースにより、私たちは開発者が製品を構築するための新しいフロントエンドフレームワークを提供しました。堅牢な製品を構築する上で重要な側面の一つは、視覚障害やその他の障害を持つ人々を含む、誰もがそれを使用できることを保証することです。React と React Native のアクセシビリティ API を使用すると、React を利用したあらゆるエクスペリエンスを、視覚障害者向けのスクリーンリーダーなどの支援技術を使用する人が利用できるようにすることができます。

この投稿では、React Native アプリケーションに焦点を当てます。React アクセシビリティ API は、Android および iOS API と同様のルック・アンド・フィールを持つように設計されています。以前に Android、iOS、または Web 用のアクセシブルなアプリケーションを開発した経験がある方なら、React AX API のフレームワークと用語に慣れているはずです。たとえば、UI 要素を_アクセシブル_に設定(したがって支援技術に公開)し、_accessibilityLabel_ を使用して要素の文字列による説明を提供できます。

<View accessible={true} accessibilityLabel=”This is simple view”>

Facebook自身のReact製プロダクトの一つである広告マネージャアプリを例に、React AX APIのもう少し踏み込んだ応用を見ていきましょう。

これは抜粋です。記事の全文はFacebook Codeでご覧いただけます。