0

8 Vue Tips (Tập 2)

Series Vue Tips vẫn tiếp tục nha mn link bài trước mn nếu có hứng thú thì đọc tại đây nha

11. Looping Over a Range in Vue

Lệnh v-for cho phép chúng ta lặp qua một Mảng, nhưng nó cũng cho phép chúng ta lặp qua một dải

<template>
  <ul>
    <li v-for="n in 5">Item #{{ n }}</li>
  </ul>
</template>

Output:

Item #1
Item #2
Item #3
Item #4
Item #5

Khi chúng ta sử dụng v-for với một phạm vi, nó sẽ bắt đầu từ 1 và kết thúc trên số mà chúng ta chỉ định.

12. watch bất cứ thứ gì trong component

Bất kỳ thứ gì trong component của bạn có phản ứng đều có thể được đặt trong watch:

export default {
  computed: {
    someComputedProperty() {
      // Update the computed prop
    },
  },
  watch: {
    someComputedProperty() {
      // Do something when the computed prop is updated
    }
  }
};

Bạn có thể:

  • computed props
  • props
  • giá trị lồng nhau

Nếu bạn đang sử dụng API tổng hợp, bạn có thể xem bất kỳ giá trị nào, miễn là đó là một ref hoặc reactive object.

13. Stealing Prop Types

Thường thì tôi thấy rằng tôi đang sao chép các loại prop từ một component con, chỉ để sử dụng chúng trong một component cha. Nhưng tôi đã phát hiện ra rằng stealing các loại prop đó tốt hơn nhiều so với việc chỉ sao chép chúng.

Ví dụ: Có một Icon thành phần đang được sử dụng trong thành phần này:

<template>
  <div>
    <h2>{{ heading }}</h2>
    <Icon
      :type="iconType"
      :size="iconSize"
      :colour="iconColour"
    />
  </div>
</template>

Để làm cho điều này hoạt động, chúng ta cần thêm các loại prop types chính xác, sao chép từ Icon component:

import Icon from './Icon';
export default {
  components: { Icon },
  props: {
    iconType: {
      type: String,
      required: true,
    },
    iconSize: {
      type: String,
      default: 'medium',
      validator: size => [
        'small',
        'medium',
        'large',
        'x-large'
      ].includes(size),
    },
    iconColour: {
      type: String,
      default: 'black',
    },
    heading: {
      type: String,
      required: true,
    },
  },
};

Và khi các loại prop types của Icon component được cập nhật, bạn có thể chắc chắn rằng mình sẽ quên quay lại component này và cập nhật chúng. Theo thời gian, các chiếc bug sẽ xuất hiện khi các loại prop types cho compnent này bắt đầu biến mất khỏi các loại prop types trong Icon component.

Vì vậy, đó là lý do tại sao chúng ta sẽ steal chúng thay vào đó:

import Icon from './Icon';
export default {
  components: { Icon },
  props: {
    ...Icon.props,
    heading: {
      type: String,
      required: true,
    },
  },
};

Nó không cần phải trở nên phức tạp hơn thế!

Ngoại trừ trong ví dụ của chúng ta, có thêm "biểu tượng" vào đầu mỗi tên prop. Vì vậy, chúng ta sẽ phải làm thêm một số công việc để điều đó xảy ra:

import Icon from './Icon';

const iconProps = {};

// Do some processing beforehand
Object.entries(Icon.props).forEach((key, val) => {
  iconProps[`icon${key.toUpperCase()}`] = val;
});

export default {
  components: { Icon },
  props: {
    ...iconProps,
    heading: {
      type: String,
      required: true,
    },
  },
};

Bây giờ, nếu các loại prop types trong Icon component được sửa đổi, thành phần của chúng ta sẽ luôn cập nhật.

Nhưng điều gì sẽ xảy ra nếu một loại prop types được thêm vào hoặc loại bỏ khỏi Ico component Để giải quyết những trường hợp đó, chúng ta có thể sử dụng v-bind và một phương án được tính toán để giữ cho mọi thứ luôn dynamic.

14. Phát hiện các click bên ngoài một phần tử (hoặc bên trong)

Thỉnh thoảng, ta cần phát hiện xem một lần click xảy ra bên trong hay bên ngoài của một phần tử cụ thể. Đây là phương pháp thường được sử dụng :

