今天来讨论一下redux源码部分
先附上demo 如果大家有问题 可以直接加我QQ:469373256
demo地址:https://github.com/fangkyi03/redux-demo.git
- demo
function funtest(state,action){
console.log('state',state,action)
switch (action.type) {
case 'test':
return Object.assign(state,{test:action.payload})
default:
return state || {}
}
}
const fun1 = (store) => {
console.log('store',store);
return (next) =>{
console.log('next',next)
return (action) =>{
next(action)
console.log('action',action);
}
}
}
const fun2 = (store) => {
console.log('store',store);
return (next) =>{
console.log('next',next)
return (action) =>{
next(action)
console.log('action',action);
}
}
}
const store = createStore(combineReducers({funtest}),{a:1},applyMiddleware(fun1,fun2,fun1))
console.log('输出state',store.getState());
console.log('输出action',store.dispatch({type:'test',payload:'asdasd'}));
console.log('输出state',store.getState())
store.subscribe(()=>{
console.log('输出store最新变化',store.getState());
})
- 暴露的接口部分
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}
redux核心暴露出来的接口只有这么几个 接下来将会按照这个demo的执行顺序一一的给大家进行一下解释 先来看一下combineReducers这个函数的使用方法
- combineReducers
先附上完整代码 然后在一一讲解
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
combineReducers主要可以分为两个部分
1.效验部分 效验你输入的参数是否符合要求,如果不符合就会在使用之前直接抛出异常
- combination部分 也就是在每次dispatch的时候 都会执行的这个
在这里会传入最新的state跟action 每次都会重新的去执行一遍 并且返回最新的一个currentState值 这里也是比较诟病的地方 因为如果你的reduce比较多的情况下 你每次dispatch都会去将所有的reduce重新去走一遍 其实是比较耗费性能的
将效验可以分成如下几种情况
1.当执行reduce以后 返回的值为undefinde的时候
在这里会调用两种模式 一种是直接传递type为init的 一种是传递type为随机数的
其实主要就是确保你在switch的时候 都加了default类型 避免 出现返回undefined的情况
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
if (typeof initialState === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`
)
}
const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
if (typeof reducer(undefined, { type }) === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined when probed with a random type. ` +
`Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined, but can be null.`
)
}
})
}
2.如果发现state不等于对象的时候报错
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` +
({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
}
3.如果在新传入的state与第一次初始化时init返回的节点不一致的时候 会直接进行错误提醒 但是并不会中断程序的运行
const unexpectedKeys = Object.keys(inputState).filter(key =>
!reducers.hasOwnProperty(key) &&
!unexpectedKeyCache[key]
)
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
4.如果在combination的运算过程中发现返回的state如果是一个undefined的话会直接报错 这个错误会直接中断程序的运行 所以reduce的要求就是 任何情况下 都不能返回一个空数据
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
这里正常应该讲中间件了 但是这边稍微等一下 因为要先看一下createStore中是如何处理的 才能继续讲applyMiddleware
- createStore部分
参数定义
export default function createStore(reducer, preloadedState, enhancer)
如果preloadedState初始化state是一个函数 并且enhancer值为空的情况下 那么就将preloadedState当做中间件处理来使用
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
如果enhancer中间件函数不等于空 并且不等于函数的话 就直接报错
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
如果reducer不等于函数则直接报错
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
从这里可以看到createStore有两种写法
createStore(reducer, preloadedState, enhancer)
createStore(reducer, enhancer)
两者的区别是一种带了默认state一种不带
当然一般用了ssr的话 都会采用第一种 因为这样会更加方便初始化state导入
上面我们看到 如果你有传入applyMiddleware中间件的话 默认会走中间件 并且将我们自身给传递过去 那么我们再看一下中间件的写法
- 中间件完整代码
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
这里传递了原有的createStore过来以后 又使用它去进行了一次初始化
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
这里两句话 才是redux最为经典的地方 将这两个地方就得先看一下中间件的结构
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
- 中间件代码结构
const fun1 = (store) => {
console.log('store',store);
return (next) =>{
console.log('next',next)
return (action) =>{
next(action)
console.log('action',action);
}
}
}
const fun2 = (store) => {
console.log('store',store);
return (next) =>{
console.log('next',next)
return (action) =>{
next(action)
console.log('action',action);
}
}
}
按照执行顺序来的话 如果你执行了这个以后
chain = middlewares.map(middleware => middleware(middlewareAPI))
实际上返回的是中间件的这部分以后的代码 并且会给所有的中间件传递两个函数一个dispatch 一个getState
return (next) =>{
console.log('next',next)
return (action) =>{
next(action)
console.log('action',action);
}
}
- compose(...chain)(store.dispatch)
这个部分其实分为几种类型 1.没有数据,2.单条数据 3.多条数据
来看一下compose的源码
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...reset) => a(b(...reset)))
}
1.如果你中间件里面没有数据的话 最走这里返回的arg其实就是你传进去的dispatch
if (funcs.length === 0) {
return arg => arg
}
dispatch = compose(...chain)(store.dispatch)
2.如果你传进去的长度为1的话 说明只有一条数据 那么这里就直接返回对应的那个数据
if (funcs.length === 1) {
return funcs[0]
}
刚才我们上面已经提到了 我们在
middlewares.map(middleware => middleware(middlewareAPI))
执行完以后 我们所有返回的值 其实都已经在next子函数下面了
所以当我们在这里直接return funcs[0]的时候 实际上就是返回我们的next
dispatch = compose(...chain)(store.dispatch)
因为这句代码的关系 我们返回的next被执行了 并且传入了store.dispatch这个参数 然后返回了next里面包裹的action函数 作为新的dispatch函数
所以从这里可以看到 单条数据的是next等于传入的store.dispatch
所以你可以在接收到action以后 直接next(action)这种方式 直接让他走对应的reduce
3.多条数据跟单条的有一点差别 单条数据的时候 next 永远是dispatch 但是如果多条的时候 next就成为了 控制你下一个中间件是否执行的关键 因为next在多条里面 传入的不单单是dispatch了 还有可能是你上一个中间件的next部分
return funcs.reduce((a, b) => (...reset) => a(b(...reset)))
如果你只有两条数据的时候 这个...reset依旧是你传递进去的store.dispatch
如果你在多余两条数据传递进去以后 这里的...reset在第一次还是store.dispatch
在第二次就变成了
return (next) =>{
console.log('next',next)
return (action) =>{
next(action)
console.log('action',action);
}
}
在这里是不是觉得有点绕 感觉这样的操作 最后是这么返回一个dispatch的呢
funcs.reduce((a, b) => (...reset) => a(b(...reset)))
在这个阶段 其实永远不知道传进去的action到底是什么 这里只是生成了一个嵌套的函数链 最后生成的一个函数 会在每次dispatch的时候 都将所有的中间件全部的执行一次 知道最后一层使用了dispatch的时候 才算是结束 了
dispatch((action)=>{
return a(b(...arg))(action)
})
ok 现在redux最核心的部分就讲完了 接下来讲一下回调监听的部分
- subscribe
这个部分其实比较简单 就是你传入的任何一个listener只要是一个函数就会 被加入到监听的数组里面 然后返回一个卸载的函数 那么这个监听函数又是在哪里被使用的呢 这里就得看一下createStore中关于dispatch的定义了
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)
}
}
- dispatch定义
我们在讲刚才的compose的时候 有说过 你可以在中间件里面直接用dispatch 也可以直接使用传进去的那个store.dispatch 甚至 你也可以不执行next来阻止接下来的中间件执行 不管如何情况 如果在中间件所有都符合的情况下 这个dispatch肯定会被执行 如果你当前中间件在运行过程中 在currentReducer还没执行完毕的情况下 你又去发起了一条dispatch的话 是会导致奔溃报错的
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
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
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
- dispatch 主要看两个部分 currentReducer与listeners
在这里要说两点 这里其实也是redux性能最大的一个问题所在
当你每次执行dispatch的时候 都会去生成一个最新的reducer并且还会将所有的监听都去执行一遍 其实这里耗费的性能是很大的 这里的currentReducer就是combination
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
- 参数定义部分
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
在这里 redux源码 基本分析完毕 如果大家有问题 可以直接加我QQ:469373256
网友评论