redux 中间件 + 函数柯里化
最近因为干活不得不接触redux,听某大佬说redux设计思想不错可以借鉴,所以来爬坑
- 中间件篇从官方文档说起
// 官方文档中利用这个函数改造了dispatch方法,在这里我们的next函数就是原来的dispatch方法,也许看这个会有点不懂看下面这个
function logger(store) {
return function wrapDispatchToAddLogging(next) {
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
}
// 作用等同于上面就是一个next作为参数一个是内置好的不过二者的目的都是在真正的next执行前后进行一些操作一个中间件函数
function logger(store) {
// 这里的 next 必须指向前一个 middleware 返回的函数:
const next = store.dispatch
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
然而我们第一种方法因为作者大大这样说:middleware 以方法参数的形式接收一个 next() 方法可以防止第一个中间件没有改变Store.dispatch 就一直不变Store.dispatch, 因为这样中间件的参数就会变得灵活
然后为了优雅一个柯里化的箭头函数出炉
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
等价
function logger(store) {
return function wrapDispatchToAddLogging(next) {
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
}
作者目的:Middleware就是中间件函数 接收了一个 next() 的 dispatch 函数,并返回一个 dispatch 函数,返回的函数会被作为下一个 middleware 的 next(),以此类推。由于 store 中类似 getState() 的方法依旧非常有用,我们将 store 作为顶层的参数,使得它可以在所有 middleware 中被使用。
然后我们再来分析一下 redux里面的中间件生成源码。打开源码代码applymiddleware
export default function applyMiddleware(...middlewares) {
//...middlewares可以知道是中间件函数数组,他们都是长这个样子
//(Store)=>(next)=>(action)=>{//自己一些操作; next(action);}
//这里创建了一个Store
return createStore => (...args) => {
const store = createStore(...args)
//这里是一个 dispatch函数当被调用的时候就会发警告
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
// 给middlewareAPI设置参数 store.getState是为了获取Store上的数据,dispatch就是刚刚那个警告函数因为在中间件中调用Store.dispatch就会形成一个死循环
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
//这是一个chain数组里面的第一个中间件被结出一层因为传入了第一个参数middlewareApI
//(Store)=>(next)=>(action)=>{//自己一些操作; next(action);}
//所以变成了(next)=>(action)=>{//自己一些操作; next(action);}
// 函数柯里化会在下一个地方讲解
const chain = middlewares.map(middleware => middleware(middlewareAPI))
//最厉害的地方就在这里改造dispatch
dispatch = compose(...chain)(store.dispatch)
//打开compose里面的代码
return {
...store,
dispatch
}
}
}
//你没看错就这么一点点
export default function compose(...funcs) {
// funcs对应[(next)=>(action)=>{//自己一些操作; next(action);}, (next)=>(action)=>{//自己一些操作; next(action);}]数组chain
//当没有中间件函数的时候返回arg => arg
if (funcs.length === 0) {
return arg => arg
}
//有一个就会返回一个中间件函数
if (funcs.length === 1) {
return funcs[0]
}
// 有多个的时候就会返回a(b(c(...args)))这样嵌套的形式
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
然后在没有中间件的时候:
dispatch = arg => arg(store.dispatch)
store.dispatch作为参数被调用得到dispatch = store.dispatch
然后在一个中间件的时候:
dispatch = func(store.dispatch)
所以store.dispatch作为参数进去变成了:
(action)=>{//自己一些操作; store.dispatch(action);}
改造之后的一个dispatch函数添加了自己的操作还可以出发reducer
多个中间件函数来了的时候
先假设2个
m1, m2
因为reduce函数的递归作用是的函数堆叠成这个样子
m1(m2(…args))长这个样子
m1(m2(…args))(store.dispatch)以后m1(m2(store.dispatch))因为执行顺序是有从里面到外面
变成了
m2 = (action)=>{//自己一些操作; store.dispatch(action);}
m1: (action)=>{//自己一些操作; m2(action);}
然后解开dispatch = m1= (action)=>{//自己一些操作; m2(action);}
至此dispatch改造完成,当我们调用dispatch就会先执行m1然后里面的next函数调用的时候就是因为在这里面的next函数就是m2,就是下一个中间件,所以就有当m1被调用的时候执行next代表进行下一个中间件,这也是为什么中间件next函数设计的原因,next就是下一个中间件因为在中间件的compose.js里面处理的结果形成了嵌套,next是在applyMiddleware和compose对中间件处理的结果。next就是下一个中间件。中间件的规定形式真的奇妙啊。
但是最终执行的时候按照传入的中间件数组顺序执行,因为store.dispatch保留在最后一层函数中,所以所有的中间件进行处理完毕才会触发reducer,这个神奇的reduce堆叠和中间件的函数定义形式。
继续推到两个中间件那么多个中间件呢三个
m3: (action)=>{//自己一些操作; store.dispatch(action);}
m2: (action)=>{//自己一些操作; m3(action);}
m1: (action)=>{//自己一些操作; m2(action);}
只有先执行了m1然后调用next因为next对应m2所以执行m2,m2的next在处理的时候对应m3所以next就会执行m3最后m3的next就是store.dispatch
喵喵喵:严格保持顺序的中间件执行原因是因为中间件需要三个参数在中间件的处理函数中:把第一参数Store传入,执行了compose以后用reduce堆叠形成了一个m1(m2(m3())))形式严格保证了next参数的执行是下一个中间件,在回来的第二个参数Store.dispatch 被存储在最后一个函数中,让我们把所有的中间件都执行完毕才会执行触发reducer。但是此时返回的仍旧是一个函数,因为缺少最后一个参数才会被调用,因为dispatch是一个函数,在我们调用dispatch(action)就执行第一个中间件,然后next第二个中间件函数,我们可以通过next(action)来得到调用第二个中间件,依次顺序,dispatch改造完成厉害惊讶于next填充哈哈。有意思的reduce函数。还是参数的设定。函数柯里化的要点在于:缓存参数,延迟执行,流批流批。这个编程思想设计得巧妙。
快捷键
- 加粗
Ctrl + B
- 斜体
Ctrl + I
- 引用
Ctrl + Q
- 插入链接
Ctrl + L
- 插入代码
Ctrl + K
- 插入图片
Ctrl + G
- 提升标题
Ctrl + H
- 有序列表
Ctrl + O
- 无序列表
Ctrl + U
- 横线
Ctrl + R
- 撤销
Ctrl + Z
- 重做
Ctrl + Y
Markdown及扩展
Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]
使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。
本编辑器支持 Markdown Extra , 扩展了很多好用的功能。具体请参考Github.
表格
Markdown Extra 表格语法:
项目 | 价格 |
---|---|
Computer | $1600 |
Phone | $12 |
Pipe | $1 |
可以使用冒号来定义对齐方式:
项目 | 价格 | 数量 |
---|---|---|
Computer | 1600 元 | 5 |
Phone | 12 元 | 12 |
Pipe | 1 元 | 234 |
定义列表
-
Markdown Extra 定义列表语法:
项目1
项目2
- 定义 A
- 定义 B 项目3
- 定义 C
-
定义 D
定义D内容
代码块
代码块语法遵循标准markdown代码,例如:
@requires_authorization
def somefunc(param1='', param2=0):
'''A docstring'''
if param1 > param2: # interesting
print 'Greater'
return (param2 - param1 + 1) or None
class SomeClass:
pass
>>> message = '''interpreter
... prompt'''
脚注
生成一个脚注1.
目录
用 [TOC]
来生成目录:
数学公式
使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.
- 行内公式,数学公式为: Γ(n)=(n−1)!∀n∈ℕ Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N 。
- 块级公式:
更多LaTex语法请参考 这儿.
UML 图:
可以渲染序列图:
或者流程图:
离线写博客
即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。
用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。
博客发表后,本地缓存将被删除。
用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。
注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱。
浏览器兼容
- 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
- IE9以下不支持
- IE9,10,11存在以下问题
- 不支持离线功能
- IE9不支持文件导入导出
- IE10不支持拖拽文件导入
- 这里是 脚注 的 内容. ↩