什么是Redux?
管理整个前端项目(单页应用)所有的状态数据,统一把整个应用的状态存到一个地方(store),保存成一个状态树,修改数据需要派发(dispatch)一个动作(action)通知store修改。组件通过订阅(subscribe)修改事件,获取最新数据来修改自身状态。
三大原则
- 整个应用的state存储在store中,有且只存在一个store。
- store里面的state是只读的,唯一改变state的方法就是派发(dispatch)一个动作(action)。
- 纯函数(reducer)修改state,每次返回一个新的state,不能直接修改原对象。
为什么要使用Redux(应用场景)
- 单页应用复杂,管理不断变化的state非常困难。
- 非父子关系组件通信。
- 所有页面的公用状态。
从源码出发,整个redux源码,总共导出5个函数,和一个__DO_NOT_USE__ActionTypes
(默认的action,所有action类型不能重复,下面会细撩这个点),那么下面就来细细的撩一下5个函数。
1.createStore(reducer, preloadedState, enhancer)
想要使用redux,首先就是创建一个全局的store(当然是唯一的一个),就得调用这个函数(createStore)。该函数接收三个参数。store里面保存了所有的数据,可以看做一个容器。
传入reducer
和initState
创建store。
store返回给钥匙
,修改器
,电话
。
有了钥匙就能随时取数据,如果需要修改数据就得通过修改器,如需要需要知道数据什么时候修改了,就打一个电话给store,告诉它,数据修改好了给我说。
先来看看它返回的函数:
getState() => currentState
返回当前的store状态树,包含所有state。这里在读源码的时候发现一个疑问。
这里返回的是原本的对象,那么外部拿到这个state,不是可以直接修改吗?这样违背了只读。为什么不返回一个新引用对象,防止此操作。
带着这个疑问,给Redux提了一个PR. 得到回复:
Yes - getState() just returns whatever the current state value is. It's up to you to avoid accidentally mutating it.
(译文)是的,getState()只返回当前状态值。你要避免不小心把它弄乱。
也就是说在这里需要注意一下,getState()返回的值不能直接修改,否则你将陷入深渊
subscribe(listener) => unsubscribe()
传入一个函数用来监听store发生变化,一旦store发生了变化,将循环执行所有的监听函数。调用该函数还返回了一个取消监听的函数。调用返回的函数,则取消该listener监听。
dispatch(action) => action
dispatch接收一个action。在函数内部会把所有的reducer执行一遍并把当前的state和action传入reducer,然后再执行所有的监听函数。从源码中截了一段出来:
const dispatch = (action) => {
currentState = currentReducer(currentState, action);
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
action
一个对象,必须包含一个type,表示所要发生的动作,其他属性自由设置,社区中有一个规范,把其他所有数据放入payload中,一般开发中会写一个函数来生成action。
function login(){
retrun {
type: 'LOGIN',
payload: {
username: 'xxx',
passworld: 'xxxx'
}
}
}
replaceReducer(nextReducer)
传入新的reducer,替换之前的reducer,并且派发一个ActionType为REPLACE的action。
再来看看它接收的三个参数
reducer
一个纯函数,接收当前的state和action做为参数,返回新的state。
Store 收到 Action 以后,会调用reducer,必须给出一个新的 State,这样 Store里的数据才会发生变化。
编写一个简单的reducer
const reducer = function (state, action) {
switch(action.type){
case 'TYPE':
return {...state, newState}
default:
return state;
}
};
preloadedState
源码里面的参数名叫这个,其实我更愿意叫它initState,初始化的状态,为什么源码不叫initState呢?因为不准确,在源码里会默认发送一个type为init的dispatch(),这个时候走到reducer里面(看看上面的reducer代码),state如果在这个时候设置一个默认值,比如:
const reducer = function (state = {list: []}, action) {
switch(action.type){
case 'TYPE':
return {...state, newState}
default:
return state;
}
};
这个时候就默认返回了{list: []},就给出了一个真正的initState。
enhancer
用来配合快速创建中间件的。
上面提到的__DO_NOT_USE__ActionTypes
,就是2个actionType:
-
@@redux/INIT
: 用来内部发送一个默认的dispatch -
@@redux/REPLACE
: 用来替换reducer
2.bindActionCreators(actionCreators, dispatch) => boundActionCreators
遍历所有生成action函数并执行,返回被dispatch包裹的函数,可供直接调用派发一个请求。
actionCreators
该参数为一个对象,包含生成action的函数,例如:
const actionCreators = {
login: () => {
return {
type: 'LOGIN',
payload: {
username: 'xxx',
passworld: 'xxxx'
}
}
},
logout: () => {
retrun {
type: 'LOGOUT'
}
}
}
如果传入一个函数,则执行函数得到action,返回一个dispatch(action)。
dispatch
这里就是createStore所返回的dispatch
该函数返回对象或函数(根据传入的actionCreators来决定),可以直接调用xx.login()去派发一个登陆。
3.combineReducers(reducers)
在项目开发中,需要分模块写reducer,利用该函数合并多个reducer模块。传入一个reducer集合。
a.js
export (state = {list: []}, action) => {
switch (action.type) {
case "LIST_CHANGE":
return { ...state, ...action.payload };
default:
return state;
}
}
b.js
export (state = {list: []}, action) => {
switch (action.type) {
case "LIST":
return { ...state, ...action.payload };
default:
return state;
}
}
combineReducers.js
import a from './a';
import b from './b';
combineReducers({
a: a,
b: b
})
a
和b
都有list这个state,但是他们并不相关,要把他们分开使用,就得用combineReducers去合并。
下面简单实现了该函数:
function combineReducers(reducers) {
return (state = {}, action) => {
return Object.keys(reducers).reduce((currentState, key) => {
currentState[key] = reducers[key](state[key], action);
return currentState;
}, {})
};
}
4.compose
可以说是js中函数式中很重要的方法,把一堆函数串联起来执行,从右至左执行(也就是倒序),函数的参数是上一个函数的结果。看一个使用例子:
const fn1 = (val) => val + 'fn1';
const fn2 = (val) => val + 'fn2';
const fn3 = (val) => val + 'fn3';
const dispatch = compose(fn1, fn2, fn3);
console.log(dispatch('test'));
最终输出结果testfn3fn2fn1
test传给fn3当参数,fn3的返回值给了fn2....
compose.js
function compose(...fns){
if(fns.length==1) return fns[0];
return fns.reduce((a,b)=>(...args)=>a(b(...args)));
}
5.applyMiddleware
该函数是用来添加中间件,在修改数据的时候做一些其他操作,redux通过改造dispatch来实现中间件.
使用中间件
const middleware = applyMiddleware(logger);
需要传入中间件函数,可以是多个函数,依次传入,在applyMiddleware里面用到了compose,所以我们传入的函数的从右至左依次执行的,这里需要注意一下。
const createStoreWithMiddleware = middleware(createStore);
因为中间件是通过改造dispatch来实现,所以需要把创建store的方法传进去。
const store = createStoreWithMiddleware(reducer, preloadedState)
这里再传入createStore需要接收的参数,返回store对象。
实现一个logger中间件
const logger = function({dispatch, getState}){
return function(next){
return function(action){
console.log('oldState',getState());
next(action); // 真实派发动作
console.log('newState',getState());
}
}
}
首先middleware会把未改造的dispatch
和getState
传入进来,这里的next
相当于dispatch,去派发一个真正的修改数据动作。
源码贴上:
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
middlewareAPI
是存储未改造的方法,compose
上面讲过,第一个参数传入的就是dispatch,返回的一个改造后dispatch就是通过compose过后的一个函数,会依次执行。
最后
一些学习心得,如有不对欢迎指正
我的github
谢谢你阅读我的文章