Component library combat | Use vue3+ts to achieve global Header and list data rendering ColumnList

Component library combat | Use vue3+ts to achieve global Header and list data rendering ColumnList

"This article has participated in the Haowen Convening Order activity, click to view: Back-end, big front-end dual-track submissions, 20,000 yuan prize pool waiting for you to challenge! "

Preface

Recently used

vue3
with
ts
I fiddled with some gadgets and found that a very common requirement in normal development is the rendering of data lists . Re-learning now and find that I am learning
vue2
Many design specifications and logic at the time were not particularly appropriate.

Therefore, write this article to record the data list rendering and the design of the global header in the component design.

Let's learn together~

1. ColumnList data rendering

1. Design draft to be a prophet

Before understanding the function implementation, let's take a look at the prototype diagram to see what the data list we want to achieve is like. As shown below:

You can first understand the renderings of the content we want to achieve later.

2. Data conception

After understanding the specific renderings, now we are going to work!

First of all, we need to first think about the data required by this component?

The data required by this component, first of all, each row of data is its own unique

id
, Followed by the title
title
, And another is an avatar
avatar
, The last one is the text description corresponding to each title
description
.

After the analysis is complete, we are now in

vue3
Under the project
src|components
Create a new file under the folder and name it
ColumnList.vue
. Then write this business code. The specific code is as follows:

< template > < div > </div > </template > < Script lang = "TS" > Import {computed, defineComponent, PropType} from 'VUE' //write an interface with ts, a data storage property list Export interface ColumnProps { ID : Number; title: string; avatar?: string; description: string; } export default defineComponent({ name : 'ColumnList' , props : { //Assign the content of the interface to the list array to facilitate receiving data from the parent component list : { type : Array as PropType<ColumnProps[]>, required: true } } }) </script > < style lang = "scss" scoped > </style > Copy code

3. View data binding

Now, after conceiving the data, we have not fetched any data that can be rendered, which is equivalent to an empty

ColumnList
. But we already have the property content of the interface, so let's bind the data to the view first. The specific code is as follows:

< template > < div class = "row" > < div v-for = "column in columnList" :key = "column.id" class = "col-4 mb-3" > < div class = "card h-100 shadow-sm" > < div class = "card-body text-center" > < img :src = "column.avatar" :alt = "column.title" class = "rounded-circle border border-light w-25 my-3" > <h5 class = "title" > {{column.title}} </h5 > < p class = "card-text text-left" > {{column.description}} </p > < a href = "#" class = "btn btn-outline-primary" > Enter the column </a > </div > </div > </div > </div > </template > < Script lang = "TS" > Import {computed, defineComponent, PropType} from 'VUE' Export interface ColumnProps { ID : Number; title: string; avatar?: string; description: string; } export default defineComponent({ name : 'ColumnList' , props : { list : { type : Array as PropType<ColumnProps[]>, required: true } } }) </script > < style lang = "scss" scoped > </style > Copy code

Note: What I used here is

bootstrap
Style library, so
css
Do not do too much writing, you can check the official Chinese document if necessary, or you can design your own style.

At this point, we have completed the first round of data binding. Next, we transfer data in the parent component.

4. Data transfer

Our in the vue3 project

src
Under the folder
App.vue
To transfer data. The specific code is as follows:

<template > < div class = "container" > < column-list :list = "list" > </column-list > </div > </template > < Script lang = "TS" > Import {defineComponent} from 'VUE' //introduced bootstrap file in the root Import 'bootstrap/dist/CSS/bootstrap.min.css' //subassembly incorporated Import ColumnList, {} ColumnProps from './components/ColumnList.vue' //Interface data for manufacturing sub-components const testData: ColumnProps[] = [ { id : 1 , title : 'test1 column' , description : 'As we all know, js is a weakly typed language with few specifications. This can easily lead to the fact that it is difficult for us to find its errors before the project goes online. When the project goes online, the bug will be UpUp without even noticing it. Therefore, in the past two years, ts has quietly risen. This column will introduce some learning records about ts. ' avatar : 'https : //img0.baidu.com/it/u=3101694723,748884042&fm=26&fmt=auto&gp=0.jpg' }, { id : 2 , title : 'test2 column' , description : 'As we all know, js is a weakly typed language with few specifications. This can easily lead to the fact that it is difficult for us to find its errors before the project goes online. When the project goes online, the bug will be UpUp without even noticing it. Therefore, in the past two years, ts has quietly risen. This column will introduce some learning records about ts. ' , avatar : 'https : //img0.baidu.com/it/u=3101694723,748884042&fm=26&fmt=auto&gp=0.jpg' } ] export default defineComponent({ name : 'App' , components : { ColumnList }, setup () { return { list : testData } } }) </script > < style > #app { font-family : Avenir, Helvetica, Arial, sans-serif; -webkit- font-smoothing : antialiased; -moz-osx- font-smoothing : grayscale; text-align : center; color : #2c3e50 ; margin-top : 60px ; } </style > Copy code

