Redux-狀態管理器

簡介

Redux是JS的狀態管理器,便於咱們更加清晰管理追蹤應用程序中變化莫測的狀態變動。Redux採用 的方式對數據進行管理,這種方式的好處在於只能從單一的方向進行數據變動,剔除了數據能五花八門改變的方式,有利於咱們對數據的變化的追蹤,同時下降項目後期的維護成本。react

Redux狀態管理器的核心思想:git

  • store狀態樹
  • action行爲狀態對象
  • reducer行爲狀態的處理

簡單的說就是首先使用定義狀態樹中全部須要發生變化的狀態,而後使用定義對應的行爲的處理並將處理後的狀態返回,最後將各個處理後的狀態按照必定的規律組合成對象就造成了store狀態樹。github

所以store狀態樹就是一個對象,定義了對象裏某個遞歸字段對應值須要發生變化時須要的信息,而後通過處理,最終使狀態樹中對應的遞歸字段的值發生變化npm

文章中展現的實例demo有些並未能直接在界面上體現,須要配合redux-tool工具觀看狀態樹的變化,瀏覽器中安裝相應的redux調試工具方法redux

示例 redux-1api

//定義一個加法行爲須要的參數
const addAction = (num1,num2) => {
    return ({
        type: 'ADD',
        num1,
        num2,
    })
}

//定義處理加法的行爲邏輯
const addReducer = (state,action) => {
    if(action.type === 'ADD') {
        return {
        	result: action.num1 + action.num2
        }
    }
}

//生成整個狀態樹
const store = createStore(addReducer,{})

//獲取整個狀態樹對象
store.getState() //undefined

//此時因爲須要觸發某個行爲
store.dispatch(addAction(1,2))

//獲取整個狀態樹對象
store.getState() //{result:3}

以上簡單的幾個操做就是Redux的整個實現思想

複製代碼

使用原則

  • 對象中必須擁有字段,redux主要是根據該字段選擇對應的進行處理
  • 處理函數必須 純函數接收相應的state,通過處理後返回新的state。不容許返回undefined或者null
  • 當須要觸發行爲變動相關的狀態樹信息時,必須調用dispatch方法觸發更新操做
  • 決定狀態樹中內容的是的返回值,並不是行爲對象,所以若是沒有對進行處理,即使使用dispatch觸發更新,狀態樹也不會發生任何的變化
  • redux是同步進行的,所以建立行爲、觸發更新操做dispatch等方法都必須是同步操做,若須要支持異步操做時,須要增長中間件的支持,好比 redux-promiseredux-thunk

API

createStore

建立狀態樹數組

@params reducer 處理行爲的函數
@params preloadedState 初始化默認狀態樹
@params enhancer 中間件,用於增長redux的處理能力

@return Object store對象

createStore(reducer: Function, preloadedState?: Object, enhancer?: Function) => Object
複製代碼

Store

狀態對象promise

getState

獲取狀態樹中的全部狀態信息瀏覽器

@return Object 狀態樹信息

getState() => Object
複製代碼

dispatch

惟一能觸發狀態樹中相關狀態變化的方法bash

dispatch會校驗對象參數的正確性,當對象沒有type字段時,會形成程序出錯

@params action 建立行爲的對象

@return Object action對象,非對象時會觸發程序錯誤,除非使用中間件

dispatch(action: Object) => Object
複製代碼

subscribe

狀態樹發生變化的監聽,該方法只單單監聽到狀態樹發生變化,未能得知變化的內容。目前以爲做用並不大

@params listener 監聽的回調

@params Function 取消監聽的方法

subscribe(listener: Function) => Function
複製代碼

replaceReducer

變動狀態樹中的處理方式,好比在建立createStore後,須要增長某個的,這個時候就須要用到該方法

前面的使用原則中闡述過,store狀態樹的內容是由的返回值組成。所以store狀態樹的內容隨着的變化而變化

@params nextReducer reducer處理函數

replaceReducer(nextReducer)
複製代碼

combineReducers

將多個不一樣的處理函數做爲對象某個key的值,合成一個函數,做爲createStore方法的參數傳遞。該方法能夠嵌套使用

@params reducers 由各個reducer合成的對象

@return Function reducer處理函數

