本文通译自: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
,是因为闭包记住了先前执行中传递的值,这就是二者的关联。
以上,后面再遇见类似的代码结构知道怎么优化了吧!
撰文不易,点赞鼓励 👍👍👍👍👍👍
我是掘金安东尼,公众号同名,输出暴露输入,技术洞见生活,再会~