详解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 相关文章推荐
JavaScript 实现简单的倒计时弹窗DEMO附图
Mar 05 Javascript
用JavaScript实现页面重定向功能的教程
Jun 04 Javascript
kindeditor编辑器点中图片滚动条往上顶的bug
Jul 05 Javascript
基于jQuery实现页面搜索功能
Mar 26 Javascript
详细分析Javascript中创建对象的四种方式
Aug 17 Javascript
AngularJS中过滤器的使用与自定义实例代码
Sep 17 Javascript
微信小程序实战之自定义抽屉菜单(7)
Apr 18 Javascript
对类Vue的MVVM前端库的实现代码
Sep 07 Javascript
Vue中控制v-for循环次数的实现方法
Sep 26 Javascript
Vue.js 中的 v-model 指令及绑定表单元素的方法
Dec 03 Javascript
vue使用nprogress实现进度条
Dec 09 Javascript
angular组件间通讯的实现方法示例
May 07 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中防止SQL注入实现代码
2011/02/19 PHP
php修改上传图片尺寸的方法
2015/04/14 PHP
php生成年月日下载列表的方法
2015/04/24 PHP
PHP7+Nginx的配置与安装教程详解
2016/05/10 PHP
JQuery对checkbox操作 (循环获取)
2011/05/20 Javascript
ajax的hide隐藏问题解决方法
2012/12/11 Javascript
jquery 追加tr和删除tr示例代码
2013/09/12 Javascript
用JavaScript实现类似于ListBox功能示例代码
2014/03/09 Javascript
javascript中with()方法的语法格式及使用
2014/08/04 Javascript
Bootstrap作品展示站点实战项目2
2016/10/14 Javascript
js简易版购物车功能
2017/06/17 Javascript
JavaScript使用atan2来绘制箭头和曲线的实例
2017/09/14 Javascript
Vue2仿淘宝实现省市区三级联动
2020/04/15 Javascript
JS实现字符串中去除指定子字符串方法分析
2018/05/17 Javascript
小程序自定义单页面、全局导航栏的实现代码
2019/03/15 Javascript
layui实现鼠标移动到单元格上显示数据的方法
2019/09/11 Javascript
解决Idea、WebStorm下使用Vue cli脚手架项目无法使用Webpack别名的问题
2019/10/11 Javascript
详解Vue后台管理系统开发日常总结(组件PageHeader)
2019/11/01 Javascript
Python安装第三方库及常见问题处理方法汇总
2016/09/13 Python
用tensorflow构建线性回归模型的示例代码
2018/03/05 Python
python生成密码字典的方法
2018/07/06 Python
python中property和setter装饰器用法
2019/12/19 Python
TensorFlow命名空间和TensorBoard图节点实例
2020/01/23 Python
英国大码女性时装零售商:Evans
2018/08/29 全球购物
Carter’s OshKosh加拿大:购买婴幼儿服装和童装
2018/11/27 全球购物
递归实现回文判断(如:abcdedbca就是回文,判断一个面试者对递归理解的简单程序)
2013/04/28 面试题
杭州-飞时达软件有限公司.net笔面试
2012/04/28 面试题
妇科医生自荐信
2013/11/05 职场文书
大学军训感言400字
2014/03/11 职场文书
群众路线党课主持词
2014/04/01 职场文书
小学数学课题方案
2014/06/15 职场文书
2015年秋季新学期寄语
2015/03/25 职场文书
2015年社区纪检工作总结
2015/04/21 职场文书
优质护理服务心得体会
2016/01/22 职场文书
Python实现视频自动打码的示例代码
2022/04/08 Python
Python 文字识别
2022/05/11 Python