combineReducers(reducers: Object) => Function
複製代碼

applyMiddleware

用於擴展redux的處理能力,做用於createStore函數的第三個參數傳遞

@params ...middleware 中間件參數

@return Function store事件的處理函數

applyMiddleware(...middleware) => Function
複製代碼

bindActionCreators

主要用戶簡化dispatch的調用,通過該方法處理後,能夠直接調用該方法返回的函數或者對象中的方法觸發store的更新,而不用使用dispatch

@params actionCreators action行爲對象或者函數
@params dispatch 觸發更新的方法

@return Object | Function 

bindActionCreators(actionCreators: Object | Function, dispatch) => Object | Function
複製代碼

API使用示例 redux-2

//定義action
export const add = (text) => ({
    type: 'ADD',
    text
})

export const extendFilter = () => ({
    type: 'EXTENDSHOW'
})

export const reduce = ()=> ({
    type: 'REDUCE'
})

export const filter = ()=> ({
    type: 'SHOW'
})

//reducer的處理
const list = (state = [], action) => {
    switch (action.type) {
        case "ADD": 
            return [
                ...state,
                {
                    id:state.length,
                    text: action.text
                }
            ]
        case "REDUCE":
            if (state.length === 0) {
                return state
            }else {
                state.pop()
                return [...state]
            }
        default: 
        return state //必須有返回內容,卻不能undefined、null
    }
}

const show = (state = false, action) => {
    if (action.type === 'SHOW') {
        return !state
    }
    return true
}

const extendShow = (state = false, action) => {
    if (action.type === 'EXTENDSHOW') {
        return !state
    }
    return true
}

export const rootReducer = combineReducers({
    list,
    show
})

const showReducer = combineReducers ({
    extendShow
})

export const secondReducer = combineReducers({
    list,
    'show': show,
    showReducer
})


//進過bindActionCreators處理後,能夠直接調用filterDispatch觸發更新
const filterDispatch = bindActionCreators(filter,store.dispatch)

//獲取狀態樹中的指定內容
showLabel.innerText = store.getState().show ? "顯示" : '隱藏'

複製代碼

中間件、擴展

redux-devtools-extension

該插件主要有助於開發過程當中,查看狀態樹的狀態以及變化過程

使用方式:

//安裝
npm i --save-dev redux-devtools-extension

//使用
import { composeWithDevTools } from 'redux-devtools-extension'

const store = createStore(rootReducer,composeWithDevTools())
複製代碼

redux-thunk

redux-thunk中間件使reduxdispatch方法中支持異步操做。

實現原理是向傳遞給dispatch的函數參數注入Store對象的dispatchgetState方法,使得能夠在函數內部調用dispatchgetState方法。同時經過支持使用注入額外的一個自定義參數

使用示例 redux-3:

const customObj1 = {
    name: 'Tom',
    age: 18
}

const customObj2 = {
    name: 'Chen',
    age: 20
}

const store = createStore(rootReducer,composeWithDevTools(applyMiddleware(thunk.withExtraArgument(customObj1,customObj2))))


addElement.onclick = () => {
    if (inputElement.value.length <= 0) {
        return
    }
    let value = inputElement.value
    store.dispatch(function(dispatch,getState) {
        //從這裏log輸出可知,經過withExtraArgument只支持傳遞一個參數
        console.log(arguments)// [ƒ, ƒ, {…}]
        console.log(getState()) // {list: Array(0)}
        setTimeout(() => {
            dispatch(add(value))
            console.log(getState()) // {list: Array(1)}
        }, 1000)
    })
    // store.dispatch(addHandle(value))
    inputElement.value = ""
}

function addHandle(value) {
    return function(dispatch,getState) {
        console.log(arguments)
        setTimeout(() => {
            dispatch(add(value))
        }, 1000)
    }
}

複製代碼

redux-promise

redux-promise中間件與redux-thunk做用相同,使redux支持異步操做的能力。

redux行爲建立函數中,只容許返回同步的對象,然而redux-promise使得行爲建立函數中支持返回promise對象,當promise執行成功時,觸發狀態樹的更新;當promise執行失敗時,狀態樹不會發生任何的變化,也不會致使程序出錯

使用示例 redux-4:

