تايب سكريبت - كيف يمكن توسيع نوع keyof بحيث يشمل النسخ المعدلة من المفاتيح، على سبيل المثال التي تبدأ بـ '-'؟


على سبيل المثال، أرغب في إضافة أمان النوع في Typescript إلى حل فرز مصفوفة الكائنات في Javascript العادية حسب قيمة خاصية السلسلة. يقبل كوسائط مفاتيح الكائن المراد فرزه، لتحديد المفتاح الذي سيتم الفرز بحسبه. إذا كان المفتاح مسبوقًا بـ -، يتم عكس الفرز.

كيف يمكنني كتابة النوع ليقبل، على سبيل المثال، كل من "age" و "-age"؟

هذه محاولتي:

export function dynamicSortMultiple<T extends object, U extends keyof T>(
  props: Array<U>,
) {
  function dynamicSort(key: U) {
    let sortOrder = 1
    if (typeof key === 'string' && key.startsWith('-')) {
      sortOrder = -1
    }
    return function (a: T, b: T) {
      const result = a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : 0

      return result * sortOrder
    }
  }

  return function (obj1: T, obj2: T) {
    let i = 0
    let result = 0
    const numberOfProperties = props?.length
    while (result === 0 && i < numberOfProperties) {
      result = dynamicSort(props[i])(obj1, obj2)
      i++
    }

    return result
  }
}

export interface User {
  userId: string
  firstName: string
  lastName: string
  login: string
  password: string
  rank: number
  score: number
  age?: number
}

dynamicSortMultiple<User, keyof User>(['firstName', 'score', '-age'])

ملعب تايب سكريبت

في السطر الأخير أرى الخطأ

Type '"-age"' is not assignable to type 'keyof User'.

هل توجد أي طريقة لتمديد keyof User بشكل صحيح بحيث تظل القيم المسبوقة بـ '-' تُعتبر قيمًا صالحة لهذا النوع؟

سأقدّر أي حل حتى لو استبدلت بحلي بالكامل.

الإجابة رقم 1

تغييراتي:

  1. كانت المعلمات العامة T و U زائدة عن الحاجة. كل ما نحتاجه هو T.

    ملاحظة: في البداية استبدلت جميع إشاراتك U بـ keyof T، ولكن بعد ذلك قمت بسحبها إلى sortArg لتسهيل #2.

  2. استخدم أنواع القوالب النصية التي تم تقديمها في TS 4.1

  3. لقد نسيت قص البادئة - في حالة startsWith('−')

  4. استخدم التأكيدات النوعية حيث لا يستطيع TypeScript تضييق النوع إلى ما يجب أن يكون عليه بناءً على سير المنطق (فريق TS يعمل دائمًا على تحسين تحليل سير العمل لمترجم TS، لذا أراهن أنه في يوم من الأيام سيكون هذا تلقائيًا)

  5. تحسينات واجهة برمجة التطبيقات: تم إعادة تسمية الدالة وإضافة دالة sort ملائمة تسهل قراءتها في الكود (انظر مثال الاستخدام الذي يلي كود الحل).

type sortArg<T> = keyof T | `-${string & keyof T}`

/**
 * Returns a comparator for objects of type T that can be used by sort
 * functions, were T objects are compared by the specified T properties.
 *
 * @param sortBy - the names of the properties to sort by, in precedence order.
 *                 Prefix any name with `-` to sort it in descending order.
 */
export function byPropertiesOf<T extends object> (sortBy: Array<sortArg<T>>) {
    function compareByProperty (arg: sortArg<T>) {
        let key: keyof T
        let sortOrder = 1
        if (typeof arg === 'string' && arg.startsWith('-')) {
            sortOrder = -1
            // Typescript is not yet smart enough to infer that substring is keyof T
            key = arg.substr(1) as keyof T
        } else {
            // Likewise it is not yet smart enough to infer that arg here is keyof T
            key = arg as keyof T
        }
        return function (a: T, b: T) {
            const result = a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : 0

            return result * sortOrder
        }
    }

    return function (obj1: T, obj2: T) {
        let i = 0
        let result = 0
        const numberOfProperties = sortBy?.length
        while (result === 0 && i < numberOfProperties) {
            result = compareByProperty(sortBy[i])(obj1, obj2)
            i++
        }

        return result
    }
}

/**
 * Sorts an array of T by the specified properties of T.
 *
 * @param arr - the array to be sorted, all of the same type T
 * @param sortBy - the names of the properties to sort by, in precedence order.
 *                 Prefix any name with `-` to sort it in descending order.
 */
export function sort<T extends object> (arr: T[], ...sortBy: Array<sortArg<T>>) {
    arr.sort(byPropertiesOf<T>(sortBy))
}

مثال على الاستخدام:

interface User {
    name: string
    id: string
    age?: number
}

const users: User[] = [
    {name: 'Harriet Tubman', id: '01', age: 53},
    {name: 'John Brown', id: '02', age: 31},
    {name: 'John Brown', id: '03', age: 59},
    {name: 'James Baldwin', id: '04', age: 42},
    {name: 'Greta Thunberg', id: '05', age: 17}
]

// using Array.sort directly 
users.sort(byPropertiesOf<User>(['name', '-age', 'id']))

// using the convenience function for much more readable code
sort(users, 'name', '-age', 'id')