React状態管理ツール-Redux学習ノート


React状態管理ツール-Redux
前端高給訓練キャンプ
1.Reduxコア
1.1 Redux紹介
JavaScript状態容器は、予測可能な状態管理を提供します.
1.2 Reduxコア概念とフロー
Store:格納状態のコンテナ、JavaScriptオブジェクトView:ビュー、HTMLページアクション:オブジェクト、状態に対してどのような操作が行われているかを記述し、Reducers:関数、操作状態を説明し、新しい状態に戻ります.
1.3 Redux使用:カウンタケース

<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Reduxtitle>
head>
<body>
  <button id="minus">-button>
  <span id="count">0span>
  <button id="plus">+button>

  <script src="./redux.min.js">script>
  <script>

    // 3.       
    const initialState = {
      
      count: 0
    }

    // 2.    reducer   
    function reducer(state = initialState, action) {
      
      switch (action.type) {
      
        case 'increment':
          return {
       count: state.count + 1 };
        case 'decrement':
          return {
       count: state.count - 1 };
        default:
          return state;
      }
    }

    // 1.    store   
    const store = Redux.createStore(reducer);

    // 4.    action 
    const increment = {
       type: 'increment' }
    const decrement = {
       type: 'decrement' }

    // 5.               
    document.getElementById('minus')
    .addEventListener('click', function () {
      
      // 6.   dispatch     action 
      store.dispatch(decrement)
    })
    document.getElementById('plus')
    .addEventListener('click', function () {
      
      // 6.   dispatch     action 
      store.dispatch(increment)
    })

    //   store {dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, Symbol(observable): ƒ}
    console.log(store)
    //    store        state
    console.log(store.getState());

    //        
    store.subscribe(() => {
      
      console.log(store.getState())
      document.getElementById('count').innerHTML = store.getState().count
    });
  script>
