这里有几个众所周知的模式可以提供帮助。
选项类型,又名 Maybe
Maybe/Option 是一个 sum 类型,在 TypeScript 中具有以下结构:
type Maybe<T> = T | void;
您可以在 typescript user manual 中阅读有关实现它的更多信息,只需 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 装饰器留给读者作为练习。
更新
举个例子说明我在 cmets 中所说的内容:
// 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.
});
};
现在您不一定希望每个小功能都使用这种级别的仪式,但如果您的团队/代码库/问题域足够大,您可能会这样做。一些不错的属性不在此范围内:
- 理想世界中的东西很容易测试。
- 理想世界中的东西可以很容易地进行静态分析。
- 理想世界中的东西是整洁的,并且在很大程度上是自我记录的
您可能还想查看this。