小知识,大挑战!本文正在参与「程序员必备小知识」创作活动
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。
本文通译自:JavaScript Currying: A Practical Example
柯里化(Currying):是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数、返回最终结果的新函数的技术。
想必大家已经不算陌生!
例🌰:
const add = (a,b,c) => {
return a+b+c
}
add(1,2,3) // 6
const addCurry = a => b => c => a+b+c
addCurry(1)(2)(3) // 6
addCurry 的 return
版本写法:
const addCurryReturn = (a) => {
return (b)=> {
return (c)=> {
return a+b+c
}
}
}
OK,有了基本的认知后,直接上实战:柯里化 && Redux
以下代码从 Redux 中摘录:
// Partial file
...
extraReducers: {
[signup.pending.toString()]: (state, action) => {
state.loading = true
state.error = false
state.fulfilled = false
},
[signup.fulfilled.toString()]: (state, action) => {
state.loading = false
state.error = false
state.fulfilled = true
},
[signup.rejected.toString()]: (state, action) => {
state.loading = false
state.error = true
state.fulfilled = false
},
[signin.pending.toString()]: (state, action) => {
state.loading = true
state.error = false
state.fulfilled = false
},
[signin.fulfilled.toString()]: (state, action) => {
state.loading = false
state.error = false
state.fulfilled = true
},
[signin.rejected.toString()]: (state, action) => {
state.loading = false
state.error = true
state.fulfilled = false
},
}
...
从感官上看,这样的写法 —— 太重复冗余!
state.loading = true
state.error = false
state.fulfilled = false
对于 state
的设置必须抽象;
我们可以创建一个函数,将 fulfilled
、loading
和 error
设为可配置项,默认值为 false
;
const setStatus = (state, action) => ({fulfilled = false,loading = false,error = false}) => {
state.fulfilled = fulfilled
state.loading = loading
state.error = error}
setStatus(state)({fulfilled:true})
然后,代码就优化成了这样:
extraReducers: {
[signup.fulfilled.toString()]: (state, action) =>
setStatus(state)({ fulfilled: true }),
[signup.pending.toString()]: (state, action) =>
setStatus(state)({ loading: true }),
[signup.rejected.toString()]: (state, action) =>
setStatus(state)({ error: true }),
[signin.fulfilled.toString()]: (state, action) =>
setStatus(state)({ fulfilled: true }),
[signin.pending.toString()]: (state, action) =>
setStatus(state)({ loading: true }),
[signin.rejected.toString()]: (state, action) =>
setStatus(state)({ error: true }),
}
没有设置为 true
的项,都默认为 fasle
;
还没完,(state, action) =>setStatus(state)
这一部分仍是重复冗余的,必须接着抽象;
我们将 setStatus
这样的写法:
const setStatus = (state) => ({fulfilled = false,loading = false,error = false}) => {
state.fulfilled = fulfilled
state.loading = loading
state.error = error}
// 调用
setStatus(state)({fulfilled:true})
改造成以下写法:(敲重点, 柯里化就在此处体现✨)
// 更改传参顺序
const setStatus = ({fulfilled = false,loading = false,error = false}) => (state) => {
state.fulfilled = fulfilled
state.loading = loading
state.error = error}
// 调用
setStatus({fulfilled:true})(state)
最终的改造结果:
1. 改造前
extraReducers: {
[signup.pending.toString()]: (state, action) => {
state.loading = true
state.error = false
state.fulfilled = false
},
[signup.fulfilled.toString()]: (state, action) => {
state.loading = false
state.error = false
state.fulfilled = true
},
[signup.rejected.toString()]: (state, action) => {
state.loading = false
state.error = true
state.fulfilled = false
},
[signin.pending.toString()]: (state, action) => {
state.loading = true
state.error = false
state.fulfilled = false
},
[signin.fulfilled.toString()]: (state, action) => {
state.loading = false
state.error = false
state.fulfilled = true
},
[signin.rejected.toString()]: (state, action) => {
state.loading = false
state.error = true
state.fulfilled = false
},
}
2. 改造后
const setStatus = ({
fulfilled = false,
loading = false,
error = false,
}) =>
(state, action) => {
state.fulfilled = fulfilled
state.loading = loading
state.error = error
}
extraReducers: {
[signup.pending.toString()]: setStatus({ loading: true }),
[signup.fulfilled.toString()]: setStatus({ fulfilled: true }),
[signup.rejected.toString()]: setStatus({ error: true }),
[signin.pending.toString()]: setStatus({ loading: true }),
[signin.fulfilled.toString()]: setStatus({ fulfilled: true }),
[signin.rejected.toString()]: setStatus({ error: true }),
}
Why?
为什么改变了一个传参顺序,就能做到这样的简化效果?
噢,原来最根本的原因是以下的两种写法是等价的!(大道至简)
// 写法 1
onClick((state)=> updateState(state))
// 写法 2
onClick(updateState)
其实,函数作为一等公民的思想 —— 即把函数当成一个值来进行传递,太开放(相对于OOP)!
比如:
const add = (a,b) => a+b
const sub = (a,b) => a-b
const calc = (a, b, cb) => cb(a,b)
calc(3,4, add) // 7
calc(3,4, sub) // -1
calc
是高阶函数(接受一个或多个函数作为输入)!
function getName(name) {
return function greet(){
console.log('Hello, ', name)
}
}
const greet = getName('Karthick')
greet() // Hello, Karthick
getName
也是高阶函数(输出一个函数),返回的是后续再调用的一个函数;
我敲!上面这段代码怎么有点眼熟,有点像我们之前在(《你觉得“惰性求值”在 JS 中会怎么实现?》)讲的 【惰性求值】 ?!
function * st1(){
setTimeout(()=>{
console.log("惰性求值")
},1000)
yield("后续再调用")
}
let aThunk=st1()
console.log(aThunk) // st1 {<suspended>}
aThunk.next() // {value: '后续再调用', done: false}
确实,闭包结构赋值的时候也不会计算,等到后续调用的时候才计算,就是惰性的呀~
新理解: 在 JavaScript 中,除了 Generator 可以实现惰性求值,闭包也可以呀!Thunk 就是一个闭包!
不是说柯里化吗?咋说到闭包了?
再看一例🌰:
const addCurryReturn = (a) => {
return (b)=> {
return (c)=> {
return a+b+c
}
}
}
const add5 = addCurryReturn(5)
console.log(add5) // (b)=> { return (c)=> { return a+b+c } }
const add12 = add5(7)
console.log(add12) // (c)=> { return a+b+c }
add12(7) // 19
当我们调用 add12(7)
的时候,为什么会知道 x = 5
、y = 7
,是因为闭包记住了先前执行中传递的值,这就是二者的关联。
以上,后面再遇见类似的代码结构知道怎么优化了吧!
撰文不易,点赞鼓励 👍👍👍👍👍👍 我是掘金安东尼,公众号同名,输出暴露输入,技术洞见生活,再会~