詳解Vue 項(xiàng)目中的幾個(gè)實(shí)用組件(ts)
前言
這段時(shí)間使用 ts 和 vue 做了一個(gè)項(xiàng)目,項(xiàng)目從 0 開始搭建,在建設(shè)和優(yōu)化的同時(shí),實(shí)現(xiàn)了很多自己的想法,有那么一兩個(gè)組件可能在我本人看來有意義,所以從頭回顧一下當(dāng)初的想法,同樣也可以做到一個(gè)記錄的作用。如果還沒有使用過 ts 的同學(xué)可以通過使用 Vue Cli3 + TypeScript + Vuex + Jest 構(gòu)建 todoList 這邊文章開始你的 ts 之旅,后續(xù)代碼也是在 todoList 的結(jié)構(gòu)上改進(jìn)的
vue 路由中的懶加載
你真的用好了路由的懶加載嗎?
在 2.x 的文檔中、cli 的初始化項(xiàng)目中都會(huì)默認(rèn)生成一個(gè)路由文件,大致如下:
{ 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') }
通過路由懶加載的組件會(huì)在 webpack 打包之后生成名為 about 的 dist/js/about.39d4f7ae.js 文件。
但是在 react 中,react-loadable 可以使路由在懶加載之前先加載一個(gè)其他的組件(一般是 loading )過度這個(gè)加載的過程。
A higher order component for loading components with promises.
其實(shí)這也就是 react 的高階組件 (HOC),那么根據(jù) HOC 的思想,我們能否在 vue 中也實(shí)現(xiàn)這樣一個(gè) 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 })
這個(gè) 2.3+ 新增的功能,使我們的開始有了可能,我們創(chuàng)建一個(gè) loadable.ts 的高階組件,利用 render 函數(shù)生成組件并返回。
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') } ]
看起來貌似已經(jīng)成功了,但是在這當(dāng)中還存在問題。
關(guān)于 vue-router ,不可避免的會(huì)涉及到路由的鉤子函數(shù),但是在以上用法中路由鉤子是失效的,why ?
路由鉤子只直接生效于注冊(cè)在路由上的組件。
那么通過 loadable 生成渲染的組件中 About 組件已經(jīng)是一個(gè)子組件,所以拿不到路由鉤子。
組件必須保證使用上的健壯性,我們換一種方案,直接返回這個(gè)組件。
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') } ]
上述用法已經(jīng)解決了路由鉤子的問題,但是仍然有兩點(diǎn)值得注意:
- asyncComponent 接受的參數(shù)是一個(gè) function , 如果直接寫成 import(/* webpackChunkName: "about" */ './views/About.vue'), 則 LoadingComponent 無法生效。
- AsyncComponent 還可以添加一個(gè) error 的組件,形成邏輯閉環(huán)。
SVG 、Iconfont 在 vue 項(xiàng)目中最優(yōu)雅的用法
能用 svg 的地方盡量不使用圖片 筆者在使用 svg 的時(shí)候一開始是使用vue-svg-loader, 具體用法,請(qǐng)自行查看。
但是在寫 sidebar 時(shí),筆者想將 svg 通過配置文件的形式寫入,讓 sidebar 形成多層的自動(dòng)渲染。
顯然 vue-svg-loader 的用法不合適。我們先了解 svg 的用法,我們可以看一篇乃夫的介紹:SVG 圖標(biāo)簡(jiǎn)介。
SVG symbol ,Symbols let you define an SVG image once, and reuse it in multiple places.
和雪碧圖原理類似,可以將多個(gè) svg 合成一個(gè),但是這里用 id 來語意化定位圖標(biāo)
// 定義 <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>
正好有這么一個(gè) svg 雪碧圖的 webpack loader,svg-sprite-loader,下面是代碼
首先根據(jù)官網(wǎng)修改配置:
// vue.config.js const svgRule = config.module.rule('svg'); // 清除已有的所有 loader。 // 如果你不這樣做,接下來的 loader 會(huì)附加在該規(guī)則現(xiàn)有的 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)(\?.*)?$/);
創(chuàng)建 ICON 文件夾,然后在文件夾中創(chuàng)建 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>
在當(dāng)前目錄下創(chuàng)建 index.ts
import Vue from 'vue'; import SvgIcon from './svgIcon.vue'; // svg組件 // 注冊(cè)到全局 Vue.component('svg-icon', SvgIcon); const requireAll = (requireContext: any) => requireContext.keys().map(requireContext); const req = require.context('./svg', false, /\.svg$/); requireAll(req);
在當(dāng)前目錄下新建 svg 文件夾,用于存放需要的 svg 靜態(tài)文件。
☁ icons [1.1.0] ⚡ tree -L 2 . ├── index.ts ├── svg │ └── loading.svg └── svgIcon.vue
使用:
<svg-icon name="loading"></svg-icon>
我們來看一下原理和值得注意的幾點(diǎn):
- svg-sprite-loader 處理完通過 import 的 svg 文件后將其生成類似于雪碧圖的形式,也就是 symbol, 通過配置中的 .options({ symbolId: 'icon-[name]' });可以使用 <use xlink:href="#symbolId" rel="external nofollow" /> 直接使用這個(gè) svg
- 添加完 svg-sprite-loader 后,由于 cli 默認(rèn)對(duì) svg 有處理,所以需要 exclude 指定文件夾的 svg。
- 使用時(shí)由于 svgIcon 組件的處理,只需要將 name 指定為文件名即可。
那么,我們使用 iconfont 和 svg 有什么關(guān)系呢?
iconfont 的使用方法有很多種,完全看個(gè)人喜好,但是其中一種使用方法,也是用到了 svg symbol 的原理,一般 iconfont 會(huì)默認(rèn)導(dǎo)出這些文件。
☁ iconfont [1.1.0] ⚡ tree -L 2 . ├── iconfont.css ├── iconfont.eot ├── iconfont.js ├── iconfont.svg ├── iconfont.ttf ├── iconfont.woff └── iconfont.woff2
我們關(guān)注于其中的 js 文件, 打開文件,可以看出這個(gè) js 文件將所有的 svg 已經(jīng)處理為了 svg symbol,并動(dòng)態(tài)插入到了 dom 節(jié)點(diǎn)當(dāng)中。
而 iconfont 生成的 symbolId 也符合我們 svg-icon 的 name 命名規(guī)則 所以我們?cè)陧?xiàng)目的入口文件中引入這個(gè) js 之后可以直接使用。
back-to-up
首先為什么會(huì)寫這個(gè)組件呢,本項(xiàng)目中使用的組件庫是 elementUI ,而 UI 庫中自帶 el-backtop,但是我能說不好用嗎? 或者說我太蠢了,在經(jīng)過一番努力的情況下我還是沒能使用成功,所以自己寫了一個(gè)。
直接上代碼:
<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 可以自行定義,返回的圖標(biāo)也可以自由替換。
注意,在 safari 中動(dòng)畫中動(dòng)畫表現(xiàn)不一致,使用 requestAnimationFrame 之后仍然不一致。希望同學(xué)們有時(shí)間可以自由發(fā)揮一下。
總結(jié)
永遠(yuǎn)抱著學(xué)習(xí)的心態(tài)去寫代碼,嘗試多種寫法,寫出你最優(yōu)雅的那一種。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
VuePress使用Algolia實(shí)現(xiàn)全文搜索
這篇文章主要為大家介紹了VuePress使用Algolia實(shí)現(xiàn)全文搜索示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07vue axios數(shù)據(jù)請(qǐng)求及vue中使用axios的方法
axios 是一個(gè)基于Promise 用于瀏覽器和 nodejs 的 HTTP 客戶端,在vue中數(shù)據(jù)請(qǐng)求需要先安裝axios。這篇文章主要介紹了vue axios數(shù)據(jù)請(qǐng)求及vue中使用axios的方法,需要的朋友可以參考下2018-09-09詳解Vue template 如何支持多個(gè)根結(jié)點(diǎn)
這篇文章主要介紹了詳解Vue template 如何支持多個(gè)根結(jié)點(diǎn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02某些場(chǎng)景下建議vue query代替pinia原理解析
這篇文章主要為大家介紹了某些場(chǎng)景下建議vue-query代替pinia原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02在vue中路由使用this.$router.go(-1)返回兩次問題
這篇文章主要介紹了在vue中路由使用this.$router.go(-1)返回兩次問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12使用vue ant design分頁以及表格分頁改為中文問題
這篇文章主要介紹了使用vue ant design分頁以及表格分頁改為中文問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2023-04-04vue2與vue3雙向數(shù)據(jù)綁定的區(qū)別說明
這篇文章主要介紹了vue2與vue3雙向數(shù)據(jù)綁定的區(qū)別說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04