React開發——step5 深刻淺出redux

redux概述

redux是一個狀態管理器。在react中使用redux能夠幫咱們實現全局的數據管理。這篇文章選擇了最簡單的計數器爲例子,經過純redux的使用, react跟redux配合使用、以及異步redux從漸到深介紹redux。
首先,咱們要先理清楚基礎的概念以及工做流程。咱們須要理解的地方就是:
store.dispatch(action) => excute reducer => change state
image.pngreact

理解store

store在我理解,它是一個管理者的概念。json

1.建立store: 咱們能夠用combineReducers把全部的reducer傳進去做爲參數,固然也能夠只傳一個reducerredux

const store = createStore(
    combineReducers({
      todos,
      counter
    })
  );

2.dispatch: 經過store.dispatch(action)能夠觸發對應的reducer(這裏的對應的,主要依賴於reducer裏面對action的type進行判斷)
3.subscribe: 經過store.subscribe能夠監聽state的變化。app

store.subscribe(() => console.log(store.getState()));
理解reducer

reducer,它是一個修改state的函數。這裏的修改,不是修改某個屬性值,而是直接返回一個新的state。能夠用咱們的...,也能夠用Object.assign。它主要負責根據action.type,修改不一樣的state屬性值。異步

const initialState = { count: 0 };
 const counter = (state = initialState, action) => {
    switch (action.type) {
      case "CUSTOM_COUNT":
        return {
          count: state.count + action.count
        };
      default:
        break;
    }
  };
理解actions

actions就是一個純函數。在同步的action裏面,它負責返回一個對象,給dispatch執行。ide

function addOne() {
      return {type: 'ADD', count: 1}
  }

用一個計數器的例子幫你理解redux

reudx開發流程

咱們寫代碼的時候,
首先肯定有多少種actions,這裏建議定義一個actionsType.js的文件。
而後寫reducer,在reducer引入actionsType.js, 裏面進行switch的時候,寫成actionsType.XXX以免拼寫錯誤問題。
而後combine reducer,建立store
最後,寫actions,引入actionsType.js,這裏面的函數就只是返回一個對象的純函數,這個對象有個type,就用actionsType.XXX,你願意返回什麼值就返回什麼值,跟reducer對的上就好啦。
僞代碼:
actionsType.js函數

export default () => (
     {
        ADD_ONE: 'ADD_ONE'
     }
 )

reducerpost

import actionsTypes from "./actionsType.js"
  const reducer1 = (state = {}, action) => {
     switch (action.tyep) {
        case actionsTypes.ADD_ONE:
            return {...state, count: action.count}
        default:
            return state;
     }
  }

actionsfetch

import actionsTypes from "./actionsType.js"
 function addOne() {
       return {actionsTypes.ADD_ONE, count: 1} // 上面的reducer取的就是action.count,就是由於咱們這裏給它的是這樣的。
 }

storethis

const store = createStore(
    combineReducers({
      reducer1
    })
  );
  
  store.dispatch(addOne())// 注意dispatch的是一個對象,因此是action執行後的結果
  store.suscibe(() => console.log(store.reducer1.getState()))// 若是建立的時候不是combineReducers就能夠直接store.getState()

下面的例子爲了便於理解,就沒有分那麼多個文件了。

pureRedux

直接上代碼,由於具體步驟解釋上面的僞代碼應該已經寫清楚了。

import {
    createStore,
    combineReducers,
    bindActionCreators
} from "redux";
import React from "react"

function run () {

    const initialState = { count: 0 };

    // reducer
    function counter (state = initialState, action) { 
        switch (action.type) {
            case 'ADD':
                return { count: state.count + action.count} // create a new state, rather than just change the state.count
            case 'REDUCE':
                return { count: state.count - action.count}
            default:
                return state
        }
    }

    const store = createStore(combineReducers({counter}))
    store.subscribe(() => {
        console.log(store.getState())
    })

    //action
    function addOne() {
        return {type: 'ADD', count: 1}
    }

    function reduceOne() {
        return {type: 'REDUCE', count: 1}
    }

    // trigger
    store.dispatch(addOne()) // dispatch the excute result of action
    // in another way to trigger
    const dispatchReduceOne = bindActionCreators(reduceOne, store.dispatch)
    dispatchReduceOne()
}


export default () => (
    <div>
      <button onClick={run}>Run</button>
      <p>* 請打開控制檯查看運行結果</p>
    </div>
  );
在react中使用redux

