引言:为什么这个问题如此常见?
\n\n每个从 JavaScript 转向 TypeScript 或直接使用 TypeScript 的开发者,迟早都会遇到一个问题:如何为传递给函数的对象正确设置默认值?在 Stack Overflow 上,这个问题获得了数千次浏览和数十个回答。为什么?因为看似简单的任务——"如果未传递对象,则使用空对象"——在 TypeScript 中变成了一个涉及类型、接口和解构的复杂挑战。
\n\n在本文中,我们将探讨所有可能的方法,从最简单到最高级,并附有代码示例和解释。您将学习如何为作为参数传递的 TypeScript 对象配置默认值,并避免常见错误。让我们开始吧!
\n\n问题:常规方法有什么问题?
\n\n假设我们有一个接受配置对象的函数:
\n\nfunction createUser(config: { name?: string; age?: number }) {\n // ...\n}\n\ncreateUser(); // 错误!需要参数\n\n\n在 JavaScript 中,我们可以简单地编写 function createUser(config = {}) 就搞定了。但在 TypeScript 中,这种技巧行不通:编译器会报类型不匹配的错误。为什么?因为空对象 {} 不符合接口 { name?: string; age?: number }——它根本没有这些属性。
这就是有趣的地方。让我们分析所有解决方案。
\n\n方法 1:使用解构赋值默认值
\n\n最优雅且流行的方法是在函数参数中直接使用解构。您提取对象的属性并立即为它们设置默认值:
\n\nfunction createUser({ name = "访客", age = 18 }: { name?: string; age?: number } = {}) {\n console.log(`已创建用户:${name},年龄:${age}`);\n}\n\ncreateUser(); // "已创建用户:访客,年龄:18"\ncreateUser({ name: "安娜" }); // "已创建用户:安娜,年龄:18"\ncreateUser({ age: 25 }); // "已创建用户:访客,年龄:25"\n\n\n请注意末尾的 = {}。这是对象本身的默认值。如果您不传递参数,TypeScript 会使用空对象,而解构会为每个属性填充默认值。
优点: 简洁、优雅、运行完美。
\n缺点: 需要为每个属性显式指定类型。
方法 2:使用带有 Partial 的接口
\n\n如果您的对象很大,在参数中描述所有类型是不合适的。将接口单独提取出来,并使用 Partial<T>:
interface UserConfig {\n name: string;\n age: number;\n email: string;\n isAdmin: boolean;\n}\n\nfunction createUser(config: Partial<UserConfig> = {}) {\n const defaults: UserConfig = {\n name: "访客",\n age: 18,\n email: "noemail@example.com",\n isAdmin: false\n };\n \n const mergedConfig = { ...defaults, ...config };\n console.log(mergedConfig);\n}\n\ncreateUser({ name: "伊万" });\n// { name: "伊万", age: 18, email: "noemail@example.com", isAdmin: false }\n\n\n这里 Partial<UserConfig> 使所有属性变为可选。在函数内部,我们将默认值与传入的值合并。效果很好,但需要手动合并。
方法 3:经典的检查方法
\n\n有时需要更多控制。例如,如果您只想为 undefined 设置默认值,而不为 null 设置。使用经典的 if:
function createUser(config?: UserConfig) {\n const name = config?.name ?? "访客";\n const age = config?.age ?? 18;\n const email = config?.email ?? "noemail@example.com";\n \n console.log({ name, age, email });\n}\n\ncreateUser();