Yêu cầu thg 12 13, 2022 3:27 CH 145 0 1
  • 145 0 1
0

Binding prop trong Vue Typescript

Chia sẻ
  • 145 0 1

Xin chào mọi người Mình đang học vue và đang làm project cơ bản để học nó Mình dùng vue 3 và typescript Mình đang có một component MenuItem có type cơ bản như sau

interface MenuItemProps {
  title: string;
  to?: string | '';
  icon?: IconName | '';
}
withDefaults(defineProps<MenuItemProps>(), {
  to: '',
  icon: '',
});

1 prop required và 2 prop optional => Ở đây mình muốn vừa là link vừa là item normal cho các action như show modal và change language.

Ở component Menu mình gọi nó như sau

export const BASE_MENU: BaseMenu[] = [
  {
    icon: 'language',
    title: 'Language',
    languages: languages.map((lang) => ({
      title: lang.title,
      lang: lang.lang,
    })),
  },
  {
    icon: 'help',
    title: 'Help',
  },
];

interface MenuItem {
  title: string;
  icon: IconName;
}

export interface MenuItemLink extends MenuItem {
  to: string;
}

export interface MenuItemLangue extends Omit<MenuItem, 'icon'> {
  languages?: {
    title: string;
    lang: string;
  }[];
}

export type BaseMenu = MenuItemLangue & Partial<Pick<MenuItemLink, 'to' | 'icon'>>;

type Menu = BaseMenu[] | MenuItemLangue;

const menuItems = reactive<{ data: Menu }>({ data: props.items });

const handleChange = (item: BaseMenu, _evt: Event) => {
  if (item.icon === 'language') {
    menuItems.data = item;
  } 
  ...
};

...
 <MenuItem
              v-for="(item, index) in currentMenuList"
              :key="index"
              :to="item.to" 
             :icon="item.icon"
              @onClick="(event) => handleChange(item, event)"
/>

Ở đây mình muốn là là khi bấm vào cái menu item language thì render list languages.

Ở đây thì mỗi item trong currentMenuList nó sẽ có 2 type là

{ languages?: { title: string; lang: string; }[] | undefined; title: string; to?: string | undefined; icon?: IconName | undefined; } | { title: string; lang: string; }

Lúc này mình passing :to="item.to" :icon="item.icon" thì typescript lỗi là Property 'icon' does not exist on type '{ title: string; lang: string; }'

Lúc này mình search gg thử thì có thấy dùng v-bind.

Mình lại thử như này thì vẫn lỗi v-bind="{ to: item?.to || '', icon: item?.icon || '', title: item.title }"

Mình lại tiếp tục thử kiểu này.

<MenuItem
              v-for="(item, index) in currentMenuList"
              :key="index"
              v-bind="item"
              @onClick="(event) => handleChange(item, event)"
            />

Vâng thế này thì nó hết lỗi. Nhưng có vẽ không hay lắm, khó biết được props mình passing.

Mọi người có cách nào binding mà không dính lỗi type không ạ

Avatar Khang @khangnd
thg 12 14, 2022 2:28 SA

Theo mình thì cách thiết kế types của bạn đang bị phức tạp hóa, mình chưa có solution cụ thể cho bạn, nhưng thiết nghĩ nên tìm cách đơn giản lại sẽ tốt hơn cho code readability cũng như dễ debug sau này, mà nhất là có thể giải quyết luôn vấn đề bạn đang hỏi 🙂

thg 12 14, 2022 3:19 CH

@khangnd Vâng Em cũng thấy rối thiệt mà em muốn bấm vào đúng item nớ thì lấy key languages trong chính nó luôn

Kiểu như dưới hình á anh

Em muốn reuse lại component MenuItem ấy

Nhưng vấn để là cái MenuItem có các props typpe là optional.

Em chưa biết passing như nào cả

image.png image.png

1 CÂU TRẢ LỜI


Đã trả lời thg 12 16, 2022 3:21 SA
0

Theo mình thì có thể thiết kế như sau:

// trong file chứa BASE_MENU,
// bạn khai báo interface MenuItem generic hết mức có thể
// để cover cả 2 loại link item và language item

export interface MenuItem { // export hẳn interface này sang component MenuItem để reuse
  title: string;
  to?: string;
  icon?: IconName;
  data?: string; // "data" ở đây chính là "lang" của bạn, mình dùng tên "data" vì nó generic hơn "lang"
  children?: MenuItem[];
}

export const BASE_MENU: MenuItem[] = [
  {
    icon: "language",
    title: "Language",
    children: languages.map((lang) => ({
      title: lang.title,
      data: lang.lang,
    })),
  },
  {
    icon: "help",
    title: "Help",
  },
];
// component MenuItem.vue
import { MenuItem } from "./baseMenu.ts";

interface MenuItemProps extends MenuItem;

Giờ việc reuse lại component MenuItem sẽ ko bị lỗi type, type của bạn cũng được reuse mà ko bị rối 🙂 Bạn thử xem được ko nhé.

Chia sẻ
thg 12 20, 2022 2:01 CH

Trước hết là cảm ơn anh với cách tiếp cận này ❤️. Dễ đọc cho mọi người.

Nhưng mà hiện tại nó lại bị lỗi như ri đây anh

// eslint-disable-next-line @typescript-eslint/no-empty-interface interface MenuItemProps extends BaseMenuItem {}

const props = withDefaults(defineProps<MenuItemProps>(), {}); Nếu mà như này sẽ lỗi

MenuItem.vue:22:26: ERROR: Unexpected "}"

const props = defineProps<MenuItemProps>();

Còn mà viết như này thì title = undefined

export interface BaseMenuItem {
  title: string;
  to?: string;
  icon?: IconName;
  children?: BaseMenuItem[];
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface

interface MenuItemProps extends BaseMenuItem {}

const props = defineProps<MenuItemProps>();

Còn như này thì mọi thử oke @@

Avatar Khang @khangnd
thg 12 21, 2022 2:46 SA

@hungify Cách 3 có vẻ là chính xác rồi đó bạn. Mình cũng là rookie TypeScript thôi nên cũng ko tránh sai sót 😅

Giải thích một tí cái lỗi eslint no-empty-interface thì đây là một convention mặc định của TS để hạn chế việc khai báo các interface thừa thãi/vô nghĩa. Trong trường hợp này mình khai báo thêm interface MenuItemProps extends BaseMenuItem với mục đích là tách biệt interface dùng cho component với interface dùng cho menu item, cover những trường hợp có thể component sẽ chứa những property khác nữa, nhưng vì chưa biết sẽ có gì nên hiện tại 2 interface hoàn toàn giống nhau => eslint báo lỗi trên.

Bạn có thể comment ignore như cách bạn đang làm hoặc bỏ hẳn MenuItemProps và dùng BaseMenuItem luôn:

const props = defineProps<BaseMenuItem>();
thg 12 21, 2022 1:43 CH

@khangnd Nhưng mà viết như này sẽ lỗi nha anh const props = defineProps<BaseMenuItem>();vue

Cái này em quên note ở comment trên

Note: https://vuejs.org/api/sfc-script-setup.html#typescript-only-features

https://github.com/vuejs/core/issues/4294

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí