redux從零開始入門筆記

爲何須要redux

學過react的都知道,react用stateprops控制組件的渲染狀況,而對於JavaScript單頁面日趨複雜的今天,JavaScript須要管理愈來愈多的state,而這些state包括着各類亂七八糟途徑來的數據。甚至有的應用的state會關係到另外一個組件的狀態。因此爲了方便對這些state的管理以及對state變化的可控性。這個時候Redux這個東西就出來了,它可讓state的變化變得可預測。前端

Redux的基本概念

什麼是redux?這裏非權威的解釋:就是一個應用的state管理庫,甚至能夠說是前端數據庫。更包括的是管理數據。react

state

state是整個應用的數據,本質上是一個普通對象。
state決定了整個應用的組件如何渲染,渲染的結果是什麼。能夠說,State是應用的靈魂,組件是應用的肉體。
因此,在項目開發初期,設計一份健壯靈活的State尤爲重要,對後續的開發有很大的幫助。
可是,並非全部的數據都須要保存到state中,有些屬於組件的數據是徹底能夠留給組件自身去維護的。git

action

數據state已經有了,那麼咱們是如何實現管理這些state中的數據的呢?那就是action,什麼是action?按字面意思解釋就是動做,也能夠理解成,一個可能!改變state的動做包裝。就這麼簡單。。。。
只有當某一個動做發生的時候纔可以觸發這個state去改變,那麼,觸發state變化的緣由那麼多,好比這裏的咱們的點擊事件,還有網絡請求,頁面進入,鼠標移入。。。因此action的出現,就是爲了把這些操做所產生或者改變的數據從應用傳到store中的有效載荷。 須要說明的是,action是state的惟一來源。它本質上就是一個JavaScript對象,可是約定的包含type屬性,能夠理解成每一個人都要有名字通常。除了type屬性,別的屬性,均可以.
那麼這麼多action一個個手動建立必然不現實,通常咱們會寫好actionCreator,即action的建立函數。調用actionCreator,給你返回一個action。這裏咱們可使用 redux-actions,嗯呢,咱們下文有介紹。
好比有一個counter數量加減應用,咱們就有兩個action,一個decrement,一個increment。 因此這裏的action creator寫成以下:github

export function decrement() {
    return{
        type:DECREMENT_COUNTER
    }
}

export function increment(){
    return{
        type:INCREMENT_COUNTER
    }
}

那麼,當action建立完成了以後呢,咱們怎麼觸發這些action呢,這時咱們是要利用dispatch,好比咱們執行count增減減小動做。數據庫

export function incrementIfOdd(){
    return(dispatch,getState)=>{
        const {counter} = getState();
        if(counter%2==0) {
            return;
        }
        dispatch(increment());
    }
}

export function incrementAsync() {
    return dispatch => {
        setTimeout(() => {
            dispatch(increment());
        }, 1000);
    };
}

爲了減小樣板代碼,咱們使用單獨的模塊或文件來定義 action type 常量npm

export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';

這麼作不是必須的,在大型應用中把它們顯式地定義成常量仍是利大於弊的。json

reducer

既然這個可能改變state的動做已經包裝好了,那麼咱們怎麼去判斷而且對state作相應的改變呢?對,這就是reducer乾的事情了。
reducer是state最終格式的肯定。它是一個純函數,也就是說,只要傳入參數相同,返回計算獲得的下一個 state 就必定相同。沒有特殊狀況、沒有反作用,沒有 API 請求、沒有變量修改,單純執行計算。
reducer對傳入的action進行判斷,而後返回一個經過判斷後的state,這就是reducer的所有職責。如咱們的counter應用:redux

import {INCREMENT_COUNTER,DECREMENT_COUNTER} from '../actions';

export default function counter(state = 0, action) {
    switch (action.type){
        case INCREMENT_COUNTER:
            return state+1;
        case DECREMENT_COUNTER:
            return state-1;
        default:
            return state;
    }
}

這裏咱們就是對增和減兩個以前在action定義好的常量作了處理。
對於一個比較大一點的應用來講,咱們是須要將reducer拆分的,最後經過redux提供的combineReducers方法組合到一塊兒。 如此項目上的:數組

const rootReducer = combineReducers({
    counter
});
export default rootReducer;

每一個reducer只負責管理全局state中它負責的一部分。每一個reducerstate參數都不一樣,分別對應它管理的那部分state數據。combineReducers()所作的只是生成一個函數,這個函數來調用你的一系列reducer,每一個reducer根據它們的key來篩選出state中的一部分數據並處理, 而後這個生成的函數再將全部reducer的結果合併成一個大的對象。promise

