Какой самый чистый способ получения свойства, на который указывает другое свойство?
Дан объект, который может быть нулевым и может иметь следующие свойства:
{
templateId: "template1",
templates: {
template1: "hello"
}
}
Как бы вы получили шаблон безотказным способом? (templateId может быть не определен, или шаблон, на который он ссылается, может быть не определен)
Я использую ramda и пытался адаптировать мою наивную версию кода, чтобы использовать что-то вроде Maybe adt, чтобы избежать явных проверок null / undefined.
Я не могу придумать элегантное и чистое решение.
наивная рамда версия:
const getTemplate = obj => {
const templateId = obj && prop("templateId", obj);
const template = templateId != null && path(["template", templateId], obj);
return template;
}
это работает, но я хотел бы избежать нулевых проверок, так как в моем коде намного больше работы, и было бы здорово стать чище
Редактировать Я получаю из нескольких ответов, что лучше всего сначала обеспечить чистоту данных. Это не всегда возможно, хотя. Я тоже придумал это, что мне и нравится.
const Empty=Symbol("Empty");
const p = R.propOr(Empty);
const getTemplate = R.converge(p,[p("templateId"), p("templates")]);
Хотелось бы получить обратную связь относительно того, насколько она чиста и читабельна (и если есть крайние случаи, которые могут ее испортить)
4 ответа
Как уже говорили другие, уродливые данные исключают красивый код. Очистите свои нули или представьте их как типы опций.
Тем не менее, ES6 позволяет вам справиться с этим с некоторыми тяжелыми задачами деструктуризации
const EmptyTemplate =
Symbol ()
const getTemplate = ({ templateId, templates: { [templateId]: x = EmptyTemplate } }) =>
x
console.log
( getTemplate ({ templateId: "a", templates: { a: "hello" }}) // "hello"
, getTemplate ({ templateId: "b", templates: { a: "hello" }}) // EmptyTemplate
, getTemplate ({ templates: { a: "hello" }}) // EmptyTemplate
)
Вы можете продолжать делать getTemplate
еще более оборонительный. Например, ниже мы принимаем вызов нашей функции с пустым объектом и даже без ввода вообще
const EmptyTemplate =
Symbol ()
const getTemplate =
( { templateId
, templates: { [templateId]: x = EmptyTemplate } = {}
}
= {}
) =>
x
console.log
( getTemplate ({ templateId: "a", templates: { a: "hello" }}) // "hello"
, getTemplate ({ templateId: "b", templates: { a: "hello" }}) // EmptyTemplate
, getTemplate ({ templates: { a: "hello" }}) // EmptyTemplate
, getTemplate ({}) // EmptyTemplate
, getTemplate () // EmptyTemplate
)
Выше мы начинаем испытывать небольшую боль. Этот сигнал важно не игнорировать, поскольку он предупреждает нас, что мы делаем что-то не так. Если вам необходимо поддерживать такое количество проверок на ноль, это означает, что вам нужно ужесточить код в других областях вашей программы. Было бы неразумно копировать / вставлять любой из этих ответов дословно и пропускать урок, который все пытаются преподавать вам.
Вот подход ADT в ванильном Javascript:
// type constructor
const Type = name => {
const Type = tag => Dcons => {
const t = new Tcons();
t[`run${name}`] = Dcons;
t.tag = tag;
return t;
};
const Tcons = Function(`return function ${name}() {}`) ();
return Type;
};
const Maybe = Type("Maybe");
// data constructor
const Just = x =>
Maybe("Just") (cases => cases.Just(x));
const Nothing =
Maybe("Nothing") (cases => cases.Nothing);
// typeclass functions
Maybe.fromNullable = x =>
x === null
? Nothing
: Just(x);
Maybe.map = f => tx =>
tx.runMaybe({Just: x => Just(f(x)), Nothing});
Maybe.chain = ft => tx =>
tx.runMaybe({Just: x => ft(x), Nothing});
Maybe.compk = ft => gt => x =>
gt(x).runMaybe({Just: y => ft(y), Nothing});
// property access
const prop =
k => o => o[k];
const propSafe = k => o =>
k in o
? Just(o[k])
: Nothing;
// auxiliary function
const id = x => x;
// test data
// case 1
const o = {
templateId: "template1",
templates: {
template1: "hello"
}
};
// case 2
const p = {
templateId: null
};
// case 3
const q = {};
// case 4
const r = null; // ignored
// define the action (a function with a side effect)
const getTemplate = o => {
const tx = Maybe.compk(Maybe.fromNullable)
(propSafe("templateId"))
(o);
return Maybe.map(x => prop(x) (o.templates)) (tx);
};
/* run the effect,
that is what it means to compose functions that may not produce a value */
console.log("case 1:",
getTemplate(o).runMaybe({Just: id, Nothing: "N/A"})
);
console.log("case 2:",
getTemplate(p).runMaybe({Just: id, Nothing: "N/A"})
);
console.log("case 3:",
getTemplate(q).runMaybe({Just: id, Nothing: "N/A"})
);
Как вы можете видеть, я использую функции для кодирования ADT, поскольку Javascript не поддерживает их на уровне языка. Эта кодировка называется кодировкой Черча / Скотта. Кодирование Скотта является неизменным по своему замыслу, и, как только вы ознакомитесь с ним, его обработка станет несложной задачей.
И то и другое Just
ценности и Nothing
имеют тип Maybe
и включать tag
свойство, на котором вы можете сделать сопоставление с образцом.
[РЕДАКТИРОВАТЬ]
С тех пор, как Скотт (а не специалист по кодированию) и ОП попросил более подробный ответ, я расширил свой код. Я до сих пор игнорирую случай, когда сам объект null
, Вы должны позаботиться об этом на предыдущем этапе.
Вы можете подумать, что это слишком сильно - с уверенностью для этого надуманного примера. Но когда сложность возрастает, этот функциональный стиль может облегчить боль. Также обратите внимание, что с помощью этого подхода мы можем обрабатывать все виды эффектов, а не только null
чеки.
Например, в настоящее время я создаю решение FRP, основанное на тех же строительных блоках. Это повторение паттернов является одной из черт функциональной парадигмы, без которой я бы больше не хотел.
Ты можешь использовать R.pathOr
, Когда какая-либо часть пути недоступна, возвращается значение по умолчанию. Например:
const EmptyTemplate = Symbol();
const getTemplateOrDefault = obj => R.pathOr(
EmptyTemplate,
[ "templates", obj.templateId ],
obj
);
Коллекция тестов может быть найдена в этом фрагменте. Пример показывает, что pathOr
хорошо обрабатывает все (?) "неправильные" случаи:
const tests = [
{ templateId: "a", templates: { "a": 1 } }, // 1
{ templates: { "a": 1 } }, // "empty"
{ templateId: "b", templates: { "a": 1 } }, // "empty"
{ templateId: null, templates: { "a": 1 } }, // "empty"
{ templateId: "a", templates: { } }, // "empty"
{ templateId: "a" } // "empty"
];
Изменить: для поддержки null
или же undefined
входы, вы могли бы составить метод с быстрым defaultTo
:
const templateGetter = compose(
obj => pathOr("empty", [ "templates", obj.templateId ], obj),
defaultTo({})
);
Попробуй это,
const input = {
templateId: "template1",
templates: {
template1: "hello"
}
};
const getTemplate = (obj) => {
const template = obj.templates[obj.templateId] || "any default value / simply remove this or part";
//use below one if you think templates might be undefined too,
//const template = obj.templates && obj.templates[obj.templateId] || "default value"
return template;
}
console.log(getTemplate(input));
Вы можете использовать комбинацию && и || закорачивать выражение.
Также используйте [] (вместо.) С объектами, чтобы получить значение, если ключ хранится в переменной.
Полная проверка
const getTemplate = (obj) => {
const template = obj && obj.templateId && obj.templates && obj.templates[obj.templateId] || "default value"
return template;
}