Programing

vuex 저장소 형식에서 개체 속성을 다이너믹하게 설정하는 방법(유형 안전 유지 중)

c10106 2022. 5. 3. 21:33
반응형

vuex 저장소 형식에서 개체 속성을 다이너믹하게 설정하는 방법(유형 안전 유지 중)

나는 (자바스크립트에서 TS로 변환하는 과정에서) 고급 플러그인은 없고 그냥 입력만 한 형태의 Vue 3 프로젝트가 있다.

나는 주문 물체의 다른 성질을 설정하는 돌연변이를 가지고 있기 때문에 모든 성질에 대해 돌연변이를 쓸 필요가 없다.JS에서는 다음과 같이 보인다.

 setMetaProp(state, { propName, value }) {
     state.order[propName] = value;
 }

이제, TS로 물건을 배우고 변환하는 과정에서, 나는 타입 안전으로 이것을 할 수 있는 기능을 작성했다.

export interface PropNameKeyValue<KeyType, ValueType> {
    propName: KeyType;
    value: ValueType;
}
export declare function setMeta<Key extends keyof Order>(payload: PropNameKeyValue<Key, Order[Key]>): void;

//type of is_quote is boolean in the type declaration of type Order

setMeta({ propName: 'is_quote', value: '123' }); 

//if I try to set is_quote to something else than boolean here
// tsc catches the problem and complains that this should be a boolean

이것은 굉장하고 제대로 작동하지만, 만약 내가 상점에 가지고 있는 돌연변이에 이것을 적용하려고 한다면, 타입 안전은 더 이상 아니다, 타입 오더에 존재하지 않는 속성을 설정하려고 한다면, 적어도 타입 오더에 속성이 존재하지 않는다는 것을 말해준다.

지금까지 내가 가지고 있는 코드는 다음과 같다. 나는 스크린샷과 함께 IDE에서 각 기능 위에 마우스를 올려 놓을 때 스크린샷을 포함할 것이다(또 :):

// type declaration of mutations
export type Mutations < S = State > = {
  [MutationTypes.setMetaProp] < PropName extends keyof Order > (
    state: S,
    payload: PropNameKeyValue < PropName, Order[PropName] >
  ): void;
};
// implementation
export const mutations: MutationTree < State > & Mutations = {
  [MutationTypes.setMetaProp](state, {
    propName,
    value
  }) { //i have tried not destructuring, did not change the outcome
    state.order && (state.order[propName] = value);
  },
};
// call the mutation: 
commit(MutationTypes.setMetaProp, {
  propName: 'is_quote',
  value: '123'
}); // no complaints from tsc

//also type declaration of commit  
commit < Key extends keyof Mutations, Params extends Parameters < Mutations[Key] > [1] > (
  key: Key,
  payload: Params
): ReturnType < Mutations[Key] > ;

간단한 기능에 의지하다

커밋에 마우스를 올리기

그래서 결국 제 질문은 어떻게 하면 이 문제를 해결하고 유형 안전을 가질 수 있을까 하는 겁니다.

