Listentolife

简单就好

  • 首页
  • 标签
  • 分类
  • 归档

工作总结(4)

发表于 2019-05-06 | 分类于 JavaScript , Vue , 工作总结

这篇工作总结,会整理一些比较零散的开发内容。

Element-UI相关

el-scrollbar组件

在查ElementUI官方文档的时候,你会发现找不到滚动相关的组件,但实际上是有一个关于滚动相关的组件,只是没有文档。就是el-scrollbar组件。

这个组件可以在查官方文档页面元素的时候找到,我是因为官方文档左右独立滚动而发现的。我们可以查查它的源码。

源码中,在scrollbar/src下有main.js文件,这个文件中就有关于srollbar的实现源码:

1
2
3
4
5
6
7
8
9
10
11
12
props: {
native: Boolean, // 是否显示滚动条
wrapStyle: {}, // 外层容器el-scrollbar__wrap同层的style样式
wrapClass: {}, // 外层容器el-scrollbar__view同层的class样式
viewClass: {}, // 内层容器el-scrollbar__view同层的style样式
viewStyle: {}, // 内层容器el-scrollbar__view同层的class样式
noresize: Boolean, // 如果 container 尺寸不会发生变化,最好设置它可以优化性能
tag: {
type: String,
default: 'div'
}
},

上面就是我们使用el-scrollbar组件时可以传入的prop:native是用于判断是否显示滚动条,从源码的实现来看,滚动条是ElementUI自己实现的,也就是如果需要显示滚动条,可以考虑使用这个组件;noresize是用于判断是否需要监听尺寸变化事件,如果滚动内容的长度固定不变,可以设置为true以优化性能;tag是用于指定内层容器渲染的标签。知道这些,我们就可以知道怎么使用el-scrollbar组件了。

我们再看看ElementUI官网是怎么使用它的:以官网导航栏为例,在.el-scrollbar的父类.page-component上设定了高度为100%,然后在.el-scrollbar同层增加.page-component__nav类实现导航栏的样式,最后在内层容器内使用.side-nav再设定100%的高度。

我自己使用的过程中是在内层容器内使用一个类设定固定的高度,然后就可以实现滚动效果。

el-table 表头跟表格宽度不一致

我在开发中发现了这个问题,在网上也发现有很多人遇到这个问题,目前我使用的解决方案是:

1
2
3
4
5
6
7
8
/* 解决elementUI表头跟表格宽度不一致 */
body .el-table th.gutter {
display: table-cell!important;
}

body .el-table colgroup.gutter {
display: table-cell!important;
}

安卓跟iOS的坑

new Date()转换时间戳

在iOS的微信H5开发中,使用new Date('2019-5-4')会出现报错,究其原因是iOS下不能使用-,应该使用/,即new Date('2019/5/4')。

安卓非body标签无滚动条

在开发的过程中,我们发现在安卓系统中,页面中除了body之外其他标签都无法传滚动条,但是iOS系统就会有。这种情况下我们可以自己实现或借助一些UI框架实现滚动条。如果选择借助的话,可以考虑ElementUI的el-scrollbar组件(前面在分析源码的时候就有这样一个prop),也可以考虑better-scroll插件,通过配置来实现滚动条。

前端开发的占位图

其实开发中如果能考虑上占位图的话,对用户的使用体验是有很高的提升的。以往通过加载条来表示加载会让用户觉得加载时间很长,而使用占位图则会给用户一种页面逐渐加载完毕的效果。

网上有很多推荐的占位图实现方案,有给未加载数据的容器加占位图样式的,有在数据加载完毕前先画一些占位容器的。这里就不一一细讲,主要是找到一个觉得挺不错的插件可以方便占位图的实现的:vue-content-loader。

这个插件是通过实现SVG组件来完成占位图,看起来效果还不错,而且官方还给出一个页面可以通过拉拽,数据调整实现占位图组件。

另外,它还有一个React版本。虽然我还没有使用过,不知道效果如何,但是我觉得应该是很有尝试的必要的。

vue中的图片懒加载

除了占位图的加载优化,图片的懒加载也是前端开发中比较常见的加载优化点。在一个页面上,如果需要加载的图片很多,一般都会考虑优先加载第一版的图片,等用户滚动页面时,出现在窗口内的图片才会被加载。

这里可以使用vue-lazyload插件。这个插件使用起来非常简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// npm
$ npm i vue-lazyload -S

//main.js
// 引入
import VueLazyload from 'vue-lazyload'
// 注册
Vue.use(VueLazyload)
// 配置
Vue.use(VueLazyload, {
preLoad: 1.3,
error: 'dist/error.png',
loading: 'dist/loading.gif',
attempt: 1
})

