【Reactシリーズ】Redux (3) 状態の管理方法

この記事は #React チュートリアル シリーズからのものです: https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg5MDAzNzkwNA==&action=getalbum&album_id=1566025152667107329 )

1.減速機分割

1.1. リデューサーコードの分割

現在あるものを見てみましょうreducer:

function reducer(state = initialState, action) {
    
    
  switch (action.type) {
    
    
    case ADD_NUMBER:
      return {
    
     ...state, counter: state.counter + action.num };
    case SUB_NUMBER:
      return {
    
     ...state, counter: state.counter - action.num };
    case CHANGE_BANNER:
      return {
    
     ...state, banners: action.banners };
    case CHANGE_RECOMMEND:
      return {
    
     ...state, recommends: action.recommends };
    default:
      return state;
  }
}
  • 現在、これにはページを処理するためのreducer処理counterコードとデータの両方が含まれています。home
  • 後続のcounter関連状態またはhome関連状態はさらに複雑になります。
  • また、ショッピング カート、カテゴリ、プレイリストなど、他の関連する状態も引き続き追加していきます。

すべての状態をreducer1 つのファイルで管理すると、プロジェクトが大きくなるにつれて必然的にコードが肥大化して保守が困難になります。

したがって、次のようにreducer分割できます。

counterまず、処理のペアを抽出しましょうreducer

// counter相关的状态
const initialCounter = {
    
    
  counter: 0
}

function counterReducer(state = initialCounter, action) {
    
    
  switch (action.type) {
    
    
    case ADD_NUMBER:
      return {
    
     ...state, counter: state.counter + action.num };
    case SUB_NUMBER:
      return {
    
     ...state, counter: state.counter - action.num };
    default:
      return state;
  }
}

別のhome処理ペアを抽出しますreducer

// home相关的状态
const initialHome = {
    
    
  banners: [],
  recommends: []
}

function homeReducer(state = initialHome, action) {
    
    
  switch (action.type) {
    
    
    case CHANGE_BANNER:
      return {
    
     ...state, banners: action.banners };
    case CHANGE_RECOMMEND:
      return {
    
     ...state, recommends: action.recommends };
    default:
      return state;
  }
}

それらが組み合わされたらどうなるでしょうか?

const initialState = {
    
    
}

function reducer(state = initialState, action) {
    
    
  return {
    
    
    counterInfo: counterReducer(state.counterInfo, action),
    homeInfo: homeReducer(state.homeInfo, action),
  }
}

1.2. リデューサファイルの分割

現在、さまざまなステータス処理をさまざまなリデューサーに分割しています。

  • これらは別の関数に配置されていますが、これらの関数の処理は依然として同じファイル内にあり、コードは非常に混乱しています。
  • さらに、reducerで使用されているetc はconstant、action依然として同じファイル内にあります。

そこで次に、ファイル構造を再度分割します。

./store
├── counter
│   ├── actioncreators.js
│   ├── constants.js
│   ├── index.js
│   └── reducer.js
├── home
│   ├── actioncreators.js
│   ├── constants.js
│   ├── index.js
│   └── reducer.js
├── index.js
├── reducer.js
└── saga.js

ここではコードは示されなくなり、各ファイルに格納されている内容のみが示されます。

  • home/actioncreators.js: ストレージhome関連action
  • home/constants.js:home関連する定数を保存します。
  • home/reducer.js: 分離されたreducerコードを保存します。
  • index.js: 外部に公開されるコンテンツを統一します。

1.3. 結合リデューサー

現在のマージ方法では、reducer関数自体を呼び出すたびに新しいオブジェクトを返します。

import {
    
     reducer as counterReducer } from './counter';
import {
    
     reducer as homeReducer } from './home';

const initialState = {
    
    
}

function reducer(state = initialState, action) {
    
    
  return {
    
    
    counterInfo: counterReducer(state.counterInfo, action),
    homeInfo: homeReducer(state.homeInfo, action),
  }
}

実際、複数を簡単にマージできる関数reduxが提供されています。combineReducersreducer

import {
    
     combineReducers } from 'redux';

import {
    
     reducer as counterReducer } from './counter';
