Redux基本概念
Redux用做状态管理,有三个基本的原则
1,整个应用的状态(state)都存在一个普通的JS对象中。比如管理用户的列表,
const state = [ {id: 1, name: 'sam', age: '20'}, {id: 2, name: 'josn', age: '21'} ]
2,状态是只读的,不能直接修改状态,只能发送(dispatch)一个action,表明要修改状态。action也是一个普通JS对象,用来描述怎样改变状态。它有一个type属性, 要对state执行什么样的操作,通常它是一个描述动作的字符串,比如'increment', increment 表示增加,一看到这个type,就知道状态要做何种变化了。其次还有一个可选的payload, 表示增加多少啊
{ type: 'increment', wantAddNum: 10 }
此时,action 也可以看成一个载体,它把应用中的数据传递出去.
3, 真正改变状态的地方是纯函数reducer。action表明了要改变状态,也有state,它两个就可以计算出一个新对象(状态)。再强调一下,reducer是一个纯函数,不要改变参数传递进来的state, 永远都要返回新的state对象。

function counter(state, action) { switch (action) { case 'increment': return state + action.wantAddNum; default: return state; } }
但谁来dispatch action?dispatch action后,reducer怎么拿到当前的state和这个action?Redux还有另外一个对象store,创建这个对象的时候,接受reducer函数,同时内部持有一个currentState的变量,再在内部定义dispatch方法。dispatch方法是一个闭包,接受action,同时它又能获取currentstate和reducer,最后把dispatch暴露出去,
function createStore(reducer, initialState) { let currentState = initialState; // 存储内部状态 function dispatch(action) { currentState = reducer(currentState, action); } return { dispatch }; }
但state改变了,外界怎么获取到?提供一个订阅方法,谁想获取state,就添加一个订阅。
function createStore(reducer, initialState) { let currentState = initialState; // 存储内部状态 let listeners = []; // 存储监听的函数(subscribe 中的回调函数) function getState() { return currentState; } function subscribe(listener) { listeners.push(listener); } function dispatch(action) { currentState = reducer(currentState, action); listeners.forEach(listener => listener()); // dispatch action后,状态改变,所有的监听函数都要执行 }
return { getState, subscribe, dispatch }; }
写一个简单的计数器,点击加号,加1,点击减号,减1, 最后还有一个重置按钮。纯粹学习Redux,可以用script标签引入。当使用script标签引入时,window对象会有一个Redux属性. 新建 一个rudex.html, 引入bootstrap css 和redux, 并新建一个counter.js
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>redux</title> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css"> <script src="https://cdn.bootcss.com/redux/4.0.4/redux.js"></script> </head> <body style="text-align: center"> <h1 id="counter">0</h1> <!-- 显示状态 --> <button type="button" class="btn btn-primary" id="add">Add</button> <button type="button" class="btn btn-success" id="minus">Minus</button> <button type="button" class="btn btn-danger" id="reset">Reset</button> <script src="./counter.js"></script> </body> </html>

state就是一个counter, 可以初始化为0, 那么initialState和state的形态就如下
const initialState = { counter: 0 }
action就三个,加,减,重置,
const add = { type: 'ADD' } const minus = { type: 'MINUS' } const reset = { type: 'RESET' }
再就是reducer 了, 分别加1, 减1, 重置为初始状态
function counter(state, action) { switch (action.type) { case 'ADD': return { ...state, counter: state.counter + 1 } case 'MINUS': return { ...state, counter: state.counter - 1 } case 'RESET': return { ...state, counter: 0 }; default: return state; } }
再次强调一下,reducer必须返回一个新对象。有了reducer,就可以创建store了。
const store = Redux.createStore(counter, initialState);
点击按钮dispatch action
document.getElementById('add').addEventListener('click', () => { store.dispatch(add); }) document.getElementById('minus').addEventListener('click', () => { store.dispatch(minus); }) document.getElementById('reset').addEventListener('click', () => { store.dispatch(reset); })
添加订阅,获取最新的state,显示到页面上。
const stateDiv = document.getElementById('counter'); function render() { stateDiv.innerHTML = store.getState().counter; } store.subscribe(render)
createStore()调用时,Redux内部会dispatch一个action("@@redux/INIT"),没有switch action.type处理它,它就走了default分支,因此一定要写default分支。其次,这种模式,可以用来初始化state
function counter(state = initialState, action) { //... }
store的创建就变成了
const store = Redux.createStore(counter);
写action时,可能会用到action creator来创建action。为什么要创建action呢?考虑Atm取钱的场景,它有100, 500, 1000和输入任意值,都是取钱这一个操作(type一致),只是携带的数据不一致。如果一个一个写action,那就要写4个,
{ type: 'ADD', data: 100 },
{ type: 'ADD', data: 500 },
{ type: 'ADD', data: 1000 }
为什么不封装一个函数呢?不一致的数据由函数参数接受,然后返回action对象,这个函数就是action creator.
function addData(data) { return { type: 'ADD', data } }
再进一步,还有bindActionCreators, 把action creator和dispatch函数绑定到一起,返回一个函数,因为action始终都是要dispatch的,直接调用返回函数就可以了
const boundAdd = data => store.dispatch(addData(data)) // boundAdd就是一个bindActionCreator
页面加上两个按钮
<button type="button" class="btn btn-primary" id="add5">Add5</button> <button type="button" class="btn btn-primary" id="add10">Add10</button>

js中修改的地方
function addData(data) { // action creator return { type: 'ADD', data } } const boundAdd = data => store.dispatch(addData(data)) // bindActionCreator case 'ADD': return { ...state, counter: state.counter + action.data } document.getElementById('add').addEventListener('click', () => { boundAdd(1); }) document.getElementById('add5').addEventListener('click', () => { boundAdd(5); }) document.getElementById('add10').addEventListener('click', () => { boundAdd(10); })
随着应用的增大,如果整个应用的状态都放到一个reducer 中,那这个reducer 就太大了,需要拆分reducer,每一个reducer对应应用中某一块的状态。如果要想改变某一块的状态,就要在对应的reducer中监听action,然后改变state。dispatch action不用变,这个action,会到所有的reducer中。但reducer分散,又不能传递给createStore函数了,因为它只接受一个reducer。这时就要用到combineReducers了,它接受一个对象作为参数,每一个key对应的就是一个小reducer, 返回一个根reducer
const rootReducer = Redux.combineReducers({ numberChange: counter }) const store = Redux.createStore(rootReducer);
console.log(store.getState());
使用combineReducers之后, 程序的state也发生了变化,state 多了一层numberChange, numberChange 下面才是原来的counter。再写一个reducer来描述一下操作,比如点击add或minus,页面上显示‘加或减’, 这个reducer 的初始状态呢,就是一个空字符串
function desc(state = '', action) { switch (action.type) { case 'ADD': return '加' + action.data case 'MINUS': return '减1'; case 'RESET': return '重置'; default: return state; } }
rootReducer的创建就变成了如下形式
const rootReducer = Redux.combineReducers({
numberChange: counter,
actionDesc: desc
})
刚才说了,state 也会发生变化,要store.getState().numberChange.counter 才以取到以前的counter. store.getState().actionDesc 才能取到文字描述, render 函数修改如下
const spanDes = document.getElementById('desc');
function render() {
stateDiv.innerHTML = store.getState().numberChange.counter;
spanDes.innerHTML = store.getState().actionDesc;
}
页面中加了一个 h6, 用于显示
<h6>操作的文字描述: <span id="desc"></span></h6>
combinerReducers有时不太好理解,写一个简易的实现,可能更明白一点。首先它是一个函数,接受一个对象,然后返回一个reducer
function combineReducers(reducers) { return function combination(state, action) {} }
dispatch action时,它遍历所有的reducer, 调用它来改变状态。和action无关的reducer也会被调用,再次证明需要defaut分支,返回state。当然在遍历之前,创建一个新的对象,来保存调用reducer产生的新对象,新的的状态。
function combineReducers(reducers) { const reducerKeys = Object.keys(reducers); // 对应reducer的keys, return function combination(state = {}, action) { const nextState = {} // reducer都是返回一个新的对象,创建一个新的对象进行保存 for (const key of reducerKeys) { // 进行真正的遍历 const reducer = reducers[key]; // 取出每一个小的reducer函数 const previousStateForKey = state[key]; // 根据key,从state中找出key对应的state const nextStateForKey = reducer(previousStateForKey, action); // 调用reducer,返回新的状态 nextState[key] = nextStateForKey; // 新的状态赋值给nextState 更新state } return nextState; // 返回新的整个的state } }
Redux开发,就是先想好state长啥样,有哪些action,然后写reducer,根据action创建新state。

浙公网安备 33010602011771号