Как получить доступ к свойствам объекта пользовательского интерфейса, используя строку в машинописном тексте

Мне нужно получить доступ к свойствам моего объекта динамически, используя заданную строку в качестве ключа.

Я проверил следующее, но я не могу найти ответ, который ищу:

Свойства динамического объекта TypeScript

Свойство динамического доступа Typescript

Ошибка возникает здесь:

state.value[container][id] // TS2571: Object is of type 'unknown'

При вызове метода getEntity я должен передавать ENUM для свойства вместо строки?

Ниже приведен упрощенный пример моего кода — ошибки в строках 57 и 69:

// interfaces for entity types - including these in this example seems unnecessary
import {
  TaskFinances,
  Plan,
  Boundary,
  BoundaryMapList
} from 'DTOs'

// for pulling data from REST
import axios, { AxiosResponse } from 'axios'

// VueJS composition AIP components
import { ref } from '@vue/composition-api'

// I want to imply that the State interface below is made up of string:unknown pairs
// so that I can reference it using a string key in the <getEntity> method below

interface Stately {
  [key: string]: unknown
}
interface State extends Stately {
  plans: { [key: number]: Plan };
  taskFinances: { [key: number]: TaskFinances };
  boundaryPointsByField: Boundary[];
}

const props: State = {
  plans: {},
  taskFinances: {},
  boundaryPointsByField: []
}

const state = ref(props)
// I reference the properties of state using state.value.xxx because it is using the Vue3 Composition API Ref syntax

// Reduced to 2 lines following D.R.Y. principles
function getTaskFinances(activityId: number): Promise<TaskFinances> {
  const taskFinances = getEntity(activityId, 'taskFinance', 'taskFinances') as unknown as Promise<TaskFinances>
  return taskFinances
}

// Reduced to 2 lines following D.R.Y. principles
function getBoundaryPointsByField(fieldId: number): Promise<BoundaryMapList> {
  const boundaryPoints = getEntity(fieldId, 'boundaryPointsByField', 'boundaryPointsByField') as unknown as Promise<BoundaryMapList>
  return boundaryPoints
}

// Reduced to 2 lines following D.R.Y. principles
function getPlan(planId: number): Promise<Plan> {
  const plan = getEntity(planId, 'plan', 'plans') as unknown as Promise<Plan>
  return plan
}

// generic method to return requested entities and add them to my state object
async function getEntity(id: number, api: string, container: string): Promise<unknown> {
  try {
    const entity: unknown = state.value[container][id] // THROWS typescript error -TS2571: Object is of type 'unknown'-
    if (entity) {
      return entity
    }
  } catch (err) {
    console.log(container, id, err)
  }

  const url = `${api}${id}`
  try {
    const { data }: AxiosResponse<unknown> = await axios.get(url)
    console.log(api + ':', data)
    state.value[container][id] = data  // THROWS typescript error -TS2571: Object is of type 'unknown'-
    return data
  } catch (err) {
    console.error(err)
  }
}

// interface for my exported uiState object - I use this interface elsewhere in the app so I am exporting it
export interface IUIState {
  getBoundaryPointsByField: (fieldId: number) => Promise<BoundaryMapList>;
  getTaskFinances: (activityId: number) => Promise<TaskFinances>;
  getPlan: (planId: number) => Promise<Plan>;
}

// export my uiState object
export const uiState: IUIState = {
  getBoundaryPointsByField,
  getTaskFinances,
  getPlan
}

person user2115620    schedule 12.11.2020    source источник
comment
Можете ли вы использовать лучшие типы? Например. container: string наверное должно быть keyof что-то.   -  person jonrsharpe    schedule 12.11.2020
comment
keyof является новым для меня, так как я новичок в TypeScript (думаю, это видно!) - сейчас я изучаю, как реализовать это в этом контексте, но если у вас есть какие-либо предложения, я был бы признателен за них;) Спасибо за ваше время @johnsharpe   -  person user2115620    schedule 12.11.2020
comment
не получается ;( ``` асинхронная функция getEntity‹K extends keyof State›(id: number, api: string, container: K): Promise‹unknown› { ```   -  person user2115620    schedule 12.11.2020
comment
спасибо @rie, но это делает мой объект реквизита совершенно универсальным. Для меня имеет смысл передать контейнер как ключ состояния, а не общую строку (как предложил johnsharpe), но я не знаю, как это сделать правильно.   -  person user2115620    schedule 12.11.2020
comment
@user2115620 user2115620 Глядя на параметры этой функции getEntity и учитывая это state.value[container][id], вы уверены, что у вас будет динамическая фабрика сущностей, названная числами? Потому что на самом деле это будет выглядеть примерно так: state.value.getAnotherMethod['123'].   -  person Yom T.    schedule 15.11.2020


Ответы (1)


Это поведение предназначено.

Где вы получаете доступ к своему состоянию здесь

 const entity: unknown = state.value[container][id]

state.value[container] теперь относится к типу unknown. Вы не можете попытаться получить доступ к [id] за ним, потому что TS понятия не имеет, существует ли он вообще на state.value[container]. Это неизвестно. Это падение использования неизвестного. Если вы знаете свои ответы API, создайте для них интерфейсы и объявите их возможными типами для вашего состояния. В противном случае, если вы хотите заставить его работать без типов, вам придется использовать any вместо unknown.

You can see this behaviour reproduced here. Вы можете видеть, что переменная b имеет неизвестный тип и уже выдает ошибку. Если вы измените тип на any в интерфейсе, он будет работать, но вы не получите никакой безопасности типов.

person discolor    schedule 12.11.2020