body>
html>
1.4 ReduxコアAPI
//    store   
const store = Redux.createStore(reducer);
// 2.    reducer   
function reducer(state = initialState, action) {
     
  switch (action.type) {
     
    case 'increment':
      return {
      count: state.count + 1 };
    case 'decrement':
      return {
      count: state.count - 1 };
    default:
      return state;
  }
}
//    store        state
store.getState()
//        
store.subscribe(() => {
     
  console.log(store.getState())
});
//   dispatch     action 
store.dispatch(increment)
2.React+Redux
2.1 ReactでReduxを使わない時に発生した問題
Reactではコンポーネント通信のデータストリームは一方向であり、トップのコンポーネントはProps属性によって下部のコンポーネントにデータを転送することができ、下部のコンポーネントは上部のコンポーネントにデータを転送することができない.下のコンポーネントでデータを修正するには、上のコンポーネントから下のコンポーネントに修正データを転送する方法が必要です.プロジェクトがますます大きくなると、コンポーネント間のデータ転送が難しくなります.
2.2 ReactプロジェクトにReduxを加えるメリット
Reduxを使用してデータを管理し、Storeはコンポーネントと独立しているため、データ管理がコンポーネントと独立し、コンポーネントとコンポーネントの間でデータを転送するのが困難な問題を解決した.
2.3ダウンロードRedux
npm install redux react-redux
2.4 Reduxワークフロー
  • コンポーネントは、dispach方法によってアクションをトリガする
  • .
  • Storeはアクションを受け取り、アクションをReducer
  • に配信する.
  • Reducerは、アクションタイプに従って状態を変更し、変更された状態をStore
  • に戻す.
  • コンポーネントはStoreの状態を購読しています.Storeの状態更新はコンポーネント
  • に同期されます.
    2.5 Redux使用手順
    2.5.1 storeを作成する
    // src/store/index.js
    import {
          createStore } from 'redux'
    import reducer from './reducers/counter.reducer'
    export const store = createStore(reducer)
    
    ルートコンポーネントにstoreを使用する.
    import React from 'react';
    import ReactDOM from 'react-dom';
    import Counter from './components/Counter'
    import {
          Provider } from 'react-redux'
    import {
         store} from './store'
    /**
     * react-redux
     * Provider
     * connect
     */
    
    ReactDOM.render(
      //    provider   , store                 
      <Provider store={
         store}>
        <Counter />
      </Provider>,
      document.getElementById('root')
    );
    
    2.5.2 reducerを作成する
    // src/store/reducers/counter.reducer.js
    import {
          DECREMENT, INCREMENT } from "../count/counter.const";
    
    const initialState = {
         
      count: 0
    }
    
    export default function reducer (state = initialState, action) {
         
      switch (action.type) {
         
        case INCREMENT:
          return {
          count: state.count + 1 };
        case DECREMENT:
          return {
          count: state.count - 1 };
        default:
          return state;
      }
    }
    
    
    // src/store/count/counter.const.js
    export const INCREMENT = 'increment'
    export const DECREMENT = 'decrement'
    
    2.5.3コンポーネントの中でconnectを使ってstoreの中のstateとdispatchを受け入れます.connect方法は、2つのパラメータを受け取り、高次のコンポーネントを返す.connect方法の最初のパラメータはmapStateToProps方法であり、storeのstateをコンポーネントのpropsに伝達し、mapStateToProps方法のパラメータはstateであり、戻り値はオブジェクトであり、コンポーネントに伝達される.
    const mapStateToProps = (state) => ({
         
      count: state.count,
      a: 'a', //       ,             
    })
    
    connect方法の2番目のパラメータはmapDispatchToProps方法であり、storedispatchをコンポーネントのpropsに伝達し、mapDispatchToProps方法のパラメータはdispatchであり、戻り値はオブジェクトであり、オブジェクト内の方法はdispatchを使用してもよく、このオブジェクト内の方法はコンポーネントに伝達される.
    const mapDispatchToProps = (dispatch) => ({
         
      increment () {
         
        dispatch({
          type: 'increment'})
      },
      decrement () {
         
        dispatch({
          type: 'decrement' })
      }
    })
    
    また、reduxbindActionCreatorsを介して、action関数を作成しても良いです.
    import {
         bindActionCreators} from 'redux'
    
    // bindActionCreators        
    const mapDispatchToProps = dispatch => (
      //   
      ...bindActionCreators({
         
        increment () {
         
          return {
          type: 'increment'}
        },
        decrement () {
         
          return {
          type: 'decrement'}
        }
      }, dispatch)
    )
    
    または作成:
    const mapDispatchToProps = dispatch => bindActionCreators({
         
        increment () {
         
          return {
          type: 'increment'}
        },
        decrement () {
         
          return {
          type: 'decrement'}
        }
      }, dispatch)
    
    bindActionCreatorsの最初のパラメータをオフにしてもよい.
    import * as counterActions from '../store/actions/counter.actions'
    
    const mapDispatchToProps = dispatch => bindActionCreators(conterActions, dispatch)
    
    // src/store/actions/counter.actions.js
    import {
          DECREMENT, INCREMENT } from "../count/counter.const"
    
    export const increment = () => ({
         type: INCREMENT})
    export const decrement = () => ({
         type: DECREMENT})
    
    connect方法は、mapStateToPropsおよびmapDispatchToPropsを受け取り、高次のコンポーネントに戻り、Counterのコンポーネントに導かれる.
    export default connect(mapStateToProps, mapDispatchToProps)(Counter)
    
    
    最終コンポーネントコードは以下の通りです.
    // src/components/Counter.js
    import React from 'react'
    import {
         connect} from 'react-redux'
    import {
         bindActionCreators} from 'redux'
    import * as counterActions from '../store/actions/counter.actions'
    
    function Counter ({
         count, increment, decrement}) {
         
      return (
        <div>
          <button onClick={
         decrement}>-</button>
          <span>{
         count}</span>
          <button onClick={
         increment}>+</button>
        </div>
      )
    }
    
    // 1. connect          store, store          ,           
    // 2. connect           store     ,        props       
    // 3. connect           dispatch   
    
    const mapStateToProps = (state) => ({
         
      count: state.count,
      a: 'a', //       ,             
    })
    
    const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch)
    export default connect(mapStateToProps, mapDispatchToProps)(Counter)
    
    2.5.4 action伝達パラメータ
  • リレーパラメータ
  • <button onClick={
         () => increment(5)}> + 5</button>
    
  • は、パラメータを受け取り、reducer
  • を伝える.
    export const increment = payload => ({
         type: INCREMENT, payload})
    export const decrement = payload => ({
         type: DECREMENT, payload})
    
  • reducerは、受信したデータに基づいて処理する.
    export default function reducer (state = initialState, action) {
         
      switch (action.type) {
         
        case INCREMENT:
          return {
          count: state.count + action.payload };
        case DECREMENT:
          return {
          count: state.count - action.payload };
        default:
          return state;
      }
    }
    
    2.6 reduxポップアップブロックの判例を実現するstore中の状態が多いほど、reducer中のswitch分岐は多くなり、維持に不利で、reducerを分割する必要がある.
    src/index.js
    // src/index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';
    import {
          Provider } from 'react-redux'
    import {
         store} from './store'
    
    ReactDOM.render(
      //    provider   , store                 
      <Provider store={
         store}>
        <App />
      </Provider>,
      document.getElementById('root')
    );
    
    src/store/index.js
    // src/store/index.js
    import {
          createStore } from 'redux'
    import reducer from './reducers/counter.reducer'
    
    export const store = createStore(reducer)
    
    src/store/reducers/counter.reducer.js
    // src/store/reducers/counter.reducer.js
    import {
          DECREMENT, INCREMENT } from "../const/counter.const";
    import {
          HIDEMODAL, SHOWMODAL } from "../const/modal.const";
    
    const initialState = {
         
      count: 0,
      show: false
    }
    
    export default function reducer (state = initialState, action) {
         
      switch (action.type) {
         
        case INCREMENT:
          return {
          ...state, count: state.count + action.payload };
        case DECREMENT:
          return {
          ...state, count: state.count - action.payload };
        case SHOWMODAL:
          return {
          ...state, show: true };
        case HIDEMODAL:
          return {
          ...state, show: false };
        default:
          return state;
      }
    }
    
    src/App.js
    // src/App.js
    import Modal from './components/Modal'
    import Counter from './components/Counter'
    
    function App() {
         
      return (
        <div className="App">
          <Counter />
          <Modal />
        </div>
      );
    }
    
    export default App;
    
    src/components/Modal.js
    // src/components/Modal.js
    import React from 'react'
    import {
          connect } from 'react-redux'
    import {
          bindActionCreators } from 'redux'
    import * as modalActions from '../store/actions/modal.actions'
    
    function Modal ({
          showStatus, show, hide }) {
         
      const styles = {
         
        display: showStatus ? 'block': 'none',
        width: 200,
        height: 200,
        position: 'absolute',
        top: 0,
        right: 0,
        bottom: 0,
        left: 0,
        margin: 'auto',
        backgroundColor: 'skyblue'
      }
      return (
        <div>
          <button onClick={
         show}>  </button>
          <button onClick={
         hide}>  </button>
          <div style={
         styles}></div>
        </div>
      )
    }
    
    const mapStateToProps = state => ({
         
      showStatus: state.show
    })
    const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch)
    
    export default connect(mapStateToProps, mapDispatchToProps)(Modal)
    
    src/store/actions/modal.actions.js
    // src/store/actions/modal.action.js
    import {
          HIDEMODAL, SHOWMODAL } from "../const/modal.const"
    
    export const show = () => ({
          type: SHOWMODAL })
    export const hide = () => ({
          type: HIDEMODAL })
    
    src/store/const/modal.co nst.js
    // src/store/const/modal.const.js
    export const SHOWMODAL = 'showModal'
    export const HIDEMODAL = 'hideModal'
    
    2.7分割reducerreducerによって提供されるツールcombineReducersを使用して、それぞれの小さなreducerをマージする.
    src/store/reducers/root.reducer.js
    // src/store/reducers/root.reducer.js
    import {
         combineReducers} from 'redux'
    import CounterReducer from './counter.reducer'
    import ModalReducer from './modal.reducer'
    
    // { counter: { count: 0 }, modal: { show: false } }
    export default combineReducers({
         
      counter: CounterReducer,
      modal: ModalReducer
    })
    
    src/store/reducers/counter.reducer.js
    // src/store/reducers/counter.reducer.js
    import {
          DECREMENT, INCREMENT } from "../const/counter.const";
    
    const initialState = {
         
      count: 0,
    }
    
    export default function counterReducer (state = initialState, action) {
         
      switch (action.type) {
         
        case INCREMENT:
          return {
          ...state, count: state.count + action.payload };
        case DECREMENT:
          return {
          ...state, count: state.count - action.payload };
        default:
          return state;
      }
    }
    
    src/store/reducers/modal.reducer.js
    // src/store/reducers/modal.reducer.js
    import {
          HIDEMODAL, SHOWMODAL } from "../const/modal.const";
    const initialState = {
         
      show: false
    }
    
    export default function modalReducer (state = initialState, action) {
         
      switch (action.type) {
         
        case SHOWMODAL:
          return {
          ...state, show: true };
        case HIDEMODAL:
          return {
          ...state, show: false };
        default:
          return state;
      }
    }
    
    storeを作成するときに入ってきたreducerは、私たちが先ほど定義したroot.reducer.jsから来ています.
    import {
          createStore } from 'redux'
    import RootReducer from './reducers/root.reducer'
    
    export const store = createStore(RootReducer)
    
    各コンポーネントのmapStateToPropsにおいても、対応する変更が発生する(state.counterおよびstate.modal).
    const mapStateToProps = (state) => ({
         
      count: state.counter.count,
    })
    
    const mapStateToProps = state => ({
         
      showStatus: state.modal.show
    })
    
    3.Redux中間部品
    3.1中間部品とは何ですか?
    中間価格は私達がreduxアプリケーションを拡張し、強化することができます.
    3.2 Redux中間部品の開発
    中間部品のテンプレートを開発する
    export default store => next => action => {
           }
    
    3.3中間部品を登録する
    中間部品は開発が完了した後、登録されてこそ、Reduxのワークフローで有効になります.
    src/store/index.js
    // src/store/index.js
    import {
          createStore, applyMiddleware } from 'redux'
    import logger from './middlewares/logger'
    
    createStore(reducer, applyMiddleware(
      logger
    ))
    
    src/store/middleware/logger.js
    const logger = store => next => action => {
         
      console.log(store)
      console.log(action)
      next(action) //         next(action)
    }
    export default logger
    
    複数の中間部品を登録する場合、中間部品の実行順序は登録順序です.
    createStore(reducer, applyMiddleware(
      logger,
      test
    ))
    
    実行順序は、loggerミドルウェアを先に実行し、testミドルウェアを実行することである.中間部品の終わりにnext(action)を起動しないと、フロー全体はここでカードになります.これ以上実行しません.
    3.4 Redux中間部品開発例thunk(非同期中間部品)
    現在のこの中間関数は、どのような非同期操作を実行したいのかに関心がなく、ただ非同期操作を実行しているかに関心があります.もしあなたが実行しているのが非同期操作であれば、actionをトリガする時に、関数を伝えてください.もし実行しているのが同期操作なら、actionオブジェクトを伝達します.非同期の操作コードは、あなたが送ってきた関数の中に書いてください.この中間部品の関数は、あなたが送ってきた関数を呼び出した時に、dispach方法を過去のsrc/store/middleware/thunk.jsに伝えます.
    // src/store/middleware/thunk.js
    import {
          DECREMENT, INCREMENT } from "../const/counter.const";
    
    const thunk = ({
         dispatch}) => next => action => {
         
      if (typeof action === 'function') {
         
        return action(dispatch) // action           dispatch 
      }
      next(action)
    }
    export default thunk
    
    非同期関数actionをactionファイルに定義します.
    src/store/actions/modal.actions.js
    // src/store/actions/modal.actions.js
    import {
          HIDEMODAL, SHOWMODAL } from "../const/modal.const"
    
    export const show = () => ({
          type: SHOWMODAL })
    export const hide = () => ({
          type: HIDEMODAL })
    
    export const show_async = () => dispatch => {
         
      setTimeout(() => {
         
        dispatch(show())
      }, 2000);
    }
    
    元々はshowを使っていたところをshow_asyncに切り替え、非同期の機能を実現しました.
    4.Redux常用ミドルウェア
    4.1 redux-thunk
    4.1.1 redux-thunkダウンロード
    npm install redux-thunk
    
    4.1.2 redux-thunkの導入
    import thunk from 'redux-thunk';
    
    4.1.3 redux-thunkを登録する
    import {
          applyMiddleware } from 'redux'
    createStore(rootReducer, applyMiddleware(thunk));
    
    4.1.4 redux-thunk中間部品を使用する
    const loadPosts = () => async dispatch => {
         
      const posts = await axios.get('/api/posts').then(response => response.data);
      dispatch({
         type: LOADPOSTSSUCCE, payload: posts});
    }
    
    4.2 redux-saga
    4.2.1 redux-saga解決の問題redux-sagaは、非同期動作をAction Creatorファイルから引き出して、別個のファイルに置くことができる.
    4.2.2ダウンロードredux-saga
    npm install redux-saga
    
    4.2.3レdux-sagaミドルウェアを作成する
    src/store/index.js
    // src/store/index.js
    import createSagaMiddleware from 'redux-saga';
    const sagaMiddleware = createSagaMiddleware();
    
    4.2.4サイガミドレワに登録する
    src/store/index.js
    // src/store/index.js
    createStore(reducer, applyMiddleware(sagaMiddleware))
    
    4.2.5 sagaを使ってaction非同期を受け入れて操作を実行する
    src/store/sagas/counter.saga.js
    // src/store/sagas/counter.saga.js
    import {
          takeEvery, put, delay } from 'redux-saga/effects'
    import {
          increment } from '../actions/counter.actions'
    import {
          INCREMENT_ASYNC } from '../const/counter.const'
    
    // takeEvery    action 
    // put    action 
    
    function * increment_async_fn (action) {
         
      yield delay(2000) //      2  
      yield put(increment(action.payload))
    }
    
    export default function * counterSaga () {
         
      //    action
      yield takeEvery(INCREMENT_ASYNC, increment_async_fn) //              action   
    }
    
    src/store/actions/counter.actions.js
    // src/store/actions/counter.actions.js
    
    //   saga   
    export const increment_async = (payload) => ({
          type: INCREMENT_ASYNC, payload });
    
    src/store/const/counter.co nst.js
    // src/store/const/counter.const.js
    export const INCREMENT_ASYNC = 'increment_async'
    
    src/components/Counter.js
          <button onClick={
         () => increment_async(20)}>+</button>
    
    4.2.6サガを起動する
    src/store/index.js
    // src/store/index.js
    import counterSaga from './sagas/counter.saga'
    
    sagaMiddleware.run(counterSaga);
    
    4.2.7合併saga
    src/store/saga/root.saga.js
    // src/store/saga/root.saga.js
    import {
          all } from 'redux-saga/effects'
    import counterSaga from './counter.saga'
    import modalSaga from './modal.saga'
    
    export default function * rootSaga () {
         
      yield all([
        counterSaga(),
        modalSaga()
      ])
    }
    
    modal.saga.jsは変わりません.modal.saga.jsは以下の通りです.
    src/store/saga/modal.saga.js
    // src/store/saga/modal.saga.js
    import {
          takeEvery, put, delay } from 'redux-saga/effects'
    import {
          show } from '../actions/modal.actions'
    import {
          SHOWMODAL_ASYNC } from '../const/modal.const'
    
    // takeEvery    action 
    // put    action 
    
    function * showModal_async_fn () {
         
      yield delay(2000)
      yield put(show())
    }
    
    export default function * modalSaga () {
         
      //    action
      yield takeEvery(SHOWMODAL_ASYNC, showModal_async_fn)
    }
    
    store入口ファイルのsagaミドルウェア起動root.saga
    src/store/index.js
    // src/store/index.js
    import rootSaga from './sagas/root.saga'
    
    sagaMiddleware.run(rootSaga)
    
    4.3 redux-actions
    4.3.1 redux-actions解決の問題
    reduxプロセスの中で大量のサンプルコードは読み书きに苦しみます.redux-actionを使ってアクションとReducerの処理を简略化できます.
    4.3.2 redux-actionのダウンロード
    npm install redux-actions
    
    4.3.3アクションを作成する
    import {
          createAction } from 'redux-actions'
    
    const increment_action = createAction('increment');
    const decrement_action = createAction('decrement');
    
    4.3.4 Reducerを作成する
    src/store/actions/counter.actions.js
    src/store/actions/counter.actions.js
    //    redux-actions 
    import {
          createAction } from 'redux-actions'
    
    export const increment = createAction('increment')
    export const decrement = createAction('decrement')
    
    src/store/reducers/counter.reducer.js
    // src/store/reducers/counter.reducer.js
    import {
          handleActions as createReducer } from 'redux-actions'
    import {
          increment, decrement } from '../actions/counter.actions'
    const initialState = {
         
      count: 0,
    }
    const handleIncrement = (state, action) => ({
         
      count: state.count + action.payload
    })
    
    const handleDecrement = (state, action) => ({
         
      count: state.count - action.payload
    })
    
    export default createReducer({
         
      [increment]: handleIncrement,
      [decrement]: handleDecrement,
    }, initialState)
    
    コンポーネント使用:src/components/Counter.js
    // src/components/Counter.js
    function Counter ({
         count, increment, decrement}) {
         
      return (
        <div>
          <button onClick={
         () => decrement(1)}>-</button>
          <span>{
         count}</span>
          <button onClick={
         () => increment(1)}>+</button>
        </div>
      )
    }
    
    redux-actionsはredux-sagaにも結合して使用できます.