store

store是對以前說到一個聯繫和管理。具備以下職責

  • 維持應用的state
  • 提供getState()方法獲取 state
  • 提供dispatch(action)方法更新 state;
  • 經過subscribe(listener)註冊監聽器;
  • 經過subscribe(listener)返回的函數註銷監聽器。

強調一下 Redux 應用只有一個單一的store。當須要拆分數據處理邏輯時,你應該使用reducer組合,而不是建立多個storestore的建立經過reduxcreateStore方法建立,這個方法還須要傳入reducer,很容易理解:畢竟我須要dispatch一個action來改變state嘛。 應用通常會有一個初始化的state,因此可選爲第二個參數,這個參數一般是有服務端提供的,傳說中的Universal渲染。 第三個參數通常是須要使用的中間件,經過applyMiddleware傳入。
說了這麼多,actionstoreactionCreatorreducer關係就是這麼以下的簡單明瞭:
redux

結合react-redux的使用

react-reduxreduxreact的橋樑工具。
react-redux將組建分紅了兩大類,UI組建component和容器組建container。 簡單的說,UI組建負責美的呈現,容器組件負責來幫你盛着,給你"力量"。
UI 組件有如下幾個特徵:

  • 只負責 UI 的呈現,不帶有任何業務邏輯
  • 沒有狀態(即不使用this.state這個變量)
  • 全部數據都由參數(this.props)提供
  • 不使用任何 Redux 的 API

如:

export default class Counter extends Component{
    render(){
        const { counter, increment, decrement, incrementIfOdd, incrementAsync } = this.props;
        return(
            <p>
                Clicked:{counter} times
                <button onClick={increment}>+</button>
                <button onClick={decrement}>-</button>
                <button onClick={incrementIfOdd}>increment if Odd</button>
                <button onClick={incrementAsync}>increment async</button>
            </p>
        )
    }
}

容器組件特性則偏偏相反:

  • 負責管理數據和業務邏輯,不負責 UI 的呈現
  • 帶有內部狀態
  • 使用 Redux 的 API
class App extends Component{
    render(){
        const { counter, increment, decrement, incrementIfOdd, incrementAsync } = this.props;
        return(
            <Counter
                counter={counter}
                increment={increment}
                decrement={decrement}
                incrementIfOdd={incrementIfOdd}
                incrementAsync={incrementAsync}/>
        )
    }
}

export default connect(
    state=>({ counter: state.counter }),
    ActionCreators
)(App);

connect方法接受兩個參數:mapStateToPropsmapDispatchToProps。它們定義了UI組件的業務邏輯。前者負責輸入邏輯,即將state映射到 UI 組件的參數(props), 後者負責輸出邏輯,即將用戶對 UI 組件的操做映射成Action。由於做爲組件,咱們只要能拿到值,能發出改變值得action就能夠了,因此mapStateToPropsmapDispatchToProps正是知足這個需求的。

redux-thunk

一個比較流行的redux的action中間件,它可讓actionCreator暫時不返回action對象,而是返回一個函數,函數傳遞兩個參數(dispatch, getState),在函數體內進行業務邏輯的封裝,好比異步操做,咱們至少須要觸發兩個action,這時候咱們能夠經過redux-thunk將這兩個action封裝在一塊兒,以下:

const fetchDataAction = (querys) => (dispatch, getState) => {
    const setLoading = createAction('SET_LOADING');
    dispatch(setLoading(true)); // 設置加載中。。。
    return fetch(`${url}?${querys}`).then(r => r.json()).then(res => {
        dispatch(setLoading(false)); // 設置取消加載中。。。
        dispatch(createAction('DATA_DO_SOMETHIN')(res))
    })
}

這裏咱們的createCreator返回的是一個fetch對象,咱們下文會介紹,咱們經過dispatch觸發改action

dispatch(fetchDataAction(querys))

在請求數據以前,經過redux-thunk咱們能夠先觸發加載中的action,等請求數據結束以後咱們能夠在次觸發action,使得加載中狀態取消,並處理請求結果。

redux-promise

既然說到了異步action,咱們可使用redux-promise,它可讓actionCreator返回一個Promise對象。
第一種作法,咱們能夠參考redux-thunk的部分。
第二種作法,action對象的payload屬性(至關於咱們的diy參數,action裏面攜帶的其餘參數)是一個Promise對象。這須要從redux-actions模塊引入createAction方法,而且寫法也要變成下面這樣。

