组件库实战 | 用vue3+ts实现全局Header和列表数据渲染ColumnList

简介: 该文章详细介绍了如何使用Vue3结合TypeScript来开发全局Header组件和列表数据渲染组件ColumnList,并提供了从设计到实现的完整步骤指导。

封面

🖼️序言

最近在用 vue3ts 捣鼓一些小工具,发现平常开发中一个很常见的需求就是,数据列表的渲染。现在重新学习,发现我在学 vue2 时的很多设计规范和逻辑都考虑的不是特别妥当。

因此,写下这篇文章,记录组件设计中数据列表渲染和全局头部的设计。

一起来学习吧~🙆

📻一、ColumnList数据渲染

1、设计稿抢先知

在了解功能实现之前,我们先来看看原型图,看我们想要实现的数据列表是怎么样的。如下图所示:

columnList设计稿

大家可以先了解一下,我们待会所要实现内容的效果图。

2、数据构思

了解完具体的效果图之后呢,现在我们要开始来干活啦!

首先我们需要先构思这个组件所需要的数据有哪一些呢?

这个组件所需要的数据,首先是每一行数据它自己唯一的 id ,其次就是标题 title ,还有一个是头像 avatar ,最后一个是每个标题对应的文字描述 description

分析完成之后,我们现在在 vue3 项目下的 src|components 文件夹下新建一个文件,命名为 ColumnList.vue 。之后再编写这段业务代码。具体代码如下:

<template>
    <div></div>
</template>

<script lang="ts">
import {
     computed, defineComponent, PropType } from 'vue'
//用ts写一个接口,存放列表数据的属性
export interface ColumnProps {
    
  id: number;
  title: string;
  avatar?: string;
  description: string;
}
export default defineComponent({
    
  name: 'ColumnList',
  props: {
    
    //将接口的内容赋值给list数组,方便接收父组件传来的数据
    list: {
    
      type: Array as PropType<ColumnProps[]>,
      required: true
    }
  }
})
</script>
<style lang="scss" scoped>

</style>

3、视图数据绑定

现在,对数据构思完毕之后,我们是还没有取到任何数据可以渲染的,相当于是一个空的 ColumnList 。但是我们已经有了接口的属性内容,所以我们先来把数据绑定到视图当中。具体代码如下:

<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">进入专栏</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>

注: 这里我用到的是 bootstrap 的样式库,所以 css 方面不做过多的编写,大家有需要可以到官方中文文档进行查看,也可以自己进行样式设计。

到此,我们就完成了第一轮的数据绑定。接下来我们在父组件中,进行数据传递。

4、数据传递

我们在vue3项目中的 src 文件夹下的 App.vue 中来进行数据传递。具体代码如下:

<template>
  <div class="container">
    <column-list :list="list"></column-list>
  </div>
</template>

<script lang="ts">
import {
     defineComponent } from 'vue'
//在根文件下引入bootstrap
import 'bootstrap/dist/css/bootstrap.min.css'
//引入子组件
import ColumnList, {
     ColumnProps } from './components/ColumnList.vue'

