这里有几个众所周知的模式可以提供帮助。
选项类型,又名 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.
});
};
现在,您不一定希望每个小功能都需要这种级别的仪式,但如果您的团队/代码库/问题域足够大,您可能会这样做。一些不错的属性不在此范围内:
- 理想世界中的东西很容易测试。
- 理想世界中的东西可以很容易地进行静态分析。
- 理想世界中的东西是整洁的,并且在很大程度上是自我记录的
您可能也想检查一下。