// 例子
<ul>
<li v-for="img in list">
<img v-lazy="img.src" >
</li>
</ul>

以上是截取官方文档的部分代码。这样就可以完成简单的图片懒加载了。还有很多配置可以使用。

工作总结(3)

发表于 2019-05-04 | 分类于 JavaScript , Axios , 工作总结

上一篇整理了一些关于vuejs的内容,这篇来整理一些axios的内容。

axios请求状态拦截

axios是平时用的比较多的用于http请求的库,平时用的比较多的相信是axios.get(),axios.post(),拦截器axios.interceptors.request.use及axios.interceptors.response.use。其实axios还可以自定义很多配置(具体可以参考这里:http://www.axios-js.com/zh-cn/docs/#%E8%AF%B7%E6%B1%82%E9%85%8D%E7%BD%AE )。

我这里主要想说的是,axios在自定义配置上,可以自定义拦截的范围。下面是文档中给出的配置项validateStatus:

1
2
3
4
5
6
7
8
{
...
// `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 reject
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
...
}

通过这项配置,可以让axios判断请求返回的内容根据状态码应该返回resolve还是reject。这个配置可以搭配axios拦截器使用比较好。

axios请求取消

实际开发中会有这样的需求:用户在快速操作中会发出很多请求,但其实有些请求已经不需要了或者因为用户的来回切换出现不必要的重新请求。这种情况下就要考虑能否取消掉已经发出的请求。

在axios中就可以通过方法CancelToken.source创建cancel token进行请求取消。在vue开发中,你可以先声明一个canceltoken的状态,然后在不需要请求的地方执行source.cancel()。

演示代码来源:http://www.axios-js.com/zh-cn/docs/#%E5%8F%96%E6%B6%88

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});

axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})

// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');

工作总结(2)

发表于 2019-05-04 | 分类于 JavaScript , Vue , 工作总结

忙了一段时间,刚好有一堆可以总结的内容。这里我会先整理一下vuejs相关的内容。

关于vuejs的watch侦听器的用法

一般都知道vuejs的watch侦听器的用法,watch就是用来响应数据的变化的。数据的每一次变化,都会调用相应的侦听回调。比如:

1
2
3
4
5
6
7
8
// 官方演示代码的部分截取
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},

但是上面这种侦听器只能侦听数据变化,换句话说,数据的第一次赋值是不会触发这个侦听方法的。所以这里就需要用到immediate这个属性了

1
2
3
4
5
6
7
8
9
10
11
// 上面部分官方演示代码的修改
watch: {
question: {
// 设置immediate为true之后,该回调将会在侦听开始之后被立即调用
immediate: true,
handler: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
}
},

还有一种情况是,需要侦听的数据是一个Object类型,上面的回调是不会被触发的,因为Vue的侦听默认情况下是handler 只监听这个Object对象它的引用的变化,不能侦听到Object内部属性的添加或删减,所以需要用到deep这个属性:

1
2
3
4
5
6
7
8
9
10
// 上面部分官方演示代码的修改,假定question是一个Object类型的数据
watch: {
question: {
// 设置deep为true之后,就能做深度侦听
deep: true,
handler: function (newQuestion, oldQuestion) {
// ...
}
}
},

vue-router路由懒加载(组件按需引用)

关于vue-router路由懒加载,应该是一个比较常规的操作,路由懒加载可以让项目在运行时减少不必要的代码加载,或者按运行时不同的需求加载部分代码,这样可以减少加载时间。

一开始用的方案是webpack的require.ensure()实现按需加载。webpack的中文文档叫代码分割(参考:https://webpack.js.org/api/module-methods/#requireensure )。

1
require.ensure(dependencies: String[], callback: function(require), chunkName: String)

这个方法传入三个参数:dependencies传入的是依赖的集合,在执行回调函数之前会声明集合中所有的依赖;callback传入的是回调函数,当所有依赖加载完毕之后,webpack就会执行这个回调函数;chunkName是提供给这个特定的 require.ensure() 的 chunk 的名称。通过提供 require.ensure() 不同执行点相同的名称,我们可以保证所有的依赖都会一起放进相同的 文件束(bundle)。

这样,就可以在vue-router中实现按需加载了:

1
2
3
4
5
6
7
8
9
10
11
const Login = resolve => {
require.ensure([], () => {
resolve(require('./views/Login/index.vue'))
})
}

const routes = [{
path: '/login',
name: 'login',
component: Login
}]

vue-router官方文档也提供了一种方案,写法上更简单:

1
2
3
4
5
6
/* 下面是一种特殊注释,可以在打包时说明打包到什么异步块中 */
const routes = [{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "login" */ './views/Login/index.vue')
}]

Vue.use

