まず3つのHooks APIを学びましょう
- useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init)
useState の代替となるもので、 => newState という形式のリデューサーを受け取り、それにマッチするディスパッチメソッドとともに現在の状態を返します。
- useEffect
useEffect に割り当てられた関数は、コンポーネントが画面にレンダリングされた後に遅延されます。デフォルトでは、effect はレンダリングの各ラウンドの最後に実行されますが、特定の値が変更されたときのみ実行するように選択することもできます。
- useLayoutEffect
この関数のシグネチャは useEffect と同じですが、すべての DOM の変更後に同期的に effect を呼び出します。effect は DOM レイアウトを読み込み、同期的に再描画をトリガするために使用できます。ブラウザが描画を実行すると、内部の更新スケジュールが同期的に更新されます。
import React, {useReducer, useLayoutEffect, useEffect} from "react";
import {counterReducer} from "../store";
const init = initArg => {
//渡された初期値を変更する。ここでは文字列をintに変換している。
return initArg - 0;
};
export default function HooksPage(props) {
const [state, dispatch] = useReducer(counterReducer, "0", init);
useEffect(() => {
console.log("useEffect"); //sy-log
});
useLayoutEffect(() => {
console.log("useLayoutEffect"); //sy-log
});
console.log("---"); //sy-log
return (
<div>
<h3>HooksPage</h3>
<p>{state}</p>
<button onClick={() => dispatch({type: "ADD"})}>add</button>
</div>
); }
react-reduxの使用
yarn add react-redux
2つのApiが用意されています:
connect
コンポーネントのデータと変更方法の提供
mapStateToProps(state,ownProps)
- コールバック関数はコンポーネントのpropsとマージされる純粋なオブジェクトを返す必要があり、このパラメータが定義されていればコンポーネントはReduxストアの変更をリッスンします。
- ownPropsは現在のコンポーネントのpropsで、指定された場合、コンポーネントが新しいpeopsを受け取るとすぐに、mapStateToPropsが呼び出され、mapStateToPropsは再計算され、mapDispatchToPropsも呼び出されます。パラメータを渡さないでください。
mapDispatchToProps(dispatch,ownProps),
- オブジェクトが渡された場合、そのオブジェクト上で定義された各関数はReduxアクションクリエイターとして扱われ、オブジェクト上で定義されたメソッド名がプロパティ名として使用されます; 各メソッドは新しい関数を返し、アクションクリエイターの戻り値を引数としてメソッドが実行されます。これらのプロパティはコンポーネントのpropsにマージされます。
- 関数を渡された場合、関数は関数を受け取り、オブジェクトをどのように返すかはあなた次第です。4. ownPropsは現在のコンポーネント自身のpropsです。 指定された場合、コンポーネントが新しいpropsを受け取るたびにmapDispatchToPropsが呼び出されます。パフォーマンスに注意してください!
mergeProps(stateProps, dispatchProps, ownProps)
- このパラメータを指定すると、mapStateToProps() および mapDispatchToProps() の結果と、コンポーネント自身の props がこのコールバック関数に渡されます。このコールバック関数が返すオブジェクトは、ラップされたコンポーネントに props として渡されます。
connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])
ステップ1:子孫コンポーネントにストアを提供し、グローバルにストア --- index.jsを提供します。
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import {Provider} from "react-redux";
import store from "./store/";
// Providerをルートコンポーネントの外側に置き、サブコンポーネントがストアを取得できるようにする
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
ステップ2: 状態データの取得 - ReactReduxPage.js
import React, { Component } from "react";
import { connect } from "react-redux";
class ReactReduxPage extends Component {
render() {
const { num, add, minus, asyAdd } = this.props;
return (
<div>
<h1>ReactReduxPage</h1>
<p>{num}</p>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
</div>
); }
}
const mapStateToProps = state => {
return {
num: state,
};
};
const mapDispatchToProps = {
add: () => {
return { type: "add" };
},
minus: () => {
return { type: "minus" };
}
};
export default connect(
mapStateToProps, //状態のマッピング mapStateToProps
mapDispatchToProps, //ディスパッチイベントのマッピング
)(ReactReduxPage);
: 詳しい使い方
// ここで使われている形式はexport default connectであり、通常プロジェクトではexport default connect(mapStateToProps)を使って分割される。,mapDispatchToProps)(Component)
import React, {Component} from "react";
import {connect} from "react-redux";
import {bindActionCreators} from "redux";
// connectReactコンポーネントをストアに接続するには、すでにストアに接続されている新しいコンポーネントクラスを返す。
export default connect(
// mapStateToProps Fucntion
// !一度ownPropsを定義すると、ownPropsが変更されるたびに現在のmapStateToPropsが呼び出されるので、ownPropsの定義は慎重に行うこと。
// !ここでの状態も再計算され、パフォーマンス状態に影響しやすくなる=> {
// console.log("mapStateToProps"); //sy-log
return {
count: state
};
},
// mapDispatchToProps オブジェクトを受け取る||Fucntion
// Object この時点では、propsにディスパッチは存在しないが、内部でディスパッチを実装しているアクションクリエイターが存在する。//
// {
// add: () => ({type: "ADD"}),
// minus: () => ({type: "MINUS"})
// }
// Fucntion パラメータはdispatchとownPropsである。
// !ownPropsを一度定義してしまうと、ownPropsが変更されるたびに現在の
mapStateToPropsすべてが呼び出されるため、パフォーマンスに影響しやすい
(dispatch, ownProps) => {
console.log("mapDispatchToProps--", ownProps); //sy-log
let creators = {
add: payload => ({type: "ADD", payload}),
minus: () => ({type: "MINUS"})
};
creators = bindActionCreators(creators, dispatch);
return {dispatch, ...creators};
}
)(
class ReactReduxPage extends Component {
add = () => {
this.props.dispatch({type: "ADD"});
};
render() {
console.log("props", this.props); //sy-log
const {count, dispatch, add, minus} = this.props;
return (
<div>
<h3>ReactReduxPage</h3>
<p>omg:{count}</p>
<button onClick={this.add}>add-use dispatch</button>
<button onClick={() => add(100)}> add</button>
<button onClick={minus}>minus</button>
</div>
);
}
});
react-reduxの実装
コネクトメソッドの高レベルコンポーネント実装
import React, {useContext, useEffect, useReducer, useLayoutEffect} from "react";
// Providerインデックス.jsReact-routerカスタムフックは、コンテキストを使ってストアを渡すために使われる。
// すべてのサブコンポーネントがストアを受け取れるようにする
const Context = React.createContext();
// 実装: connect(){count}) => ({count}), { add: ()=>({type: 'ADD})})(Cmp)
export const connect = (
mapStateToProps = state => state,
mapDispatchToProps
) => WrappedComponent => props => {
// ストアの状態を読み込む
const store = useContext(Context);
const {getState, dispatch, subscribe} = store;
const stateProps = mapStateToProps(getState());
// dispatch object | function
let dispatchProps = {dispatch};
if (typeof mapDispatchToProps === "function") {
dispatchProps = mapDispatchToProps(dispatch);
} else if (typeof mapDispatchToProps === "object") {
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
}
// 以下は、関数コンポーネントのバージョンに対するforceUpdateの実装である。
const [forceUpdate] = useReducer(x => x + 1, 0);
// useLayoutEffectは、サブスクリプションを遅延させる代わりにリアルタイムで更新し、実装後にサブスクリプションをキャンセルするために戻るために、ここで使用される。[store]リンクデータ
useLayoutEffect(() => {
const unsubscribe = subscribe(() => {
// store state 変更が発生する forceUpdateは強制更新である。
forceUpdate();
});
return () => {
if (unsubscribe) {
unsubscribe();
}
};
}, [store]);
return <WrappedComponent {...props} {...stateProps} {...dispatchProps} />;
};
// プロバイダがストアを提供するのは、ストアにステートディスパッチがサブスクライブされているからである。
export function Provider({store, children}) {
return <Context.Provider value={store}>{children}</Context.Provider>;
}
function bindActionCreator(creator, dispatch) {
return (...args) => dispatch(creator(...args));
}
export function bindActionCreators(creators, dispatch) {
const obj = {};
for (let key in creators) {
obj[key] = bindActionCreator(creators[key], dispatch);
}
return obj;
}
react-redux hooks API
- セレクタを使用してデータを取得
- useSelectorデータの取得
export function useSelector(selector) {
const store = useStore();
const {getState, subscribe} = store;
const selectedState = selector(getState());
// 手動でforceUpdateを実装する
const [forceUpdate] = useReducer(x => x + 1, 0);
// これは、前のフックの説明で使ったuseLayoutEffectを、遅延更新ではなくリアルタイム更新のために使う。
useLayoutEffect(() => {
const unsubscribe = subscribe(() => {
// store state 変更が発生する forceUpdateは強制更新である。
forceUpdate();
});
return () => {
if (unsubscribe) {
unsubscribe();
}
};
}, [store]);
return selectedState;
}
export function useDispatch() {
const store = useStore();
return store.dispatch;
}
function useStore() {
const store = useContext(Context);
return store;
}
react-router カスタムフックの使い方
import React, {useCallback} from "react";
import {useSelector, useDispatch} from "../ReactRedux";
export default function ReduxHooksPage(props) {
// ステートの値を取得する
const count = useSelector(({count}) => count);
// ディスパッチを取得する
const dispatch = useDispatch();
// [] をuseCallbackの依存リストとして使用する。これにより、再レンダリング時にコールバックが変更されないので、Reactは不要な時にコールバックを呼び出さない。
// useMemoパラメータのキャッシュ、関数のuseCallbackキャッシュ、依存関係が変更されたときのみ実行または変更される
// useCallbackでラッピングし、レンダリングによる変更を避ける
const add = useCallback(() => {
dispatch({type: "ADD"});
}, []);
return (
<div>
<h3>ReduxHooksPage</h3>
<p>{count}</p>
<button onClick={add}>add</button>
</div>
);
}