Now, let's take a look at the running effect of the browser at this time:

As you can see, through the above code writing, the data is passed normally and runs successfully.

5. Head scratching

Seeing this, I feel that the design of the entire component is quite perfect. But, have you ever thought about a special situation, suppose that there is a row of data in the data from the backend, and there is no avatar value? . At this time, if we did not consider the various situations that may be encountered in the early stage, the program estimated that it would easily report an error.

So one thing we still have to do is, when the data of the avatar cannot be received, we have to add an initial picture to it, so as to keep the list content consistent.

Now we come to

ColumnList.vue
The file is modified, the specific code is as follows:

< template > < div class = "row" > < div v-for = "column in columnList" :key = "column.id" class = "col-4 mb-3" > < div class = "card h-100 shadow-sm" > < div class = "card-body text-center" > < img :src = "column.avatar" :alt = "column.title" class = "rounded-circle border border-light w-25 my-3" > <h5 class = "title" > {{column.title}} </h5 > < p class = "card-text text-left" > {{column.description}} </p > < a href = "#" class = "btn btn-outline-primary" > Enter the column </a > </div > </div > </div > </div > </template > < Script lang = "TS" > Import {computed, defineComponent, PropType} from 'VUE' //write an interface with ts, a data storage property list Export interface ColumnProps { ID : Number; title: string; avatar?: string; description: string; } export default defineComponent({ name : 'ColumnList' , props : { //Assign the content of the interface to the list array to facilitate receiving data from the parent component list : { type : Array as PropType<ColumnProps[]>, required: true } }, //Pass props to setup setup ( props ) { const columnList = computed( () => { //Traverse each row of the list array data return props.list.map( column => { //When encountering the current row of data When there is no avatar if (!column.avatar) { //Give the initial image column.avatar = require ( '@/assets/logo.png' ) } return column }) }) return { columnList } } }) </script > < style lang = "scss" scoped > </style > Copy code

Continue, we put

App.vue
in
testData
The data is deleted. The specific code is as follows:

< template > < div class = "container" > < column-list :list = "list" > </column-list > </div > </template > < script lang = "ts" > //Interface data for manufacturing sub-components const testData: ColumnProps[] = [ { id : 1 , title : 'test1 column' , description : 'As we all know, js is a weakly typed language with few specifications. This can easily lead to the fact that it is difficult for us to find its errors before the project goes online. When the project goes online, the bug will be UpUp without even noticing it. Therefore, in the past two years, ts has quietly risen. This column will introduce some learning records about ts. ' //avatar:'https://img0.baidu.com/it/u=3101694723,748884042&fm=26&fmt=auto&gp=0.jpg' }, { id : 2 , title : 'test2 column' , description : 'As we all know, js is a weakly typed language with few specifications. This can easily lead to the fact that it is difficult for us to find its errors before the project goes online. When the project goes online, the bug will be UpUp without even noticing it. Therefore, in the past two years, ts has quietly risen. This column will introduce some learning records about ts. ' , avatar : 'https : //img0.baidu.com/it/u=3101694723,748884042&fm=26&fmt=auto&gp=0.jpg' } ] Copy code

Everyone is positioned

testData
middle
avatar
In that line, we put the first data
avatar
Attributes are annotated. Now, let's look at the effect of the browser:

As you can see, the lack of

avatar
When property, according to our expectations, the browser automatically displays our pre-initialized image . In this way, whether from the component structure design or the code logic structure design, does it feel that the scalability has been enhanced a lot.

2. GlobalHeaderGlobal Header

1. First look at the design draft

Finished