//制造子组件的接口数据
const testData: ColumnProps[] = [
  {
    
    id: 1,
    title: 'test1专栏',
    description: '众所周知, js 是一门弱类型语言,并且规范较少。这就很容易导致在项目上线之前我们很难发现到它的错误,等到项目一上线,浑然不觉地,bug就UpUp了。于是,在过去的这两年,ts悄悄的崛起了。 本专栏将介绍关于ts的一些学习记录。'
    avatar: 'https://img0.baidu.com/it/u=3101694723,748884042&fm=26&fmt=auto&gp=0.jpg'
  },
  {
    
    id: 2,
    title: 'test2专栏',
    description: '众所周知, js 是一门弱类型语言,并且规范较少。这就很容易导致在项目上线之前我们很难发现到它的错误,等到项目一上线,浑然不觉地,bug就UpUp了。于是,在过去的这两年,ts悄悄的崛起了。 本专栏将介绍关于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>

现在,我们来看下此时浏览器的运行效果:

columnList静态组件

大家可以看到,通过以上的代码编写,数据正常的传递并运行成功了。

5、挠头情况

看到这里,感觉整个组件的设计还挺尽善尽美的。但是呢,大家有没有想过有一种特殊情况,假设后端传来的数据中,有一行数据里面,没有头像avatar的值。那这个时候,如果我们前期没有考虑清楚有可能遇到的各种情况,程序估计很容易地就报错了。

所以我们还要做的一件事情就是,当收不到头像的数据时,我们要给它加一张初始化的图片,以至于保持列表内容一致。

现在我们来对 ColumnList.vue 文件进行改造,具体代码如下:

<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">进入专栏</a>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import {
     computed, defineComponent, PropType } from 'vue'
//用ts写一个接口,存放列表数据的属性
export interface ColumnProps {
    
  id: number;
  title: string;
  avatar?: string;
  description: string;
}
export default defineComponent({
    
  name: 'ColumnList',
  props: {
    
    //将接口的内容赋值给list数组,方便接收父组件传来的数据
    list: {
    
      type: Array as PropType<ColumnProps[]>,
      required: true
    }
  },
  //将props传递给setup
  setup(props) {
    
    const columnList = computed(() => {
    
      //遍历list数组数据的每一行
      return props.list.map(column => {
    
        //当遇到当前行数据没有头像时
        if (!column.avatar) {
    
          //赋予初始化图片
          column.avatar = require('@/assets/logo.png')
        }
        return column
      })
    })
    return {
    
      columnList
    }
  }
})
</script>

<style lang="scss" scoped>

</style>

继续,我们把 App.vuetestData 的数据进行删减。具体代码如下:

<template>
  <div class="container">
    <column-list :list="list"></column-list>
  </div>
</template>

<script lang="ts">

//制造子组件的接口数据
const testData: ColumnProps[] = [
  {
    id: 1,
    title: 'test1专栏',
    description: '众所周知, js 是一门弱类型语言,并且规范较少。这就很容易导致在项目上线之前我们很难发现到它的错误,等到项目一上线,浑然不觉地,bug就UpUp了。于是,在过去的这两年,ts悄悄的崛起了。 本专栏将介绍关于ts的一些学习记录。'
    //avatar: 'https://img0.baidu.com/it/u=3101694723,748884042&fm=26&fmt=auto&gp=0.jpg'
  },
  {
    id: 2,
    title: 'test2专栏',
    description: '众所周知, js 是一门弱类型语言,并且规范较少。这就很容易导致在项目上线之前我们很难发现到它的错误,等到项目一上线,浑然不觉地,bug就UpUp了。于是,在过去的这两年,ts悄悄的崛起了。 本专栏将介绍关于ts的一些学习记录。',
    avatar: 'https://img0.baidu.com/it/u=3101694723,748884042&fm=26&fmt=auto&gp=0.jpg'
  }
]

大家定位到 testData 中的 avatar 那一行,我们把第一个数据的 avatar 属性进行注释。现在,我们来看下浏览器的效果:

缺avatar时的效果

大家可以看到,缺 avatar 属性时,按照我们预期的,浏览器自动显示了我们预先初始化的图片。这样,不论从组件结构设计还是从代码逻辑结构设计来说,是不是感觉可扩展性又增强了许多。

☎️二、GlobalHeader全局Header

1、设计稿抢先看

写完 columnList 组件,我们用一个新的组件来强化这种设计方法。接下来我们来写一个新的组件,GlobalHeader ,即全局头部。先来看下我们要实现的效果图。详情见下图:

globalHeader原型图

2、数据构思

了解完具体的效果图之后呢,同样地,我们先来构思这个组件所需要的数据有哪一些。

这个组件所需要的数据,首先是针对每一个用户的,所以它每个用户拥有自己唯一的 id ,其次就是用户名 name ,最后一个是 是否登录 isLogin

分析完成之后,我们现在在 vue3 项目下的 src|components 文件夹下新建一个文件,命名为 GlobalHeader.vue 。之后编写这段业务代码。具体代码如下:

<template>
    <div></div>
</template>

<script lang="ts">
import {
     defineComponent, PropType } from 'vue'

//用ts写一个接口,存放列表数据的属性
//name和id加?表示是可选项
export interface UserProps{
    
    isLogin: boolean;
    name?: string;
    id?: number;
}
export default defineComponent({
    
  name: 'GlobalHeader',
  props: {
    
    //将接口的内容赋值给user对象,方便接收父组件传来的数据
    user: {
    
      type: Object as PropType<UserProps>,
      required: true
    }
  }
})
</script>
<style lang="scss" scoped>

</style>

3、视图数据绑定

现在,对数据构思完毕之后,我们来把数据绑定到视图当中。具体代码如下:

<template>
  <nav class="navbar navbar-dark bg-primary justify-content-between mb-4 px-4">
        <a class="navbar-brand" href="#">周一专栏</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">登录</a></li>
            <li class="list-inline-item"><a href="#" class="btn btn-outline-light my-2">注册</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">欢迎你 {
  {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>

4、数据传递

现在,我们在 vue3 项目中的 src 文件夹下的 App.vue 中来进行数据传递。具体代码如下:

<template>
  <div class="container">
    <global-header :user="user"></global-header>
  </div>
</template>

<script lang="ts">
import {
     defineComponent } from 'vue'
//在根文件下引入bootstrap
import 'bootstrap/dist/css/bootstrap.min.css'
//引入子组件
import GlobalHeader, {
     UserProps } from './components/GlobalHeader.vue'

//制造子组件的接口数据
const currentUser: UserProps = {
    
  isLogin: false,
  name: 'Monday'
}

export default defineComponent({
    
  name: 'App',
  components: {
    
    GlobalHeader
  },
  setup () {
    
    return {
    
      user: currentUser
    }
  }
})
</script>

<style lang="scss" scoped>

</style>

当前 isLogin 的状态我们是设置成 false 。现在,我们来看下此时浏览器的运行效果:

isLogin为false时

大家可以看到,当前状态为 false ,所以 header 的右边显示的是登录注册两个按钮,如预期所料。

现在,我们来把 isLogin 的状态改为 true具体代码如下:

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

此时我们来看下浏览器的显示效果,如下图所示:

isLogin为true时

现在,可以看到,当 isLogintrue 时,表示用户成功登录了。所以 header 的右方显示的是 欢迎你 Monday 的字样,也如我们预期所料。

📸三、Dropdown下拉菜单

看完上面的内容,大家心里是否有一个疑惑,我们 header 最右方的下拉菜单还没有实现。不着急,接下来我们就来设计这个组件。

1、组件基本功能

我们现在先来设计下这个组件的基本功能。首先在 vue3 项目的 src|components 文件夹下,新增一个 .vue 文件,命名为 Dropdown.vue 。之后编写该文件的代码,具体代码如下:

<template>
    <div class="dropdown">
        <!-- 下拉菜单标题 -->
        <a href="#" class="btn btn-outline-light my-2dropdown-toggle" @click.prevent="toggleOpen()">
            {
  {title}}
        </a>
        <!-- 下拉菜单内容 -->
        <ul class="dropdown-menu" :style="{
       display: 'block' }" v-if="isOpen">
          <li class="dropdown-item">
            <a href="#">新建文章</a>
          </li>
          <li class="dropdown-item">
            <a href="#">编辑资料</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)
    //点击后打开菜单
    const toggleOpen = () => {
    
      isOpen.value = !isOpen.value
    }
    return {
    
      isOpen,
      toggleOpen
    }
  }
})
</script>