window.addEventListener('mousedown', e => {
  // Get the element that was clicked
  const clickedEl = e.target;

  // `el` is the element you're detecting clicks outside of
  if (el.contains(clickedEl)) {
    // Clicked inside of `el`
  } else {
    // Clicked outside of `el`
  }
});

15. Recursive slots

Một lần, tôi quyết định xem liệu tôi có thể tạo một componentn v-for chỉ bằng cách sử dụng template hay không.

Đây là thành phần trông như này :

<!-- VFor.vue -->
<template>
    <div>
        <!-- Render the first item -->
    {{ list[0] }}
        <!-- If we have more items, continue!
                 But leave off the item we just rendered -->
    <v-for
      v-if="list.length > 1"
            :list="list.slice(1)"
        />
    </div>
</template>

Nếu chúng ta muốn làm điều này với các scoped slots - và tại sao chúng ta lại không ?! - chỉ cần một vài chỉnh sửa:

<template>
  <div>
    <!-- Pass the item into the slot to be rendered -->
    <slot v-bind:item="list[0]">
      <!-- Default -->
      {{ list[0] }}
    </slot>

    <v-for
      v-if="list.length > 1"
      :list="list.slice(1)"
    >
      <!-- Recursively pass down scoped slot -->
      <template v-slot="{ item }">
        <slot v-bind:item="item" />
      </template>
    </v-for>
  </div>
</template>

Đây là cách thành phần này được sử dụng:

<template>
  <div>
    <!-- Regular list -->
    <v-for :list="list" />

    <!-- List with bolded items -->
    <v-for :list="list">
      <template v-slot="{ item }">
        <strong>{{ item }}</strong>
      </template>
    </v-for>
  </div>
</template>

16. Component Metadata

Không phải mọi thông tin bạn thêm vào một component đều là state. Đôi khi bạn cần thêm một số metadata để cung cấp thêm thông tin cho các thành phần khác.

Ví dụ: nếu bạn đang xây dựng một loạt các tiện ích con component cho một trang tổng quan phân tích như Google Analytics:

Nếu bạn muốn layout biết mỗi tiện ích con sẽ chiếm bao nhiêu cột, bạn có thể thêm cột đó trực tiếp trên thành phần dưới dạng metadata:

export default {
  name: 'LiveUsersWidget',
  // 👇 Just add it as an extra property
  columns: 3,
  props: {
    // ...
  },
  data() {
    return {
      //...
    };
  },
};

Bạn sẽ tìm thấy metadata này dưới dạng thuộc tính trên thành phần:

import LiveUsersWidget from './LiveUsersWidget.vue';
const { columns } = LiveUsersWidget;

Bạn cũng có thể truy cập siêu dữ liệu từ bên trong thành phần thông qua thuộc tính đặc biệt $options:

export default {
  name: 'LiveUsersWidget',
  columns: 3,
  created() {
    // 👇 `$options` contains all the metadata for a component
    console.log(`Using ${this.$options.metadata} columns`);
  },
};

Chỉ cần lưu ý rằng metadata này giống nhau đối với mỗi phiên bản của thành phần và không mang tính phản ứng.

Các mục đích sử dụng khác cho việc này bao gồm (nhưng không giới hạn):

  • Giữ số phiên bản cho individual components
  • Flag tùy chỉnh cho các tool xây dựng để xử lý các thành phần khác nhau
  • Thêm các tính năng tùy chỉnh vào các thành phần ngoài các đạo cụ được props, data, watchers, ...
  • và nhiều hơn nữa mà mình chưa nhớ ra

17. Multi-file single-file components

Đây là một tính năng ít được biết đến của SFC.

Bạn có thể nhập tệp giống như cách bạn làm với tệp HTML thông thường:

<!-- A "single" file component -->
<template src="./template.html"></template>
<script src="./script.js"></script>
<style scoped src="./styles.css"></style>

Nếu bạn cần chia sẻ styles, tài liệu hoặc bất kỳ thứ gì khác, điều này có thể thực sự hữu ích. Cũng hoàn hảo cho tệp thành phần siêu dài đó khiến ngón tay của bạn bị mất khỏi tất cả các thao tác cuộn =))

Đây là bản demo hoạt động của nó trong hoạt động: https://codesandbox.io/s/interesting-rosalind-9wwmr?file=/src/components/HelloWorld.vue

18. Các component có thể được tái sử dụng

