详解Vue 项目中的几个实用组件(ts)


Posted in Javascript onOctober 29, 2019

前言

这段时间使用 ts 和 vue 做了一个项目,项目从 0 开始搭建,在建设和优化的同时,实现了很多自己的想法,有那么一两个组件可能在我本人看来有意义,所以从头回顾一下当初的想法,同样也可以做到一个记录的作用。如果还没有使用过 ts 的同学可以通过使用 Vue Cli3 + TypeScript + Vuex + Jest 构建 todoList  这边文章开始你的 ts 之旅,后续代码也是在 todoList 的结构上改进的

vue 路由中的懒加载

你真的用好了路由的懒加载吗?

在 2.x 的文档中、cli 的初始化项目中都会默认生成一个路由文件,大致如下:

{
   path: '/about',
   name: 'about',
   // route level code-splitting
   // this generates a separate chunk (about.[hash].js) for this route
   // which is lazy-loaded when the route is visited.
   component: () =>
    import(/* webpackChunkName: "about" */ './views/About.vue')
}

通过路由懒加载的组件会在 webpack 打包之后生成名为 about 的  dist/js/about.39d4f7ae.js 文件。
但是在 react 中,react-loadable 可以使路由在懒加载之前先加载一个其他的组件(一般是 loading )过度这个加载的过程。

A higher order component for loading components with promises.

其实这也就是 react 的高阶组件 (HOC),那么根据 HOC 的思想,我们能否在 vue 中也实现这样一个 HOC 呢?答案是 YES

让我们看一下官方的介绍:

const AsyncComponent = () => ({
 // The component to load (should be a Promise)
 component: import('./MyComponent.vue'),
 // A component to use while the async component is loading
 loading: LoadingComponent,
 // A component to use if the load fails
 error: ErrorComponent,
 // Delay before showing the loading component. Default: 200ms.
 delay: 200,
 // The error component will be displayed if a timeout is
 // provided and exceeded. Default: Infinity.
 timeout: 3000
})

这个 2.3+ 新增的功能,使我们的开始有了可能,我们创建一个 loadable.ts 的高阶组件,利用 render 函数生成组件并返回。

import LoadingComponent from './loading.vue';

export default (component: any) => {
  const asyncComponent = () => ({
    component: component(),
    loading: LoadingComponent,
    delay: 200,
    timeout: 3000
  });
  return {
    render(h: any) {
      return h(asyncComponent, {});
    }
  };
};

在 routes 中使用该组件

import loadable from './loadable';

const routes = [
 {
    path: '/about',
    name: 'about',
    // component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    component: loadable( () => import(/* webpackChunkName: "about" */ './views/About.vue')
 }
]

看起来貌似已经成功了,但是在这当中还存在问题。

关于 vue-router ,不可避免的会涉及到路由的钩子函数,但是在以上用法中路由钩子是失效的,why ?

路由钩子只直接生效于注册在路由上的组件。

那么通过 loadable 生成渲染的组件中 About 组件已经是一个子组件,所以拿不到路由钩子。

组件必须保证使用上的健壮性,我们换一种方案,直接返回这个组件。

const asyncComponent = importFunc => () => ({
  component: importFunc(),
  loading: LoadingComponent,
  delay: 200,
  timeout: 3000
});

我们重新更换 routes :

const routes = [
 {
    path: '/about',
    name: 'about',
    // component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    component: asyncComponent( () => import(/* webpackChunkName: "about" */ './views/About.vue')
 }
]

上述用法已经解决了路由钩子的问题,但是仍然有两点值得注意:

  • asyncComponent 接受的参数是一个 function , 如果直接写成  import(/* webpackChunkName: "about" */ './views/About.vue'), 则 LoadingComponent 无法生效。
  • AsyncComponent 还可以添加一个 error 的组件,形成逻辑闭环。

SVG 、Iconfont 在 vue 项目中最优雅的用法

能用 svg 的地方尽量不使用图片 笔者在使用 svg 的时候一开始是使用vue-svg-loader, 具体用法,请自行查看。