<style lang="scss" scoped>

</style>

继续,我们来改造下刚刚的 GlobalHeader.vue 文件。具体代码如下:

<template>
    <nav class="navbar navbar-dark bg-primary justify-content-between mb-4 px-4">
        <a class="navbar-brand" href="#">周一专栏</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">登录</a></li>
            <li class="list-inline-item"><a href="#" class="btn btn-outline-light my-2">注册</a></li>
        </ul>
        <ul v-else class="list-inline mb-0">
            <li class="list-inline-item">
                <dropdown :title="`欢迎你 ${user.name}`"></dropdown>
            </li>
        </ul>
    </nav>
</template>

<script lang="ts">
import {
     defineComponent, PropType } from 'vue'
import 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>

现在,我们来看下浏览器的显示效果:

dropdown组件基本功能

2、自定义菜单内容DropdownItem

现在,我们已经完成了组件的基本功能。但是细心的小伙伴已经发现,下拉菜单没有办法自定义,因为它被写成固定的了。还有一个问题就是,点击其他区域我们不能收起菜单,这间接地,用户体验好像就没有那么好了。所以呢,有需求了,我们就来完成需求。现在,我们就来解决上述所说的两个问题。

同样地,在vue3项目中的 src|components 文件夹下添加一个 .vue 文件,命名为 DropdownItem.vue 。具体代码如下:

<template>
    <li
    class="dropdown-option"
    :class="{
    'is-disabled': disabled}"
    >
        <!-- 定义一个插槽,供给父组件使用 -->
        <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 文件,具体代码如下:

<template>
    <div class="dropdown">
        <!-- 下拉菜单标题 -->
        <a href="#" class="btn btn-outline-light my-2dropdown-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  } from 'vue'

export default defineComponent({
    
  name: 'Dropdown',
  props: {
    
    title: {
    
      type: String,
      required: true
    }
  },
  setup() {
    
    const isOpen = ref(false)
    //点击后打开菜单
    const toggleOpen = () => {
    
      isOpen.value = !isOpen.value
    }
    return {
    
      isOpen,
      toggleOpen
    }
  }
})
</script>

<style lang="scss" scoped>

</style>

通过以上代码我们可以了解到,将插槽放到 dropdown-menu 中去。


现在,到了最后一步,我们来看把它引入 GlobalHeader.vue 文件中。具体代码如下:

<template>
    <nav class="navbar navbar-dark bg-primary justify-content-between mb-4 px-4">
        <a class="navbar-brand" href="#">周一专栏</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">登录</a></li>
            <li class="list-inline-item"><a href="#" class="btn btn-outline-light my-2">注册</a></li>
        </ul>
        <ul v-else class="list-inline mb-0">
            <li class="list-inline-item">
                <dropdown :title="`欢迎你 ${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>

此时,我们来看下浏览器的显示效果:

dropdown自定义菜单内容

大家可以看到,此时的编辑资料已经变成了灰色并且无法进行点击和跳转。同时,自定义菜单的内容也一一显示了出来。

到这里,这一步的内容我们就完成啦!接下来我们继续升华这个组件,让它的用户体验更为极致。

3、组件点击外部区域自动隐藏

大家可以联想到平常自己点击各大网站时的场景,当点击菜单外部区域时,组件是不是就会自动隐藏。所以,接下来我们就来实现这个功能。

首先我们要明确,需要完成的两个任务:

  • onMounted 时候添加 click 事件,在 onUnmounted 的时候将事件删除;
  • 拿到 DropdownDOM 元素,从而判断点击的内容是否被这个元素包含。

接下来我们定位到 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>

此时我们来看下浏览器的显示效果:

dowpdown组件点击外部区域自动隐藏

大家可以看到,改造完 dropdown 的逻辑后,如我们预期所料的,当我们点击组件外部区域时,组件也自动隐藏了。