import {
    
     reducer as homeReducer } from './home';

const reducer = combineReducers({
    
    
  counterInfo: counterReducer,
  homeInfo: homeReducer
})

export default reducer;

では、combineReducersそれはどのように達成されるのでしょうか?

  • 実際、これは、渡した関数をreducersオブジェクトにマージし、最終的にcombination関数 (前の関数と同等reducer) を返すことでもあります。
  • 関数の実行中に、combination前後に返されたデータが同じであるかどうかを判断して、前のデータを返すstateか新しいデータを返すかを決定しますstate
  • 新しいものはstateサブスクライバの対応するリフレッシュをトリガーしますが、古いものはstateサブスクライバのリフレッシュを効果的に防ぐことができます。
export default function combineReducers(reducers) {
    
    
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {
    
    }
  for (let i = 0; i < reducerKeys.length; i++) {
    
    
    const key = reducerKeys[i]

    if (process.env.NODE_ENV !== 'production') {
    
    
      if (typeof reducers[key] === 'undefined') {
    
    
        warning(`No reducer provided for key "${
      
      key}"`)
      }
    }

    if (typeof reducers[key] === 'function') {
    
    
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  // This is used to make sure we don't warn about the same
  // keys multiple times.
  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    
    
    unexpectedKeyCache = {
    
    }
  }

  let shapeAssertionError
  try {
    
    
    assertReducerShape(finalReducers)
  } catch (e) {
    
    
    shapeAssertionError = e
  }

  return function combination(state = {
     
     }, action) {
    
    
    if (shapeAssertionError) {
    
    
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
    
    
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
    
    
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState = {
    
    }
    for (let i = 0; i < finalReducerKeys.length; i++) {
    
    
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
    
    
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}

二.不変JS

2.1. データのばらつきの問題

React 開発では、データの不変性を常に強調しています。

  • それがクラスコンポーネント内であってもstateredux管理内であってもstate
  • 実際、JavaScript コーディング プロセス全体において、データの不変性は非常に重要です。

データのばらつきによって生じる問題:

const obj = {
    
    
  name: "why",
  age: 18
}

console.log(obj); // {name: "why", age: 18}
const obj2 = obj;
obj2.name = "kobe";
console.log(obj); // {name: "kobe", age: 18}
  • 明らかに私たちはそれを変更したのではなくobj、ただ変更しただけですobj2が、最終的にはobj私たちが変更したのです。
  • 理由は非常に単純で、オブジェクトは参照型であり、同じメモリ空間を指し、両方の参照を自由に変更できるためです。

上記の問題を解決する方法はありますか?

  • object:Object.assignまたはスプレッド演算子をコピーするだけです
console.log(obj); // {name: "why", age: 18}
const obj2 = {
    
    ...obj};
obj2.name = "kobe";
console.log(obj); // {name: "kobe", age: 18}

このようなオブジェクトの浅いコピーに問題はありますか?

  • コードの観点からは問題はなく、実際の開発における潜在的なリスクのいくつかは解決されています。
  • パフォーマンスの観点から見ると、オブジェクトが大きすぎる場合、このコピー方法ではパフォーマンスの問題とメモリの浪費が発生するという問題があります。

これは開発中に行われたのではないかと言う人もいるでしょう。

  • 私は次のような文の方が好きです。「これまでこうだったら、それでいいのですか?」

2.2. ImmutableJS について理解する

Immutable上記の問題を解決するために、オブジェクトの概念が登場しました。

  • Immutableオブジェクトの特性は、オブジェクトが変更される限り新しいオブジェクトが返され、古いオブジェクトは変更されないことですが、
    このメソッドはメモリを無駄にしないでしょうか?

  • メモリを節約するために、次の新しいアルゴリズムが登場しました。Persistent Data Structure(永続的なデータ構造または一貫したデータ構造)。

もちろん、永続性と聞くと、データがローカルまたはデータベースに保存されるのではないかと最初に考えるはずですが、これはここでの意味ではありません。

  • データ構造を使用してデータを保存します。
  • データが変更されるとオブジェクトが返されますが、新しいオブジェクトはメモリを無駄にせずに可能な限り以前のデータ構造を利用します。

これを行う方法?構造の共有:

ここに画像の説明を挿入します

2.3. ImutableJS の共通 A​​PI

一般的に使用される APIについて学びましょうImmutableJS

  • 注: ここでは一部の API のみを紹介します。その他のメソッドについては、公式 Web サイトを参照してください。
const imjs = Immutable;

// 1.定义JavaScript的Array和转换成immutable的List
const friends = [
  {
    
     name: "why", age: 18 },
  {
    
     name: "kobe", age: 30 }
]

// 不会进行深层转换
const imArray1 = imjs.List(friends);
// 会进行深层转换
const imArray2 = imjs.fromJS(friends);
// console.log(imArray1);
// console.log(imArray2);

// 1.定义JavaScript的Object和转换成immutable的Map
const info = {
    
    
  name: "coderwhy",
  age: 18,
  friend: {
    
    
    name: "kobe",
    age: 30
  }
}

const imObj1 = imjs.Map(info);
const imObj2 = imjs.fromJS(info);
// console.log(imObj1);
// console.log(imObj2);

// 3.对immutable操作
// 3.1.添加数据
// 产生一个新的immutable对象
console.log(imArray1.push("aaaa"));
console.log(imArray1.set(2, "aaaa"));
// 原来的是不变的
console.log(imArray1);

// 3.2.修改数据
console.log(imArray1.set(1, "aaaa"));
console.log(imArray2.set(2, imjs.fromJS("bbbb")));

// 3.3.删除数据
console.log(imArray1.delete(0).get(0)); // {name: "kobe", age: 30}

// 3.4.查询数据
console.log(imArray1.get(1));
console.log(imArray2.get(1));
console.log(imArray1.get(1).name);
console.log(imArray2.getIn([1, "name"]));

// 4.转换成JavaScript对象
const array = imArray1.toJS();
const obj = imObj1.toJS();
console.log(array);
console.log(obj);

2.4. ImmutableJS リファクタリング Redux

現在、Redux は研究されすぎており、多くの学生が Redux の難しさを実感しているため、ここでは ImmutableJS と redux を併用する方法については説明しません。

ImmutableJS と Redux の具体的な組み合わせについては、後続のプロジェクト演習で詳しく説明します。

3. Reduxに関するよくある質問

すべての状態をreduxに適用するかどうか

私たちはアプリケーションのステータスを管理するために Redux を学習しましたが、使い方は非常に簡単です (もちろん、学習していることが前提ですが、学習していない場合は注意深く確認してください)。

これまで主に 3 つのステータス管理方法を学習しました。

  • 方法 1:stateコンポーネント内で独自に管理する。
  • 方法 2:Contextデータの共有状況。
  • 方法 3:Reduxアプリケーションのステータスを管理する。

開発中にどのように選択すればよいですか?

  • まず第一に、これに対する標準的な答えはありません。
  • redux一部のユーザーは、追跡と共有が容易になるため、アプリですべてのステータスを管理することを選択します。
  • 一部のユーザーは、コンポーネント内の特定のコンポーネントのステータスを管理することを選択します。
  • 共有や管理のためにトピックやユーザー情報などのデータを入れるユーザーもいますContext
  • 開発者として、状態管理方法を選択することは仕事の 1 つであり、最適なバランスを見つけることができます (自分に合ったバランスを見つけて、それを採用してください)。

redux の作者は次のことを推奨しています。
ここに画像の説明を挿入します

現在のプロジェクトで使用している管理計画state:

  • UI 関連コンポーネント内で維持できる状態は、コンポーネント自体内で維持されます。
  • 状態を共有する必要がある限り、状態は redux によって管理および維持されます。
  • サーバーから要求されたデータ (要求された操作を含む) は、メンテナンスのために redux に渡されます。

もちろん、さまざまな状況に応じて適切な調整が行われますが、NetEase Cloud Music プロジェクトをフォローする際に、実践的な観点からデータ管理計画を設計する方法をもう一度説明します。

おすすめ

転載: blog.csdn.net/lyabc123456/article/details/135350879