Какой самый чистый способ получения свойства, на который указывает другое свойство?

Дан объект, который может быть нулевым и может иметь следующие свойства:

{
  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;
}

Другие вопросы по тегам