(어쨌든 내가 여기 있다면, 이것을 바꿀 방법이 있을까? 그러면 동적 유형을 유형 매개변수로 지정할 수 있다(따라서 다른 장소에서 사용할 경우 순서 또는 유형을 입력할 필요가 없음)

업데이트: 플레이그라운드 링크 표시 문제:

업데이트 2: 지연된 솔루션...(SpecialCommit ???

기본적으로 나는 AugmentActionContext에 다른 유형의 돌연변이를 선언했고, 타입 Order에만 작용한다(가장 지연된 솔루션이지만 내가 생각할 수 있는 유일한 솔루션)

업데이트 3:플레이그라운드에 대답하기 위해. 그래서 기본적으로 나는 질문의 경우 내가 하고 싶었던 것뿐만 아니라 다른 종류의 돌연변이를 갖게 될 것이다.당신의 답변은 배경에서 무슨 일이 일어나고 있는지 어느 정도 밝혀주었는데, 나는 그것에 대해 감사하고 있다.다른 생각이 있나?아니면 그냥 내가 내 지체된 해결책에서 했던 것처럼 해야 할까?기본적으로 내 지체된 해결책은 네 것과 비슷해, 나는 그 파라암 파트의 문제가 단지 왜 그것이 작동하지 않는지 몰랐어.하지만 설명해줘서 고마워!또한 이 해결책에 대한 나의 또 다른 문제점은 모든 커밋에 대해 오직 하나의 유형으로 나의 사용을 제한한다는 것이다.아마도 나는 순서 하위 오브젝트의 속성을 설정하기를 원할 것이다. 이것은 또한 그 옵션도 없앨 것이다.

지금 내 새로운 질문은 내가 TypeScript와 Vuex 콤보를 너무 많이 요구하는가 하는 것이다.

어떤 때는 이 모든 제네릭들과 어울리기가 어렵다.

setMetaTS함수는 당신이 이 함수를 부르기 때문에 그 유형을 유추할 수 있다.

문제는 다음과 같다.

Parameters<Mutations[Key]>[1]

적어도 이 경우에는 기능을 호출하지 않고는 유하중을 유추할 수 없다.TS는 단지 그가 어떤 가치를 기대해야 하는지 모를 뿐이다.

으로 유추할 수는 없다.Parameters로부터의setMeta:

export declare function setMeta<Key extends keyof Order>(payload: PropNameKeyValue<Key, Order[Key]>): void;

type O = Parameters<typeof setMeta>[0] // PropNameKeyValue<keyof Order, any>

가장 간단한 해결책은 허용된 모든 값의 조합 유형을 만드는 것이다.

type Values<T> = T[keyof T]

type AllowedValues = Values<{
  [Prop in keyof Order]: PropNameKeyValue<Prop, Order[Prop]>
}>

이제 예상대로 작동함:

import {
  ActionContext,
  ActionTree,
  MutationTree,
} from 'vuex';

type Order = {
  is_quote: boolean;
  order_switches: any;
  api_version: string;
  order_type: string;
  customer_unique_id: string;
  crm_customer_id: string;
  first_name: string;
  last_name: string;
}

type Values<T> = T[keyof T]

type AllowedValues = Values<{
  [Prop in keyof Order]: PropNameKeyValue<Prop, Order[Prop]>
}>

export interface PropNameKeyValue<KeyType, ValueType> {
  propName: KeyType;
  value: ValueType;
}

enum ActionTypes {
  DoStuff = "DO_STUFF"
}
enum MutationTypes {
  setMetaProp = "SET_META_PROP"
}

export type State = {
  order: Order | null;
};

export const state: State = {
  order: null,
};


export const mutations: MutationTree<State> & Mutations = {
  [MutationTypes.setMetaProp](state, payload) {
    state.order && (state.order[payload.propName] = payload.value);
  },
};

export type Mutations<S = State> = {
  [MutationTypes.setMetaProp]<PropName extends keyof Order>(
    state: S,
    payload: PropNameKeyValue<PropName, Order[PropName]>
  ): void;
};


export const actions: ActionTree<State, State> & Actions = {
  [ActionTypes.DoStuff]({ commit }) {
    commit(MutationTypes.setMetaProp, { propName: 'is_quote', value: true });
    commit(MutationTypes.setMetaProp, { propName: 'is_quote', value: 1 }); // expected error

  },
}

interface Actions {
  [ActionTypes.DoStuff](context: AugmentedActionContext): void;
}

type AugmentedActionContext = {
  commit<Key extends keyof Mutations>(
    key: Key,
    payload: AllowedValues // change is here
  ): ReturnType<Mutations[Key]>;
} & Omit<ActionContext<State, State>, 'commit' | 'getters' | 'dispatch'>;

놀이터

갱신하다

과부하했다.commit기능을 발휘하다

상태 :: 관련: TS는 일반적으로 돌연변이를 잘 다루지 못한다.물체는 열쇠와 어긋나기 때문에string|boolean에 평가되다never왜냐하면string & boolean = never.

변형에 대한 내 을 타이프로 봐줘.

import {
  ActionContext,
  ActionTree,
  MutationTree,
} from 'vuex';

type Order = {
  is_quote: boolean;
  //  order_switches: any;
  api_version: string;
  order_type: string;
  customer_unique_id: string;
  crm_customer_id: string;
  first_name: string;
  last_name: string;
}
type Artwork = {
  artwork_id: number;
  artwork_size: string;
}

type Values<T> = T[keyof T]

type AllowedValues<Type> = Values<{
  [Prop in keyof Type]: {
    propName: Prop;
    value: Type[Prop];
  }
}>

export interface PropNameKeyValue<KeyType, ValueType> {
  propName: KeyType;
  value: ValueType;
}

enum ActionTypes {
  DoStuff = "DO_STUFF"
}
enum MutationTypes {
  setMetaProp = "SET_META_PROP",
  setArtworkProp = "SET_ARTWORK_PROP",
  setRandomVar = "SET_RANDOM_VAR",
}

export type State = {
  order: Order | null;
  artwork: Artwork | null;
  randomVar: string;
};



export const state: State = {
  order: null,
  artwork: null,
  randomVar: '',
};

function setMeta<S extends Values<State>, Key extends keyof S>(state: S, payload: AllowedValues<S>) { }


export const mutations: MutationTree<State> & Mutations = {
  [MutationTypes.setMetaProp]: (state, payload) => {
    const x = state.order as Order

    if (state.order) {
      // TS is unsure about safety
      const q = state.order[payload.propName] // string|boolean
      const w = payload.value // string | boolean"
      /**
       * Because both
       * state.order[payload.propName] and payload.value
       * evaluated to stirng | boolean
       * TS thinks it is not type safe operation
       * 
       * Pls, keep in mind, objects are contravariant in their key types
       */
      // workaround
      Object.assign(state.order, { [payload.propName]: payload.value })
    }

    if (payload.propName === 'api_version') {
      state.order && (state.order[payload.propName] = payload.value);
    }

    state.order && (state.order[payload.propName] = payload.value);
  },
  [MutationTypes.setArtworkProp](state, payload) {
    state.artwork && (state.artwork[payload.propName] = payload.value);
  },
  [MutationTypes.setRandomVar](state, payload) {
    state.randomVar = payload;
  },
};



export type Mutations<S = State> = {
  [MutationTypes.setMetaProp]: (
    state: S,
    payload: AllowedValues<Order>
  ) => void;
  [MutationTypes.setArtworkProp]: (
    state: S,
    payload: AllowedValues<Artwork>
  ) => void;
  [MutationTypes.setRandomVar]: (
    state: S,
    payload: string
  ) => void; //added these mutations: setArtworkProp and setRandomVar, they will be unusable from now on...
};


export const actions: ActionTree<State, State> & Actions = {
  [ActionTypes.DoStuff]({ commit }) {
    commit(MutationTypes.setMetaProp, { propName: 'is_quote', value: true });
    commit(MutationTypes.setArtworkProp, { propName: 'artwork_id', value: 2 });
    commit(MutationTypes.setArtworkProp, { propName: 'artwork_id', value: '2' });// expected error

    commit(MutationTypes.setRandomVar, '2');
    commit(MutationTypes.setRandomVar, 2); // expected error
  },
}

interface Actions {
  [ActionTypes.DoStuff](context: AugmentedActionContext): void;
}

// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

type Overloading = UnionToIntersection<Values<{
  [Prop in keyof Mutations]: {
    commit(
      key: Prop,
      payload: Parameters<Mutations[Prop]>[1]
    ): ReturnType<Mutations[Prop]>
  }
}>>

type AugmentedActionContext = Overloading & Omit<ActionContext<State, State>, 'commit' | 'getters' | 'dispatch'>;

참조URL: https://stackoverflow.com/questions/68177711/how-to-set-properties-of-object-dinamically-while-keeping-type-safety-in-vuex

반응형