Các thành phần có thể tái sử dụng không cần phải là những thứ lớn hoặc phức tạp.

Có thể thường sử dụng cho các thành phần nhỏ và ngắn có thể tái sử dụng.

Vì tôi không viết lại mã này khắp nơi nên việc cập nhật nó trở nên dễ dàng hơn nhiều và tôi có thể đảm bảo rằng mọi OverflowMenu đều trông và hoạt động hoàn toàn giống nhau - bởi vì chúng giống nhau!

<!-- OverflowMenu.vue -->
<template>
  <Menu>
    <!-- Add a custom button to trigger our Menu -->
    <template #button v-slot="bind">
      <!-- Use bind to pass click handlers,
           a11y attributes, etc. -->
      <Button v-bind="bind">
        <!-- Use our own "..." icon and no text
             for this button -->
        <template #icon>
          <svg src="./ellipsis.svg" />
        </template>
      </Button>
    </template>
  </Menu>
</template>

Ở đây chúng tôi đang sử dụng một Menu thành phần, nhưng thêm '...' vào nút kích hoạt nó mở.

Có vẻ như không đáng để tạo ra một thành phần có thể tái sử dụng từ điều này, bởi vì nó chỉ có một vài dòng. Chúng ta không thể chỉ thêm biểu tượng mỗi khi chúng ta muốn sử dụng một biểu tượng Menu như thế này?

Nhưng điều này OverflowMenu sẽ được sử dụng hàng chục lần, và bây giờ nếu chúng ta muốn cập nhật biểu tượng hoặc hành vi của nó, chúng ta có thể làm điều đó rất dễ dàng. Và sử dụng nó cũng đơn giản hơn nhiều!

<template>
  <OverflowMenu
    :menu-items="items"
    @click="handleMenuClick"
  />
</template>

19. Calling a Method from Outside of the Component

Bạn có thể gọi một phương thức từ bên ngoài của một thành phần bằng cách cung cấp cho nó một ref:

<!-- Parent.vue -->
<template>
  <ChildComponent ref="child" />
</template>
// Somewhere in Parent.vue
this.$refs.child.method();

Thỉnh thoảng "các phương pháp hay nhất" không phù hợp với những gì bạn đang làm và bạn cần một escape hatch như thế này.

Thông thường, chúng tôi giao tiếp giữa các thành phần bằng cách sử dụng đạo cụ và sự kiện. Đạo cụ được gửi xuống các component con và các sự kiện được phát ngược trở lại các component cha.

<template>
  <ChildComponent
    :tell-me-what-to-do="someInstructions"
    @something-happened="hereIWillHelpYouWithThat"
  />
</template>

Tuy nhiên, thỉnh thoảng, bạn có thể rơi vào tình huống mà bạn cần parent của mình kích hoạt một phương thức trong component con. Đây là nơi chỉ chuyển props xuống cũng không hoạt động.

Bạn có thể chuyển một boolean xuống và để thành phần con xem nó:

<!-- Parent.vue -->
<template>
  <ChildComponent :trigger="shouldCallMethod" />
</template>
// Child.vue
export default {
  props: ['trigger'],
  watch: {
    shouldCallMethod(newVal) {
      if (newVal) {
        // Call the method when the trigger is set to `true`
        this.method();
      }
    }
  }
}

Điều này hoạt động tốt, nhưng chỉ trong lần gọi đầu tiên. Nếu bạn cần kích hoạt điều này nhiều lần, bạn phải clean và đặt lại state. Logic sau đó sẽ như thế này:

Thành phần chính chuyển true sang trigger prop Watch được kích hoạt và component con gọi phương thức Thành phần con phát ra một sự kiện để cho thành phần chính biết rằng phương thức đã được kích hoạt thành công Thành phần chính đặt trigger lại trở lại false, vì vậy chúng tôi có thể thực hiện lại tất cả Thay vào đó, nếu chúng ta đặt một ref trong thành phần con, chúng ta có thể gọi phương thức đó trực tiếp:

<!-- Parent.vue -->
<template>
  <ChildComponent ref="child" />
</template>
// Somewhere in Parent.vue
this.$refs.child.method();

Đúng, chúng ta đang phá vỡ quy tắc "props down, event up" và chúng ta đang phá vỡ tính đóng gói, nhưng nó rõ ràng và dễ hiểu hơn rất nhiều nên rất đáng giá!


All rights reserved

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í