export const add = (text) => {
    return new Promise((fulfill,reject) => {
        setTimeout(() => {
            fulfill({
                type: 'ADD',
                text
            })
        }, 1000);
    })
}

export const reduce = ()=> {
    return new Promise((fulfill,reject) => {
        setTimeout(() => {
            reject()
        }, 500);
    })
}
複製代碼

redux-actions

redux-actions擴展主要便於編寫,從而簡化redux的使用

action

建立單一的

@params type action中的type字段
@params payloadCreator payloadCreator的處理返回結果,將成爲action的payload字段的內容
@params metaCreator metaCreator的處理返回結果,將成爲action的meta字段的內容

createAction(type, payloadCreator?: Function, metaCreator?: Function) => Function`

複製代碼

:

export default function createAction(
  type,
  payloadCreator = value => value, //當未設置該參數時,會默認返回傳遞的參數
  metaCreator
) {
  invariant(
    isFunction(payloadCreator) || isNull(payloadCreator),
    "Expected payloadCreator to be a function, undefined or null"
  );
  /*
	此處檢查payloadCreator函數的第一個參數若是是error將不執行該函數,直接返回error
	*/
  const finalPayloadCreator =
    isNull(payloadCreator) || payloadCreator === identity
      ? identity
      : (head, ...args) =>
          head instanceof Error ? head : payloadCreator(head, ...args);

  //metaCreator 只要是函數時就會執行
  const hasMeta = isFunction(metaCreator);
  const typeString = type.toString();

  const actionCreator = (...args) => {
    const payload = finalPayloadCreator(...args);
    //建立action對象
    const action = { type };

    if (payload instanceof Error) {
      action.error = true;
    }

    if (payload !== undefined) {
    	//若是是error,則添加特殊的字段
      action.payload = payload;
    }

    if (hasMeta) {
      action.meta = metaCreator(...args);
    }

    return action;
  };

  actionCreator.toString = () => typeString;

  return actionCreator;
}
複製代碼

示例:

const todo = createAction('TODO', name => {
  return {name: 'action' + name}
}, name => {
  return {age: 18}
});
console.log(todo('name'))
結果:
{type: "TODO", payload: {name: "actionname"}, meta: {age: 18}}

/*
當不須要對action的行爲參數進行處理時,
能夠將payloadCreator設置爲undefined或者null。
同理不須要額外處理其餘數據時,metaCreator也能夠忽略
*/
const todo = createAction('TODO',undefined, name => {
  return {age: 18}
})
console.log(todo('name'))
結果:
{type: "TODO", payload: 'name', meta: {age: 18}}


const todo = createAction('TODO')
console.log(todo('name'))
結果:
{type: "TODO", payload: 'name'}


/*
當action行爲的參數是error時,action返回的對象中會額外增長error字段,
而且其值爲true,並且不會調用payloadCreator方法
*/
const todo = createAction('TODO', name => {
  return {name: 'action' + name}
}, name => {
  return {age: 18}
});
console.log(todo(new Error('error')))
結果:
{type: "TODO", payload: Error: error, error: true, meta: {age: 18}}
複製代碼

同時建立多個

@params actionMap action的集合
@params ...identityActions action的type,單獨定義該字段至關於action的參數不須要進過處理轉換
@params options action中type的前綴

createActions(actionMap, ...identityActions?, options?) => Object
複製代碼

1.createActions中的actionMap...identityActions?參數,,不然會忽略actionMap(見示例及源碼分析)
2.同時實現payloadCreatormetaCreator方法時,須要將其歸入[]數組中(以下示例)
3.當沒有定義options,而且建立的action遞歸時,默認的使用/
4.createActions生成的遞歸對象的key中使用to-camel-case插件,將其轉換爲駝峯的方式,但具體action中type保持不變(見示例)

:

function createActions(actionMap, ...identityActions) {
  const options = isPlainObject(getLastElement(identityActions))
    ? identityActions.pop()
    : {};
  //限制createActions參數的規則,若是不符合條件則拋出異常
  invariant(
    identityActions.every(isString) &&
      (isString(actionMap) || isPlainObject(actionMap)),
    'Expected optional object followed by string action types'
  );
  
  //若是以identityActions開頭,則直接返回identityActions所生成的
  if (isString(actionMap)) {
    return actionCreatorsFromIdentityActions(
      [actionMap, ...identityActions],
      options
    );
  }
  return {
    ...actionCreatorsFromActionMap(actionMap, options),
    ...actionCreatorsFromIdentityActions(identityActions, options)
  };
}

複製代碼

示例:

//先identityActions在actionMap的狀況
export const list = createActions('ADD',{
    'REDUCE': value => value
})
console.log(list)
結果:{add: ƒ}


const todos = createActions(
  {
    profile: {
      add: name => name,
      DELETE_ITEM: [name => ({ name, age: 18 }), name => ({ gender: 'female' })]
    },
    show: name => name
  },
  'hidden',
  { prefix: 'todo', namespace: '-' }
)
console.log(todos.show('name'))
結果:
{type: "todo-show", payload: "name"}

console.log(todos.profile.deleteItem('name'))
結果:
{type: "todo-profile-delete", payload: {name: "name", age: 18}, meta: {gender: "female"}}


const todos = createActions('ADD','DELETE')
console.log(todos.add('name'))
結果:
{type: "ADD", payload: "name"}
複製代碼

reducer

建立單一的

@params type action中的type
@params reducer 行爲處理函數
@params defaultState 默認值(必須有默認值,當state未null時,使用defaultState)

handleAction(type,reducer | reducerMap = Identity, defaultState)

複製代碼

使用nextthrow處理action行爲邏輯是時,能夠有效處理action對象中出現Error的狀況(createAction中已經介紹如何出現Error)

:

export default function handleAction(type, reducer = identity, defaultState) {
  const types = toString(type).split(ACTION_TYPE_DELIMITER);
  
  //defaultState指定該參數是必須的
  invariant(
    !isUndefined(defaultState),
    `defaultState for reducer handling ${types.join(', ')} should be defined`
  );
  
  //reducer參數必須是對象或者函數
  invariant(
    isFunction(reducer) || isPlainObject(reducer),
    'Expected reducer to be a function or object with next and throw reducers'
  );

  const [nextReducer, throwReducer] = isFunction(reducer)
    ? [reducer, reducer]
    : [reducer.next, reducer.throw].map(
        aReducer => (isNil(aReducer) ? identity : aReducer)
      );

  //此時解析了爲什麼須要defaultState
  return (state = defaultState, action) => {
    const { type: actionType } = action;
    if (!actionType || types.indexOf(toString(actionType)) === -1) {
      return state;
    }
	
	//當acton出現Error時使用throwReducer函數處理
    return (action.error === true ? throwReducer : nextReducer)(state, action);
  };
}
複製代碼

示例:

const todoReducer = handleAction('TODO',(state,action)=> ({
  name: action.payload
}),'default')


//next、throw的方式
export const err = createAction('ERROR')
export const flag = handleAction('ERROR',{
    next(state,action){
        console.log("next",state,action)
        return !state
    },
    throw(state,action){
        console.log("throw",state,action)
        return !state
    }
},true)

err(new Error('自定義錯誤')) //此時會執行throw方法

複製代碼

同時建立多個

@params reducerMap reducer處理函數
@params defaultState 默認值(必須有默認值)
@params options 定義遞歸的前綴(同createActions)

handleActions(reducerMap, defaultState, options?)
複製代碼

示例:

const todos =  handleActions({
  'ADD_TODO': (state = [],action) => {
    return [
      ...state,
      {
        id: action.payload.id,
        text: action.payload.text,
        completed: false
      }
    ]
  },
  'TOGGLE_TODO': (state = [],action) => {
    return state.map(todo => {
      console.log(todo,action)
      return (todo.id === action.payload)
      ? {...todo, completed: !todo.completed}
      : todo
    }
    )
  },
  'DELETE_TODO':(state = [], action) => {
    state.pop()
    return state
  }
},[])

//使用map的方式處理
const todos = handleActions(
  new Map([
    [
      // "ADD_TODO",//使用action的type方式
      addTodo,//使用action的方式
      (state = [], action) => {
        return [
          ...state,
          {
            id: action.payload.id,
            text: action.payload.text,
            completed: false
          }
        ];
      }
    ],
    [
      "TOGGLE_TODO",
      (state = [], action) => {
        return state.map(todo => {
          console.log(todo, action);
          return todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo;
        });
      }
    ]
  ]),
  []
);
複製代碼

redux-promise結合使用

示例 redux-5:

export const promiseAction = createAction('PROMISE', (value) => {
    console.log(value)
    return new Promise((fulfill,reject) => {
        setTimeout(() => {
            fulfill(value)
        }, 1000);
    })
})
複製代碼

react-redux

React框架提供的只是一個抽象的DOM層,組件間的通信處理麻煩。react-redux有效的協助咱們處理這些難題。有關 React 介紹能夠自行瀏覽官網。

該插件所展現的示例來自於Redux官方提供的todos項目改造而來

Provider

使最終的store狀態樹 在任何被嵌套的組件中都能獲取

示例 redux-6:

const store = createStore(rootReducer)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

複製代碼

connect

將React組件鏈接到store狀態樹

@params mapStateToProps 須要獲取的狀態樹相關信息
@params mapDispatchToProps dispath相關觸發更新
@params mergeProps 自定義映射到組件props字段的處理
@params options 自定義選項

connect(mapStateToProps?: Function, mapDispatchToProps?: Function | Object, mergeProps?: Function, options?: Object)
複製代碼

mapStateToProps

根據須要從store中獲取相關字段信息與調用組件時所傳遞參數構建對象,對象中的每一個字段都將成爲組件的prop,同時字段中的值也將肯定該組件是否須要從新渲染.若是定義mergeProps函數(見mergeProps方法說明),則做爲stateProps參數

store發生變化時會回調該函數,若是不須要訂閱變化,可設置爲null或undefined

方法內不能存在異步的行爲,全部的操做都應該保持同步

@params state 整個store的狀態樹
@params ownProps 調用該組件時傳遞的參數
@return Object

mapStateToProps?: (state, ownProps?) => Object
複製代碼

示例 redux-6:

const mapStateToProps = (state, ownProps) => ({
  //獲取狀態樹中的filter字段信息
  active: ownProps.filter === state.visibilityFilter
})
複製代碼

mapDispatchToProps

用於定義觸發store更新的操做也就是dispatch, 返回對象中的每一個字段都將成爲組件的prop中的字段。若是定義mergeProps函數(見mergeProps方法說明),則做爲dispatchProps參數

當未定義該方法時,組件中默認接收dispatch參數;一旦定義了該方法,組件中將不接收dispatch參數。但能夠經過手動注入的方式向props傳遞dispatch(見示例)

@params dispatch store中觸發更新的操做
@parmas ownProps 調用該組件時傳遞的參數,當接收到新的props時會回調該函數
@return Object 必須返回一個對象,該對象中定義觸發store更新的操做
        
mapDispatchToProps?: Object | (dispatch, ownProps?) => Object
複製代碼

示例 redux-6:

const mapDispatchToProps = (dispatch, ownProps) => ({
  onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
  dispatch //因爲定義了mapDispatchToProps函數,組件默認不會接收dispatch參數,所以手動傳入
})

//當不須要獲取相關信息時,也能夠直接返回對象的方式
const increment = () => ({ type: 'INCREMENT' })
const decrement = () => ({ type: 'DECREMENT' })
const reset = () => ({ type: 'RESET' })
const mapDispatchToProps = {
	increment,
	decrement,
	reset
}
複製代碼

mergeProps

自定義映射組件props的操做,若是未實現該方法,則默認實現{ ...ownProps, ...stateProps, ...dispatchProps }

@params stateProps mapStateToProps方法返回對象
@params dispatchProps mapDispatchToProps方法返回對象
@params ownProps 調用該組件時傳遞的參數
@return Object
        
mergeProps?: (stateProps, dispatchProps, ownProps) => Object
複製代碼

options

一些經常使用選項,具體說明見官方文檔

{
	context?: Object,
	pure?: boolean,
	areStatesEqual?: Function,
	areOwnPropsEqual?: Function,
	areStatePropsEqual?: Function,
   areMergedPropsEqual?: Function,
   forwardRef?: boolean,
}
複製代碼

待續.....

相關文章
相關標籤/搜索