3

将 Typescript 用于 Angular ngrx 应用程序,我必须做很多检查,无论我访问或传递的值是否为空/未定义,例如在可能存在的列表上调用 .map()。

要走的路是什么?确保整个代码库永远不会移交空值?在每次通话之前检查,以防生成空列表之类的空对象?或者是否有某种类似空对象的函数式编程模式?

@Pavel 的评论:我们有一个可以选择的项目列表,如果是这样,详细信息会在视图中展开。我碰巧在删除列表中打开的项目时,应用程序崩溃了,因为对打开项目的引用设置为 null。我通过对我为修复应用程序崩溃而编写的所有选择器进行空检查来解决此问题,正如我在许多其他选择器中看到的那样,它们在浏览器中使用应用程序本地状态。

4

1 回答 1

3

这里有几个众所周知的模式可以提供帮助。

选项类型,又名 Maybe

Maybe/Option 是一个 sum 类型,在 TypeScript 中具有以下结构:

type Maybe<T> = T | void;

您可以在typescript 用户手册中阅读更多关于实现它的信息,只是ctrl-f为了“也许”,因为它们似乎出于某种原因避开了片段标识符。

装饰器

Maybe(其中一个)的问题在于它很快变得病毒化,它接管了你的整个代码库,并且每个值都变成了 Maybe。如果没有模式匹配,处理这个特别烦人(打字稿手册中的示例非常冗长)。恕我直言,一个更好的选择,因为 JavaScript/TypeScript 缺少任何类型的存在运算符,是编写没有空检查的函数,然后用进行检查的函数装饰它们。

function argCheckDecorator(f) {
  return function(...args) {
    // Functions have a length property that is their arity.
    // You could modify this to only check the first argument,
    // not check arity for varargs, etc.
    if (
      args.length === f.length &&
      args.every(arg => arg !== null && arg !== undefined)
    ) {
      return f.apply(this, args);
    } else {
      // optionally warn console
      // console.warn(`Called ${f.name} with invalid arguments ${args}.`);
      return null;
    }
  }
}https://www.destroyallsoftware.com/talks/boundaries

这里明显的问题是您正在进行相当广泛的运行时检查(AFAIK 是导致您首先提出问题的原因),而不是依赖编译器。不幸的是,在 JavaScript/TypeScript 中,您的选择总是有限的,无法保证对 DOM 的调用永远不会返回null或对象属性访问永远不会返回undefined(至少如果您正在解析 JSON HTTP 响应到一个对象)。

至少使用函数装饰器,您可以将空检查移出每个函数。将我的高阶函数变成 TypeScript 装饰器留给读者作为练习。

更新

只是举一个我在评论中谈论的例子:

// messy-shell.js
// All DOM mutation, AJAX calls, optional params, null checks, etc.
// go here.
import * as ideal from './perfect-world.js';
const getDOMElement = (selector, element=document) => {
  return (selector && element instanceof HTMLElement) ?
    ideal.getDOMElement(selector, element) : null;
};

const getAJAXData => (url, method='GET', params) => {
  let p = params ? // IRL you'll want to do more checking than this
    fetch(url, { body: params, method }) :
    fetch(url, { method });

  return p.then(resp => {
    if (resp.statusCode >= 200 && resp.statusCode < 400) {
      return resp.json();
    } else {
      throw new Error(`${resp.statusCode} response.`);
    }
  }).then(data => {
    if (data && (data.length || Object.keys(data).length)) {
      return ideal.processAJAXData(data);
    } else {
      throw new Error('Empty data response.');
    }
  }).then(processedData => {
    // update DOM here, or skip this and just return Promise<processedData>
  }).catch(err => {
    // do error handling
  });
};

与此同时,回到牧场……

// perfect-world.js
// Assumes that no args are ever omitted, nothing ever
// throws (catching is up to the caller). Functions in this
// file may also call each other, but with caution. Any function
// that uses the result of something that might fail go in messy-shell.
// Functional, in the Functional Programming sense.
export const getDOMElement = (selector, element) => {
  return element.querySelector(selector);
};

// NOTE: knows nothing of Promises, try/catch, JSON.parse, etc.
// Doesn't mutate the DOM either, just processes server response.
export const processAJAXData = data => {
  return Object.entries(data).forEach(datum => {
    // do stuff.
  });
};

现在,您不一定希望每个小功能都需要这种级别的仪式,但如果您的团队/代码库/问题域足够大,您可能会这样做。一些不错的属性不在此范围内:

  1. 理想世界中的东西很容易测试。
  2. 理想世界中的东西可以很容易地进行静态分析。
  3. 理想世界中的东西是整洁的,并且在很大程度上是自我记录的

您可能也想检查一下

于 2018-03-13T15:51:05.313 回答