深层属性,轻松提取

809 阅读2分钟

面临的问题

假设有这样一个对象,表示的是 用户是否启用了回复通知的设置

const settings = {
    notification: {
    	reply: {
            active: {
            	true
            }
        }
        // ...其他设置项
    }
    // ...其他设置项
}

当开发者想要提取 active 的值,最直接的方法是这么做

const isNotificationReplyActive = settings.notification.reply.active

但这是不安全的,因为 JavaScript 中通常使用 nullundefined 分别表示未定义或未声明的值

typeof someVar === 'undefined' // 未声明
let someVar = null // 已声明,未定义

实际开发过程中可能因为比如节省资源的考虑,当用户未进行过设置时,它的 notification 或者更深的某一级的值是 nullundefined,而非对象。

// 比如当未设置回复通知时,它是这样的
const settings = {
    notification: {
    	// 没有 reply
    }
}

// 这种情况下, settings.notification.reply 的值是 undefined
// JS 中试图获取 undefined 上的 key 时会触发 TypeError
const isNotificationReplyActive = settings.notification.reply.active // TypeError!

于是开发者采取了这样的措施

const isNotificationReplyActive = settings
    && settings.notification
    && settings.notification.reply
    && settings.notification.reply.active
// 或者
try {
    const isNotificationReplyActive = settings.notification.reply.active
} catch (err) {
    // 错误处理
}

经验丰富的开发者都知道,这样做的缺点很多,在此就不展开了。

于是一些工具函数诞生了,比如 lodash 的 _.get

import _ from 'lodash'
const isNotificationReplyActive = _.get(settings, 'notification.reply.active')

虽然它保证了开发者在提取属性的过程中不会因为遇到 undefinednull 之类的值而抛出 TypeError ,但缺点也很明显——

  1. 属性的路径被写成了字符串,开发者无法获得 IDE/编辑器 的自动补全与智能纠错。
  2. 不能使用便捷的解构语法—— const { notification: { reply: { active } } } = settings

简直是一夜回到解放前。

解决方法 —— safe-touch

现在让我们来回顾一下本文开头的那张图——它即是本文的主角、上述所有问题的解决方案。

// 引入
import safeTouch from 'safe-touch'

const settings = { /* ... */ }
// 包裹要提取的对象
const touched = safeTouch(settings)

// 把它当作函数调用,可以获得原始值
touched() === settings // true

// 亦可以直接获取 settings 上存在的属性,同样通过调用取得属性值
// 在现代化的 IDE/编辑器 中,这一过程可以给出智能提示与自动补全
touched.notification.reply.active() // 若依本文开头给出的例子,值为 true

// 可以安全地获取并不存在的属性,返回 undefined ,不会抛出 TypeError
touched.something.does.not.exist[Math.random()]() // undefined

// 支持解构
const { notification: { reply: { active, notExistingKey } } } = touched
active() // true
notExistingKey() // undefined

怎么做到的

其实不难,核心功能通过 ES6 的 Proxy 就可以实现。再结合 TypeScript 的类型判断(初学 TS,看了好久文档),生成 index.d.ts 供智能化的 IDE/编辑器 使用,就有了自动补全与智能提示。

短小的源码 repo (欢迎 Star / issue)

可以从 npm 获取