React Nativeプロジェクトフレームワークの構築経験

React Nativeプロジェクトフレームワークの構築経験

React Native は、2015 年 4 月に Facebook によってオープンソース化されたクロスプラットフォームのモバイル アプリケーション開発フレームワークです。わずか 1 ~ 2 年で、多くの企業がこのフレームワークをサポートし、自社のモバイル アプリケーションの構築に採用しました。
React Native を使用すると、Javascript と React に基づいた完全に一貫した開発エクスペリエンスを実現し、世界クラスのネイティブ アプリを構築できます。

プロジェクトの枠組みとプロジェクト構造

1. プロジェクトで使用されるテクノロジースタック

react native、react hook、typescript、immer、tslint、jest など。

どれもごく一般的なものなので、詳しくは紹介しません。

2. データ処理では、ReactフックでuseContext+useReducerを使用します。

この考え方は redux と一致しており、比較的簡単に使用でき、それほど複雑でないビジネス シナリオに適しています。

const HomeContext = createContext<IContext>({
  状態: デフォルト状態、
  ディスパッチ: () => {}
});
const ContextProvider = ({ urlQuery, pageCode }: IProps) => {
  const initState = getInitState(urlQuery, pageCode);
  const [状態、ディスパッチ]: [IHomeState、IDispatch] = useReducer(homeReducer、initState);

  戻る (
    <HomeContext.Provider 値 = {{ 状態、ディスパッチ }}>
      <ホームコンテナ />
    </ホームコンテキスト.プロバイダー>
  );
};
const ホームコンテナ = () => {
const { ディスパッチ、状態 } = useContext(HomeContext);
...

3. プロジェクトの構造は以下のとおりです

|-ページ1
    |-handler // ロジックを処理するための純粋な関数。UT でカバーする必要があります|-container // データ、動作、コンポーネントを統合します|-component // 純粋な UI コンポーネント。コンテンツとユーザー インタラクションを表示しますが、ビジネス ロジックは処理しません|-store // データ構造は 3 層を超えることはできません。また、外部参照と冗長フィールドによってレベルを減らすことができます|-reducer // immer を使用して新しいデータ (不変データ) を返します
    |-...
|-ページ2
|-...

プロジェクトの仕様

1. ページ

プロジェクト全体は複数ページのアプリケーションであり、最も基本的な分割単位はページです。

各ページには対応するストアがあり、プロジェクト全体で 1 つのストアが使用されるわけではありません。その理由は次のとおりです。

  • 各ページのロジックは比較的独立している
  • 各ページはプロジェクトエントリとして使用できます
  • RN ページ ライフ サイクルを組み合わせてデータを処理する (データの初期化、キャッシュなどの問題を回避)

各ページの外部操作はページコンポーネントで定義されます

  • ページジャンプロジック
  • ロールバック後に処理されるイベント
  • どのストレージデータを操作する必要がありますか?
  • どのようなサービスを依頼する必要があるかなど。

ページコンポーネントの主な機能

独自のビジネス モジュールに基づいて、抽象化できるすべての外部依存関係と外部相互作用がこのコンポーネントのコードに集中しています。

開発者は、ロジックを記述したりページ間でトラブルシューティングを行ったりするときに、特定のページ + データ ソースに応じて特定のコードを正確に見つけることができるので便利です。

2. リデューサー

以前のプロジェクトでは、リデューサーには、データ処理、ユーザーの動作、ログ ポイント、ページ ジャンプ、その他のコード ロジックが含まれる場合があります。

なぜなら、開発者は、コードを書く過程で、リデューサーが特定の処理ロジックの終点(状態を更新した後、イベントが終了する)であることに気づき、それがこれらのことを実行するのに非常に適しているからです。

プロジェクトのメンテナンスと要件の反復により、リデューサーのサイズは増加し続けます。

組織化が不十分でコード量が膨大であるため、コードを調整するのは困難です。

そのようなプロジェクトを維持するのがあなたにとってどれほど苦痛であるかは想像に難くありません。

これを実現するために、リデューサー内のコードでいくつかの減算が行われました。

  • リデューサー内の状態データのみを変更する
  • immerのproduceを使用して不変データを生成する
  • 冗長な個々のフィールドを変更し、それらを統合し、ページの動作に対応するアクションを列挙する

減速機の主な機能

列挙可能な形式で、ページ内のデータを操作するすべてのシナリオを要約します。

React フレームワークに適した独自の機能に加えて、特定のビジネス ロジック読み取り属性が備わっているため、UI コンポーネントに依存せずにページ上のすべてのデータ処理ロジックを大まかに読み取ることができます。

// ディスパッチを2回実行したり、単一フィールドの更新ケースを過度に定義したりしないようにする
// このロジックを統合すると、ページ上の動作に関連付けられ、理解と読み取りに役立ちます。ケース EFHListAction.updateSpecifyQueryMessage:
    リターンproduce(state, (draft: IFHListState) => {
        draft.specifyQueryMessage = ペイロードを文字列として;
        ドラフト.showSpecifyQueryMessage = true;
    });    
ケース EFHListAction.updateShowSpecifyQueryMessage:
    リターンproduce(state, (draft: IFHListState) => {
        draft.showSpecifyQueryMessage = ペイロードをブール値として;
    });

3. ハンドラー

ここでまず純粋関数の概念を紹介します。

関数の戻り値がパラメータのみに依存し、実行中に副作用がない場合、その関数は純粋関数と呼ばれます。

可能な限り多くのロジックを純粋関数に抽象化し、ハンドラーに配置します。

  • より多くのビジネスロジックをカバー
  • 純粋関数のみ
  • UTのカバー範囲が必要です

ハンドラーの主な機能

データ ソースからストア、コンテナーからコンポーネント、ディスパッチからリデューサーなどのシナリオでの論理処理を担当します。

さまざまなシナリオにおける論理処理関数の保存場所として、ファイル全体はページプロセス上の関連関係を伴いません。各関数は、その入力と出力の使用シナリオを満たす限り再利用でき、主にコンテナファイルで使用されます。

エクスポート関数 getFilterAndSortResult(
  フライトリスト: IFlightInfo[],
  フィルターリスト: IFilterItem[],
  フィルターシェア: ブール値、
  filterOnlyDirect: ブール値、
  ソートタイプ: EFlightSortType
){
  if (!isValidArray(フライトリスト)) {
    戻る [];
  }

  ソート関数は、sortType を引数として受け取ります。
  const result = FlightList.filter(v => doFilter(v, filterList, filterShare, 1, filterOnlyDirect)).sort(sortFn);

  結果を返します。
}
記述(getFilterAndSortResult.name, () => {
  テスト('getFilterAndSortResult', () => {
    getFilterAndSortResult(flightList, filterList, false, EFlightSortType.PriceAsc)).toEqual(filterSortResult) を期待します。
  });
});

