内容简介:原文:https://www.freecodecamp.org/news/a-practical-guide-to-writing-more-functional-javascript-db49409f71/译者:zhicheng
原文:https://www.freecodecamp.org/news/a-practical-guide-to-writing-more-functional-javascript-db49409f71/
译者:zhicheng
校对者:lily
提示:文中的蓝色字体大家可以点击文末“阅读原文”在 freeCodeCamp 中文论坛访问链接
一切都是函数。
函数式编程很棒。随着 React 的推进,越来越多的 JavaScript 前端代码开始基于 FP 原则开发。怎样在日常的编码中使用 FP 呢?让我们来一起写一些日常代码,然后一步步重构它。
场景: 用户来到 /login
页,可能带有 redirect_to
参数。比如 /login?redirect_to=%2Fmy-page
。注意 %2Fmy-page
其实是 /my-page
做为 URL 的部分编码后的样子。我们需要将请求参数取出来,然后存储到 local Storage 里,这样登录成功,用户就可以直接跳转到 my-page
页了。
第 0 步:迫切的方法
如果需要紧急上线一个解决方案,应该怎么写呢?需求如下:
-
解析请求参数。
-
得到
redirect_to
的值。 -
解码。
-
存储到 localStorage。
对于可能会抛出异常的函数,还要用 try catch 语句嵌套起来。综上,代码如下:
function persistRedirectToParam() { let parsedQueryParam; try { parsedQueryParam = qs.parse(window.location.search); // https://www.npmjs.com/package/qs } catch (e) { console.log(e); return null; } const redirectToParam = parsedQueryParam.redirect_to; if (redirectToParam) { const decodedPath = decodeURIComponent(redirectToParam); try { localStorage.setItem("REDIRECT_TO", decodedPath); } catch (e) { console.log(e); return null; } return decodedPath; } return null; }
第 1 步:每一步都改为函数
此刻,先忘掉 try catch 语句,把一切都改写成函数。
// let's declare all of the functions we need to have const parseQueryParams = (query) => qs.parse(query); const getRedirectToParam = (parsedQuery) => parsedQuery.redirect_to; const decodeString = (string) => decodeURIComponent(string); const storeRedirectToQuery = (redirectTo) => localStorage.setItem("REDIRECT_TO", redirectTo); function persistRedirectToParam() { // and let's call them const parsed = parseQueryParams(window.location.search); const redirectTo = getRedirectToParam(parsed); const decoded = decodeString(redirectTo); storeRedirectToQuery(decoded); return decoded; }
当所有的输出都改成函数时,会发现已经重构了主函数的所有的内容。这样做的好处是,函数复用性更强,更易于测试。
之前,可以做把函数当成整体来测试。现在有 4 个小函数,其中有一些只是另一个函数的重命名,测试不需要覆盖到它们。
找到重命名函数,移除代理,这样代码又简洁了一点。
const getRedirectToParam = (parsedQuery) => parsedQuery.redirect_to; const storeRedirectToQuery = (redirectTo) => localStorage.setItem("REDIRECT_TO", redirectTo); function persistRedirectToParam() { const parsed = qs.parse(window.location.search); const redirectTo = getRedirectToParam(parsed); const decoded = decodeURIComponent(redirectTo); storeRedirectToQuery(decoded); return decoded; }
第 2 步: 尝试组合函数
现在, presisRedirectToParams
看起来更像是其它 4 个函数的合集。看看能不能把它们写成一个,从而省去 const
定义的中间结果。
const getRedirectToParam = (parsedQuery) => parsedQuery.redirect_to; // we have to re-write this a bit to return a result. const storeRedirectToQuery = (redirectTo) => { localStorage.setItem("REDIRECT_TO", redirectTo) return redirectTo; }; function persistRedirectToParam() { const decoded = storeRedirectToQuery( decodeURIComponent( getRedirectToParam( qs.parse(window.location.search) ) ) ); return decoded; }
看起来不错,但是函数多层嵌套看着有点别扭,如果能去掉就好了。
第 3 步: 可读性更强
如果用过 redux 或者 recompose,那么你应该知道 compose
。 Compose 是可以接受多个函数的 工具 函数,它依次调用传入的函数,最终返回一个函数。关于 composition 这里有详细的介绍 ,我就不详细展开了。
经过 compose , 代码如下:
const compose = require("lodash/fp/compose"); const qs = require("qs"); const getRedirectToParam = (parsedQuery) => parsedQuery.redirect_to; const storeRedirectToQuery = (redirectTo) => { localStorage.setItem("REDIRECT_TO", redirectTo) return redirectTo; }; function persistRedirectToParam() { const op = compose( storeRedirectToQuery, decodeURIComponent, getRedirectToParam, qs.parse ); return op(window.location.search); }
需要注意的是 compse 从右向左执行函数,也就是说,第一个被 compose
链调用的反而是最后一个函数。
站在数学角度讲这不难理解,就像概念描述的那样,从右向左很自然。但对于重构代码的我们来说,更希望是从左向右的顺序。
第 4 步: Piping 以及 flattening
万幸,有 pipe
, pipe
和 compose
做同样的事,它的调用顺序更直观。调用链里的第一个函数首先被执行。
同样的,如果 persistRedirectToParams
函数嵌套了其它函数,则称之为 op
。换言之,所有要做的就是执行 op
。这样就可以甩开嵌套并且以直观的方式调用函数。
const pipe = require("lodash/fp/pipe"); const qs = require("qs"); const getRedirectToParam = (parsedQuery) => parsedQuery.redirect_to; const storeRedirectToQuery = (redirectTo) => { localStorage.setItem("REDIRECT_TO", redirectTo) return redirectTo; }; const persistRedirectToParam = fp.pipe( qs.parse, getRedirectToParam, decodeURIComponent, storeRedirectToQuery ) // to invoke, persistRedirectToParam(window.location.search);
好事将近。别忘了,我们之前剥离了 try-catch
语句块,无视了一些风险,现在需要以某种方式再将其引入进来。 qs.parse
和 storeRedirectToQuery
是不安全的,方法之一是把他们嵌套在一个 try-catch
语句块内,另一个更函数式的方法是将 try-catch
做为一个函数。
第 5 步:处理函数的异常
有很多库可以搞定它,但是现在需要自己动手。
function tryCatch(opts) { return (args) => { try { return opts.tryer(args); } catch (e) { return opts.catcher(args, e); } }; }
函数的参数是一个 opt
对象,包含了 tryper
和 catcher
函数。当传参时调用 tryer
,异常时调用 catcher
,然后返回。现在,不安全的操作可以把它放在 tryer
部分里,失败时处理异常,在 catcher
里给出一个备用的结果 ( 即使是出错了)。
第 6 步:把所有的东西都放在一起
最终,代码如下:
const pipe = require("lodash/fp/pipe"); const qs = require("qs"); const getRedirectToParam = (parsedQuery) => parsedQuery.redirect_to; const storeRedirectToQuery = (redirectTo) => { localStorage.setItem("REDIRECT_TO", redirectTo) return redirectTo; }; const persistRedirectToParam = fp.pipe( tryCatch({ tryer: qs.parse, catcher: () => { return { redirect_to: null, // we should always give back a consistent result to the subsequent function } } }), getRedirectToParam, decodeURIComponent, tryCatch({ tryer: storeRedirectToQuery, catcher: () => null, // if localstorage fails, we get null back }), ) // to invoke, persistRedirectToParam(window.location.search);
这就是最终想要的。但是为了确保可读性以及易于测试,还可以抽离出不抛异常的函数。
const pipe = require("lodash/fp/pipe"); const qs = require("qs"); const getRedirectToParam = (parsedQuery) => parsedQuery.redirect_to; const storeRedirectToQuery = (redirectTo) => { localStorage.setItem("REDIRECT_TO", redirectTo); return redirectTo; }; const safeParse = tryCatch({ tryer: qs.parse, catcher: () => { return { redirect_to: null, // we should always give back a consistent result to the subsequent function } } }); const safeStore = tryCatch({ tryer: storeRedirectToQuery, catcher: () => null, // if localstorage fails, we get null back }); const persistRedirectToParam = fp.pipe( safeParse, getRedirectToParam, decodeURIComponent, safeStore, ) // to invoke, persistRedirectToParam(window.location.search);
现在我们实现了一下更庞大的函数,同时也是四个高内聚、低耦合、可测试、可复用、高容错、声明式的函数(当然,也更易读)。
还可以使用一些 FP 语法糖来做到更优雅,改天再聊。
推荐阅读:
FCC DevTalk 001丨柳星:从材料工程师到 Apple 开发者
点击 阅读原文 访问 freeCodeCamp 中文论坛 的更多内容
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 编写短小的函数/方法
- 在Clojure中编写累加器函数
- [译] 编写函数式的 JavaScript 实用指南
- 如何通过 JavaScript 编写高质量的函数(三):函数式编程之理论篇
- 如何通过 JavaScript 编写高质量的函数(四):函数式编程之实战篇
- 如何在Haskell中编写一个恒定空间长度函数?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
移动Web前端高效开发实战
iKcamp / 电子工业出版社 / 2017-9 / 89.00
移动互联网的兴起和快速普及,给前端开发人员带来了前所未有的新机遇。移动Web前端技术作为整个技术链条中重要的一环,却乱象丛生。《移动Web前端高效开发实战:HTML 5 + CSS 3 + JavaScript + Webpack + React Native + Vue.js + Node.js》是一本梳理移动前端和Native客户端技术体系的入门实战书。 《移动Web前端高效开发实战:HTML......一起来看看 《移动Web前端高效开发实战》 这本书的介绍吧!