导航组件
NavItem.vue

目标 Objective

内容 Content

回顾 Review

引言 Introduction

原则 Principle

过程 Procedure

  1. 需求分析 Requirement Analysis
  2. . 明确组件的功能、输入输出,确定组件的使用场景

    . 确定需要暴露的 props 和 emits

  3. 组件设计 Design
  4. . 拆分组件结构,包括模板 template、逻辑 script 和样式style

    . 使用 <slot> 实现内容扩展,提高灵活性

  5. 组件开发 Implementation
  6. . 在 Vue 中,使用 defineProps() 接收父组件传入的数据

    . 使用 defineEmits() 定义子组件向父组件通信的事件

    . 编写组件内部逻辑和交互

    . 完善组件的基本样式

  7. 组件注册 Registration
  8. . 全局注册

    . 在父组件中引入并注册子组件

  9. 组件使用 Usage
  10. . 通过属性绑定传递数据

    . 通过插槽传递内容

  11. 测试 Testing
  12. . 测试组件在不同场景下的表现

    . 开发者工具或第三方工具

  13. 优化 Optimization
  14. . 对组件进行性能优化,如避免不必要的渲染、合理拆分逻辑等

  15. 文档编写 Documentation
  16. . 提供组件的使用说明、API 文档以及示例代码

前置 Prerequisite

温故而知新

