详解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学习阶段性总结(2)--(X)HTML学习
Feb 03 Javascript
javascript 时间比较实现代码
Oct 28 Javascript
jquery插件 cluetip 关键词注释
Jan 12 Javascript
Javascript 获取链接(url)参数的方法[正则与截取字符串]
Feb 09 Javascript
JavaScript在IE和Firefox浏览器下的7个差异兼容写法小结
Jun 18 Javascript
js 中的switch表达式使用示例
Jun 03 Javascript
node.js中的buffer.Buffer.byteLength方法使用说明
Dec 10 Javascript
JavaScript Sort 的一个错误用法示例
Mar 20 Javascript
jquery实现放大镜简洁代码(推荐)
Jun 08 jQuery
利用 spin.js 生成等待效果(js 等待效果)
Jun 25 Javascript
使用Node.js实现RESTful API的示例
Aug 01 Javascript
Vue.js添加组件操作示例
Jun 13 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
一个简单实现多条件查询的例子
2006/10/09 PHP
PHP新手上路(七)
2006/10/09 PHP
PHP图片处理之图片旋转和图片翻转实例
2014/11/19 PHP
php实现图片添加描边字和马赛克的方法
2014/12/10 PHP
php模拟服务器实现autoindex效果的方法
2015/03/10 PHP
使用JavaScript创建新样式表和新样式规则
2016/06/14 PHP
Laravel 登录后清空COOKIE的操作方法
2019/10/14 PHP
基于jQuery的让非HTML5浏览器支持placeholder属性的代码
2011/05/24 Javascript
JQuery获取当前屏幕的高度宽度的实现代码
2011/07/12 Javascript
元素未显示设置width/height时IE中使用currentStyle获取为auto
2014/05/04 Javascript
jQuery源码解读之addClass()方法分析
2015/02/20 Javascript
BOM系列第二篇之定时器requestAnimationFrame
2016/08/17 Javascript
Bootstrap 网站实例之单页营销网站
2016/10/20 Javascript
React-Native左右联动List的示例代码
2017/09/21 Javascript
Vue.js组件通信的几种姿势
2017/10/23 Javascript
代码详解JS操作剪贴板
2018/02/11 Javascript
vue项目打包后打开页面空白解决办法
2018/06/29 Javascript
Vue在chrome44偶现点击子元素事件无法冒泡的解决方法
2019/12/15 Javascript
Python中用Spark模块的使用教程
2015/04/13 Python
Python字符串逐字符或逐词反转方法
2015/05/21 Python
Python使用grequests(gevent+requests)并发发送请求过程解析
2019/09/25 Python
给我一面国旗 python帮你实现
2019/09/30 Python
Python Numpy数组扩展repeat和tile使用实例解析
2019/12/09 Python
python绘制动态曲线教程
2020/02/24 Python
Python devel安装失败问题解决方案
2020/06/09 Python
keras读取h5文件load_weights、load代码操作
2020/06/12 Python
美国全球旅游运营商:Pacific Holidays
2018/06/18 全球购物
国际奢侈品品牌童装购物网站:Designer Childrenswear
2019/05/08 全球购物
远东集团网络工程师面试题
2014/10/20 面试题
竞职演讲稿范文
2014/01/11 职场文书
电力公司个人求职信范文
2014/02/04 职场文书
仓管员岗位责任制
2014/02/19 职场文书
2014年招生工作总结
2014/11/26 职场文书
幼儿园安全教育月活动总结
2015/05/08 职场文书
经典格言警句:没有热忱,世间便无进步
2019/11/13 职场文书
详解Spring Bean的配置方式与实例化
2022/06/10 Java/Android