PAYATHON 2026

Angular signal 和 input 的初始值应该用 null 还是 undefined?

数据阿明

结论

题目中的两种 signal 写法都可以使用。选择时,先确定空值在业务中代表什么,再遵循项目已有约定。仅凭它是 signal 的初始值,无法决定必须用 null 还是 undefined。

如果项目没有现成约定,可以采用以下规则:

  • 用 undefined 表示尚未提供值,或某个可选值缺失。
  • 用 null 表示业务上明确没有对象,例如当前没有选中项。
  • 如果需要区分加载中、加载完成但无结果等状态,单独建模这些状态。

对于没有默认值的可选 input,可以直接写 input<MyCustomType>(),其值类型包含 undefined。

为什么“尚未初始化”不能直接决定答案?

下面两个 signal 都已经完成初始化:

const nullableValue = signal<MyCustomType | null>(null);
const optionalValue = signal<MyCustomType | undefined>(undefined);

第一个当前保存 null,第二个当前保存 undefined。显式传入 undefined 的 signal 同样已经初始化。Angular 的 signal API 要求提供 initialValue,这两种写法都满足要求。

“未提供值用 undefined,明确无值用 null”是一种语义约定,变量的具体业务含义仍需由代码约定。Angular 也不会仅凭这两个值判断数据是否正在加载、请求是否失败,或用户是否清除了选择。

普通 signal 应该怎样选择?

先检查业务类型和数据来源,再统一空值的表示方式。例如,下面两种声明表达了不同的约定:

// 业务含义:当前没有选中项。
const selectedItem = signal<MyCustomType | null>(null);

// 业务含义:数据尚未提供。
const pendingItem = signal<MyCustomType | undefined>(undefined);

这里假定 MyCustomType 是已经定义的业务类型。

如果接口和现有代码已经统一使用 null 表示空结果,沿用 null 通常更容易维护。如果周边 API 普遍使用 undefined 表示缺失值,选择 undefined 可以减少转换。没有业务区别时,团队统一使用其中一种即可,不必同时引入两种空值。

如果需要表达多个状态,可以使用可辨识联合类型。例如:

type ItemState<T> =
  | { status: 'loading' }
  | { status: 'empty' }
  | { status: 'ready'; value: T };

const itemState = signal<ItemState<MyCustomType>>({
  status: 'loading',
});

// 加载完成,但没有结果。
itemState.set({ status: 'empty' });

调用方可以直接判断 status,不用猜测某个空值代表哪个阶段。

input 的选择有什么不同?

input 还涉及组件对调用方的输入约定。以下是组件或指令类中的字段声明,分别适用于不同需求:

// 可选输入;未绑定时为 undefined。
optionalItem = input<MyCustomType>();

// 可选输入;未绑定时使用初始值 null。
nullableItem = input<MyCustomType | null>(null);

// 必填输入;调用方需要提供绑定。
requiredItem = input.required<MyCustomType>();

input<MyCustomType>() 返回的类型是 InputSignal<MyCustomType | undefined>。如果输入是可选的,可以直接使用这个形式;如果组件用 null 表示没有对象,可以显式设置 null;如果调用方必须提供输入,则使用 input.required。相关行为见 Angular input API。

必填输入应在 Angular 提供输入值后读取,不能把 input.required 当作创建时就已有业务数据的普通 signal。

使用时要注意什么?

建议开启 TypeScript 的 strictNullChecks,让编译器检查空值分支。开启后,null 和 undefined 是不同的类型,使用业务对象前需要完成相应检查。参见 TypeScript strictNullChecks 文档。

读取 signal 后,可以先保存当前值再判断:

const item = selectedItem();

if (item !== null) {
  // 此处分支中的 item 类型为 MyCustomType。
}

不要为了消除类型错误而直接使用非空断言 !,它不会在运行时补上缺失的数据。团队约定也应说明空值的含义、允许出现的位置和处理方式,这比单独规定“全部用 null”或“全部用 undefined”更有帮助。

备注:内容仅供参考。