我们都知道vue.use是用于注册安装插件的,一般用的比较多的地方是一些UI框架的按需引入:

1
2
3
4
5
6
import {
Upload,
Progress
} from 'element-ui';
Vue.use(Upload);
Vue.use(Progress);

这里其实我只是想说一下,vue.use其实是可以链式使用的,因为源码中,vue.use在执行完之后会return this,而this指向的是vue本身,所以是可以链式使用的:

1
2
3
4
5
import {
Upload,
Progress
} from 'element-ui';
Vue.use(Upload).use(Progress);

多个UI框架组件冲突

一般其实不会出现这种情况,但难不了会出现,需要有解决方案。比如在一个项目中,有一部分页面是公众号H5,而其他页面是手机端页面,他们因为需求不同导致需要用不同UI框架的同名组件,这种情况下,可以尝试在引入时使用不同的命名:

1
2
3
4
5
6
7
8
9
import {
Dialog
} from 'vant';
Vue.use(Dialog);

import {
Dialog as muDialog
} from 'muse-ui';
Vue.use(muDialog);

延伸一下:如果是一个文件中有多个需要引入的方法,但同时这些方法都放在同一个文件中,在引入时可以直接写成import { * as xxx } from './xxx.vue,使用时就可以通过xxx.xx形式来使用。

工作总结(1)

发表于 2019-03-13 | 分类于 JavaScript , Vue , 工作总结

今天来做点学习总结,这些学习总结都是从工作中整理来的,自然做了总结,就比较容易积累经验,提高以后的工作效率了。好吧,开始吧。

Vuejs中图片的引入问题

这里指的图片引入问题是这样的: 一般开发使用比较多vue-cli脚手架,然后我在开发过程中遇到的是在项目其他位置存放的图片,虽然引入的路径是正确的,但是无法显示到页面上。我在网上搜索了不少这样的问题,有些是在打包后发现的。

目前有几个方案是可行的:

  1. 把图片全部放到vue-cli脚手架生成的assets文件夹中,这个文件夹本身也放着vue-cli自带的图片。这个文件夹中的图片可以直接通过路径引入,而且能够正常的显示到页面上;

  2. 不改变图片的位置,直接通过import imageXX from 'path'的方法引入到文件中。这种方式可以通过变量来引用图片的路径,也是可以正常的显示到页面上。但是在css样式中就实现不了,可能要在模板上直接通过:style="background-image:..."来实现,比较麻烦;

  3. 同上方案,但是是通过require('path')的方法引入到文件中。问题一样,就是对于css的样式实现来说比较困难;

  4. 网上还有一种方案是在build/utils.js中找到ExtractTextPlugin位置在对象中加入这句publicPath: '../../'。这个方案没有试过,因为在vue-cli3中,文件结构有变化了,我猜测这个可能要写到vue.config.js中。以后有机会再测试。

关于手机号码的正则表达式

这个简单带过,这个正则表达式就是/^1(3|4|5|7|8)\d{9}$/。

不过,有个地方需要关注的,就是在使用reg.test(str)时,reg正则表达式不要带g修饰符。我们都知道,g是匹配全部可匹配结果,比如说:

1
2
3
4
5
let str = 'aaaaaaaa'
const reg1 = /a/
const reg2 = /a/g
str.match(reg1) // ["a", index: 0, input: "aaaaaaaa"]
str.match(reg2) // ["a", "a", "a", "a", "a", "a", "a", "a"]

不带g时,字符串从左到右匹配,只要匹配成功就停止匹配;带g时,同样的匹配方向,但是会一直匹配到整个字符串结束。
在使用reg.test(str)时,如果带修饰符g,在第一次验证时是从字符串第一位开始,但是如果有第二次验证时,就会从第一次验证结束的位置开始验证。这样就可能导致两次验证结果不同:

1
2
3
4
const reg=/\./g
reg.test(0.5) // true,第一次验证从字符串第一位开始
reg.test(0.5) // false,因为接着上一次的字符串位置查找,没找到正则匹配的内容,所以结果是false,false以后,index会自动归0
reg.test(0.5) // true

所以,在做reg.test(str)验证时,正则表达式最好不带修饰符g。

关于CSS背景图片的设置

这里要提的是,CSS背景图片是可以同时设置多张的,而且每张背景图片的样式都可以设置,但是我在开发中发现设置了repeat的背景图片会覆盖掉排在后面的图片,所以最好是把这张图片的顺序放在最后,参考如下:

1
2
3
background-image: url("bg_top.jpg"), url("bg_bottom.png"), url("bg_middle.jpg");
background-repeat: no-repeat, no-repeat, repeat;
background-position: top, bottom, center;

关于rem布局

