前言
关于redux, 声明以下几点,便于理解
- redux 是一个状态管理器。
- redux 和 react 没有关系,redux 可以用在任何框架中。
- connect 和 不属于 redux,属于 react-redux。
- redux的专有名词,一个个看
- store
- createStore(返回 dispatch、getState、subscribe, replaceReducer)
- action
- reducer
- combineReducers
- middleware
- applyMiddleware
一、createStore
写在前面
明确store概念(包括但不限于state的读取, 还包括分发actions 和 订阅state变化)
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
1、内部总览
把源码的注释和强大异常处理都干掉, 看下 createStore的结构, 如此清晰.
所以说 createStore就是一个用于创建 store 对象的函数,store包含 getState, dispatch, subscribe, replaceReducer, observable这些方法.
2、getState
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
源码就这么点, 作用就是获取当前state
3、subscribe
let currentListeners = []
let nextListeners = currentListeners
function subscribe(listener) {
// 异常处理 直接跳过 主要看核心思想
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 异常处理 直接跳过 主要看核心思想
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
)
}
let isSubscribed = true
// 核心
nextListeners.push(listener)
// 取消订阅
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
)
}
isSubscribed = false
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
订阅的核心 其实就是 nextListeners.push(listener) 这行, 把使用state的listener, 放到一个数组里, 美其名曰:“订阅”, 订阅的主要调用位置在dispatch里.
4、dispatch
let currentReducer = reducer
let currentListeners = []
function dispatch(action) {
try {
isDispatching = true
// 通过reducer触发action, 生成新的state
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// nextListeners 就是上面subscribe的时候存放的多个listener
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
// 挨着个调用listener, 以达到通知使用者state更新的目的
listener()
}
return action
}
dispatch源码的核心部分是这些, 作用就是 通过当前reducer生成新的state, 然后通知各个state的使用者
5、replaceReducer
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
// This action has a similiar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({ type: ActionTypes.REPLACE })
}
redux提供了一个替换当前reducer的方法, 实际使用场景很少
6、observable
function observable() {
const outerSubscribe = subscribe
return {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
二、Reducer
1、概念
reducer: 是一个根据action的‘type’, 接收老的 state,返回新的 state的函数(switch case)
action: action 是一个对象,必须包含 type 字段
combineReduces: 合并多个reducer, 返回 state
2、redux-demo-async源码分析
import { combineReducers } from 'redux';
import {
INVALIDATE_SUBREDDIT,
RECEIVE_POSTS, REQUEST_POSTS, SELECT_SUBREDDIT
} from '../actions';
const selectedSubreddit = (state = 'reactjs', action) => {
switch (action.type) {
case SELECT_SUBREDDIT:
return action.subreddit
default:
return state
}
}
const posts = (state = {
isFetching: false,
didInvalidate: false,
items: []
}, action) => {
switch (action.type) {
case INVALIDATE_SUBREDDIT:
return {
...state,
didInvalidate: true
}
case REQUEST_POSTS:
return {
...state,
isFetching: true,
didInvalidate: false
}
case RECEIVE_POSTS:
return {
...state,
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
}
default:
return state
}
}
const postsBySubreddit = (state = { }, action) => {
switch (action.type) {
case INVALIDATE_SUBREDDIT:
case RECEIVE_POSTS:
case REQUEST_POSTS:
return {
...state,
[action.subreddit]: posts(state[action.subreddit], action)
}
default:
return state
}
}
/**
* 相当于 const rootReducer = combineReducers({
postsBySubreddit: postsBySubreddit
selectedSubreddit: selectedSubreddit
})
* 里面是state: stateReduceHandle
*/
const rootReducer = combineReducers({
postsBySubreddit,
selectedSubreddit
})
export default rootReducer
可以清晰的看到reducer的样子, 我们直接来看核心 combineReducers
3、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]
// 健壮校验, 保证传进来的reducer都是function
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// 返回的这个combination, 就是 createStore中的 currentReducer(currentState, action)
return function combination(state = {}, action) {
let hasChanged = false
// 这个就是最终返回的state
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
/** 这是state本身的形状
* state: {
* reducerKey1: {
*
* },
* reducerKey2: {
*
* },
* ...
* }
*/
// 根据key先获取到当前reducer要更改的state
const previousStateForKey = state[key]
// 调用当前reducer生成新的state
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
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
// 返回state
return hasChanged ? nextState : state
}
}
所以, 其实 combineReducers 所做的就是把多个reducer合并成一个总的combination reducer, 而这个 combination reducer 会在 createStore里作为currentReducer调用 以生成最新的state ,然后在通知到各个使用state的地方,吧啦吧啦....
三、Middleware
1、概念
官方这个注释还是很清楚的, enhancer 是用来增强store的, 可选的方式有 中间件、时间旅行、持久性等. redux提供的唯一store增强函数就是 applyMiddleware.
中间件: 也就是用来增强store功能的, 具体的说,是扩展dispatch的函数
2、Applymiddleware
先上源码
// =====index.js===========
import { createLogger } from 'redux-logger'
const middleware = [ thunk ]
if (process.env.NODE_ENV !== 'production') {
// middleware 是个数组,
middleware.push(createLogger())
}
// 调用applyMiddleware (函数调用的'...‘: 扩展运算符, 把数组转为用逗号分隔的参数序列)
const store = createStore(
reducer,
applyMiddleware(...middleware)
);
// =====createStore.js===========
// export default function createStore(reducer, preloadedState, enhancer) {
// ...省略,
// if (typeof enhancer !== 'undefined') {
// if (typeof enhancer !== 'function') {
// throw new Error('Expected the enhancer to be a function.')
// }
// enhancer 就相当于applyMiddleware, 可以看到接受createStore作为第一个参数
// return enhancer(createStore)(reducer, preloadedState)
// }
// ...省略,
// }
// =====applyMiddleware.js===========
// 函数入参的‘...’: 剩余参数运算符, 将一个不定数量的参数表示为一个数组
export default function applyMiddleware(...middlewares) {
// 返回一个接收createStore作为参数的函数, args实际上是createStore中enhancer的第二个参数(reducer, preloadedState)
return createStore => (...args) => {
// 获取到当前store
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// middlewares 外边调用的时候展开,里面使用的时候又合成数组, 好处就是写法简单
// 给每个middleware传入middlewareAPI, 原因得看middleware的长相(写在第三点)
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 那上面得到的就是多个符合redux要求的 middleware chain
// 接下来看compose 这个函数 (写在第四点)
// 把store.dispatch透传给多个middleware
dispatch = compose(...chain)(store.dispatch)
// 更新store里的dispatch, 至此完成原有dipatch的增强
return {
...store,
dispatch
}
}
}
3、middleware
//这个是 Redux.Middleware 的长相
/** @template DispatchExt Extra Dispatch signature added by this middleware.
* @template S The type of the state supported by this middleware.
* @template D The type of Dispatch of the store where this middleware is installed.
*/
// 可以看到 Redux的对middleware的入参要求 MiddlewareAPI<D, S>
export interface Middleware<
DispatchExt = {},
S = any,
D extends Dispatch = Dispatch
> {
(api: MiddlewareAPI<D, S>): (
next: Dispatch<AnyAction>
) => (action: any) => any
}
4、compose
源码非常精炼
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
// 如果只有一个中间件,就直接返回它
if (funcs.length === 1) {
return funcs[0]
}
// 这个写法太骚了 reduce里面curry(先看第五点)
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
5、Curry
A curried function is a function that takes multiple arguments
one at a time.
Given a function with 3 parameters, the curried version will take one argument and return a function that takes the next argument, which returns a function that takes the third argument. The last function returns the result of applying the function to all of its arguments.
柯里化函数是一次接受多个参数的函数。给定一个具有3个参数的函数,当前版本将使用一个参数,并返回一个使用下一个参数的函数,该函数返回一个使用第三个参数的函数。最后一个函数返回将函数应用于所有参数的结果。
// =======demo1
const add = a => b => a + b;
const result = add(2)(3); // => 5
// =======demo2
const g = n => n + 1;
const f = n => n * 2;
const h = x => f(g(x));
h(20); //=> 42
据此 可以写出这么个函数
const compose = (g,f) => x => f(g(x));
如果参数很多怎么办? 肯定不能像这么一直写...
const compose1 = (g,f,h,j) => x => j(h(f(g(x))));
// compose2 容易理解, 第一次调用,返回一个接收x作为初始值的reduce函数,x作为reduce函数的init值
const compose2 = (...fns) => x => fns.reduceRight((y, f) => f(y), x);
// compose3 相当于把 compose1 的写法放进 reduce
const compose3 = (...funcs)=> funcs.reduce((a,b) => (...args) => a(b(...args)))
//使用:
var add1 = x => x+1
var add6 = x => x+6
var mul = o => o *100
var test2 = compose2(add1,add6,mul);
var test3= compose3(add1,add6,mul);
test2(10) // => 1007
test3(10) // => 1007
// =======demo3
const map = fn => mappable => mappable.map(fn);
可以给每个数组element依次调用fn
使用1:
var add6 = x => x+6
var testMap = map(add6)
var arr = [1,2,4,5]
testMap(arr) // => [7,8,10,11]
使用2:
const arr = [1, 2, 3, 4];
const isEven = n => n % 2 === 0;
const stripe = n => isEven(n) ? 'dark' : 'light';
const stripeAll = map(stripe);
const striped = stripeAll(arr);
log(striped);
// => ["light", "dark", "light", "dark"]
总结:
还是这张图