但是在写 sidebar 时,笔者想将 svg 通过配置文件的形式写入,让 sidebar 形成多层的自动渲染。
显然 vue-svg-loader 的用法不合适。我们先了解 svg 的用法,我们可以看一篇乃夫的介绍:SVG 图标简介。

SVG symbol ,Symbols let you define an SVG image once, and reuse it in multiple places.

和雪碧图原理类似,可以将多个 svg 合成一个,但是这里用 id 来语意化定位图标

// 定义
<svg class="hidden">
 <symbol id="rectangle-1" viewBox="0 0 20 20">
  <rect x="0" y="0" width="300" height="300" fill="rgb(255,159,0)" />
 </symbol>
  <symbol id="reactangle-2" viewBox="0 0 20 20">
  <rect x="0" y="0" width="300" height="300" fill="rgb(255,159,0)" />
 </symbol>
</svg>

// 使用
<svg>
 <use xlink:href="#rectangle-1" rel="external nofollow" href="#rectangle" rel="external nofollow" />
</svg>

正好有这么一个 svg 雪碧图的 webpack loader,svg-sprite-loader,下面是代码

首先根据官网修改配置:

// vue.config.js
    const svgRule = config.module.rule('svg');

    // 清除已有的所有 loader。
    // 如果你不这样做,接下来的 loader 会附加在该规则现有的 loader 之后。
    svgRule.uses.clear();
    svgRule.exclude.add(/node_modules/);
    // 添加要替换的 loader
    // svgRule.use('vue-svg-loader').loader('vue-svg-loader');
    svgRule
      .test(/\.svg$/)
      .pre()
      .include.add(/\/src\/icons/)
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      });

    const imagesRule = config.module.rule('images');
    imagesRule.exclude.add(resolve('src/icons'));
    config.module.rule('images').test(/\.(png|jpe?g|gif|svg)(\?.*)?$/);

创建 ICON 文件夹,然后在文件夹中创建 svgIcon.vue 组件。

<template>
  <svg v-show="isShow" :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName" rel="external nofollow" />
  </svg>
</template>
 
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';

@Component
export default class SvgIcon extends Vue {
  @Prop({ required: true }) private readonly name!: string;
  @Prop({ default: () => '' }) private readonly className!: string;

  private get isShow() {
    return !!this.name;
  }

  private get iconName() {
    return `#icon-${this.name}`;
  }

  private get svgClass() {
    if (this.className) {
      return 'svg-icon ' + this.className;
    } else {
      return 'svg-icon';
    }
  }
}
</script>
 
<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  fill: currentColor;
  overflow: hidden;
}
</style>

在当前目录下创建 index.ts

import Vue from 'vue';
import SvgIcon from './svgIcon.vue'; // svg组件

// 注册到全局
Vue.component('svg-icon', SvgIcon);

const requireAll = (requireContext: any) =>
  requireContext.keys().map(requireContext);
const req = require.context('./svg', false, /\.svg$/);
requireAll(req);

在当前目录下新建 svg 文件夹,用于存放需要的 svg 静态文件。

☁ icons [1.1.0] ⚡ tree -L 2
.
├── index.ts
├── svg
│  └── loading.svg
└── svgIcon.vue

使用:

<svg-icon name="loading"></svg-icon>

我们来看一下原理和值得注意的几点:

  • svg-sprite-loader 处理完通过 import 的 svg 文件后将其生成类似于雪碧图的形式,也就是 symbol, 通过配置中的 .options({ symbolId: 'icon-[name]' });可以使用 <use xlink:href="#symbolId" rel="external nofollow" /> 直接使用这个 svg
  • 添加完 svg-sprite-loader 后,由于 cli 默认对 svg 有处理,所以需要 exclude 指定文件夹的 svg。
  • 使用时由于 svgIcon 组件的处理,只需要将 name 指定为文件名即可。

那么,我们使用 iconfont 和 svg 有什么关系呢?

iconfont 的使用方法有很多种,完全看个人喜好,但是其中一种使用方法,也是用到了 svg symbol  的原理,一般 iconfont 会默认导出这些文件。