rem布局其实还是挺不错的,可以动态的屏幕宽度来设置html的font-size,并且调整所所有元素的样式。可以参考网上关于网易rem布局的原理。

关于axios跟promise

这里留一个promise配合axios开发的参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// api
export const submitApi = function (url, params, xxx) {
return new Promise((resolve, reject) => {
axios.post(url, params, {
headers: {
XXX: xxx
}
}).then(res => {
resolve(res);
}).catch(error => {
reject(error);
})
});
}

// 调用
submitApi(url, params, xxx).then(res => {
if (res.status === 200) {
console.log(res.data)
}
}).catch(error => {
console.log('something wrong!')
});

先这些吧。

新的开始

发表于 2019-03-10 | 分类于 其他

最近家里来了新的小成员,所有事情都围着他团团转。这几天终于能放松一点点,想着为了以后在工作上能效率高一些,折腾了一下给电脑加个ssd,重装了系统,结果忘记备份写好的好几篇博客稿子……

都怪自己没有及时的安排发布,还好所有资料也就丢失了这几篇稿子,后面还可以找时间再补回来。

把自己的事情做好才是最重要的,整理好,准备迎接各种新的开始。

JavaScript鼠标事件中event的各种x/y属性梳理

发表于 2019-02-09 | 分类于 JavaScript , 杂谈

最近在敲代码的时候遇到关于JavaScript事件中求各种长宽,涉及到event中各种x/y,所以尝试做一些梳理。

这里说的event是指js中鼠标事件event的实例属性,而各种x/y就是指以下这些:

1
2
3
4
5
6
e.clientX/e.clientY
e.screenX/e.screenY
e.offsetX/offsetY
e.pageX/e.pageY
e.layerX/e.layerX
e.x/e.y

关于各种x/y属性的描述

在网上找了一些资料,对于这些属性都有描述。http://www.w3school.com.cn w3c给出了clientX/Y跟screenX/Y的描述,不过为了统一,我选择了阮一峰的一份JavaScript教程(https://wangdoc.com/javascript/events/mouse.html) 中的描述:

1
2
3
4
5
6
7
8
9
10
11
MouseEvent.clientX属性返回鼠标位置相对于浏览器窗口左上角的水平坐标(单位像素)
MouseEvent.clientY属性返回垂直坐标。这两个属性都是只读属性。

MouseEvent.screenX属性返回鼠标位置相对于屏幕左上角的水平坐标(单位像素),
MouseEvent.screenY属性返回垂直坐标。这两个属性都是只读属性。

MouseEvent.offsetX属性返回鼠标位置与目标节点左侧的padding边缘的水平距离(单位像素)
MouseEvent.offsetY属性返回与目标节点上方的padding边缘的垂直距离。这两个属性都是只读属性。

MouseEvent.pageX属性返回鼠标位置与文档左侧边缘的距离(单位像素),
MouseEvent.pageY属性返回与文档上侧边缘的距离(单位像素)。它们的返回值都包括文档不可见的部分。这两个属性都是只读。

layerX/e.layerX跟x/y则没有找到比较权威的描述。

这些描述看完还是有点稀里糊涂的,不过可以看出,这些属性的值都是相对的,是有参考坐标的,比如浏览器,屏幕,目标节点,文档等。所以我们就试试在代码中获得更深入的理解。

代码实现实例属性

这里尝试做一个简单的演示项目,body中如下结构:

1
2
3
4
<div class="canvas-wrapper">
<div id="container"></div>
<canvas id="canvas" height="2000" width="2000"></canvas>
</div>

div#container元素是作为鼠标事件的目标节点,而canvas画布则是用于绘出点击事件的坐标跟垂直线。

1
2
3
4
5
* {
margin: 0px;
padding: 0px;
box-sizing: border-box;
}

由于这几个实例属性中有一些是涉及页面可视,所以给div#container元素绝对定位,脱离文档流;为了页面可以上下左右滚动,所以body跟canvas元素的尺寸都很大。
而且为了减少盒模型的影响,所以设置了box-sizing: border-box;

js逻辑部分,给div#container添加鼠标点击事件,回调函数中返回event,把event中的这些关于x/y的实例属性通过new传入一个位置构造函数的实例中,通过实例来绘出点击点跟各个x/y属性的线。

x/y各属性的表现梳理

代码中各x/y属性的演示

这张截图是在浏览器向左向下滚动后(浏览器的上边贴着绿色线,浏览器的左边贴着绿色线),点击(点击点中心为黑点中心)触发事件得到的绘图。

clientX/clientY & x/y

clientX/clientY在点击事件触发时是等于点击点到浏览器的上边跟左边,说明 clientX/clientY的参考坐标轴是沿着浏览器的上边为x轴,浏览器的左边为y轴 。跟目标节点的border,margin跟padding没有关系。
相同的x/y在表现上也一样。不过最好使用的时候使用前者。

