React でのコンポーネント通信 (react18) 05 - redux ➕ act-redux (データ共有を含む)
1 はじめに
1.1 React におけるコンポーネント通信のその他の方法
- React (react18) のコンポーネント通信 01 - props。
- React (react18) 02 でのコンポーネント通信 - メッセージのサブスクリプションと公開、サブスクリプション解除とコンポーネントのアンインストール時のサブスクリプション解除。
- React (react18) 03 でのコンポーネント通信 - Context を使用してパラメーターを深く渡すだけです。
- React のコンポーネント通信 04 (react18) - Redux 入門.
この記事のコード変更点は次のとおりです。この記事 (Redux の概要) に基づいて修正, そのため、redux について質問がある場合は、この記事を読んでください。 - React (react18) 06 でのコンポーネント通信 - redux-toolkit + reverse-redux。
1.2 React-Redux の概要
1.2.1 React-Redux の簡単な紹介
- React-Redux は、Redux が公式に提供する React バインディング ライブラリです。効率的かつ柔軟。
- React-redux は、React アプリケーション開発用に設計された Redux ベースのライブラリであり、React 固有の機能とコンポーネントをいくつか提供します。
- React コンポーネントでの状態管理のための Redux の使用を容易にする一連の API とコンポーネントを提供します。
- React-Redux は概念的には非常にシンプルです。Redux ストアをサブスクライブし、コンポーネントに必要なデータが変更されたかどうかを確認します。コンポーネントを再レンダリングします。
- React-redux は、Connect 関数や Provider コンポーネントなど、React 固有の関数をいくつか提供します。これらの関数は、Redux ストアに接続し、状態を React コンポーネントに渡すために使用されます。
- React Redux によって提供されるため
<Provider /> 组件
、Redux ストアをアプリケーション内の他の場所で使用できるようになります (つまり、ストアはエントリ ファイルで 1 回渡すだけでよく、ストアを必要とする他のコンテナ コンポーネントで取得できます)。
- React Redux によって提供されるため
1.2.2 公式ウェブサイト
- 参考公式サイト:
- 公式ウェブサイトのアドレス: https://react-redux.js.org/。
- gitHub: https://github.com/reduxjs/react-redux。
- Redux中国語公式ウェブサイト。
- React Redux の中国語ドキュメント。
- React-redux について学ぶための他のブログ:
- 以下で使用される情報については
connect API
、公式 Web サイト:
https://cn.react-redux.js.org/tutorials/connectにアクセスしてください。
1.3 リアクトリダックスをインストールする
- インストールコマンドは以下のとおりです。
# If you use npm: npm install react-redux # Or if you use Yarn: yarn add react-redux
2. reduxを単純に書き換える例
- なお、この書き換えは redux プロジェクトのバージョンに基づいていますので、redux のバージョンについては
React のコンポーネント通信 (react18) 04 - Redux 入門 を参照してください。
2.1 ストアの提供
- 最初のステップは、ストアをアプリケーションで認識できるようにすることです。これを行うには、React Redux が提供する API を使用して
<Provider />
アプリケーションをラップします。:- まず、書き換えられたディレクトリ構造を与えます
- 次に、app.js を見て、インデックス.js
- まず、書き換えられたディレクトリ構造を与えます
2.2 Connect Components + UIコンポーネントの変更
2.2.1 コンポーネントの接続
-
まずは公式サイトの説明を見てみましょう
-
次のように、最初に実装効果を簡単に書き留めてから、後で最適化しましょう。
import CountNumRedux from "../components/CountNumRedux"; import { connect } from "react-redux"; import store from '../redux/store' //这里ownProps如果用不到的话,可以不传,可以只传state const mapStateToProps = (state, ownProps) => ({ // ...依据 state 和 自定义 ownProps 生成 computed data /** * 即状态统一在容器组件中管理 * UI组件使用的话直接通过props取就行了,这种方式也相当于通过props传递 * 如果监听state的变化,一有变化就调用,并把state通过props传递给UI组件 */ count:state // name:'麦兜' }); const mapDispatchToProps = ()=>({ // ... 通常是一个充满 action creators 的 object addNumber:(number)=>{ store.dispatch( { type: 'INCREMENT', number:number } ) }, reduceNumber:(number)=>{ store.dispatch( { type: 'DECREMENT', number:number } ) } }); // // 1. `connect` 返回一个接收要包装的组件的新函数: // const connectToStore = connect(mapStateToProps, mapDispatchToProps); // // 2. 并且该函数返回连接的,包装的组件: // const ConnectedComponent = connectToStore(Component); // 通常我们会将两者一步完成,像这样: const CountNumContainer = connect(mapStateToProps, mapDispatchToProps)(CountNumRedux); export default CountNumContainer;
2.2.2 UIコンポーネントの変更
-
次のように:
import { createRef } from "react"; // import store from '../redux/store' // import countAction from '../redux/countAction' function CountNumRedux(props){ console.log(props); // const [count,setCount] = useState(0); const numberRef = createRef(); function add(){ let number = numberRef.current.value; // console.log(typeof number); //string // store.dispatch(countAction.incrementNum(parseInt(number))); props.addNumber(parseInt(number)); } function subtract(){ let number = parseInt(numberRef.current.value); props.reduceNumber(number); } // useEffect(()=>{ // store.subscribe(()=>{ // console.log('订阅更新,打印2-----',store.getState()); // setCount(store.getState()); // }); // }); return( <div> { /* 当前数字是:{count} 当前数字是:{store.getState()} */} 当前数值是:{ props.count} <br /> 浮动数字:<input type="number" ref={ numberRef}/> <br /><br /> <button onClick={ add}>点我 加数</button> <br /><br /> <button onClick={ subtract}>点我 减数</button> </div> ) } export default CountNumRedux;
2.2.3 効果を確認する
- 次のように:
2.3 コンポーネントの接続 - 最適化(コンテナコンポーネントの最適化)
-
主に次のように、mapDispatchToProps を最適化し、カプセル化されたアクションを使用します。
import CountNumRedux from "../components/CountNumRedux"; import { connect } from "react-redux"; // import store from '../redux/store' import { incrementNum,decrementNum} from "../redux/countAction"; const mapStateToProps = (state) => ({ count:state }); // const mapDispatchToProps = ()=>({ // addNumber:(number)=>{ // store.dispatch( // { type: 'INCREMENT', number:number } // ) // }, // reduceNumber:(number)=>{ // store.dispatch( // { type: 'DECREMENT', number:number } // ) // } // }); /** * 1. dispatch:react-redux 会将dispatch传入,所以不用引入store来调了 * 2. 引入已经封装好的action:countAction */ const mapDispatchToProps = (dispatch)=>({ addNumber:(number)=>{ dispatch( incrementNum(number) ) }, reduceNumber:(number)=>{ dispatch( decrementNum(number) ) } }); const CountNumContainer = connect(mapStateToProps, mapDispatchToProps)(CountNumRedux); export default CountNumContainer;
2.4 コンテナコンポーネントの最適化 (ひどい単純化)
- mapDispatchToProps: このパラメータには関数またはオブジェクトを指定できます。
- 上記はすべて関数で記述されていますが、オブジェクトに変更すると、コードが本当に少なすぎます。
- 当局者が強調したことをもう一度見てみましょう。
- 簡略化されたコードは次のとおりです。
比較する:/** * 优化2 */ const mapDispatchToProps = { //通常是一个充满 action creators 的 object addNumber: incrementNum, //addNumber:是通过props传递给UI组件的方法, incrementNum:是封装好的action函数 reduceNumber: decrementNum }
2.5 付属コード
- redux ファイル以下のコードは変更がないので載せませんが、必要な方は前回の記事を参照してください。その他は以下の通りです。
- CountNumContainer.jsx
import CountNumRedux from "../components/CountNumRedux"; import { connect } from "react-redux"; import { incrementNum,decrementNum} from "../redux/countAction"; const mapStateToProps = (state) => ({ count:state }); const mapDispatchToProps = { //通常是一个充满 action creators 的 object addNumber: incrementNum, //addNumber:是通过props传递给UI组件的方法, incrementNum:是封装好的action函数 reduceNumber: decrementNum } const CountNumContainer = connect(mapStateToProps, mapDispatchToProps)(CountNumRedux); export default CountNumContainer;
- CountNumRedux.jsx
import { createRef } from "react"; function CountNumRedux(props){ console.log(props); const numberRef = createRef(); function add(){ let number = numberRef.current.value; props.addNumber(parseInt(number)); } function subtract(){ let number = parseInt(numberRef.current.value); props.reduceNumber(number); } return( <div> { /* 当前数字是:{count} 当前数字是:{store.getState()} */} 当前数值是:{ props.count} <br /> 浮动数字:<input type="number" ref={ numberRef}/> <br /><br /> <button onClick={ add}>点我 加数</button> <br /><br /> <button onClick={ subtract}>点我 减数</button> </div> ) } export default CountNumRedux;
- App.js
import CountNumContainer from './container/CountNumContainer.jsx' function App() { return ( <div> { /* <CountNumRedux/> */} <CountNumContainer/> </div> ); } export default App;
- インデックス.js
import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import store from './redux/store'; import { Provider } from 'react-redux'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={ store}> <App /> </Provider> ); export default root;
- CountNumContainer.jsx
3. 複数のReducerでデータ共有を実現
3.1 combinReducers() 関数の概要
-
アプリケーションがより複雑になるにつれて、次の点を考慮してください。レデューサー関数を個別の関数に分割する、分割後の各関数は、状態の一部を独立して管理する責任を負います。
combinReducers 補助関数は、複数の異なるリデューサー関数を持つオブジェクトを値として最終的なリデューサー関数に結合し、このリデューサーで createStore メソッドを呼び出します。
マージされたリデューサーは、各サブリデューサーを呼び出し、返された結果を状態オブジェクトにマージできます。comboReducers() によって返される状態オブジェクトは、combineReducers() に渡されるときに、対応するキーに従って、各受信リデューサーによって返される状態に名前を付けます。。
例:rootReducer = combineReducers({ potato: potatoReducer, tomato: tomatoReducer}) // 这将返回如下的 state 对象 { potato: { // ... potatoes, 和一些其他由 potatoReducer 管理的 state 对象 ... }, tomato: { // ... tomatoes, 和一些其他由 tomatoReducer 管理的 state 对象,比如说 sauce 属性 ... } }
-
関数の紹介
combineReducers()
は公式 Web サイトからのもので、combineReducers()
詳細については、次の公式 Web サイトを参照してください:
https://cn.redux.js.org/api/combinereducers。
3.2 複数の減速機の統合例
3.2.1 達成したい効果
- まず、次のように 3 つのコンポーネントのレンダリングを設計します。
- これら 3 つのコンポーネントの状態はすべて React-redux によって管理されており、最初にデータ共有なしでこれを実装し、次にコンポーネント間でデータ共有を有効にする方法を実装します。
3.2.2 プロジェクトディレクトリの設計構造
- 次のように:
3.2.3 3 つの減速機を統合する (または分割減速機)
- レデューサーの分割については、公式サイトにアクセスできます。上記の機能を紹介する
combineReducers()
ときに言及したので、ここでは詳細は説明しません。また、公式ウェブサイトにアクセスして、分割リデューサーのロジックを確認することもできます。アドレスは次のとおりです:
Split Reducer Logic。 - 上記の cat、dog、pet はそれぞれ 1 つのレデューサーに対応しますが、ストアを作成するときに必要なレデューサーは 1 つだけなので、最終的にはこれら 3 つのレデューサー関数が必要になります。最終リデューサー関数に結合ストアを作成するときに使用します。
- このプロジェクトでの使用方法
combineReducers()
、3 つすべてが公開されている限り、他の 3 つのレデューサーがどのように見えるかを知る必要はありません。そのため、次のようにここで直接マージを紹介します。
3.2.4 各コンポーネントの 3 つのコア ファイル
3.2.4.1 簡単な紹介 - 猫を例に取る
- ここでは便宜上、定数は抽出せず、UIコンポーネントとコンテナコンポーネントを1つのファイルに統合しているため、上記の3つのモジュールコンポーネントは、アクション、リデューサ、コンテナコンポーネントの3つのコアファイルにそれぞれ対応しています。
- 以下では、例として cat コンポーネントを使用して説明します。
- catAction + catReducer:
ここの猫は、「今日最も人気のある猫の品種」を変更したいだけなので、これは比較的単純で、単なるアクション関数です。アクションが適切に設計されていれば、次のようにリデューサーも完成させることができます。
- 猫コンテナコンポーネントは次のとおりです。
- catAction + catReducer:
3.2.4.2 犬とペットパークについて
- 犬の場合は、次のように直接見てください。
- ドッグアクション + ドッグリデューサー:
- 犬コンテナコンポーネントは次のとおりです。
- ドッグアクション + ドッグリデューサー:
- petPark の値は次のとおりです:
これは比較的単純です。設計ステータスに変更がないため、対応するアクションはありません。これらはすべて次のような暫定値です。
3.3 データ共有の実現
3.3.1 データ共有の実現
- 上記の効果に基づいて、データ共有を実現するのは非常に簡単です。次のように、2 行のコードを追加することは、独自のコードを追加するのと同じです。
- petPark は、他の 2 つのコンポーネントからのデータにアクセスします。
- Cat は同じ方法で petPark データにアクセスします。コンポーネント内で管理されないため、必要に応じてアクセスできますが、react-redux が管理します。使用する人は誰でも取得できます。
- petPark は、他の 2 つのコンポーネントからのデータにアクセスします。
3.4 付属コアコード
3.4.1 2 つのアクション
-
catAction は次のとおりです。
function changeCatKindAction(newKind){ return { type: 'CHANGE_CAT_KIND', kind: newKind } } export { changeCatKindAction}
-
DogAction は次のとおりです。
function addDogAction(dogObj){ return { type:'ADD_DOG', dog:dogObj } } export { addDogAction}
3.4.2 3 つの減速機 + 1 つの統合減速機
-
最初の 3 つは次のとおりです。
const catKindInit = '布偶'; function catReducer(state=catKindInit, action){ switch (action.type) { case 'CHANGE_CAT_KIND': return action.kind; default: return state; } } export default catReducer;
const dogInit = []; // const dogInit = [{dogName:'狗狗',dogAge:1}]; function dogReducer(state = dogInit, action){ const { type,dog} = action; switch (type) { case 'ADD_DOG': return [...state,dog]; default: return state; } } export default dogReducer;
const adminInit = { parkAdmin:'素素',parkAdminPhone:'176XXXXX'}; function PetParkReducer(state=adminInit, action){ return state; //没有action,初始state不需要修改,直接返回 } export default PetParkReducer;
-
最終的なものは次のとおりです。
import catReducer from "./catsReducer"; import dogReducer from "./dogReducer"; import petsParkReducer from "./petsParkReducer"; import { combineReducers } from "redux"; /** * 1. 合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 * 2. 由 combineReducers() 返回的 state 对象, * 会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。 */ const rootReducer = combineReducers({ petParkState: petsParkReducer, dogState: dogReducer, catState: catReducer, }); export default rootReducer;
3.4.3 3 つのコンポーネント
-
CatContainer.jsx次のように:
import { connect } from "react-redux"; import { useRef } from "react"; import { changeCatKindAction} from '../redux/actions/CatAction' //1. UI组件 function CatUI(props){ const catKindNode = useRef(); function chagePopularKind(){ const newKind = catKindNode.current.value; props.changKind(newKind); } return( <div> <h1>我是cat组件</h1> 今日最受欢迎的小猫品种是:{ props.popularCatKind} <br/><br/> <input type="text" ref={ catKindNode} placeholder="请输入今日最受欢迎的"/> <button onClick={ chagePopularKind}>修改最受欢迎的小猫品种</button> <br /> 今日管理员是:{ props.guanliyuan} <br/> 管理员:{ props.guanliyuan2.parkAdmin} </div> ) } //2. 容器组件 function mapStateToProps(state) { return { popularCatKind: state.catState, guanliyuan: state.petParkState.parkAdmin, //可以直接访问其中某个属性 guanliyuan2: state.petParkState, //也可以直接访问整个对象 } } const mapDispatchToProps = { changKind: changeCatKindAction } const CatContainer = connect(mapStateToProps,mapDispatchToProps)(CatUI); export default CatContainer;
-
DogContainer.jsx次のように:
import { useRef } from "react"; import { connect } from "react-redux" import { addDogAction } from "../redux/actions/DogAction"; //1. 定义UI组件 function DogUI(props){ // console.log(props); const dogList = props.dogListState;//获取狗狗列表信息 const dogNameRef = useRef(); const dogAgeRef = useRef(); function addParkDog(){ const dogName = dogNameRef.current.value; const dogAge = dogAgeRef.current.value; const dogObj = { dogName:dogName,dogAge:dogAge} props.addOneDog(dogObj); } return( <div> <h1>我是dog组件</h1> <br /> 1. 狗狗园区地址:{ props.dogParkAdress} <br /><br /> 2. 狗狗姓名:<input type="text" ref={ dogNameRef} /> <br /> 狗狗年龄:<input type="number" ref={ dogAgeRef}/> <button onClick={ addParkDog}>添加狗狗</button> <br /><br /> 3. 狗狗列表信息: <ul> { dogList.map((dog,index)=>( <li key={ index}>{ dog.dogName}---{ dog.dogAge}</li>) ) } </ul> </div> ) } //2.容器组件 并导出容器组件 const mapStateToProps = (state)=>{ /** * 1. 返回的是一个对象(dog组件 管理自己组件的state) * 2. 语法问题:当返回的是一个对象时,用一对()括起来,否则语法报错 */ return( { dogListState:state.dogState, dogParkAdress:'北京海淀区' } ) } const mapDispatchToProps = { addOneDog: addDogAction } const DogContainer = connect(mapStateToProps,mapDispatchToProps)(DogUI); export default DogContainer;
-
PetParkContainer.jsx次のように:
import { connect } from "react-redux"; import { useState } from "react"; //1. UI组件 function PetParkUI(props){ console.log(props); const [closeFlag,setCloseFlag] = useState(false); console.log(closeFlag); return( <div> <h1>我是PetPark组件</h1> 1. 管理员:{ props.parkAdminInfo.parkAdmin} <br /> 管理员电话:{ props.parkAdminInfo.parkAdminPhone} <br /> 2. 现有的宠物有:{ JSON.stringify(props.petKinds)} <br /> 3. 雨天是否闭园:{ closeFlag ? '是' : '否'} <br /><br /> 今日猫猫种类王是:{ props.catKindKing} <br /><br /> 今日dog园区有多少条狗狗:{ props.dogsNum} </div> ) } //2.容器组件 const mapStateToProps = (state)=>({ parkAdminInfo: state.petParkState,//这个交给react-redux管理的可以共享 petKinds: ['猫猫','狗狗'] ,//这个如果是自身组件用的,可以用useState放自身组件上 //下面是数据共享的 catKindKing: state.catState, //直接取cat组件里的状态 dogsNum: state.dogState.length }) //connect 的两个参数都是可选的,可传可不传 const PetParkContainer = connect(mapStateToProps)(PetParkUI); export default PetParkContainer;
4. 付属品
- プロジェクトディレクトリ
プロジェクトの React (react18) コンポーネント通信のさまざまな方法とさまざまな詳細なサンプル (react-redux + redux-toolkit を含む) をダウンロードします。