4. コンテナ

上記のプロジェクト構造図からわかるように、各ページにはデータ処理の中心となる基本コンテナーがあります。

この基本コンテナの下で、各サブコンテナは異なるモジュールに従って定義されます。

  • ライフサイクル処理(初期化中に実行されるいくつかの非同期操作)
  • レンダリングコンポーネント用のデータソースを提供します
  • ページ内の動作関数を定義する

コンテナの主な機能

プロジェクト全体において、コード量が過剰になったりメンテナンスが困難になったりすることを避けるために、さまざまなデータ、UI、ユーザー動作の収束ポイントを関連モジュールから可能な限り分離する必要があります。

コンテナの定義は、ページに表示されるモジュールによって抽象化する必要があります。たとえば、ヘッド コンテナ、コンテンツ コンテナ、フッター コンテナ、その他の一般的な分割方法などです。

一部のページ内の比較的独立したモジュールも、クーポン提供モジュール、ユーザー フィードバック モジュールなど、関連するロジックを集約するために、対応するコンテナーを生成する必要があります。

行動機能に特に注意する

  • 複数のコンテナに共通する動作は、ベースコンテナに直接配置できます。
  • 上記のアーキテクチャ図のアクション例 (setAction) は、特定のシナリオに応じて適用される別の動作の再利用です。

コードの読み取りに便利、モジュールAのフローティング表示ロジック、モジュールBが使用されるときにモジュールが生成される順序、最初にモジュールA、次にモジュールBはAの方法を使用する必要があります

  • データポイントとユーザー行動ポイントを定義する
  • ページジャンプメソッドの呼び出し (ページ --> ベースコンテナ --> サブコンテナ)
  • 行動のその他の副作用