columnList
Components, we use a new component to enhance this design method. Next we will write a new component,
GlobalHeader
, The global header. Let's first look at the renderings we want to achieve. See the picture below for details:

2. Data conception

After understanding the specific renderings, in the same way, let's first think about the data required for this component.

The data required by this component is first for each user, so each user has its own unique

id
, Followed by username
name
The last one is whether or login
isLogin
.

After the analysis is complete, we are now in

vue3
Under the project
src|components
Create a new file under the folder and name it
GlobalHeader.vue
. Then write this business code. The specific code is as follows:

< template > < div > </div > </template > < Script lang = "TS" > Import {defineComponent, PropType} from 'VUE' //Use ts to write an interface to store the attributes of the list data.//Add name and id? Indicates that it is optional export interface UserProps{ isLogin : boolean; name?: string; id?: number; } export default defineComponent({ name : 'GlobalHeader' , props : { //Assign the content of the interface to the user object to facilitate receiving data from the parent component user : { type : Object as PropType<UserProps>, required: true } } }) </script > < style lang = "scss" scoped > </style > Copy code

3. View data binding

Now, after thinking about the data, let's bind the data to the view. The specific code is as follows:

< Template > < NAV class = "Navbar Navbar-Dark BG-Primary The justify-Content-BETWEEN MB-. 4 PX-. 4" > < A class = "Navbar-Brand" the href = "#" > Monday column </A > < ul v-if = "!user.isLogin" class = "list-inline mb-0" > < li class = "list-inline-item" > < a href = "#" class = "btn btn-outline-light my-2" > Login</A > </Li > < Li class = "List-inline-Item" > < A the href = "#" class = "BTN BTN-Outline-Light My-2" > Register </A > </Li > </ul > < ul v-else class = "list-inline mb-0" > < li class = "list-inline-item" > < a href = "#" class ="btn btn-outline-light my-2" > Welcome {{user.name}} </a > </li > </ul > </nav > </template > < Script lang = "TS" > Import {computed, defineComponent, PropType} from 'VUE' Export interface ColumnProps { ID : Number; title: string; avatar?: string; description: string; } export default defineComponent({ name : 'ColumnList' , props : { list : { type : Array as PropType<ColumnProps[]>, required: true } } }) </script > < style lang = "scss" scoped > </style > Copy code

4. Data transfer

Now we are

vue3
In the project
src
Under the folder
App.vue
To transfer data. The specific code is as follows:

< template > < div class = "container" > < global-header :user = "user" > </global-header > </div > </template > < Script lang = "TS" > Import {defineComponent} from 'VUE' //introduced bootstrap file in the root Import 'bootstrap/dist/CSS/bootstrap.min.css' //subassembly incorporated Import GlobalHeader, {} userprops from './components/GlobalHeader.vue' //Interface data for manufacturing subcomponents const currentUser: UserProps = { isLogin : false , name : 'Monday' } export default defineComponent({ name : 'App' , components : { GlobalHeader }, setup () { return { user : currentUser } } }) </script > < style lang = "scss" scoped > </style > Copy code

current

isLogin
Is set to
false
. Now, let's take a look at the running effect of the browser at this time:

As you can see, the current status is

false
,and so
header
The two buttons displayed on the right are login and register , as expected.

Now let's put

isLogin
Changes to
true
, The specific code is as follows:

const currentUser: UserProps = { isLogin : true , name : 'Monday' } Copy code

At this point, let's take a look at the display effect of the browser, as shown in the following figure:

Now, you can see that when

isLogin
for
true
, It means that the user has successfully logged in. and so
header
The right side shows the welcome you Monday the words, but also as we expect expected.

3. Dropdown drop-down menu

After reading the above content, do you have any doubts in your mind, we

header
The drop-down menu on the far right has not yet been implemented. Don't worry, let's design this component next.

1. Basic functions of components

Let's first design the basic functions of this component. First at

vue3
The project
src|components
Add one under the folder
.vue
File, named
Dropdown.vue
. Then write the code of the file, the specific code is as follows:

< template > < div class = "dropdown" > <!-- Drop-down menu title--> < a href = "#" class = "btn btn-outline-light my-2dropdown-toggle" @ click.prevent = "toggleOpen ()" > {{title}} </a > <!-- Dropdown menu content--> < ul class = "dropdown-menu" :style = "{ display:'block' }" v-if = "isOpen" > < li class = "dropdown- Item " > < A the href = " # " > New Base </A > </Li > < Li class = " DropDown-Item " > < A the href = " # " >Edit data </a > </ li > </ ul > </div > </template > < script lang = "TS" > Import {defineComponent, REF, onMounted, onUnmounted, Watch} from 'VUE' export default defineComponent({ name : 'Dropdown' , props : { title : { type : String , required : true } }, setup () { const isOpen = ref( false ) //Open the menu after clicking const toggleOpen = () => { isOpen.value = !isOpen.value } return { isOpen, toggleOpen } } }) </script > < style lang = "scss" scoped > </style > Copy code

Go on, let s remake the one just now

GlobalHeader.vue
file. The specific code is as follows:

< Template > < NAV class = "Navbar Navbar-Dark BG-Primary The justify-Content-BETWEEN MB-. 4 PX-. 4" > < A class = "Navbar-Brand" the href = "#" > Monday column </A > < ul v-if = "!user.isLogin" class = "list-inline mb-0" > < li class = "list-inline-item" > < a href = "#" class = "btn btn-outline-light my-2" > Login</A > </Li > < Li class = "List-inline-Item" > < A the href = "#" class = "BTN BTN-Outline-Light My-2" > Register </A > </Li > </ul > < ul v-else class = "list-inline mb-0" > < li class = "list-inline-item" > < dropdown :title = "`Welcome to ${user. >name}`" </dropdown > </li > </ul > </nav > </template > < Script lang = "TS" > Import {defineComponent, PropType} from 'VUE' Import the Dropdown from './Dropdown.vue' export interface UserProps{ isLogin : boolean; name?: string; id?: number; } export default defineComponent({ name : 'GlobalHeader' , components : { Dropdown }, props : { user : { type : Object as PropType<UserProps>, required: true } } }) </script > < style lang = "scss" scoped > </style > Copy code

Now, let's look at the display effect of the browser:

2. Customize the menu content DropdownItem

Now, we have completed the basic functions of the component. But my careful friends have discovered that there is no way to customize the drop-down menu because it is written as fixed. Another problem is that we can't collapse the menu by clicking on other areas. Indirectly, the user experience doesn't seem so good. So, when there is a demand, we will complete the demand. Now, let's solve the two problems mentioned above.

Similarly, in the vue3 project

src|components
Add one under the folder
.vue
File, named
DropdownItem.vue
. The specific code is as follows:

< template > < li class = "dropdown-option" :class = "{'is-disabled': disabled}" > <!-- Define a slot for the parent component to use --> < slot > </slot > </li > </template > <script lang="ts"> import { defineComponent } from 'vue' export default defineComponent({ props: { // disabled: { type: Boolean, default: false } } }) </script> <style> /* * */ .dropdown-option.is-disabled * { color: #6c757d; /* pointer-events none */ pointer-events: none; background: transparent; } </style>

Dropdown.vue
File, the specific code is as follows:

< template > < div class = "dropdown" > <!-- Drop-down menu title--> < a href = "#" class = "btn btn-outline-light my-2dropdown-toggle" @ click.prevent = "toggleOpen ()" > {{title}} </a > <!-- Drop-down menu content--> < ul v-if = "isOpen" class = "dropdown-menu" :style = "{ display:'block' }" > < slot > </slot > </ul > </div > </template > < Script lang = "TS" > Import {defineComponent, REF} from 'VUE' export default defineComponent({ name : 'Dropdown' , props : { title : { type : String , required : true } }, setup () { const isOpen = ref( false ) //Open the menu after clicking const toggleOpen = () => { isOpen.value = !isOpen.value } return { isOpen, toggleOpen } } }) </script > < style lang = "scss" scoped > </style > Copy code

Through the above code, we can understand that the slot is placed in

dropdown-menu
Go in.


Now, at the last step, let s look at introducing it

GlobalHeader.vue
File. The specific code is as follows:

< Template > < NAV class = "Navbar Navbar-Dark BG-Primary The justify-Content-BETWEEN MB-. 4 PX-. 4" > < A class = "Navbar-Brand" the href = "#" > Monday column </A > < ul v-if = "!user.isLogin" class = "list-inline mb-0" > < li class = "list-inline-item" > < a href = "#" class = "btn btn-outline-light my-2" > Login</A > </Li > < Li class = "List-inline-Item" > < A the href = "#" class = "BTN BTN-Outline-Light My-2" > Register </A > </Li > </ul > < ul v-else class = "list-inline mb-0" > < li class = "list-inline-item" > < dropdown :title = "`Welcome to ${user.name}`" > <drop-down-item><a href="#" class="dropdown-item"> </a></drop-down-item> <drop-down-item disabled><a href="#" class="dropdown-item"> </a></drop-down-item> <drop-down-item><a href="#" class="dropdown-item"> </a></drop-down-item> </dropdown> </li> </ul> </nav> </template> <script lang="ts"> import { defineComponent, PropType } from 'vue' import Dropdown from './Dropdown.vue' import DropDownItem from './DropDownItem.vue' export interface UserProps{ isLogin: boolean; name?: string; id?: number; } export default defineComponent({ name: 'GlobalHeader', components: { Dropdown, DropDownItem }, props: { user: { type: Object as PropType<UserProps>, required: true } } }) </script> <style lang="scss" scoped> </style>

3

  • onMounted
    click
    onUnmounted
  • Dropdown
    DOM

Dropdown.vue

<template> <div class="dropdown" ref="dropdownRef"> <a href="#" class="btn btn-outline-light my-2 dropdown-toggle" @click.prevent="toggleOpen()"> {{title}} </a> <ul v-if="isOpen" class="dropdown-menu" :style="{ display: 'block' }"> <slot></slot> </ul> </div> </template> <script lang="ts"> import { defineComponent, ref, onMounted, onUnmounted, watch } from 'vue' export default defineComponent({ name: 'DropDown', props: { title: { type: String, required: true } }, setup() { const isOpen = ref(false) // ref dow const dropdownRef = ref<null | HTMLElement>(null) const toggleOpen = () => { isOpen.value = !isOpen.value } const handler = (e: MouseEvent) => { if (dropdownRef.value) { // contains dow // if (!dropdownRef.value.contains(e.target as HTMLElement) && isOpen.value) { isOpen.value = false } } } onMounted(() => { document.addEventListener('click', handler) }) onUnmounted(() => { document.removeEventListener('click', handler) }) return { isOpen, toggleOpen, dropdownRef, handler } } }) </script> <style lang="scss" scoped> </style>

dropdown

4

GlobalHeader
dropdown
dropdown.vue

~

vue3
src
hooks
hooks
useClickOutside.ts
useClickOutside

import { ref, onMounted, onUnmounted, Ref } from "vue"; const useClickOutside = (elementRef: Ref<null | HTMLElement>) => { const isClickOutside = ref(false) const handler = (e: MouseEvent) => { if (elementRef.value){ if(elementRef.value.contains(e.target as HTMLElement)){ isClickOutside.value = true }else{ isClickOutside.value = false } } } onMounted( () => { document.addEventListener('click', handler) }) onUnmounted(() => { document.removeEventListener('click', handler) }) return isClickOutside } export default useClickOutside

Dropdown.vue

<template> <div class="dropdown" ref="dropdownRef"> <a href="#" class="btn btn-outline-light my-2 dropdown-toggle" @click.prevent="toggleOpen()"> {{title}} </a> <ul v-if="isOpen" class="dropdown-menu" :style="{ display: 'block' }"> <slot></slot> </ul> </div> </template> <script lang="ts"> import { defineComponent, ref, onMounted, onUnmounted, watch } from 'vue' import useClickOutside from '../hooks/useClickOutside' export default defineComponent({ name: 'DropDown', props: { title: { type: String, required: true } }, setup() { const isOpen = ref(false) // ref dow const dropdownRef = ref<null | HTMLElement>(null) const toggleOpen = () => { isOpen.value = !isOpen.value } const isClickOutside = useClickOutside(dropdownRef) if (isOpen.value && isClickOutside) { isOpen.value = false } watch(isClickOutside, () => { if (isOpen.value && isClickOutside.value) { isOpen.value = false } }) return { isOpen, toggleOpen, dropdownRef } } }) </script> <style lang="ts" scoped> </style>

5

GlobalHeader Columnist

ColumnList
GlobalHeader
~

~

GlobalHeader
ColumnList

~

  • ~

  • ~

One More Thing

Axure RP 9

Axure RP

~