[译] 实用指南: 编写函数式 JavaScript

栏目: Html5 · 发布时间: 5年前

内容简介:原文:https://www.freecodecamp.org/news/a-practical-guide-to-writing-more-functional-javascript-db49409f71/译者:zhicheng

[译] 实用指南: 编写函数式 JavaScript

原文:https://www.freecodecamp.org/news/a-practical-guide-to-writing-more-functional-javascript-db49409f71/

译者:zhicheng

校对者:lily

提示:文中的蓝色字体大家可以点击文末“阅读原文”在 freeCodeCamp 中文论坛访问链接

一切都是函数。

函数式编程很棒。随着 React 的推进,越来越多的 JavaScript 前端代码开始基于 FP 原则开发。怎样在日常的编码中使用 FP 呢?让我们来一起写一些日常代码,然后一步步重构它。

[译] 实用指南: 编写函数式 JavaScript

场景: 用户来到  /login  页,可能带有  redirect_to  参数。比如  /login?redirect_to=%2Fmy-page 。注意  %2Fmy-page  其实是  /my-page  做为 URL 的部分编码后的样子。我们需要将请求参数取出来,然后存储到 local Storage 里,这样登录成功,用户就可以直接跳转到  my-page  页了。

第 0 步:迫切的方法

如果需要紧急上线一个解决方案,应该怎么写呢?需求如下:

  1. 解析请求参数。

  2. 得到  redirect_to  的值。

  3. 解码。

  4. 存储到 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 语法糖来做到更优雅,改天再聊。

推荐阅读:

从全职妈妈到 Web 前端开发者

freeCodeCamp 教全世界编程的首个 10 亿分钟

FCC DevTalk 001丨柳星:从材料工程师到 Apple 开发者

[译] 实用指南: 编写函数式 JavaScript

点击 阅读原文 访问  freeCodeCamp 中文论坛 的更多内容


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

移动Web前端高效开发实战

移动Web前端高效开发实战

iKcamp / 电子工业出版社 / 2017-9 / 89.00

移动互联网的兴起和快速普及,给前端开发人员带来了前所未有的新机遇。移动Web前端技术作为整个技术链条中重要的一环,却乱象丛生。《移动Web前端高效开发实战:HTML 5 + CSS 3 + JavaScript + Webpack + React Native + Vue.js + Node.js》是一本梳理移动前端和Native客户端技术体系的入门实战书。 《移动Web前端高效开发实战:HTML......一起来看看 《移动Web前端高效开发实战》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

html转js在线工具
html转js在线工具

html转js在线工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试