定数OWFlightListContainer = () => {
    // コンテキストを通じてデータを取得します。const { state, dispatch } = useContext(OWFlightListContext);
    ...

    // 初期読み込み中のタイムアウトのカウントダウン useOnce(overTimeCountDown);
    ...
    
    // ユーザーがソートをクリックする const onPressSort = (lastSortType: EFlightSortType, isTimeSort: boolean) => {
        // ハンドラー内の getNextSortType 関数を参照します。const sortType = getNextSortType(lastSortType, isTimeSort);
        ディスパッチ({ タイプ: EOWFlightListAction.updateSortType、 ペイロード: sortType });
        
        // ポイント埋め込み操作 logSort(state, sortType);
    };
    
    // 表示コンポーネントのレンダリング return <.../>;
}

まとめ

コーディングのしやすさから読みやすさまで
プロジェクト全体を通じて、機能の実現に加えて、プロジェクト担当者による保守を容易にするために、多くの仕様が定義されます。

  • ページコンポーネントにはページ関連の外部依存関係が含まれています
  • Reducerはページデータを操作するすべてのイベントを列挙します
  • ハンドラーはビジネス ロジックの処理を統合し、純粋な関数の実装と UT カバレッジによってプロジェクトの品質を保証します。
  • コンテナ内の動作関数は、ユーザー操作に関連するすべてのイベントを定義し、埋め込まれたデータを記録します。
  • コンポーネント内でのビジネスロジック処理を避け、UI表示のみを実行し、UI自動化ケースを減らし、UTケースを増やす

仕様を定義するのは比較的簡単です。プロジェクトをうまく維持するには、合意に達することを前提にチームメンバーが粘り強く取り組むことが重要です。

実用的な機能をいくつか紹介します

オブジェクトパスに基づいて値を取得する

/**
 * オブジェクトパスに基づいて値を取得します* @param target {a: { b: { c: [1] } } }
 * @param パス 'abc0'
 */
エクスポート関数 getVal(ターゲット: 任意、パス: 文字列、デフォルト値: 任意 = 未定義) {
  ret = ターゲットとします。
  キー: 文字列 | undefined = '';
  pathList を分割します。

  する {
    キー = pathList.shift();
    if (ret && key !== undefined && typeof ret === 'object' && key in ret) {
      ret = ret[キー];
    } それ以外 {
      ret = 未定義;
    }
  } while (pathList.length && ret !== undefined);

  ret === undefined || ret === null ? defaultValue : ret を返します。
}

//デモ
const errorCode = getVal(結果、'rstlist.0.type'、0);

設定情報に従って読み取る

// 外部と接続する場合、いくつかの固定構造とスケーラブルなデータリストが定義されることがよくあります。 // このような契約に適応し、読みやすさとメンテナンス性を向上させるために、次の関数がまとめられています。 export const GLOBAL_NOTE_CONFIG = {
  2: 「払い戻し」、
  3: 'ソートタイプ'、
  4: '機能スイッチ'
};

/**
 * 設定に従って、attrListの値を取得し、jsonオブジェクト型のデータを返します * @private
 * 詳細サービスの@member
 */
エクスポート関数 getNoteValue<T>(
  noteList: Array<T> | 未定義 | null、
  設定: { [_: 文字列]: 文字列 },
  キー名: 文字列 = 'type'
){
  const ret: { [_: 文字列]: T | Array<T> } = {};

  if (!isValidArray(noteList!)) {
    ret を返します。
  }

  //@ts-無視
  noteList.forEach((note: 任意) => {
    const typeStr: string = (('' + note[keyName]) as unknown) as string;

    if (!(config内のtypeStr)) {
      戻る;
    }

    if (note === undefined || note === null) {
      戻る;
    }

    定数キー = config[typeStr];

    // 値が複数ある場合は配列型に変更します if (ret[key] === undefined) {
      ret[キー] = メモ;
    } そうでない場合 (Array.isArray(ret[key])) {
      (ret[key]をT[]として)push(note);
    } それ以外 {
      const first = ret[キー];
      ret[key] = [first, note];
    }
  });

  ret を返します。
}

//デモ
// 外部で定義された拡張可能なノート ノード リストの値ロジックに適用可能 const { sortType, featureSwitch } = getNoteValue(list, GLOBAL_NOTE_CONFIG, 'ntype');


複数条件配列ソート

/**
 * ソートに使用するソート関数を取得します * @param fn 同じ型の要素の比較関数、true はソートの優先順位を意味します */