☁ iconfont [1.1.0] ⚡ tree -L 2
.
├── iconfont.css
├── iconfont.eot
├── iconfont.js
├── iconfont.svg
├── iconfont.ttf
├── iconfont.woff
└── iconfont.woff2

我们关注于其中的 js 文件, 打开文件,可以看出这个 js 文件将所有的 svg 已经处理为了 svg symbol,并动态插入到了 dom 节点当中。

而 iconfont 生成的 symbolId 也符合我们 svg-icon 的 name 命名规则 所以我们在项目的入口文件中引入这个 js 之后可以直接使用。

back-to-up

首先为什么会写这个组件呢,本项目中使用的组件库是 elementUI ,而 UI 库中自带 el-backtop,但是我能说不好用吗? 或者说我太蠢了,在经过一番努力的情况下我还是没能使用成功,所以自己写了一个。

直接上代码:

<template>
  <transition :name="transitionName">
    <div v-show="visible" :style="localStyle" class="back-to-ceiling" @click="backToTop">
      <slot>
        <svg
          viewBox="0 0 17 17"
          xmlns="http://www.w3.org/2000/svg"
          aria-hidden="true"
          style="height: 16px; width: 16px;"
        >
          <g>
            <path
              d="M12.036 15.59c0 .55-.453.995-.997.995H5.032c-.55 0-.997-.445-.997-.996V8.584H1.03c-1.1 0-1.36-.633-.578-1.416L7.33.29c.39-.39 1.026-.385 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.004z"
              fill-rule="evenodd"
            />
          </g>
        </svg>
      </slot>
    </div>
  </transition>
</template>

<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';

@Component
export default class BackToTop extends Vue {
  @Prop({ default: () => 400 }) private readonly visibilityHeight!: number;
  @Prop({ default: () => 0 }) private readonly backPosition!: number;
  @Prop({
    default: () => ({})
  })
  private readonly customStyle!: any;
  @Prop({ default: () => 'fade' }) private readonly transitionName!: string;

  private visible: boolean = false;
  private interval: number = 0;
  private isMoving: boolean = false;

  private detaultStyle = {
    width: '40px',
    height: '40px',
    'border-radius': '50%',
    color: '#409eff',
    display: 'flex',
    'align-items': 'center',
    'justify-content': 'center',
    'font-size': '20px',
    cursor: 'pointer',
    'z-index': 5
  };
  private get localStyle() {
    return { ...this.detaultStyle, ...this.customStyle };
  }
  private mounted() {
    window.addEventListener('scroll', this.handleScroll);
  }

  private beforeDestroy() {
    window.removeEventListener('scroll', this.handleScroll);
    if (this.interval) {
      clearInterval(this.interval);
    }
  }

  private handleScroll() {
    this.visible = window.pageYOffset > this.visibilityHeight;
  }

  private backToTop() {
    
    window.scrollTo({
      left: 0,
      top: 0,
      behavior: 'smooth'
    });

   
  }
  
}
</script>

<style scoped>
.back-to-ceiling {
  background-color: rgb(255, 255, 255);
  box-shadow: 0 0 6px rgba(0, 0, 0, 0.12);
  background-color: '#f2f6fc';
  position: fixed;
  right: 50px;
  bottom: 50px;
  cursor: pointer;
}

.back-to-ceiling:hover {
  background-color: #f2f6fc;
}
.fade-enter-active,
.fade-leave-active {
  display: block;
  transition: display 0.1s;
}
.fade-enter,
.fade-leave-to {
  display: none;
}

</style>

使用:

<back-to-top :custom-style="myBackToTopStyle" :visibility-height="300" :back-position="0">
      <i class="el-icon-caret-top"></i>
    </back-to-top>

custom-style 可以自行定义,返回的图标也可以自由替换。

注意,在 safari 中动画中动画表现不一致,使用 requestAnimationFrame 之后仍然不一致。希望同学们有时间可以自由发挥一下。

总结

