简介
Redux是JS
的状态管理器,便于我们更加清晰管理追踪应用程序中变化莫测的状态变更。Redux采用 单一数据流 的方式对数据进行管理,这种方式的好处在于只能从单一的方向进行数据变更,剔除了数据能五花八门改变的方式,有利于我们对数据的变化的追踪,同时降低项目后期的维护成本。
Redux状态管理器的核心思想:
store
状态树action
行为状态对象reducer
行为状态的处理
简单的说就是首先使用 action 定义状态树中所有需要发生变化的状态,然后使用 reducer 定义对应的 action 行为的处理并将处理后的状态返回,最后将各个 reducer 处理后的状态按照一定的规律组合成对象就形成了store
状态树。
因此store
状态树就是一个对象, action 定义了对象里某个递归字段对应值需要发生变化时需要的信息,然后经过 reducer 处理,最终使状态树中对应的递归字段的值发生变化
文章中展示的实例demo有些并未能直接在界面上体现,需要配合redux-tool工具观看状态树的变化,浏览器中安装相应的方法
示例 :
//定义一个加法行为需要的参数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
等方法都必须是同步操作,若需要支持异步操作时,需要增加中间件的支持,比如 、 等
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使用示例
//定义actionexport 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 ? "显示" : '隐藏'复制代码
中间件、扩展
该插件主要有助于开发过程中,查看状态树的状态以及变化过程
使用方式:
//安装npm i --save-dev redux-devtools-extension//使用import { composeWithDevTools } from 'redux-devtools-extension'const store = createStore(rootReducer,composeWithDevTools())复制代码
redux-thunk
中间件使redux
在dispatch
方法中支持异步操作。
实现原理是向传递给dispatch
的函数参数注入Store
对象的dispatch
与getState
方法,使得可以在函数内部调用dispatch
与getState
方法。同时通过支持使用 withExtraArgument 注入额外的一个自定义参数
使用示例 :
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-thunk
作用相同,使redux
支持异步操作的能力。
在redux
中 action 行为创建函数中,只允许返回同步的对象,然而redux-promise
使得 action 行为创建函数中支持返回promise
对象,当promise
执行成功时,触发状态树的更新;当promise
执行失败时,状态树不会发生任何的变化,也不会导致程序出错
使用示例 :
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
扩展主要便于编写 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.
2.同时实现createActions
中的actionMap
与...identityActions?
参数, 不可倒置 ,否则会忽略actionMap
(见示例及源码分析)payloadCreator
与metaCreator
方法时,需要将其纳入[]
数组中(如下示例) 3.当没有定义options
,并且创建的action递归时,默认的使用/
4.createActions
生成的递归对象的key
中使用插件,将其转换为驼峰的方式,但具体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)复制代码
使用
next
、throw
处理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; }); } ] ]), []);复制代码
与结合使用
示例 :
export const promiseAction = createAction('PROMISE', (value) => { console.log(value) return new Promise((fulfill,reject) => { setTimeout(() => { fulfill(value) }, 1000); })})复制代码
React框架提供的只是一个抽象的DOM
层,组件间的通讯处理麻烦。react-redux
有效的协助我们处理这些难题。有关 介绍可以自行浏览官网。
该插件所展示的示例来自于
Redux
官方提供的todos
项目改造而来
Provider
使最终的store
状态树 在任何被嵌套的组件中都能获取
示例 :
const store = createStore(rootReducer)render(, 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 ObjectmapStateToProps?: (state, ownProps?) => Object复制代码
示例 :
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复制代码
示例 :
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,}复制代码