内容简介:原文: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中编写一个恒定空间长度函数?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Algorithms on Strings, Trees and Sequences
Dan Gusfield / Cambridge University Press / 1997-5-28 / USD 99.99
String algorithms are a traditional area of study in computer science. In recent years their importance has grown dramatically with the huge increase of electronically stored text and of molecular seq......一起来看看 《Algorithms on Strings, Trees and Sequences》 这本书的介绍吧!