我正在编写一个相当简约的配置系统.这个 idea 是有config.template.js分和config.custom.js分.现在,custom中的所有设置值都应覆盖template中的值.custom中缺失的值将从template中读取.

其中的逻辑似乎是这样的:

const isObject = item => item && typeof item === "object" && !Array.isArray(item);

const deepMerge = function(target, source){
    if (isObject(target) && isObject(source)){
        for (const key in source){
            if (isObject(source[key])){
                if (!target[key]) target[key] = {};
                deepMerge(target[key], source[key]);
            }
            else target[key] = source[key];
        }
    }
    return target;
};

// ...

const configCustom = (await import("./config.custom.js")).default;
const configBase = (await import("./config.template.js")).default;

export const config = {
    ...deepMerge(configBase, configCustom),
};

现在我的问题是:

VSCode对结果配置的实际外观一无所知.所以没有自动补全或键类型.

如果我只需执行以下操作,VSCode would将能够提供自动完成功能:

export const config = {
    ...configBase,
    ...configCustom,
};

然而,这会导致嵌套键的浅表副本,有效地覆盖整个对象/array.

因为我已经大量使用了JSDoc,所以我想我可以像这样注释deepMerge函数

/**
 * @param {object} target
 * @param {object} source
 * @return {import("./config.template.js").default}
 */

但这当然是一厢情愿的 idea ,行不通.

所以我的问题是:

如何才能在不依赖浅层副本的情况下为此配置系统提供自动完成/类型?


I know that there are a ton of config systems and this is kinda re-inventing the wheel. I still want to understand and learn.
And yes, TypeScript would make this easier.

最新情况:

@creepsore型S answer的性能非常好.但是,我不得不更改了几个注释,因为我遇到了一些重载错误
(由VSCode提出,"js/ts.implicitProjectConfig.checkJs": true):

/**
 * @template {object} T
 * @template {object} T2
 * @param {T} target
 * @param {T2 & Partial<T>} source
 * @returns {T & T2}
 */
const deepMerge = function(target, source){
    if (isObject(target) && isObject(source)){
        for (const key in source){
            if (isObject(source[key])){
                if (!target[key]) target[key] = {};
                deepMerge(target[key], source[key]);
            }
            else target[key] = source[key];
        }
    }
    return /** @type {T & T2} */ (target);
};

// ...

export const config = {
    ...deepMerge(
        configBase,
        /** @type {Partial<typeof configBase>} */ (configCustom),
    ),
};

可能不是最干净的方法,但效果非常好!

推荐答案

当您将DeepMerge的两个参数定义为泛型并按如下方式使用它们作为其返回类型时,它就会起作用:

const isObject = item => item && typeof item === "object" && !Array.isArray(item);

/**
 * @template T
 * @template T2
 * @param {T} target 
 * @param {T2} source 
 * @returns {T&T2}
 */
const deepMerge = function(target, source){
    if (isObject(target) && isObject(source)){
        for (const key in source){
            if (isObject(source[key])){
                if (!target[key]) target[key] = {};
                deepMerge(target[key], source[key]);
            }
            else target[key] = source[key];
        }
    }
    return target;
};

const configCustom = (await import("./config.custom.js")).default;
const configBase = (await import("./config.template.js")).default;

export const config = {
    ...deepMerge(configBase, configCustom),
};

Example

以下是我用于测试的示例配置:

// config.template.js
export default {
    a: 420,
    b: 1337,
    c: 360
};

// config.custom.js
export default {
    b: 420
};

Javascript相关问答推荐

React对话框模式在用户单击预期按钮之前出现

为什么我达到了时间限制!?LeetCode链接列表循环(已解决,但需要解释!)

如何在JavaScript中在文本内容中添加新行

通过嵌套模型对象进行Mongoose搜索

我不知道为什么setwritten包装promise 不能像我预期的那样工作

Next.js(react)使用moment或不使用日期和时间格式

使用Promise.All并发解决时,每个promise 的线性时间增加?

使用Ace编辑器对子组件实例的native-element 进行Angular 获取时面临的问题

为什么当我更新数据库时,我的所有组件都重新呈现?

无法设置RazorPay订阅API项目价格

MongoDB中的嵌套搜索

Next.js无法从外部本地主机获取图像

是否可以在不更改组件标识的情况下换出Reaction组件定义(以维护状态/引用等)?如果是这样的话,是如何做到的呢?

如何在FiRestore中的事务中使用getCountFromServer

如何让SVG图标在被点击和访问后改变 colored颜色 ,并在被访问后取消点击时恢复到原来的 colored颜色 ?

Played link-Initialize.js永远显示加载符号

Firefox的绝对定位没有达到预期效果

使用onClick单击子元素时,使用交点观察器的关键帧动画意外运行

与find()方法一起使用时,Mongoose中的$or运算符没有提供所有必需的数据

如何从图表中映射一组图表-js使用REACT