この記事は #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
関連状態はさらに複雑になります。 - また、ショッピング カート、カテゴリ、プレイリストなど、他の関連する状態も引き続き追加していきます。
すべての状態をreducer
1 つのファイルで管理すると、プロジェクトが大きくなるにつれて必然的にコードが肥大化して保守が困難になります。
したがって、次のように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
が提供されています。combineReducers
reducer
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 開発では、データの不変性を常に強調しています。
- それがクラスコンポーネント内であっても
state
、redux
管理内であっても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 の共通 API
一般的に使用される 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 プロジェクトをフォローする際に、実践的な観点からデータ管理計画を設計する方法をもう一度説明します。