4、自定义函数

到这里,整个 GlobalHeader 组件可以说是相对比较完美了。但是大家有没有发现,在设计 dropdown 组件时,dropdown.vue 的代码好像看起来还稍微有一点点冗余。

这个时候我们可以考虑把它的逻辑抽离出来,单独放到一个自定义函数里面。接下来一起来实现一下吧~

首先我们在 vue3 项目中的 src 文件夹下,新建一个文件夹,命名为 hooks 。之后在 hooks 下新建一个文件,命名为 useClickOutside.tsuseClickOutside具体代码如下:

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>

此时浏览器的显示效果如下:

dowpdown组件点击外部区域自动隐藏

大家可以看到,最终的显示效果也是一样的。但是呢,通过代码逻辑抽离,我们整个组件的设计看起来也更加完美,可扩展性也变得更高。

5、联合效果

最后,我们把上面所学的GlobalHeader和Columnist结合起来,来看一下一体化的效果。详情见下图:

联合效果

以上就是关于 ColumnListGlobalHeader 两个组件的实现方式。不知道大家是否还意犹未尽呢~

后面还会持续出关于组件设计的文章,欢迎关注~

🛒四、结束语

讲到这里,关于组件 GlobalHeaderColumnList 的设计就结束啦!在设计组件的时候呢,要特别考虑组件的可扩展性。如果一个组件在写的时候,感觉没什么复用度,那么这个时候可能就得思考下,是不是哪个环节出现问题了。多问自己为什么,多问自己这个组件是否能抽离的更好。

以上就是本文的全部内容!如有疑问或文章有误欢迎评论区留言或公众号后台加我微信交流~

🐣彩蛋 One More Thing

基础知识回顾

软件推荐

这里给大家推荐文章用到的一个画图软件:Axure RP 9

Axure RP 旨在用于画低保真原型图,对于开发者来说也是极其友好的。丰富的控件库和动画交互,可以满足日常画图的大部分需求。安利一波~

番外篇

  • 关注公众号星期一研究室,第一时间关注学习干货,更多精选专栏待你解锁~

  • 如果这篇文章对你有用,记得留下脚印再走哦~

  • 我们下期见!👋👋👋

相关文章
|
10天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
6天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2506 14
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
6天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1519 14
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
8天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
531 13
|
1月前
|
运维 Cloud Native Devops
一线实战:运维人少,我们从 0 到 1 实践 DevOps 和云原生
上海经证科技有限公司为有效推进软件项目管理和开发工作,选择了阿里云云效作为 DevOps 解决方案。通过云效,实现了从 0 开始,到现在近百个微服务、数百条流水线与应用交付的全面覆盖,有效支撑了敏捷开发流程。
19282 30
|
1月前
|
人工智能 自然语言处理 搜索推荐
阿里云Elasticsearch AI搜索实践
本文介绍了阿里云 Elasticsearch 在AI 搜索方面的技术实践与探索。
18836 20
|
1月前
|
Rust Apache 对象存储
Apache Paimon V0.9最新进展
Apache Paimon V0.9 版本即将发布,此版本带来了多项新特性并解决了关键挑战。Paimon自2022年从Flink社区诞生以来迅速成长,已成为Apache顶级项目,并广泛应用于阿里集团内外的多家企业。
17524 13
Apache Paimon V0.9最新进展
|
8天前
|
人工智能 自动驾驶 机器人
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
过去22个月,AI发展速度超过任何历史时期,但我们依然还处于AGI变革的早期。生成式AI最大的想象力,绝不是在手机屏幕上做一两个新的超级app,而是接管数字世界,改变物理世界。
458 48
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
|
1天前
|
云安全 存储 运维
叮咚!您有一份六大必做安全操作清单,请查收
云安全态势管理(CSPM)开启免费试用
354 4
叮咚!您有一份六大必做安全操作清单,请查收
|
2天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。