在react中使用redux,咱們不會再用store.subscribe去監聽值的變化,而是用react-reudx裏面的connect方法,把咱們要監聽的值和要調用的action方法做爲屬性值傳給組件。
connect方法,它是一個返回高階組件的高階組件。先看第一層,connect(mapStateToProps, mapDispatchToProps)返回咱們能夠在組件裏面使用到的state值和dispatch方法。第二層就是把咱們的組件傳遞進去,讓它具有這些值跟方法。
這裏有個須要講解的地方就是bindActionCreators,它負責把actions跟dispatch綁定在一塊兒,返回一個新的函數。這個新的函數,直接執行就至關於store.dispatch。
看源碼:

function bindActionCreator()actionCreator, dispatch) {
   return function () {
     return dispatch(actionCreator.apply(this, arguments))
   }
 }
import {
    createStore,
    combineReducers,
    bindActionCreators
} from "redux";
import React from "react"
import { connect, Provider } from "react-redux";

const initialState = { count: 0 };

function counter (state = initialState, action) { // reducer
    switch (action.type) {
        case 'ADD':
            return { count: state.count + action.count} // create a new state, rather than just change the state.count
        case 'REDUCE':
            return { count: state.count - action.count}
        default:
            return state
    }
    return state
}

const store = createStore(combineReducers({counter}))

//action
function addCount(count) {
    return {type: 'ADD', count}
}

function reduceCount(count) {
    return {type: 'REDUCE', count}
}

class counterElement extends React.Component {
    render () {
        const { count, addCount, reduceCount } = this.props
        return <Provider store={store}>
            <div>
                <p>the count is: {count}</p>
                <input type="button" value="add" onClick={() => addCount(5)}/>
                <input type="button" value="reduce" onClick={() => reduceCount(5)}/>
            </div>
        </Provider> 
    }
}

function mapStateToProps (state) {
    console.log(state)
    return {
        count: state.counter.count
    }
}


function mapDispatchToProps (dispatch) {
    return bindActionCreators({addCount, reduceCount}, dispatch)
}


const WrappedCounterElement = connect(mapStateToProps, mapDispatchToProps)(counterElement)

class counterContainer extends React.Component {
    render () {
        return <Provider store={store}>
            <WrappedCounterElement/>
        </Provider>
    }
}

export default counterContainer
異步redux

若是在action裏面調用的方法是異步的,那麼是須要經過redux中間件,先把這個action攔截了,等到它完成了,再dispatch出去。
這裏主要是咱們在建立store的時候,要使用thunk中間件。

const store = createStore(
        rootReducer,
        applyMiddleware(
        thunkMiddleware, // 容許咱們 dispatch() 函數
        loggerMiddleware // 一個很便捷的 middleware,用來打印 action 日誌
        )
    )
import React from "react"
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'

function run () {
    const loggerMiddleware = createLogger()

    const actionsType = {
        REQUEST_POSTS: 'REQUEST_POSTS',
        RECEIVE_POSTS: 'RECEIVE_POSTS',
        INVALIDATE_SUBREDDIT: 'INVALIDATE_SUBREDDIT'
    }

    //reducer
    const initialState = {
        isFetching: false,
        didInvalidate: false,
        items: []
    }
    function posts(
        state = initialState,
        action
    ) {
        switch (action.type) {
            case actionsType.INVALIDATE_SUBREDDIT:
                return Object.assign({}, state, {
                    didInvalidate: true
                })
            case actionsType.REQUEST_POSTS:
                return Object.assign({}, state, {
                    isFetching: true,
                    didInvalidate: false
                })
            case actionsType.RECEIVE_POSTS:
                return Object.assign({}, state, {
                    isFetching: false,
                    didInvalidate: false,
                    items: action.posts,
                    lastUpdated: action.receivedAt
                })
            default:
            return state
        }
    }

    const rootReducer = combineReducers({
        posts
    })

    // store 
    const store = createStore(
        rootReducer,
        applyMiddleware(
        thunkMiddleware, // 容許咱們 dispatch() 函數
        loggerMiddleware // 一個很便捷的 middleware,用來打印 action 日誌
        )
    )


    // action
    function requestPosts(subreddit) {
        return {
            type: actionsType.REQUEST_POSTS,
            subreddit
        }
    }

    function receivePosts(subreddit, json) {
        return {
            type: actionsType.RECEIVE_POSTS,
            subreddit,
            posts: json.data.children.map(child => child.data),
            receivedAt: Date.now()
        }
    }


    function fetchPosts(subreddit) {
        return function (dispatch) {
        dispatch(requestPosts(subreddit)) // display the loading status
        return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
            .then(
                response => response.json(),
                error => console.log('An error occurred.', error)
            )
            .then(json =>
                dispatch(receivePosts(subreddit, json))
            )
        }
    }

    // excute
    store.dispatch(fetchPosts('reactjs')).then(() =>    console.log(store.getState()))
}



export default () => (
    <div>
      <button onClick={run}>Run</button>
      <p>* 請打開控制檯查看運行結果</p>
    </div>
  );
相關文章
相關標籤/搜索