layerX/layerY

layerX/layerY在点击事件触发之后显示出来是点击点到目标节点的边界,而且是在目标节点的边框上。目标节点的样式是有添加border跟padding样式的,而这个属性绘出的线跟目标节点的边框相交但不超出,说明 layerX/layerY的参考坐标轴是沿着目标节点的上边界(有边框则是沿着上边框)为x轴,目标节点的左边界(有边框则是沿着左边框)为y轴 。跟目标节点的margin,页面的可视范围没有关系。

offsetX/offsetY

offsetX/offsetY跟layerX/layerY很接近,但是它跟目标节点的边框没有相交,也就是当目标节点没有边框的情况下,offsetX/offsetY跟layerX/layerY的值是相等的。offsetX/offsetY的参考坐标轴是沿着目标节点的上边界为x轴,目标节点的左边界为y轴 。跟目标节点的margin,页面的可视范围没有关系。

pageX/pageY

pageX/pageY在点击事件触发之后显示出来是点击点到文档页面的边界,及时是页面发生了滚动,这个属性仍然显示的是点击点在文档页面的相对位置。所以 pageX/pageY的参考坐标轴是沿着文档页面的上边界为x轴,文档页面的左边界为y轴 。跟目标节点,页面的可视范围没有关系。

screenX/screenY

screenX/screenY代码中没有绘制,原因在于这个属性的坐标是整个屏幕,跟浏览器无关,所以无法绘制展示出来。

上述就是鼠标事件event中关于x/y属性的梳理。文中的代码已上传github:
https://github.com/listentolife/mouseEventPositionAttributes

Vue.js keep-alive组件

发表于 2019-01-16 | 分类于 JavaScript , Vue

有时候会觉得学习vue像在寻宝一样。因为vue中有很多很有用的组件,像之前写的关于插槽组件的那篇一样,我也是认为插槽组件很有用,可以更好的实现组件复用。这篇主要整理keep-alive组件的内容。

keep-alive组件是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。

keep-alive组件可以使用到动态组件<component>,也可以使用到路由组件<router-view>上:

1
2
3
4
5
6
7
8
9
// 使用到动态组件上
<keep-alive>
<compunent :is="view"></component>
</keep-alive>

// 使用到路由组件上
<keep-alive>
<router-view></router-view>
</keep-alive>

keep-alive组件内可以由多个子组件,但是要求只能同时渲染一个子组件,也就是多子组件时,可以使用v-if,v-else,但不支持v-for。

它有三个props:include,exclude,max。

include

include支持组件名匹配字符串或正则表达式的组件被缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<keep-alive include="book">
<!-- 将缓存name为book的组件,name为movie的组件将不会被缓存 -->
<component></component>
</keep-alive>

<keep-alive include="a,b">
<!-- 将缓存name为a或者b的组件,结合动态组件使用 -->
<component :is="view"></component>
</keep-alive>

<!-- 使用正则表达式,需使用v-bind -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>

<!-- 动态匹配判断是否缓存 -->
<keep-alive :include="['books', 'movies'] ">
<router-view></router-view>
</keep-alive>

exclude

exclude跟include刚好相反,组件名不匹配字符串或正则表达式的组件才会被缓存。用法跟include一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<keep-alive exclude="book">
<!-- 除name为book的组件之外其他组件都会被缓存,如name为movie的组件 -->
<component></component>
</keep-alive>

<keep-alive exclude="a,b">
<!-- 将不缓存name为a或者b的组件 -->
<component :is="view"></component>
</keep-alive>

<!-- 使用正则表达式,需使用v-bind -->
<keep-alive :exclude="/a|b/">
<component :is="view"></component>
</keep-alive>

<!-- 动态匹配判断是否缓存 -->
<keep-alive :exclude="['books', 'movies'] ">
<router-view></router-view>
</keep-alive>

max

max表示最多可以缓存多少个组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。

1
2
3
4
<!-- 最多只能缓存10个组件实例,而且是最新被访问的组件实例才会被缓存 -->
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive>

Vue Router学习(二)

发表于 2019-01-16 | 分类于 JavaScript , Vue

这篇整理一下vue-router的两个部分:编程式导航及路由传值。

编程式导航

编程式导航其实是跟声明式导航相对的:

1
2
3
4
5
6
7
// 声明式路由导航
<router-link to='/books'>To Books List</router-link>

// 编程式路由导航
router.push({
path: '/books'
})

两种方式都可以实现路由导航,使用<router-link>相当于调用了router.push。不过编程式导航,还有router.replace(),router.go(),router.back()及router.forward()方法可以调用。

