详解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高级程序设计 读书笔记之八 Function类及闭包
Feb 27 Javascript
JS 去前后空格大全(IE9亲测)
Jul 15 Javascript
javascript仿php的print_r函数输出json数据
Sep 13 Javascript
用js读、写、删除Cookie代码分享及详细注释说明
Jun 05 Javascript
node.js中的fs.readlinkSync方法使用说明
Dec 17 Javascript
js图片卷帘门导航菜单特效代码分享
Sep 10 Javascript
javascript显示上周、上个月日期的处理方法
Feb 03 Javascript
在vue.js中抽出公共代码的方法示例
Jun 08 Javascript
JavaScript实现瀑布流图片效果
Jun 30 Javascript
弱类型语言javascript开发中的一些坑实例小结【变量、函数、数组、对象、作用域等】
Aug 07 Javascript
解决vue.js提交数组时出现数组下标的问题
Nov 05 Javascript
vue和H5 draggable实现拖拽并替换效果
Jul 29 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
dedecms采集中可以过滤多行代码的正则表达式
2007/03/17 PHP
PHP中去除换行解决办法小结(PHP_EOL)
2011/11/27 PHP
PHP浮点数的一个常见问题
2016/03/10 PHP
PHP模板引擎Smarty中的保留变量用法分析
2016/04/11 PHP
使用PHP反射机制来构造&quot;CREATE TABLE&quot;的sql语句
2019/03/21 PHP
PHP 加密 Password Hashing API基础知识点
2020/03/02 PHP
初学prototype,发个JS接受URL参数的代码
2006/09/25 Javascript
防止xss和sql注入:JS特殊字符过滤正则
2013/04/18 Javascript
JS(JQuery)操作Array的相关方法介绍
2014/02/11 Javascript
javascript里绝对用的上的字符分割函数总结
2014/07/31 Javascript
node模块机制与异步处理详解
2016/03/13 Javascript
JS代码防止SQL注入的方法(超简单)
2016/04/12 Javascript
详解JavaScript设计模式开发中的桥接模式使用
2016/05/18 Javascript
原生js实现键盘控制div移动且解决停顿问题
2016/12/05 Javascript
Vue2.0 从零开始_环境搭建操作步骤
2017/06/14 Javascript
jQuery实现一个简单的验证码功能
2017/06/26 jQuery
详解vue2.0 使用动态组件实现 Tab 标签页切换效果(vue-cli)
2017/08/30 Javascript
集合Bootstrap自定义confirm提示效果
2017/09/19 Javascript
解决Layui选择全部,换页checkbox复选框重新勾选的问题方法
2018/08/14 Javascript
详解@angular/cli 改变默认启动端口两种方式
2018/11/29 Javascript
JavaScript中import用法总结
2019/01/20 Javascript
JavaScript 格式化数字、金额、千分位、保留几位小数、舍入舍去
2019/07/23 Javascript
[01:51]2014DOTA2西雅图邀请赛 MVP 外卡赛black场间采访
2014/07/09 DOTA
在django中使用自定义标签实现分页功能
2017/07/04 Python
python3.5安装python3-tk详解
2019/04/26 Python
CSS3 仿微信聊天小气泡实例代码
2017/04/05 HTML / CSS
Chemist Warehouse官方海外旗舰店:澳洲第一连锁大药房
2017/08/25 全球购物
军用级手机壳,专为冒险而建:Zizo Wireless
2019/08/07 全球购物
入党积极分子自我鉴定
2014/02/18 职场文书
总会计师岗位职责
2014/02/19 职场文书
《庐山的云雾》教学反思
2014/04/22 职场文书
企业宣传口号
2014/06/12 职场文书
新农村建设汇报材料
2014/08/15 职场文书
高中升旗仪式演讲稿
2014/09/09 职场文书
处级领导干部四风问题自我剖析材料
2014/09/29 职场文书
党干部专题民主生活会对照检查材料思想汇报
2014/10/06 职场文书