import { createAction } from 'redux-actions';
class AsyncApp extends Component {
  componentDidMount() {
    const { dispatch, selectedPost } = this.props
    // 發出異步 Action
    dispatch(createAction(
      'FETCH_DATA', 
      fetch(`url`).then(res => res.json())
    ));
  }

其實redux-actionscreateAction的源碼是拿到fetch對象的payload結果以後又觸發了一次action

redux-actions

當咱們的在開發大型應用的時候,對於大量的action,咱們的reducer須要些大量的swich來對action.type進行判斷。redux-actions能夠簡化這一煩瑣的過程,它能夠是actionCreator,也能夠用來生成reducer,其做用都是用來簡化actionreducer
主要函數有createActioncreateActionshandleActionhandleActionscombineActions

createAction

建立action,參數以下

import { createAction } from 'redux-actions';
createAction(
  type,  // action類型
  payloadCreator = Identity, // payload數據 具體參考Flux教程
  ?metaCreator // 具體我也沒深究是啥
)

例子以下:

export const increment = createAction('INCREMENT')
export const decrement = createAction('DECREMENT')

increment() // { type: 'INCREMENT' }
decrement() // { type: 'DECREMENT' }
increment(10) // { type: 'INCREMENT', payload: 10 }
decrement([1, 42]) // { type: 'DECREMENT', payload: [1, 42] }

createActions

建立多個action

import { createActions } from 'redux-actions';
createActions(
  actionMap,
  ?...identityActions,
)

第一個參數actionMap爲一個對象,以action type爲鍵值,值value有三種形式,