学以致用

  1. 列表渲染 v-for
  2. . 基于一个数组或对象来渲染一个列表

    . 以数组为例;titles 是数据源;title 是迭代变量,表示数据项,可以是任何有效标识符;index 是数据项索引/位置序号

    . 更多信息,请访问 列表渲染 v-for

    import { ref } from 'vue';
    const  titles = ref([
      { id:0, title: 'Foo' },
      { id:1, title: 'Bar' },
      { id:2, title: 'Baz' },
    ])
    <div v-for="(title, index) in titles" :key = "title.id">
      // 其它内容
      <div>{{ title.title }}</div>
    </div>
  3. 自定义属性 defineProps()
  4. . 父组件 → 子组件传递参数/通信

    . 更多信息,请访问 父传子 defineProps()

    • 子组件 Child.vue
    • . 定义属性参数,暴露接口;这里暴露了一个字符串类型的属性 msg,默认为空;使用变量 props 接受

      const props = defineProps({
        msg: {
          type: String,
          default: ''
        }
      })

      . 在结构中使用属性参数

      <div>{{ msg }}</div>
    • 父组件 Super.vue
    • . 引入子组件、定义属性参数

      import { ref } from 'vue';
      import Child from './components/Child.vue';
      const  msg = ref('hi, there.')

      . 在结构中使用子组件并传递属性参数

      <Child :msg = 'msg'></Child>
  5. 插槽 <slot>
  6. . 从结构上扩展组件功能;以默认插槽为例

    . 更多信息,请访问 插槽 Slot

    • 子组件 Child.vue
    • . 定义插槽

      <div>
        // 组件其它内容
        <slot></slot>
      </div>
    • 父组件 Super.vue
    • . 引入、使用子组件

      import Child from './components/Child.vue';

      . 传递插槽,可以是任意内容;只能使用双标签形式

      <Child>
        <div>
          // 插槽内容
        </div>
      </Child>
  7. 阿里字体图标 iconfont
  8. . 基于矢量图形的字体文件;轻量、兼容性好、易于使用、可定制

    . 更多信息,请访问 阿里字体图标 Iconfont

    • 创建项目
    • 查找图标;添加到购物车
    • 选择使用方式:类 class
    • 生成链接
    • 复制链接到项目 main.css 中并引入
    • @import url(//at.alicdn.com/t/c/font_3859342_qe1ea0cgv5.css);
    • 使用图标:基类和图标类
    • <span class="iconfont icon-home"></span>
  9. 定位 position
  10. . 最好显式的指定较高的层级 z-index,确保位于其它元素上层

    . 更多信息,请访问 固定定位 Fixed粘性定位 Sticky

    • 主导航固定定位在视图底部
    • .main-nav {
        position: fixed;
        bottom: 0;
        z-index: 99;
      }
    • 菜单导航粘性定位在视图顶部
    • .menu-nav {
        position: sticky;
        top: 0;
        z-index: 99;
      }

实操 Drill

熟能生巧

没有规矩不成方圆

  1. 需求分析
  2. . 功能:导航

    . 必选:使用文字提示

    . 可选:其它提示,如图片、字体图标等

  3. 组件设计
  4. . 核心功能:使用 <RouterLink> 配合属性传参 defineProps() 实现

    . 拓展功能:使用插槽 <slot> 实现;采用主流字体图标 Iconfont

  5. 素材准备
  6. . 字体图标:查找并引入字体图标在线链接到主样式文件 app.css

    @import url(//at.alicdn.com/t/c/font_4235521_imop9zdge2.css);

    Show Time

  7. 组件开发
  8. . 遵循 先静态后动态 的开发原则

    . 在项目适当目录创建导航组件 NavItem.vue

    . 静态设计

    <RouterLink class="nav-item" to="/">
      <span>Home</span>
    </RouterLink>

    . 动态设计:使用自定义的属性参数替换静态数据部分

    <RouterLink class="nav-item" :to="item.to">
      <slot></slot>
      <span>{{ item.label }}</span>
    </RouterLink>
    const props = defineProps({
      item: {
        type: Object,
        default: {}
      }
    })

    Show Time

  9. 首页视图 HomeView.vue 中注册并使用导航组件 NavItem.vue
  10. . 引入导航组件、准备数据

    . 这里采用静态数据源 items

    import NavItem from '@/components/NavItem.vue'
    const items = [
      { label: 'Home', icon: 'icon-home', to: '/' },
      { label: 'Menu', icon: 'icon-thlist', to: '/menu' },
      { label: 'Mall', icon: 'icon-shoppingcart', to: '/mall' },
      { label: 'Memeber', icon: 'icon-statis', to: '/memeber' },
      { label: 'Mine', icon: 'icon-account', to: '/mine' }
    ]

    . 额外使用一个 <div> 便于布局

    . 列表渲染导航数据

    . 使用字体图标填充插槽;因为使用插槽,自定义组件必须使用双标签形式

    <div class="nav">
      <NavItem v-for="item in items" :key="item.label" :item>
        <span class="iconfont" :class="item.icon"></span>
      </NavItem>
    </div>

    . 固定定位在视图底部

    . 提升层级

    . 弹性盒子水平分布导航项

    .nav {
      position: fixed;
      left: 0;
      bottom: 0;
      width: 100%;
      height: var(--nav-h);
      z-index: 9;
      display: flex;
      justify-content: space-between;
      align-items: center;
      background-color: #f7f7f7;
      padding: 0 var(--p-m-g);
    }

    Show Time

  11. 菜单视图 MennuView.vue 中注册并使用导航组件 NavItem.vue
  12. . 引入导航组件、准备数据

    . 这里采用静态数据源 items;注意:to 应为全路径

    import NavItem from '@/components/NavItem.vue'
    const items = [
      { label: 'Goods', to: '/menu/goods' },
      { label: 'Vip', to: '/menu/vip' },
      { label: 'Rank', to: '/menu/rank' },
      { label: 'Favorite', to: '/menu/favorite' },
    ]

    . 额外使用一个 <div>便于布局

    . 列表渲染导航数据

    . 没有使用插槽,自定义组件可以使用单标签形式

    <div class="nav">
      <NavItem v-for="item in items" :key="item.label" :item></NavItem>
    </div>
    

    . 粘性定位在视图底部:视图滚动时,保持二级菜单始终显式在顶部

    . 提升层级

    . 弹性盒子水平分布导航项

    .nav {
      position: sticky;
      Top:  0;
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 0 var(--p-m-g);
    }

    Show Time

  13. 测试 - 略
  14. 优化 - 略
  15. 文档 - 略

小结 Summary

  1. 模块化开发的基本原则
  2. 模块化开发的基本方法和流程

作业 Homework

  1. 实操:完成导航组件 NavItem.vue 的设计与开发;
  2. 思考:是否可以全部采用插槽的方式封装导航组件?
  3. 拓展:使用模块化开发的思想,优化项目中其它部分;

参考 Reference

  1. 组件 Component
  2. 列表渲染 v-for
  3. 父传子 defineProps()
  4. 子传父 defineEmits()
  5. 双向绑定 defineModel()
  6. 插槽 Slot
  7. 阿里字体图标 Iconfont
  8. 弹性盒子 Flex
  9. 定位 Position
  10. 固定定位 Fixed
  11. 粘性定位 Sticky