エクスポート関数 getSort<T>(fn: (a: T, b: T) => boolean): (a: T, b: T) => 1 | -1 | 0 {
  戻り値 (a: T, b: T): 1 | -1 | 0 => {
    ret = 0 とします。

    (fn.call(null、a、b))の場合{
      戻り値 -1;
    } そうでなければ (fn.call(null, b, a)) {
      戻り値 1;
    }

    ret を 0 として返します。
  };
}

/**
 * 複数の並べ替え */
エクスポート関数 getMultipleSort<T>(arr: Array<(a: T, b: T) => 1 | -1 | 0>) {
  戻り値 (a: T, b: T) => {
    tmp にします。
    i = 0 とします。

    する {
      tmp = arr[i++](a, b);
    } while (tmp === 0 && i < arr.length);

    tmp を返します。
  };
}

//デモ
const ageSort = getSort(function(a, b) {
  a.age < b.age を返します。
});

const nameSort = getSort(function(a, b) {
  a.name < b.name を返します。
});

const sexSort = getSort(function(a, b) {
  a.sex && !b.sex; を返します。
});

//判定条件の順序は調整可能 const arr = [nameSort, ageSort, sexSort];

const ret = data.sort(getMultipleSort(arr));

上記は、React Native プロジェクト フレームワークの構築に関する私の経験の一部です。React Native プロジェクト フレームワークの構築の詳細については、123WORDPRESS.COM の他の関連記事をご覧ください。

以下もご興味があるかもしれません:
  • React-Native環境のセットアップと基本的な紹介
  • VSCodeはReact Native環境を構築します
  • Win10+Android+Yoshi Android エミュレータを使用して ReactNative 開発環境を構築する
  • React Native開発環境構築手順
  • React Native環境設定チュートリアル
  • React NativeはiOS開発環境を構築します
  • プロジェクト内のReact NativeコンポーネントをテストするためにJestを使用する方法の詳細な説明

<<:  CentOS で新しいユーザーを作成し、キーログインを有効にする方法

>>:  MySQL 全文あいまい検索 MATCH AGAINST メソッドの例

推薦する

CentOS7.6にMYSQL8.0をインストールする詳細な手順

1. 一般的に、CentOS では mariadb がデフォルトでインストールされているため、まず ...

VS2022 リモート デバッグ ツールの使い方

WeChat 関連サービスをデバッグする場合など、職場のサーバー環境でリモートデバッグを行う必要があ...

Vueはログイン認証コードを実装する

この記事では、ログイン認証コードを実装するためのvueの具体的なコードを例として紹介します。具体的な...

CocosCreator で http と WebSocket を使用する方法

目次1. HTTPGET 2. HTTP POSTウェブソケット4. Egretのhttpとwebs...

JS はシンプルなブロック崩しピンボールゲームを実装します

この記事では、ブロック崩しピンボールゲームを実装するためのJSの具体的なコードを参考までに紹介します...

Ubuntu 16.04 カーネルのアップグレード手順

1. 環境仮想マシン上で実行されているUbuntu 16.04では、コマンドuname -rを使用し...

中国における中国語ドメイン名の人気は新たなクライマックスを迎えた

<br />外交部などの中央政府機関、各レベルの地方政府、その他の国家機関や部門が率先し...

Linuxはシェルスクリプトを使用して履歴ログファイルを定期的に削除します

1. ツールディレクトリのファイル構造 [root@www tools]# ツリーツール/ ツール/...

MySQLデータ遅延ジャンプの問題の解決策

今日は、データベース遅延ジャンプに関する別の典型的な問題を分析しました。このプロセスでは、参考のため...

Linuxでスワップパーティションファイルを作成する方法

スワップの紹介Linux のスワップ (スワップ パーティション) は、Windows の仮想メモリ...

Nginx サービス クイック スタート チュートリアル

目次1. Nginx の紹介1. Nginx とは何ですか? 2. Nginx を使用する理由3. ...

Nginxはhttpリクエスト実装プロセス分析を処理する

Nginxはまず、設定ファイル内のどのserver{}ブロックを処理に使用するかを決定します。次のs...

vue-routerフック関数はルーティングガードを実装します

目次概要グローバルフック関数ルーティング固有のフック関数コンポーネント内のフック関数概要ルートガード...

a タグをクリックして入力ファイルのアップロードダイアログボックスを表示する方法

htmlコードをコピーコードは次のとおりです。 <SPAN class=tag><...

Dockerコンテナ内にkibanaトークナイザーをインストールする方法

ステップ: 1. 仮想マシンディレクトリに新しいdocker-compose.ymlファイルを作成し...