永远抱着学习的心态去写代码,尝试多种写法,写出你最优雅的那一种。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js一组验证函数
Dec 20 Javascript
jquery $(document).ready() 与window.onload的区别
Dec 28 Javascript
JS+ACTIVEX实现网页选择本地目录路径对话框
Mar 18 Javascript
JS文本框默认值处理详解
Jul 10 Javascript
jQuery之Deferred对象详解
Sep 04 Javascript
jQuery对JSON数据进行排序输出的方法
Jun 24 Javascript
纯jquery实现模仿淘宝购物车结算
Aug 20 Javascript
前端学习笔记style,currentStyle,getComputedStyle的用法与区别
May 28 Javascript
Vue 实用分页paging实例代码
Apr 12 Javascript
angular json对象push到数组中的方法
Feb 27 Javascript
Vue中Quill富文本编辑器的使用教程
Sep 21 Javascript
Vue如何基于es6导入外部js文件
May 15 Javascript
JS操作json对象key、value的常用方法分析
Oct 29 #Javascript
JQuery 实现文件下载的常用方法分析
Oct 29 #jQuery
Vue 设置axios请求格式为form-data的操作步骤
Oct 29 #Javascript
js单线程的本质 Event Loop解析
Oct 29 #Javascript
解决axios post 后端无法接收数据的问题
Oct 29 #Javascript
使用axios请求接口,几种content-type的区别详解
Oct 29 #Javascript
vue+elementui 对话框取消 表单验证重置示例
Oct 29 #Javascript
You might like
php计算两个日期时间差(返回年、月、日)
2014/06/19 PHP
JQuery Ajax 跨域访问的解决方案
2010/03/12 Javascript
Array.prototype.slice.apply的使用方法
2010/03/17 Javascript
extjs 如何给column 加上提示
2014/07/29 Javascript
使用JavaScript+canvas实现图片裁剪
2015/01/30 Javascript
浅谈$(document)和$(window)的区别
2015/07/15 Javascript
jQuery轮播图效果精简版完整示例
2016/09/04 Javascript
jQuery实现花式轮播之圣诞节礼物传送效果
2016/12/25 Javascript
Javascript中return的使用与闭包详解
2017/01/11 Javascript
js date 格式化
2017/02/15 Javascript
jquery easyui dataGrid动态改变排序字段名的方法
2017/03/02 Javascript
快速解决brew安装特定版本flow的问题
2018/05/17 Javascript
详解vue axios二次封装
2018/07/22 Javascript
Angular2之二级路由详解
2018/08/31 Javascript
Vue弹出菜单功能的实现代码
2018/09/12 Javascript
vue生命周期的探索
2019/04/03 Javascript
vue移动端使用canvas签名的实现
2020/01/15 Javascript
[39:52]2018DOTA2亚洲邀请赛 4.3 突围赛 EG vs Newbee 第一场
2018/04/04 DOTA
[03:02]安得倚天剑,跨海斩长鲸——中国军团出征DOTA2国际邀请赛
2018/08/14 DOTA
pydev使用wxpython找不到路径的解决方法
2013/02/10 Python
Python学习笔记_数据排序方法
2014/05/22 Python
详解Python自建logging模块
2018/01/29 Python
Python中一些不为人知的基础技巧总结
2018/05/19 Python
python实现嵌套列表平铺的两种方法
2018/11/08 Python
python脚本监控logstash进程并邮件告警实例
2020/04/28 Python
python如何实现读取并显示图片(不需要图形界面)
2020/07/08 Python
HOTEL INFO英国:搜索全球酒店
2019/08/08 全球购物
一些关于MySql加速和优化的面试题
2014/01/30 面试题
Lucene推荐的分页方式是什么?
2015/12/07 面试题
车祸赔偿收入证明
2014/01/09 职场文书
学生党员的自我评价范文
2014/03/01 职场文书
工人先进事迹材料
2014/12/26 职场文书
2015年小学英语教师工作总结
2015/05/12 职场文书
2015年机关党建工作总结
2015/05/22 职场文书
教师节班会主持词
2015/07/06 职场文书
浅谈Python响应式类库RxPy
2021/06/14 Python