为什么先讲createStore呢,根据代码书写逻辑,一般都把创建store这一部放在入口文件里(一步一步反推)。
首先npm装下redux包,进入src/index.js目录
//就暴露了5个对外接口,开不开心。只有这么点api
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}
今天我们首先先分析createStore.js(ps:如果你看到index.js的话,一定一眼就看到createStore.js)向外暴露了
1、export const ActionTypes = {
INIT: '@@redux/INIT'
}
2、export default function createStore(reducer, preloadedState, enhancer) {
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
很惊奇ActionTypes定义、暴露这个干嘛?何用?其实用处很大,初始化的state状态仓库就是dispatch(ActionTypes),之前很惊奇好奇,redux怎么初始化状态(ps:用惯了vuex),习惯很思维很难改。。。
再看第二个暴露,又return出dispatch,subscribe,getState,replaceReducer,[?observable]: observable 前三个api 很熟悉
那我们就看看源码createStore怎么定义的。
进入正题
1.参数,依次是reducer,初始预加载的状态,增强中间件插件
2.代码逻辑
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
三段if 无非判断传入参数,实现函数重载
第一个if,判断如果preloadedState(第二个参数)传人是函数类型,并且enhancer(第三个参数)没传,则将传入的第二个参数函数传递给第三个参数.
第二个if,如果有enhancer,确定enhancer是函数类型,不是,则抛错。如果是函数,则返回一个增强的中间件处理的dispatch。关于enhancer这一块详解,具体解释在,applymiddlewares时候再详细解释。
第三个if,reducer必须是一个函数,不是则抛错。
接下来,代码定义了一些初始变量,应该一看变量名就一清二楚了
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
接下来
//主要处理copy currentListeners的值,但nextListeners和currentListeners指向不一样,不是同一个数组
//这个函数,用在subscribe(订阅函数)内
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
剩下的代码,都是createStore暴露出来的api。
1. 获取当前状态的 getState
function getState() {
return currentState
}
2.订阅函数,参数是listener回掉函数。将listener函数放进nextListener数组内在用户触发dispatch时候,会依次触发nextListeners调用
注意,调用函数后,返回一个取消该listener函数的方法,如果你的订阅是一次性的,可以继续调用,取消订阅
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
3.派发用户传递的动作。function dispatch(action) {
//检验是不是个纯对象。
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
//传入参数action.type必须有值
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
//一次只能执行一个,防止同时触发。
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
//currentReducer是用户传进来的reducer,其实就是调用reducer
//两个参数是currentState,当前状态,行动。
//返回一个新的state,并存入currentState变量内
currentState = currentReducer(currentState, action)
} finally {
//try执行结束后,重置isDispatching状态
isDispatching = false
}
//触发subscribe订阅函数
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
//返回action,很重要,对于中间件来说
return action
}
至此,常用api 都已经定义完了。最重要的,初始化statedispatch({ type: ActionTypes.INIT })
源码结束。那现在就捋一捋dispatch({ type: ActionTypes.INIT })做了什么。
首先 肯定需要调用下createStore,代码才能执行。
以下是node环境写的,通常函数用es6的import来写
const reduxLib = require("redux")
const createStore = reduxLib.createStore
//定义了个reducer
function testReducer(state={total:0},action){
if(action.type == 'add'){
var total = state.total + 1
return Object.assign({},state,{total:total})
}else{
return state
}
}
//当我们不需要预知state,和enhancer时,一个reducer就够了let store = createStore(testReducer)
//基本代码写完了,不知道源码里dispatch({ type: ActionTypes.INIT })这句还有没有忘记//呢?当创建仓库时,源码手动触发了dispatch,这里面做什么了?看上文dispatch定义。//testReducer(undefined,{ type: ActionTypes.INIT })返回值赋值给currenState.var initState = store.getState();//拿到是撒?{total:0},是传入reducer参数state的默认值;
console.log('initState',initState)
//再尝试调用订阅函数
store.subscribe(function(){
var state = store.getState()
console.log(state)
})
//再手动触发dispatch,写个循环吧,😂
for(var i = 0 ; i< 10 ; i++){
store.dispatch({
type:'add'
})
}
//控台截图,是不是恍然大雾。。。createStore 第二个参数时,是一个对象,最好和reducer里的state数据模型保持一致。依我上例,继续说,源码dispatch({ type: ActionTypes.INIT }),会调用
let preloadedState = {
total:0
}
let store = createStore(testReducer,preloadedState);
console.log(store.getState()) //拿到的是preloadedState
剩下的关于createStore暴露的api,本系列暂时不讲(一般真的用不上。。。,经过我的梳理,相信你看下代码,一定能看的懂)。关于createStore第三个参数,在applyMiddleware节,详细讲解