router.push()&router.replace()

router.push()和router.replace()传参都是一样,分别是必须传值的location和可选的onComplete,onAbort回调。

location传入的参数可以是字符串,直接指向路由地址,也可以是描述地址的对象,还可以是路由对象及带查询参数的对象:

1
2
3
4
5
6
7
8
9
10
11
12
/* 官方文档的代码*/
// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

下面是实际项目中对编程式导航的实现及router.push()的运用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 项目中的实现 */
<!-- 监听点击事件,再使用编程式导航到新路由 -->
<li
v-for="(movie,index) in movies"
:key="index"
class="movie-list"
@click="selectMovie(movie)"
>...</li>

// 这里使用的this.$router等同于router对象
// 上一篇中说到在各个组件中都可以调用到this.$router
selectMovie (movie) {
this.$router.push({
name: 'movie-item',
params: {
id: movie.title,
movie: movie
}
})
}

router.push()实际上是在history栈添加一个新的记录,也就是用户点击浏览器后退时,是可以返回到上一个路由的。

官方文档中提醒了一句:如果提供了path,params会被忽略。如果要传入无法通过路径传入的值,或者传入值较多,建议使用parmas。

可选的onComplete,onAbort回调主要用在导航完成之后及导航终止的时候进行调用。目前我还没有看到网上有什么人有讲到这部分,所以了解就好,以后再深入。

router.replace()跟router.push()有一个很大的区别,在于它不是在history栈添加一个新的记录,而是替换当前的记录,也就是无法返回上一个路由记录。其他使用方面都是一样的。

router.go(),router.back()及router.forward()

router.go()的参数是一个整数,表示在浏览器记录中向前或向后几步。如果是向前一步,就是router.go(1),即router.back();如果是向后一步,就是router.go(-1),即router.forward()。

路由传值

其实前面的篇幅中,多少已经涉及到路由传值了。路由传值主要是在<router-link>的to属性中跟router.push()中使用,传值也分params跟query。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用动态路由时,在to属性中使用params对象传值
<router-link
class="music-list"
tag="li"
:to="{name:'music-item',params:{id:music.title,music:music}}"
v-for="music in musics"
:key="music.title"
>...</router-link>

// 使用编程式导航时,直接在location参数中写入params传值
// query的写法一样
selectMovie (movie) {
this.$router.push({
name: 'movie-item',
params: {
id: movie.title,
movie: movie
}
})
}

上面有提到,如果提供了path,params会被忽略,所以一般使用params时,就需要传入name指明路由名,如果使用query时,就可以直接传入路由路径。

传值在组件内可以通过this.$route.params及this.$route.query获得。

使用path,params时有一点需要注意,使用path传值,刷新页面参数会丢失;使用params查询参数,刷新页面参数就不会丢失。

vue-router的这两篇内容算入门内容,简单做整理,加深理解。vue-router的其他内容也会在深入学习使用之后再整理出来。

Vue Router学习(一)

发表于 2019-01-10 | 分类于 JavaScript , Vue

最近在尝试用Vue.js来重构我之前用react写的豆瓣项目了,感觉vue-router这块的内容还是挺多的,在项目的重构中也看了官方文档跟上网其他人的博客,也决定自己总结一下相关的一些内容,把自己在项目中遇到的点做一些整理。这个估计一次性也是说不完的,所以也分几篇来整理了。

以下使用的一些示例代码是来源于我的豆瓣项目:https://github.com/listentolife/Vue-doubanPocket

这里先整理动态路由跟嵌套路由。

基础路由

首先,vue-router是vue.js的官方路由管理器,功能在官方文档中有列表介绍,我会根据开发中对vue-router使用的程度一点点整理。

最基础的路由使用,官方给出了完整的操作,包括JavaScript的五步操作:引入调用vue-router,定义组件,定义路由,创建配置router实例,创建和挂载根实例。

JavaScript操作

实际操作中,我在项目的src目录下会创建一个router目录,在这里完成上面JavaScript的前四步操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Book from 'components/book/book'
import Movie from 'components/movie/movie'

// 注册Router
Vue.use(Router)

export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
redirect: '/book'
},
{
path: '/book',
name: 'book',
component: Book
},
{
path: '/movie',
name: 'movie',
component: Movie
}
]
})

然后在根实例上挂载:

1
2
3
4
5
6
7
8
9
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'

new Vue({
router,
render: h => h(App)
}).$mount('#app')

router组件操作

