このような疑問は、Reduxライブラリの使用を開始する開発者や、積極的に使用する開発者にとっても発生します。
Reactでの5年間の開発で、BENOVATEは、そのようなアプリケーションのアーキテクチャを構築するためのさまざまなアプローチを実際にテストしました。この記事では、アプリケーションのどこにデータを格納するかを選択するための考えられる基準を検討します。
それともReduxなし?はい、それなしでできるなら。この問題については、ライブラリの作成者の1人であるDan Abramovの記事を読むことができます。Reduxが不可欠であることを開発者が理解している場合、データウェアハウスを選択するためのいくつかの基準があります。
- データの寿命
- 使用頻度
- 状態の変化を追跡する機能
データの寿命
2つのカテゴリがあります。
- 頻繁に変更されるデータ。
- まれに変更されるデータ。このようなデータは、ユーザーがアプリケーションを使用して直接作業している間、またはアプリケーションとのセッション間で変更されることはほとんどありません。
頻繁に変化するデータ
このカテゴリには、たとえば、オブジェクトのリストを操作するコンポーネントのフィルタリング、並べ替え、およびページングオプション、またはアプリケーションに個々のUI要素を表示するフラグ(ドロップダウンリストやモーダルウィンドウなど)が含まれます(バインドされていない場合)。ユーザー設定に)。これには、サーバーに送信されるまで、入力中のフォームのデータを含めることもできます。
そのようなデータをコンポーネントの状態で保存する方が良いです。それらはグローバルストレージを混乱させ、それらとの作業を複雑にします。アクション、レデューサーを記述し、状態を初期化し、時間内にクリアする必要があります。
悪い例
import React from 'react';
import { connect } from 'react-redux';
import { toggleModal } from './actions/simpleAction'
import logo from './logo.svg';
import './App.css';
import Modal from './elements/modal';
const App = ({
openModal,
toggleModal,
}) => {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<main className="Main">
<button onClick={() => toggleModal(true)}>{'Open Modal'}</button>
</main>
<Modal isOpen={openModal} onClose={() => toggleModal(false)} />
</div>
);
}
const mapStateToProps = (state) => {
return {
openModal: state.simple.openModal,
}
}
const mapDispatchToProps = { toggleModal }
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
// src/constants/simpleConstants.js
export const simpleConstants = {
TOGGLE_MODAL: 'SIMPLE_TOGGLE_MODAL',
};
// src/actions/simpleAction.js
import { simpleConstants} from "../constants/simpleConstants";
export const toggleModal = (open) => (
{
type: simpleConstants.TOGGLE_MODAL,
payload: open,
}
);
// src/reducers/simple/simpleReducer.js
import { simpleConstants } from "../../constants/simpleConstants";
const initialState = {
openModal: false,
};
export function simpleReducer(state = initialState, action) {
switch (action.type) {
case simpleConstants.TOGGLE_MODAL:
return {
...state,
openModal: action.payload,
};
default:
return state;
}
}
良い例え
import React, {useState} from 'react';
import logo from './logo.svg';
import './App.css';
import Modal from './elements/modal';
const App = () => {
const [openModal, setOpenModal] = useState(false);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<main className="Main">
<button onClick={() => setOpenModal(true)}>{'Open Modal'}</button>
</main>
<Modal isOpen={openModal} onClose={() => setOpenModal(false)} />
</div>
);
}
export default App;
まれに変化するデータ
これは通常、ページの更新間またはユーザーによるページへの個別の訪問間で変化しないデータです。
Reduxストアはページが更新されるときに再作成されるため、このタイプのデータは、サーバー上のデータベースまたはブラウザーのローカルストアなど、別の場所に保存する必要があります。
これは、ディレクトリまたはカスタム設定からのデータにすることができます。たとえば、ユーザー設定を使用するアプリケーションを開発する場合、ユーザー認証後、これらの設定をReduxストアに保存します。これにより、サーバーにアクセスせずにアプリケーションコンポーネントを使用できます。
ユーザーの介入なしにサーバー上で一部のデータが変更される可能性があることを覚えておく必要があります。アプリケーションがデータにどのように応答するかを考慮する必要があります。
悪い例
// App.js
import React from 'react';
import './App.css';
import Header from './elements/header';
import ProfileEditForm from './elements/profileeditform';
const App = () => {
return (
<div className="App">
<Header />
<main className="Main">
<ProfileEditForm />
</main>
</div>
);
}
export default App;
// src/elements/header.js
import React from "react";
import logo from "../logo.svg";
import Menu from "./menu";
export default () => (
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Menu />
</header>
)
// src/elements/menu.js
import React, {useEffect, useState} from "react";
import { getUserInfo } from '../api';
const Menu = () => {
const [userInfo, setUserInfo] = useState({});
useEffect(() => {
getUserInfo().then(data => {
setUserInfo(data);
});
}, []);
return (
<>
<span>{userInfo.userName}</span>
<nav>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
</ul>
</nav>
</>
)
}
export default Menu;
// src/elements/profileeditform.js
import React, {useEffect, useState} from "react";
import {getUserInfo} from "../api";
const ProfileEditForm = () => {
const [state, setState] = useState({
isLoading: true,
userName: null,
})
const setName = (e) => {
const userName = e.target.value;
setState(state => ({
...state,
userName,
}));
}
useEffect(() => {
getUserInfo().then(data => {
setState(state => ({
...state,
isLoading: false,
userName: data.userName,
}));
});
}, []);
if (state.isLoading) {
return null;
}
return (
<form>
<input type="text" value={state.userName} onChange={setName} />
<button>{'Save'}</button>
</form>
)
}
export default ProfileEditForm;
良い例え
// App.js
import React, {useEffect} from 'react';
import {connect} from "react-redux";
import './App.css';
import Header from './elements/header';
import ProfileEditForm from './elements/profileeditform';
import {loadUserInfo} from "./actions/userAction";
const App = ({ loadUserInfo }) => {
useEffect(() => {
loadUserInfo()
}, [])
return (
<div className="App">
<Header />
<main className="Main">
<ProfileEditForm />
</main>
</div>
);
}
export default connect(
null,
{ loadUserInfo },
)(App);
// src/elements/header.js
import React from "react";
import logo from "../logo.svg";
import Menu from "./menu";
export default () => (
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Menu />
</header>
)
// src/elements/menu.js
import React from "react";
import { connect } from "react-redux";
const Menu = ({userName}) => (
<>
<span>{userName}</span>
<nav>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
</ul>
</nav>
</>
)
const mapStateToProps = (state) => {
return {
userName: state.userInfo.userName,
}
}
export default connect(
mapStateToProps,
)(Menu);
// src/elements/profileeditform.js
import React from "react";
import { changeUserName } from '../actions/userAction'
import {connect} from "react-redux";
const ProfileEditForm = ({userName, changeUserName}) => {
const handleChange = (e) => {
changeUserName(e.target.value);
};
return (
<form>
<input type="text" value={userName} onChange={handleChange} />
<button>{'Save'}</button>
</form>
)
}
const mapStateToProps = (state) => {
return {
userName: state.userInfo.userName,
}
}
const mapDispatchToProps = { changeUserName }
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ProfileEditForm);
// src/constants/userConstants.js
export const userConstants = {
SET_USER_INFO: 'USER_SET_USER_INFO',
SET_USER_NAME: 'USER_SET_USER_NAME',
UNDO: 'USER_UNDO',
REDO: 'USER_REDO',
};
// src/actions/userAction.js
import { userConstants } from "../constants/userConstants";
import { getUserInfo } from "../api/index";
export const changeUserName = (userName) => (
{
type: userConstants.SET_USER_NAME,
payload: userName,
}
);
export const setUserInfo = (data) => (
{
type: userConstants.SET_USER_INFO,
payload: data,
}
)
export const loadUserInfo = () => async (dispatch) => {
const result = await getUserInfo();
dispatch(setUserInfo(result));
}
// src/reducers/user/userReducer.js
import { userConstants } from "../../constants/userConstants";
const initialState = {
userName: null,
};
export function userReducer(state = initialState, action) {
switch (action.type) {
case userConstants.SET_USER_INFO:
return {
...state,
...action.payload,
};
case userConstants.SET_USER_NAME:
return {
...state,
userName: action.payload,
};
default:
return state;
}
}
使用頻度
2番目の基準は、Reactアプリケーションのコンポーネントのうち、同じ状態にアクセスできるコンポーネントの数です。状態が同じデータを使用するコンポーネントが多いほど、Reduxストアを使用することで得られるメリットが大きくなります。
状態が特定のコンポーネントまたはアプリケーションのごく一部に分離されていることを理解している場合は、別のコンポーネントまたはHOCコンポーネントのReact状態を使用することをお勧めします。
状態の深さ
Redux以外のアプリケーションでは、同じデータを別の場所に格納しないことを前提として、React状態データは、子コンポーネントがこのデータにアクセスする必要がある最上位(ツリー内)コンポーネントに格納する必要があります。
場合によっては、親コンポーネントの状態からのデータが、さまざまな入れ子レベルの多数の子コンポーネントで必要となるため、コンポーネントが強力に連動し、それらに不要なコードが表示されることがあります。このような場合、Reduxに状態を保存し、適切なコンポーネントのストレージから目的のデータを取得する方が理にかなっています。
状態データを1つまたは2つのネストレベルの子コンポーネントに渡す必要がある場合は、Reduxなしでこれを行うことができます。
悪い例
//App.js
import React from 'react';
import './App.css';
import Header from './elements/header';
import MainContent from './elements/maincontent';
const App = ({userName}) => {
return (
<div className="App">
<Header userName={userName} />
<main className="Main">
<MainContent />
</main>
</div>
);
}
export default App;
// ./elements/header.js
import React from "react";
import logo from "../logo.svg";
import Menu from "./menu";
export default ({ userName }) => (
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Menu userName={userName} />
</header>
)
// ./elements/menu.js
import React from "react";
export default ({userName}) => (
<>
<span>{userName}</span>
<nav>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
</ul>
</nav>
</>
)
良い例え
// App.js
import React from 'react';
import './App.css';
import Header from './elements/header';
import MainContent from './elements/maincontent';
const App = () => {
return (
<div className="App">
<Header />
<main className="Main">
<MainContent />
</main>
</div>
);
}
export default App;
//./elements/header.js
import React from "react";
import logo from "../logo.svg";
import Menu from "./menu";
export default () => (
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Menu />
</header>
)
//./elements/menu.js
import React from "react";
import { connect } from "react-redux";
const Menu = ({userName}) => (
<>
<span>{userName}</span>
<nav>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
</ul>
</nav>
</>
)
const mapStateToProps = (state) => {
return {
userName: state.userInfo.userName,
}
}
export default connect(
mapStateToProps,
)(Menu)
状態が同じデータを操作する非バインドコンポーネント
比較的関連のないいくつかのコンポーネントが同じ状態にアクセスする必要がある状況があります。たとえば、アプリケーションは、ユーザープロファイルとヘッダーを編集するためのフォームを作成する必要があり、ヘッダーもユーザーデータを表示する必要があります。
もちろん、ユーザープロファイルデータを格納する最上位のスーパーコンポーネントを作成し、最初にそれをヘッダーコンポーネントとその子に渡し、次に、ツリーのより深いところに渡すことで、極端に進むことができます。プロファイル編集コンポーネントに。同時に、ユーザーデータが変更されたときに呼び出されるプロファイル編集フォームにもコールバックが必要です。
まず、このアプローチは、コンポーネントの強力な連動、中間コンポーネントでの不要なデータと不要なコードの出現につながる可能性が高く、更新と保守に時間がかかります。
第2に、追加のコードを変更しないと、渡されたデータを使用しないコンポーネントを取得する可能性が高くなりますが、このデータが更新されるたびにレンダリングされるため、アプリケーションの速度が低下します。
簡単にするために、ユーザーのプロファイルデータをReduxストアに保存し、ヘッダーコンテナーコンポーネントとプロファイル編集コンポーネントがReduxストアのデータを受信および変更できるようにします。
悪い例
// App.js
import React, {useState} from 'react';
import './App.css';
import Header from './elements/header';
import ProfileEditForm from './elements/profileeditform';
const App = ({user}) => {
const [userName, setUserName] = useState(user.user_name);
return (
<div className="App">
<Header userName={userName} />
<main className="Main">
<ProfileEditForm onChangeName={setUserName} userName={userName} />
</main>
</div>
);
}
export default App;
// ./elements/header.js
import React from "react";
import logo from "../logo.svg";
import Menu from "./menu";
export default ({ userName }) => (
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Menu userName={userName} />
</header>
)
// ./elements/menu.js
import React from "react";
const Menu = ({userName}) => (
<>
<span>{userName}</span>
<nav>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
</ul>
</nav>
</>
)
export default Menu;
// ./elements/profileeditform.js
import React from "react";
export default ({userName, onChangeName}) => {
const handleChange = (e) => {
onChangeName(e.target.value);
};
return (
<form>
<input type="text" value={userName} onChange={handleChange} />
<button>{'Save'}</button>
</form>
)
}
良い例え
// App.js
import React from 'react';
import './App.css';
import Header from './elements/header';
import ProfileEditForm from './elements/profileeditform';
const App = () => {
return (
<div className="App">
<Header />
<main className="Main">
<ProfileEditForm />
</main>
</div>
);
}
export default App;
//./elements/header.js
import React from "react";
import logo from "../logo.svg";
import Menu from "./menu";
export default () => (
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Menu />
</header>
)
//./elements/menu.js
import React from "react";
import { connect } from "react-redux";
const Menu = ({userName}) => (
<>
<span>{userName}</span>
<nav>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
</ul>
</nav>
</>
)
const mapStateToProps = (state) => {
return {
userName: state.userInfo.userName,
}
}
export default connect(
mapStateToProps,
)(Menu)
//./elements/profileeditform
import React from "react";
import { changeUserName } from '../actions/userAction'
import {connect} from "react-redux";
const ProfileEditForm = ({userName, changeUserName}) => {
const handleChange = (e) => {
changeUserName(e.target.value);
};
return (
<form>
<input type="text" value={userName} onChange={handleChange} />
<button>{'Save'}</button>
</form>
)
}
const mapStateToProps = (state) => {
return {
userName: state.userInfo.userName,
}
}
const mapDispatchToProps = { changeUserName }
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ProfileEditForm)
状態の変化を追跡する機能
別のケース:アプリケーションでユーザー操作を元に戻す/やり直す機能を実装する必要がある場合、または単に状態の変化をログに記録したい場合。
このようなニーズは、チュートリアルデザイナーの開発中に発生しました。ユーザーは、マニュアルページでテキスト、画像、ビデオを使用してブロックを追加および構成したり、元に戻す/やり直し操作を実行したりできます。
これらの場合、Reduxは優れたソリューションです。作成されるすべてのアクションは、状態へのアトミックな変更です。Reduxは、これらのタスクを1つの場所(Reduxストア)に集中させることで、すべてのタスクを簡素化します。
元に戻す/やり直しの例
// App.js
import React from 'react';
import './App.css';
import Header from './elements/header';
import ProfileEditForm from './elements/profileeditform';
const App = () => {
return (
<div className="App">
<Header />
<main className="Main">
<ProfileEditForm />
</main>
</div>
);
}
export default App;
// './elements/profileeditform.js'
import React from "react";
import { changeUserName, undo, redo } from '../actions/userAction'
import {connect} from "react-redux";
const ProfileEditForm = ({ userName, changeUserName, undo, redo, hasPast, hasFuture }) => {
const handleChange = (e) => {
changeUserName(e.target.value);
};
return (
<>
<form>
<input type="text" value={userName} onChange={handleChange} />
<button>{'Save'}</button>
</form>
<div>
<button onClick={undo} disabled={!hasPast}>{'Undo'}</button>
<button onClick={redo} disabled={!hasFuture}>{'Redo'}</button>
</div>
</>
)
}
const mapStateToProps = (state) => {
return {
hasPast: !!state.userInfo.past.length,
hasFuture: !!state.userInfo.future.length,
userName: state.userInfo.present.userName,
}
}
const mapDispatchToProps = { changeUserName, undo, redo }
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ProfileEditForm)
// src/constants/userConstants.js
export const userConstants = {
SET_USER_NAME: 'USER_SET_USER_NAME',
UNDO: 'USER_UNDO',
REDO: 'USER_REDO',
};
// src/actions/userAction.js
import { userConstants } from "../constants/userConstants";
export const changeUserName = (userName) => (
{
type: userConstants.SET_USER_NAME,
payload: userName,
}
);
export const undo = () => (
{
type: userConstants.UNDO,
}
);
export const redo = () => (
{
type: userConstants.REDO,
}
);
// src/reducers/user/undoableUserReducer.js
import {userConstants} from "../../constants/userConstants";
export function undoable(reducer) {
const initialState = {
past: [],
present: reducer(undefined, {}),
future: [],
};
return function userReducer(state = initialState, action) {
const {past, present, future} = state;
switch (action.type) {
case userConstants.UNDO:
const previous = past[past.length - 1]
const newPast = past.slice(0, past.length - 1)
return {
past: newPast,
present: previous,
future: [present, ...future]
}
case userConstants.REDO:
const next = future[0]
const newFuture = future.slice(1)
return {
past: [...past, present],
present: next,
future: newFuture
}
default:
const newPresent = reducer(present, action)
if (present === newPresent) {
return state
}
return {
past: [...past, present],
present: newPresent,
future: []
}
}
}
}
// src/reducers/user/userReducer.js
import { undoable } from "./undoableUserReducer";
import { userConstants } from "../../constants/userConstants";
const initialState = {
userName: 'username',
};
function reducer(state = initialState, action) {
switch (action.type) {
case userConstants.SET_USER_NAME:
return {
...state,
userName: action.payload,
};
default:
return state;
}
}
export const userReducer = undoable(reducer);
まとめ
次の場合は、Reduxストアにデータを格納することを検討してください。
- このデータがめったに変更されない場合。
- 同じデータが複数の(2-3を超える)関連コンポーネントまたは非関連コンポーネントで使用されている場合。
- データの変更を追跡する場合。
それ以外の場合はすべて、React状態を使用することをお勧めします。
PSどうもありがとうmamdaxx111 記事の準備を手伝ってください!