  • 函數,該函數參數傳入的是action建立的時候傳入的參數,返回結果會做爲到生成的actionpayload的value。
  • 數組,長度爲二,第一個值爲一個函數,前面的同樣,返回payload的值,第二個值也爲一個函數,返回meta的值,不知道有什麼用。
  • 一個 actionMap對象,遞歸做用吧。

例子以下

createActions({
  ADD_TODO: todo => ({ todo })
  REMOVE_TODO: [
    todo => ({ todo }), // payloa
    (todo, warn) => ({ todo, warn }) // meta
  ]
});
const actionCreators = createActions({
  APP: {
    COUNTER: {
      INCREMENT: [
        amount => ({ amount }),
        amount => ({ key: 'value', amount })
      ],
      DECREMENT: amount => ({ amount: -amount }),
      SET: undefined // given undefined, the identity function will be used
    },
    NOTIFY: [
      (username, message) => ({ message: `${username}: ${message}` }),
      (username, message) => ({ username, message })
    ]
  }
});

expect(actionCreators.app.counter.increment(1)).to.deep.equal({
  type: 'APP/COUNTER/INCREMENT',
  payload: { amount: 1 },
  meta: { key: 'value', amount: 1 }
});
expect(actionCreators.app.counter.decrement(1)).to.deep.equal({
  type: 'APP/COUNTER/DECREMENT',
  payload: { amount: -1 }
});
expect(actionCreators.app.counter.set(100)).to.deep.equal({
  type: 'APP/COUNTER/SET',
  payload: 100
});
expect(actionCreators.app.notify('yangmillstheory', 'Hello World')).to.deep.equal({
  type: 'APP/NOTIFY',
  payload: { message: 'yangmillstheory: Hello World' },
  meta: { username: 'yangmillstheory', message: 'Hello World' }
});

第二個參數identityActions,可選參數,也是一個action type吧,官方例子沒看懂,以下:

const { actionOne, actionTwo, actionThree } = createActions({
  // function form; payload creator defined inline
  ACTION_ONE: (key, value) => ({ [key]: value }),

  // array form
  ACTION_TWO: [
    (first) => [first],             // payload
    (first, second) => ({ second }) // meta
  ],

  // trailing action type string form; payload creator is the identity
}, 'ACTION_THREE');

expect(actionOne('key', 1)).to.deep.equal({
  type: 'ACTION_ONE',
  payload: { key: 1 }
});

expect(actionTwo('first', 'second')).to.deep.equal({
  type: 'ACTION_TWO',
  payload: ['first'],
  meta: { second: 'second' }
});

expect(actionThree(3)).to.deep.equal({
  type: 'ACTION_THREE',
  payload: 3,
});

handleAction

字面意思理解,處理action,那就是一個reducer,包裹返回一個reducer,處理一種類型的action type

import { handleAction } from 'redux-actions';

handleAction(
  type,  // action類型
  reducer | reducerMap = Identity
  defaultState // 默認state
)

當第二個參數爲一個reducer處理函數時,形式以下,處理傳入的state並返回新的state

handleAction('APP/COUNTER/INCREMENT', (state, action) => ({
  counter: state.counter + action.payload.amount,
}), defaultState);

當第二個參數爲reducerMap時,也爲處理state並返回新的state,只是必須傳入key值爲nextthrow的兩個函數,分別用來處理state和異常以下:

handleAction('FETCH_DATA', {
  next(state, action) {...},
  throw(state, action) {...},
}, defaultState);

官方推薦使用reducerMap形式,由於與ES6的generator相似。

handleActions

handleAction不一樣,handleActions能夠處理多個action,也返回一個reducer

import { handleActions } from 'redux-actions';

handleActions(
  reducerMap,
  defaultState
)

reducerMapaction type爲key,value與handleAction的第二個參數一致,傳入一個reducer處理函數或者一個只有nextthrow兩個鍵值的對象。
另外,鍵值key也可使用createAction建立:

import { createActions, handleActions } from 'redux-actions';

const { increment, decrement } = createActions({
  'INCREMENT': amount => ({ amount: 1 }),
  'DECREMENT': amount => ({ amount: -1 })
});

const reducer = handleActions({
  [increment](state, { payload: { amount } }) {
    return { counter: state.counter + amount }
  },
  [decrement](state, { payload: { amount } }) {
    return { counter: state.counter + amount }
  }
}, defaultState);

combineActions

將多個action或者actionCreator結合起來,看起來不多用,具體例子以下:

const { increment, decrement } = createActions({
  INCREMENT: amount => ({ amount }),
  DECREMENT: amount => ({ amount: -amount })
});

const reducer = handleActions({
  [combineActions(increment, decrement)](state, { payload: { amount } }) {
    return { ...state, counter: state.counter + amount };
  }
}, { counter: 10 });

expect(reducer({ counter: 5 }, increment(5))).to.deep.equal({ counter: 10 });
expect(reducer({ counter: 5 }, decrement(5))).to.deep.equal({ counter: 0 });
expect(reducer({ counter: 5 }, { type: 'NOT_TYPE', payload: 1000 })).to.equal({ counter: 5 });
expect(reducer(undefined, increment(5))).to.deep.equal({ counter: 15 });

redux-actions說到這裏,大概是這樣,有什麼不瞭解看看官方文檔吧。

reselect

Reselect用來記憶selectors的庫,咱們定義的selectors是做爲函數獲取state的某一部分。使用記憶能力,咱們能夠組織沒必要要的衍生數據的重渲染和計算過程,由此加速了咱們的應用。具體細節大概是在mapStateToProps的時候,講state的某一部分交給reselectselectors來管理,使用selectors的記憶功能讓組件的props儘可能不變化,引發沒必要要的渲染。
下面咱們以一個todolist爲例子。
當咱們沒有reselect的時候,咱們是直接經過mapStateToProps把數據傳入組件內,以下。

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = (state, props) => {
  return {
    todolist: getVisibleTodos(state, props)
  }
}

這個代碼有一個潛在的問題。每當state tree改變時,selector都要從新運行。當state tree特別大,或者selector計算特別耗時,那麼這將帶來嚴重的運行效率問題。爲了解決這個問題,reselect爲selector設置了緩存,只有當selector的輸入改變時,程序才從新調用selector函數。
這時咱們把state轉化爲props的數據交給reselect來處理,咱們重寫mapStateToProps

const getVisibilityFilter = state => state.todo.showStatus

const getTodos = state => state.todo.todolist

const getVisibleTodos = createSelector([getVisibilityFilter, getTodos], (visibilityFilter, todos) => {
  switch (visibilityFilter) {
    case 'SHOW_COMPLETED':
      return todos.filter(todo => todo.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(todo => !todo.completed)
    default:
      return todos
  }
})
const mapStateToProps = (state, props) => {
  const todolist = getVisibleTodos(state, props);
  return {
    todolist
  }
}

咱們使用createSelector包裹起來,將組件內須要的兩個props包裹起來,而後在返回一個獲取數據的函數getVisibleTodos,這樣返回的todolist就不會受到一些沒必要要的state的變化而變化引發衝渲染。

最後

總結了那麼多的用法,其實也是redux的基本用法,而後本身寫了半天的todolist,把上面說到的技術都用了,這是 github地址,上面的內容若有錯誤,勿噴,畢竟入門級別。。。

相關文章
相關標籤/搜索