然后就可以使用<router-link>导航组件跟<router-view>组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// components/tag.vue
/* 在router-link导航,
* tag指定组件会被渲染成什么标签,默认为a标签
* to传入的是指定的链接,也可以是一个对象,这个后面再细说
*/
<router-link class="tab-item" tag="div" to="/book">
<span class="tab-link">图书</span>
</router-link>
<router-link class="tab-item" tag="div" to="/movie">
<span class="tab-link">电影</span>
</router-link>

// 路由匹配到的组件会渲染在这里
<router-view></router-view>

this.$router和this.$route

这样就可以完成最简单的路由。“我们可以在任何组件内通过this.$router访问路由器,也可以通过this.$route访问当前路由”。

上面这段引号的话是需要注意的。通过this.$router是访问路由器,通过this.$route是访问当前路由。

一开始我也不是很明白,就在项目中写了一段console.log,尝试打印一下这两个:

this.$router & this.route

这样看就比较明白了:this.$router指向的是router目录中创建的路由实例,this.$route指向的是当前渲染的路由对象。

通过this.$route跟this.$router,我们有一些常用的方法可以调用,这里就属于编程式导航中会提及的内容,等后面再整理了。

动态路由

上面的例子是比较简单的路由实现,但实际上很多时候是没办法把路由路径写死的,比较用户页面,比如我的项目中每个图书页面都是一样的,但是图书是各不相同的。这个时候就需要用到动态路由来实现。

实现动态路由,需要以下几步(基于上面的例子):

1
2
3
4
5
6
7
// router/index.js
// 先在路由器上配置路由
{
path: '/musicDetail/:id',
name: 'music-item',
component: MusicItem
}

/musicDetail/:id这个路径包含了一个路径参数id,前面使用:标记。这个路径参数会被设置到this.$route.params中,可以被组件使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
// music.vue
// 使用动态路由,就需要用对象来传值给to
// 路由渲染后,地址上也是显示/musicDetail/(music.title)
<router-link
class="music-list"
tag="li"
:to="{name:'music-item',params:{id:music.title,music:music}}"
v-for="music in musics"
:key="music.title"
>...</router-link>

// 路由匹配到的组件会渲染在这里
<router-view></router-view>

在组件内,就可以利用this.$route.params可以获得父组件传入的值。由于在路由配置时路径参数只有id,所以只有id会显示在地址上,其他值不会显示在地址上,可以传入很多数据。

有几个地方需要留意的:

  1. 一个路由可以设置多段路径参数,但是一定要一一匹配上;
  2. 使用动态路由,匹配同一动态路径的路由之间导航,组件实例会被复用,组件的声明钩子将不会再被调用。可以使用watch或导航守卫响应路由变化;
  3. 多个路由同时匹配同一路径时,最先定义的优先级越高。

嵌套路由

嵌套路由就是在路由内再嵌套路由组件,通过路由做跳转,跳转前后父路由组件不受影响。比如根路径‘/’下路由导航到‘/book’,渲染book组件,在book组件内有子路由‘/book/detail/:id’,路由跳转时,book组件内<router-view>之外不受影响。

子路由配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
// router/index.js
// 在'/book'路由下配置子路由
{
path: '/book',
name: 'book',
component: Book,
children: [{
// 当/book/:id匹配成功,就会在book组件内的<router-view>中渲染bookItem组件
path: ':id',
name: 'book-item',
component: BookItem
}]
},

子路由导航与出口:

1
2
3
4
5
6
7
8
9
10
11
12
13
// book.vue
// 写法跟动态路由中的差不多
<router-link
class="book-list"
tag="li"
:to="{name:'book-item',params:{id:book.title,book:book}}"
v-for="book in books"
:key="book.title"
>...</router-link>

// 路由匹配到的组件会渲染在这里
// 注意,这个<router-view>是写在book组件中的
<router-view></router-view>

嵌套路由也有需要注意的:上面子路由配置中,path的值是 ‘:id’ ,不是 ‘/:id’ 。如果路径前有/斜杠,则嵌套路径指向根路径上的。

以上就是Vue Router关于动态路由跟嵌套路由的内容整理。

Vue.js插槽组件

发表于 2018-12-19 | 分类于 JavaScript , Vue

这里写一下关于Vue.js插槽的内容。之前在学习vue.js的时候就觉得这个插槽非常有意思,非常神奇,在官方文档跟一些教程的学习后,觉得有必要单独记录一下,所以这里准备了这么一篇。

插槽是什么

官方文档是这么解释的:Vue 实现了一套内容分发的 API,这套 API 基于当前的 Web Components 规范草案,将 <slot> 元素作为承载分发内容的出口。

简单的说,插槽是vue.js的一套API,是用于实现内容分发的,使用<slot> 元素做接口。

从我个人的使用理解来说,就是子组件上留了一些口,这些口就是插槽,这些口可以提供给父组件进行自定义的插入内容,包括字符串,html标签,甚至其他组件。

