「[[.NET 開発基盤部会 Wiki>http://dotnetdevelopmentinfrastructure.osscons.jp]]」は、「[[Open棟梁Project>https://github.com/OpenTouryoProject/]]」,「[[OSSコンソーシアム .NET開発基盤部会>https://www.osscons.jp/dotNetDevelopmentInfrastructure/]]」によって運営されています。

-戻る
--[[React]] -> [[Redux]]
-戻る([[React]] -> [[Redux]])
--...[[Reactの5thステップ]]
--[[Reactのファースト・ステップ2]]
--[[Reactのセカンド・ステップ2]]
--Reactのサード・ステップ2

*目次 [#bf704575]
#contents

*概要 [#rbd0d9e0]
引き続き[[Redux]]対応を行う。

*詳細 [#k95528b7]

**プロンプト [#gad08276]
CrudSampleとCrudSample2があるが、構成が複雑なCrudSample2をサンプリングし同様の修正方法でCrudSampleも対応

 以下のCrudSample2のmessageをRedux対応させたい。修正方法を教えてください。
 
 .\src\store\index.ts
 .\src\store\counterSlice.ts
 .\src\pages\CrudSample2.tsx
 .\src\components\CrudSample2\Buttons.tsx
 .\src\components\CrudSample2\DropDownLists.tsx
 .\src\components\CrudSample2\Inputs.tsx
 .\src\components\CrudSample2\Outputs.tsx
 
 ```
 import { configureStore } from '@reduxjs/toolkit'
 import counterReducer from './counterSlice'
 
 export const store = configureStore({
   reducer: {
     counter: counterReducer,
   },
 })
 
 // 型エクスポート(useSelector / useDispatch で使用)
 export type RootState = ReturnType<typeof store.getState>
 export type AppDispatch = typeof store.dispatch
 ```
 
 ```
 import { createSlice } from '@reduxjs/toolkit'
 import type { PayloadAction } from '@reduxjs/toolkit'
 
 interface CounterState {
   value: number
 }
 
 const initialState: CounterState = {
   value: 0,
 }
 
 const counterSlice = createSlice({
   name: 'counter',
   initialState,
   reducers: {
     increment: (state) => {
       state.value += 1
     },
     decrement: (state) => {
       state.value -= 1
     },
     incrementByAmount: (state, action: PayloadAction<number>) => {
       state.value += action.payload
     },
     reset: (state) => {
       state.value = 0
     },
   },
 })
 
 export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions
 export default counterSlice.reducer
 ```
 
 ```
 import * as React from 'react';
 import constants from '../const';
 import common from '../common.ts';
 import oauth_oidc from '../touryo/oauth_oidc';
 import Inputs from '../components/CrudSample2/Inputs';
 import DropDownLists from '../components/CrudSample2/DropDownLists';
 import Buttons from '../components/CrudSample2/Buttons';
 import Outputs from '../components/CrudSample2/Outputs';
 
 // ===== 型定義 =====
 
 interface DdlState {
   ddlDap: string;
   ddlMode1: string;
   ddlMode2: string;
   ddlIso: string;
   ddlExRollback: string;
   ddlOrder: string;
   ddlOrderSequence: string;
 }
 
 interface ShipperState {
   shipperID: string;
   companyName: string;
   phone: string;
 }
 
 interface CrudSample2State {
   message: string;
   ddl: DdlState;
   shipper: ShipperState;
   shippers: ShipperState[];
   loading: boolean;
 }
 
 // ===== コンポーネント =====
 
 export class CrudSample2 extends React.Component<Record<string, never>, CrudSample2State>
 {
   constructor(props: Record<string, never>) {
     super(props);
 
     this.state = {
       message: '',
       ddl: {
         ddlDap: 'SQL',
         ddlMode1: 'individual',
         ddlMode2: 'static',
         ddlIso: 'NT',
         ddlExRollback: '-',
         ddlOrder: 'c1',
         ddlOrderSequence: 'A',
       },
       shipper: {
         shipperID: '',
         companyName: '',
         phone: '',
       },
       shippers: [
         {
           shipperID: '',
           companyName: '',
           phone: '',
         },
       ],
       loading: true,
     };
   }
 
   // ===== render =====
 
   render() {
     const containerStyle: React.CSSProperties = { textAlign: 'left' };
     const div0Style: React.CSSProperties = {};
     const div1Style: React.CSSProperties = { display: 'inline-block' };
     const div2Style: React.CSSProperties = { display: 'inline-block' };
 
     return (
       <div style={containerStyle}>
         <h1>CRUD sample</h1>
         <p>This component demonstrates CRUD.</p>
         <div style={div0Style}>
           <DropDownLists onChangeDdl={(e) => this.receiveDDLChanged(e)} />
         </div>
         <div style={div1Style}>
           <Inputs
             shipper={this.state.shipper}
             onChangeInput={(e) => this.receiveInputChanged(e)}
           />
         </div>
         <div style={div2Style}>
           <Outputs
             loading={this.state.loading}
             shippers={this.state.shippers}
             message={this.state.message}
           />
         </div>
         <div>
           <Buttons
             onClickButton={(e) => this.receiveButtonClick(e)}
           />
         </div>
       </div>
     );
   }
 
   // ─── 子コンポーネントからのイベント受信 ────────────────────
 
   receiveDDLChanged(ddl: Partial<CrudSample2State>): void {
     this.setState(ddl as CrudSample2State);
   }
 
   receiveInputChanged(shipper: Partial<CrudSample2State>): void {
     this.setState(shipper as CrudSample2State);
   }
 
   receiveButtonClick(actionType: string): void {
     switch (actionType) {
       case 'SelectCount':    this.selectCount();    return;
       case 'SelectAll_DT':   this.selectAll_DT();   return;
       case 'SelectAll_DS':   this.selectAll_DS();   return;
       case 'SelectAll_DR':   this.selectAll_DR();   return;
       case 'SelectAll_DSQL': this.selectAll_DSQL(); return;
       case 'Select':         this.select();         return;
       case 'Insert':         this.insert();         return;
       case 'Update':         this.update();         return;
       case 'Delete':         this.delete();         return;
       default:               return;
     }
   }
 
   // ===== WebAPI イベントハンドラ =====
 
   selectCount() {
     this.setState({ message: '' });
     common.postFetch(
       constants.CrudSampleRootUrl + 'SelectCount',
       oauth_oidc.createHttpRequestHeader(false),
       'ddlDap=' + this.state.ddl.ddlDap
         + '&ddlMode1=' + this.state.ddl.ddlMode1
         + '&ddlMode2=' + this.state.ddl.ddlMode2
         + '&ddlExRollback=' + this.state.ddl.ddlExRollback,
       (data) => {
         if (data.message) {
           this.setState({ message: JSON.stringify(data.message) });
         }
       },
       (msg) => this.setState({ message: JSON.stringify(msg) }),
     );
   }
 
   selectAll_DT() {
     this.setState({ message: '' });
     common.postFetch(
       constants.CrudSampleRootUrl + 'SelectAll_DT',
       oauth_oidc.createHttpRequestHeader(false),
       'ddlDap=' + this.state.ddl.ddlDap
         + '&ddlMode1=' + this.state.ddl.ddlMode1
         + '&ddlMode2=' + this.state.ddl.ddlMode2
         + '&ddlExRollback=' + this.state.ddl.ddlExRollback,
       (data) => {
         if (data.result) {
           this.setState({ message: '', shippers: data.result as ShipperState[], loading: false });
         }        
       },
       (msg) => this.setState({ message: JSON.stringify(msg) }),
     );    
   }
 
   selectAll_DS() {
     this.setState({ message: '' });
     common.postFetch(
       constants.CrudSampleRootUrl + 'selectAll_DS',
       oauth_oidc.createHttpRequestHeader(false),
       'ddlDap=' + this.state.ddl.ddlDap
         + '&ddlMode1=' + this.state.ddl.ddlMode1
         + '&ddlMode2=' + this.state.ddl.ddlMode2
         + '&ddlExRollback=' + this.state.ddl.ddlExRollback,
       (data) => {
         if (data.result) {
           this.setState({ message: '', shippers: data.result as ShipperState[], loading: false });
         }        
       },
       (msg) => this.setState({ message: JSON.stringify(msg) }),
     );  
   }
 
   selectAll_DR() {
     this.setState({ message: '' });
     common.postFetch(
       constants.CrudSampleRootUrl + 'selectAll_DR',
       oauth_oidc.createHttpRequestHeader(false),
       'ddlDap=' + this.state.ddl.ddlDap
         + '&ddlMode1=' + this.state.ddl.ddlMode1
         + '&ddlMode2=' + this.state.ddl.ddlMode2
         + '&ddlExRollback=' + this.state.ddl.ddlExRollback,
       (data) => {
         if (data.result) {
           this.setState({ message: '', shippers: data.result as ShipperState[], loading: false });
         }        
       },
       (msg) => this.setState({ message: JSON.stringify(msg) }),
     );  
   }
 
   selectAll_DSQL() {
     this.setState({ message: '' });
     common.postFetch(
       constants.CrudSampleRootUrl + 'selectAll_DSQL',
       oauth_oidc.createHttpRequestHeader(false),
       'ddlDap=' + this.state.ddl.ddlDap
         + '&ddlMode1=' + this.state.ddl.ddlMode1
         + '&ddlMode2=' + this.state.ddl.ddlMode2
         + '&ddlExRollback=' + this.state.ddl.ddlExRollback
         + '&orderColumn=' + this.state.ddl.ddlOrder
         + '&orderSequence=' + this.state.ddl.ddlOrderSequence,
       (data) => {
         if (data.result) {
           this.setState({ message: '', shippers: data.result as ShipperState[], loading: false });
         }        
       },
       (msg) => this.setState({ message: JSON.stringify(msg) }),
     );  
   }
 
   select() {
     this.setState({ message: '' });
     common.postFetch(
       constants.CrudSampleRootUrl + 'select',
       oauth_oidc.createHttpRequestHeader(true),
       JSON.stringify({
         ddlDap: this.state.ddl.ddlDap,
         ddlMode1: this.state.ddl.ddlMode1,
         ddlMode2: this.state.ddl.ddlMode2,
         ddlExRollback: this.state.ddl.ddlExRollback,
         shipper: {
           shipperID: this.state.shipper.shipperID,
           companyName: '',
           phone: '',
         },
       }),
       (data) => {
         if (data.result) {
           const result = data.result as ShipperState;
           this.setState({
             shipper: {
               shipperID: result.shipperID,
               companyName: result.companyName,
               phone: result.phone,
             },
           });
         }        
       },
       (msg) => this.setState({ message: JSON.stringify(msg) }),
     ); 
   }
 
   insert() {
     this.setState({ message: '' });
     common.postFetch(
       constants.CrudSampleRootUrl + 'insert',
       oauth_oidc.createHttpRequestHeader(true),
       JSON.stringify({
         ddlDap: this.state.ddl.ddlDap,
         ddlMode1: this.state.ddl.ddlMode1,
         ddlMode2: this.state.ddl.ddlMode2,
         ddlExRollback: this.state.ddl.ddlExRollback,
         shipper: {
           shipperID: '0',
           companyName: this.state.shipper.companyName,
           phone: this.state.shipper.phone,
         },
       }),
       (data) => {
         if (data.message) {
           this.setState({ message: JSON.stringify(data.message) });
         }        
       },
       (msg) => this.setState({ message: JSON.stringify(msg) }),
     );
   }
 
   update() {
     this.setState({ message: '' });
     common.postFetch(
       constants.CrudSampleRootUrl + 'update',
       oauth_oidc.createHttpRequestHeader(true),
       JSON.stringify({
         ddlDap: this.state.ddl.ddlDap,
         ddlMode1: this.state.ddl.ddlMode1,
         ddlMode2: this.state.ddl.ddlMode2,
         ddlExRollback: this.state.ddl.ddlExRollback,
         shipper: {
           shipperID: this.state.shipper.shipperID,
           companyName: this.state.shipper.companyName,
           phone: this.state.shipper.phone,
         },
       }),
       (data) => {
         if (data.message) {
           this.setState({ message: JSON.stringify(data.message) });
         }        
       },
       (msg) => this.setState({ message: JSON.stringify(msg) }),
     );
   }
 
   delete() {
     this.setState({ message: '' });
     common.postFetch(
       constants.CrudSampleRootUrl + 'delete',
       oauth_oidc.createHttpRequestHeader(true),
       JSON.stringify({
         ddlDap: this.state.ddl.ddlDap,
         ddlMode1: this.state.ddl.ddlMode1,
         ddlMode2: this.state.ddl.ddlMode2,
         ddlExRollback: this.state.ddl.ddlExRollback,
         shipper: {
           shipperID: this.state.shipper.shipperID,
           companyName: '',
           phone: '',
         },
       }),
       (data) => {
         if (data.message) {
           this.setState({ message: JSON.stringify(data.message) });
         }        
       },
       (msg) => this.setState({ message: JSON.stringify(msg) }),
     );
   }
 }
 ```
 
 ```
 import * as React from 'react';
 
 // 型定義
 interface Shipper {
   shipperID: string;
   companyName: string;
   phone: string;
 }
 
 interface OutputsProps {
   shippers: Shipper[];
   loading: boolean;
   message?: string;
 }
 
 interface OutputsState {
   shippers: Shipper[];
   loading: boolean;
   message?: string;
 }
 
 export class Outputs extends React.Component<OutputsProps, OutputsState> {
   // constructor
   constructor(props: OutputsProps) {
     super(props);
 
     this.state = {
       shippers: [
         {
           shipperID: '',
           companyName: '',
           phone: '',
         },
       ],
       loading: true,
       message: undefined,
     };
   }
 
   // lifecycle
   // componentWillReceiveProps は非推奨のため getDerivedStateFromProps に移行
   static getDerivedStateFromProps(
     newProps: OutputsProps
   ): OutputsState {
     return {
       loading: newProps.loading,
       shippers: newProps.shippers,
       message: newProps.message,
     };
   }
 
   // render
   render() {
     let contents: React.ReactNode = null;
 
     if (this.state.loading) {
       contents = (
         <p>
           <em>...Table...</em>
         </p>
       );
     } else {
       contents = (
         <table className="table">
           <thead>
             <tr>
               <th>ShipperID</th>
               <th>CompanyName</th>
               <th>Phone</th>
             </tr>
           </thead>
           <tbody>
             {this.state.shippers.map((shipper) => (
               <tr key={shipper.shipperID}>
                 <td>{shipper.shipperID}</td>
                 <td>{shipper.companyName}</td>
                 <td>{shipper.phone}</td>
               </tr>
             ))}
           </tbody>
         </table>
       );
     }
 
     return (
       <div>
         {contents}
         <p>処理結果:{this.state.message}</p>
       </div>
     );
   }
 }
 
 export default Outputs;
 ```
 
 ```
 import * as React from 'react';
 
 // Propsの型定義
 interface ButtonsProps {
     onClickButton: (actionType: string) => void;
 }
 
 export class Buttons extends React.Component<ButtonsProps> {
     constructor(props: ButtonsProps) {
         super(props);
     }
 
     render() {
         return (
             <div>
                 <button className='btn-primary' onClick={() => { this.onClickButton('SelectCount') }}>SelectCount</button>&nbsp;
                 <button className='btn-primary' onClick={() => { this.onClickButton('SelectAll_DT') }}>SelectAll_DT</button>&nbsp;
                 <button className='btn-primary' onClick={() => { this.onClickButton('SelectAll_DS') }}>SelectAll_DS</button>&nbsp;
                 <button className='btn-primary' onClick={() => { this.onClickButton('SelectAll_DR') }}>SelectAll_DR</button>&nbsp;
                 <button className='btn-primary' onClick={() => { this.onClickButton('SelectAll_DSQL') }}>SelectAll_DSQL</button>&nbsp;
                 <button className='btn-primary' onClick={() => { this.onClickButton('Select') }}>Select</button>&nbsp;
                 <button className='btn-primary' onClick={() => { this.onClickButton('Insert') }}>Insert</button>&nbsp;
                 <button className='btn-primary' onClick={() => { this.onClickButton('Update') }}>Update</button>&nbsp;
                 <button className='btn-primary' onClick={() => { this.onClickButton('Delete') }}>Delete</button>
             </div>
         );
     }
 
     onClickButton(actionType: string): void {
         this.props.onClickButton(actionType);
     }
 }
 
 export default Buttons;
 
 ```

**修正内容 [#e4537aef]
crudSample2Sliceは、crudSample、crudSample2で共用するため、crudSampleSliceにリネーム。

-先ずは「store」の内容
--crudSample2Slice.ts
 import { createSlice } from '@reduxjs/toolkit'
 import type { PayloadAction } from '@reduxjs/toolkit'
 
 interface CrudSample2SliceState {
   message: string;
 }
 
 const initialState: CrudSample2SliceState = {
   message: '',
 }
 
 const crudSample2Slice = createSlice({
   name: 'crudSample2',
   initialState,
   reducers: {
     setMessage: (state, action: PayloadAction<string>) => {
       state.message = action.payload;
     },
     clearMessage: (state) => {
       state.message = '';
     },
   },
 })
 
 export const { setMessage, clearMessage } = crudSample2Slice.actions
 export default crudSample2Slice.reducer

--index.ts
 import { configureStore } from '@reduxjs/toolkit'
 import counterReducer from './counterSlice'
 import crudSample2Reducer from './crudSample2Slice'  // ← 追加
 
 export const store = configureStore({
   reducer: {
     counter: counterReducer,
     crudSample2: crudSample2Reducer,  // ← 追加
   },
 })
 
 export type RootState = ReturnType<typeof store.getState>
 export type AppDispatch = typeof store.dispatch

-CrudSample2.tsx

--ヘッダ
 // Redux 関連のインポート
 import { connect } from 'react-redux';
 import type { RootState, AppDispatch } from '../store';
 import { setMessage, clearMessage } from '../store/crudSampleSlice';
 
 // ===== 型定義 =====
 
 // Propsの型定義
 interface StateProps {
   message: string;
 }
 interface DispatchProps {
   onSetMessage: (msg: string) => void;
   onClearMessage: () => void;
 }
 
 // コンポーネント自身のProps(connect後に外から渡すものは空)
 type CrudSampleProps = StateProps & DispatchProps;
 
 ...
 
 interface CrudSample2State {
  //message: string; は Redux管理のため削除
  ddl: DdlState;
  shipper: ShipperState;
  shippers: ShipperState[];
  loading: boolean;
 }

--ボディ

---クラス冒頭部
 export class CrudSample2 extends React.Component<CrudSampleProps, CrudSample2State> //<Record<string, never>, CrudSample2State>
 {
   constructor(props: CrudSampleProps) {//Record<string, never>) {
     super(props);
 
     this.state = {
       // message: '', は Redux管理のため削除

---Render部
 this.props.message → this.state.message

---イベントハンドラ
  // ===== WebAPI イベントハンドラ =====
  // this.setState({ message: ... }) → this.props.onSetMessage(...) に変更
  // this.setState({ message: '' })  → this.props.onClearMessage()  に変更

--フッタ
 // ===== Redux connect =====
 
 const mapStateToProps = (state: RootState): StateProps => ({
   message: state.crudSample.message,
 });
 
 const mapDispatchToProps = (dispatch: AppDispatch): DispatchProps => ({
   onSetMessage: (msg: string) => dispatch(setMessage(msg)),
   onClearMessage: () => dispatch(clearMessage()),
 });
 
 export default connect(mapStateToProps, mapDispatchToProps)(CrudSample2);

**同様にAboutを修正 [#e29b5de3]
同様に

 import { useSelector } from 'react-redux'
 import type { RootState } from '../store'
 
 export default function About() {
   // Reduxのstoreから値を取得
   const count = useSelector((state: RootState) => state.counter.value)
   const message = useSelector((state: RootState) => state.crudSample.message)
   
   return <div>
     <h1>About</h1>
     <p>About Pageです。</p>
     {/* countを表示 */}
     <p>現在のカウント: {count}</p>
     {/* messageを表示 */}
     <p>現在のメッセージ: {message}</p>
   </div>  
 }

*参考 [#u1559c3a]
-FrontendTemplates/UI/SPA/React at develop · OpenTouryoProject/FrontendTemplates~
https://github.com/OpenTouryoProject/FrontendTemplates/tree/develop/UI/SPA/React

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS