内容简介:一切皆为函数函数式编程很棒。随着 React 的引入,越来越多的 JavaScript 前端代码正在考虑 FP 原则。但是我们如何在我们编写的日常代码中开始使用 FP 思维模式?我将尝试使用日常代码块并逐步重构它。我们的问题:用户来到我们的登录页面链接后会带一个
- 原文地址: A practical guide to writing more functional JavaScript
- 原文作者:Nadeesha Cabral
- 本文永久链接: github-heyushuo-blob
- 译者:heyushuo
一切皆为函数
函数式编程很棒。随着 React 的引入,越来越多的 JavaScript 前端代码正在考虑 FP 原则。但是我们如何在我们编写的日常代码中开始使用 FP 思维模式?我将尝试使用日常代码块并逐步重构它。
我们的问题:用户来到我们的登录页面链接后会带一个 redirect_to
参数。就像 /login?redirect_to =%2Fmy-page
。请注意,当 %2Fmy-page
被编码为 URL
的一部分时,它实际上是 / my-page
。我们需要提取此参数,并将其存储在本地存储中,以便在完成登录后,可以将用户重定向到 my-page
页面。
第 0 步:必要的方法
如果我们以最简单方式来呈现这个解决方案,我们将如何编写它?我们需要如下几个步骤
- 解析链接后参数。
- 获取 redirect_to 值。
- 解码该值。
- 将解码后的值存储在 localStorage 中。
我们还必须将 不安全
的函数放到 try catch
块中。有了这些,我们的代码将如下所示:
function persistRedirectToParam() { let parsedQueryParam; try { //获取连接后的参数{redirect_to:'/my-page'} 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; } //返回 my-page 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 步 尝试编写函数式
好的。现在,似乎 persistRedirectToParam
函数是 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 步 更具可读性的组合
如果你已经完成了以上的一些重构,那么你就会遇到 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); } 复制代码
compose 内的函数执行顺序为从右向左,即最右边的函数(最后一个参数)最先执行,执行完的结果作为参数传递给前一个函数。因此,在 compose 链中调用的第一个函数是最后一个函数。
如果你是一名数学家并且熟悉这个概念,这对你来说不是一个问题,所以你自然会从右到左阅读。但对于熟悉命令式代码的其他人来说,我们想从左到右阅读。
第 4 步 pipe(管道)和扁平化
幸运的是这里有 pipe(管道)
和 compose
做了同样的事情,但是执行顺序和 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 ); 复制代码
差不多了。请记住,我们适当地将 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); } }; } 复制代码
我们的函数在这里需要一个包含 tryer 和 catcher 函数的 opts 对象。它将返回一个函数,当使用参数调用时,使用所述参数调用 tryer 并在失败时调用 catcher。现在,当我们有不安全的操作时,我们可以将它们放入 tryer 部分,如果它们失败,则从捕获器部分进行救援并提供安全结果(甚至记录错误)。
第 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); 复制代码
这或多或少是我们想要的。但是为了确保代码的可读性和可测试性得到改善,我们也可以将“安全”函数(tryCatch 函数)分解出来。
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 ); 复制代码
现在,我们得到的是一个更强大功能的函数,由 4 个独立的函数组成,这些函数具有高度内聚性,松散耦合,可以独立测试,可以独立重用,考虑异常场景,并且具有高度声明性。
有一些 FP 语法糖使这变得更好,但是这是以后的某一天。
如果发现译文存在错误或其他需要改进的地方请指出。
以上所述就是小编给大家介绍的《[译] 编写函数式的 JavaScript 实用指南》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 编写短小的函数/方法
- [译] 实用指南: 编写函数式 JavaScript
- 在Clojure中编写累加器函数
- 如何通过 JavaScript 编写高质量的函数(三):函数式编程之理论篇
- 如何通过 JavaScript 编写高质量的函数(四):函数式编程之实战篇
- 如何在Haskell中编写一个恒定空间长度函数?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
图片转BASE64编码
在线图片转Base64编码工具
RGB CMYK 转换工具
RGB CMYK 互转工具