没有使用插槽的情况

一般来说,在没有使用插槽的情况下,或者子组件没有留插槽的情况下,父组件上在组件标签之间码下的所有内容,都会被抛弃。这也是vue.js官方文档中明确提醒的。也就是说,没有插槽,父子组件之间只有通信的操作,父组件并不能控制在子组件内展示什么内容。

插槽的实现

实现插槽不难,理解插槽就好办了。上面说了,我理解的插槽就是在子组件上留一些口,父组件引用子组件时就可以在这些口上插入内容了。因此,先要在子组件上开口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- child-component -->
<div>
<h2>child component</h2>
<p>slot标签就是子组件留的口,下面的内容就是父组件通过插槽传给子组件的</p>
<slot><slot>
</div>

<!-- parent-component -->
<div>
<h1>parent component</h1>
<child-component>
这部分就是传入子组件slot标签的内容
<p>如果没有指定,所有父组件传入的内容都会通过这个插槽进入子组件</p>
<p>甚至下面的组件也可以被传入</p>
<icon-arrow-right></icon--arrow-right>
</child-component>
</div>

这样就可以实现简单的插槽功能了。但是插槽不只是这些。插槽还分具名插槽,默认插槽。

具名插槽&默认插槽

有时候子组件不止设置一个插槽,还会设置多个插槽。而在父组件上,只能在子组件标签之后传入插槽内容。所以需要一个区分的标识。就像子组件上有多个插槽,父组件要往这些插槽上分别放内容,怎么知道哪些内容放到哪个插槽呢?这就用到<slot>元素上的属性name了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!-- child-component -->
<div>
<h2>child component</h2>
<header>
<slot name="header"><slot>
</header>
<main>
<slot><slot>
</main>
<footer>
<slot name="footer"><slot>
</footer>
</div>

<!-- parent-component -->
<div>
<h1>parent component</h1>
<child-component>

<template slot="header">
具名插槽
</template>

<p>父组件的内容传入子组件插槽时,可以使用template标签,并使用slot标识对应插槽的位置。这样就可以正确的分发到组件的插槽中。</p>
<p>如果插槽未命名,那么父组件传入的为指定分发位置的内容都会统一放在未命名插槽中,这个插槽就是默认插槽。</p>

<p slot="footer">最后,slot也可以用到普通的元素中</p>

</child-component>
</div>

上面的例子中就实现了默认插槽,父组件所有未指定插槽的内容都会分发到默认插槽中。

默认内容&编译作用域

不过有时候父组件不一定会用上子组件的插槽。这种情况下子组件就可以指定默认的内容。如果父组件有传入内容,指定默认的内容就会被替换掉.

1
2
3
4
5
<main>
<slot>
<p>如果父组件没有给这个插槽传入内容,则在页面上能看到这段子组件默认的内容。</p>
<slot>
</main>

如果想在插槽中使用数据,需要注意作用域的问题:插槽中的内容所在的作用域是父组件的作用域,只能访问父组件作用域内的数据,不能访问子组件作用域内的数据。

1
2
3
<template slot="header">
{{ article.title }}
</template>

作用域插槽

虽然插槽所使用的数据只能是父组件的作用域,但是还是有办法使用子组件的数据的,就是作用域插槽。

作用域插槽就是把子组件需要在插槽内使用的数据作为插槽的prop传入,然后父组件在插槽内的元素或组件中使用slot-scope获取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- 引用官方文档的例子 -->
<!-- 子组件todo-list -->
<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
<!-- 我们为每个 todo 准备了一个插槽,-->
<!-- 将 `todo` 对象作为一个插槽的 prop 传入。-->
<slot v-bind:todo="todo">
<!-- 回退的内容 -->
{{ todo.text }}
</slot>
</li>
</ul>

<!-- 父组件中引用子组件部分 -->
<todo-list v-bind:todos="todos">
<!-- 将 `slotProps` 定义为插槽作用域的名字 -->
<template slot-scope="slotProps">
<!-- 为待办项自定义一个模板,-->
<!-- 通过 `slotProps` 定制每个待办项。-->
<span v-if="slotProps.todo.isComplete">✓</span>
{{ slotProps.todo.text }}
</template>
</todo-list>

这里确认一点:slot-scope不仅可以在<template>元素上使用,还可以在插槽内的任何元素或组件上使用。另外,官方文档还说明了,基于ES2015解构语法,上面父组件中<template slot-scope="slotProps">可以写成<template slot-scope="{ todo }">。

以上就是关于vue.js插槽的内容。

12345

Listentolife

Listentolife's Blog

49 日志
19 分类
61 标签
GitHub E-Mail
© 2020 Listentolife
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4
   |