Redux-状态管理器

3,947 阅读12分钟

简介

Redux是JS的状态管理器,便于我们更加清晰管理追踪应用程序中变化莫测的状态变更。Redux采用 单一数据流 的方式对数据进行管理,这种方式的好处在于只能从单一的方向进行数据变更,剔除了数据能五花八门改变的方式,有利于我们对数据的变化的追踪,同时降低项目后期的维护成本。

Redux状态管理器的核心思想:

  • store状态树
  • action行为状态对象
  • reducer行为状态的处理

简单的说就是首先使用 action 定义状态树中所有需要发生变化的状态,然后使用 reducer 定义对应的 action 行为的处理并将处理后的状态返回,最后将各个 reducer 处理后的状态按照一定的规律组合成对象就形成了store状态树。

因此store状态树就是一个对象, action 定义了对象里某个递归字段对应值需要发生变化时需要的信息,然后经过 reducer 处理,最终使状态树中对应的递归字段的值发生变化

文章中展示的实例demo有些并未能直接在界面上体现,需要配合redux-tool工具观看状态树的变化,浏览器中安装相应的redux调试工具方法

示例 redux-1

//定义一个加法行为需要的参数
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的整个实现思想

使用原则

  • action 对象中必须拥有 type 字段,redux主要是根据该字段选择对应的 reducer 进行处理
  • reducer 处理函数必须 纯函数, reducer 接收相应的state,经过处理后返回新的state。不允许返回undefined或者null
  • 当需要触发行为变更相关的状态树信息时,必须调用dispatch方法触发更新操作
  • 决定状态树中内容的是 reducer 的返回值,并非 action 行为对象,因此如果没有对 action 进行 reducer 处理,即便使用dispatch触发更新,状态树也不会发生任何的变化
  • redux是同步进行的,因此创建 action 行为、触发更新操作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

状态对象

getState

获取状态树中的所有状态信息

@return Object 状态树信息

getState() => Object

dispatch

唯一能触发状态树中相关状态变化的方法

dispatch会校验 action 对象参数的正确性,当 action 对象没有type字段时,会造成程序出错

@params action 创建行为的对象

@return Object action对象,非对象时会触发程序错误,除非使用中间件

dispatch(action: Object) => Object

subscribe

状态树发生变化的监听,该方法只单单监听到状态树发生变化,未能得知变化的内容。目前觉得作用并不大

@params listener 监听的回调

@params Function 取消监听的方法

subscribe(listener: Function) => Function

replaceReducer

变更状态树中 reducer 的处理方式,比如在创建createStore后,需要增加某个 reducer 的,这个时候就需要用到该方法

前面的使用原则中阐述过,store状态树的内容是由 reducer 的返回值组成。因此store状态树的内容随着 reducer 的变化而变化

@params nextReducer reducer处理函数

replaceReducer(nextReducer)

combineReducers

将多个不同的 reducer 处理函数作为对象某个key的值,合成一个 reducer 函数,作为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方法。同时通过支持使用 withExtraArgument 注入额外的一个自定义参数

使用示例 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中 action 行为创建函数中,只允许返回同步的对象,然而redux-promise使得 action 行为创建函数中支持返回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扩展主要便于编写 action 和 reducer ,从而简化redux的使用

action

创建单一的 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}}

同时创建多个 action

@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

创建单一的 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方法

同时创建多个 reducer

@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,
}

待续.....