Сколько раз вы писали новую функцию-преобразователь Array.sort () для некоторого универсального объекта Javascript? У меня слишком часто и слишком много шаблонов. Я, конечно, говорю об этих монстрах:

// Our generic object to sort
type Person = {
    name: string,
    age: number
}
// Converter function (monster)
const sortPersonByNameAscending = (personA: Person, personB: Person) => {
    const nameA = personA.name.toUpperCase();
    const nameB = personB.name.toUpperCase();
    if (nameA < nameB) {
        return -1;
    }
    if (nameA > nameB) {
        return 1;
    }
    return 0;
}
// Usage
...
allThePeople.sort(sortPersonByNameAscending)

А теперь давайте начнем с хорошего: этот преобразователь типобезопасен, поэтому любые изменения в объекте Person приведут к ошибке (при условии, что мы используем Typescript). Кроме того, код использования становится очень понятным и читаемым.

С другой стороны, sortPersonByNameAscending только сортирует объекты Person, используя свое уникальное свойство name. Если мы хотим отсортировать любой другой объект, мы быстро получаем несколько почти идентичных функций конвертера, привязанных к уникальным свойствам каждого типа объекта.

Общее решение

Нам нужно лучшее решение: такое, которое устраняет шаблон и по-прежнему поддерживает безопасность типов (в большинстве решений, найденных в Интернете, нет):

const byTextAscending = <T>(getTextProperty: (object: T) => String) => (objectA: T, objectB: T) => {
    const upperA = getTextProperty(objectA).toUpperCase();
    const upperB = getTextProperty(objectB).toUpperCase();
    if (upperA < upperB) {
        return -1;
    }
    if (upperA > upperB) {
        return 1;
    }
    return 0;
};
// Sort by the "name" property
allThePeople.sort(byTextAscending((person: Person) => person.name))

В приведенном выше фрагменте мы определяем функцию, которая позволяет нам описывать, как читать свойство, по которому мы хотим выполнить сортировку, для любого типа объекта. Затем он возвращает функцию конвертера для этого свойства и типа объекта. Все это делается типобезопасным способом, сохраняя при этом удобочитаемость.

А как насчет нестроковых свойств?

Поскольку функция getTextProperty, переданная в функцию конвертера, может быть настолько обширной, насколько вы хотите, вы можете, например, использовать ее для преобразования любого нестрокового свойства в строку перед сравнением:

// Sort by the "age" property
allThePeople.sort(byTextAscending((person: Person) => person.age.toString()))

Конечно, если getTextProperty станет слишком сложным, пострадает удобочитаемость. Когда это произойдет, возможно, пришло время реализовать другую функцию конвертера или оболочку для ваших конкретных нужд.

Обновление. Как указано в комментариях, мы преобразуем числа в строки, а затем сортируем числа по алфавиту, что во многих случаях приводит к нежелательным результатам (например, ['200', '10' , '3'] будет отсортировано до ['10 ',' 200 ',' 3 ']). Возможно, лучше было бы создать отдельную функцию преобразования для числовых свойств.

Полный пример (Jest)

Ниже приведен полный пример, написанный для Jest, где мы сортируем список объектов Person сначала по имени, а затем по возрасту:

describe("Sorting a generic object using byTextAscending", () => {

    type Person = {
        name: string,
        age: number,
    }

    test("Sorting people by name (string) property", () => {

        const unsortedPeople: Person[] = [
            {name: "Bob", age: 32},
            {name: "Andrew", age: 55},
            {name: "Caroline", age: 21},
        ];

        const expectedOrder: Person[] = [
            {name: "Andrew", age: 55},
            {name: "Bob", age: 32},
            {name: "Caroline", age: 21},
        ];

        const sortedPeople = unsortedPeople.sort(byTextAscending((person: Person) => person.name));

        expect(sortedPeople[0]).toMatchObject(expectedOrder[0]);
        expect(sortedPeople[1]).toMatchObject(expectedOrder[1]);
        expect(sortedPeople[2]).toMatchObject(expectedOrder[2]);
    });
    
    test("Sorting people by age (number) property", () => {

        const unsortedPeople: Person[] = [
            {name: "Bob", age: 32},
            {name: "Andrew", age: 55},
            {name: "Olga", age: 101},
            {name: "Caroline", age: 21},
        ];

        const expectedOrder: Person[] = [
            {name: "Olga", age: 101},
            {name: "Caroline", age: 21},
            {name: "Bob", age: 32},
            {name: "Andrew", age: 55},
        ];

        const sortedPeople = unsortedPeople.sort(byTextAscending((person: Person) => person.age.toString()));

        expect(sortedPeople[0]).toMatchObject(expectedOrder[0]);
        expect(sortedPeople[1]).toMatchObject(expectedOrder[1]);
        expect(sortedPeople[2]).toMatchObject(expectedOrder[2]);
        expect(sortedPeople[3]).toMatchObject(expectedOrder[3]);
    });
});

Что вы думаете? Может ли это быть полезным инструментом или просто еще одним элементом сложности в вашей кодовой базе?