VUE开发
快速原型开发
可以使用 vue serve 和 vue build 命令对单个 .vue 文件进行快速原型开发。但是需要先安装@vue/cli-service-global
插件。
npm install -g @vue/cli-service-global
新建一个.vue文件,然后启动一个服务并运行原型:
vue serve 文件名.vue
脚手架创建项目
vue create
vue create my-vue
选择Default ([Vue 2] babel, eslint)
就可以了。
如果想要自己配置,那么可以选择第三个:
默认还是选择了默认配置babel、linter,其它的根据自己需求选择。
enter下一步:(2.x版本和3.x版本的选择)
enter下一步进入EsLint配置:(很严格还是只是在命令行中给提示,建议选择默认的第一个)
enter下一步:(保存的时候做Lint还是提交的时候)
enter下一步:(配置文件的选择)
然后执行:
cd 项目名
yarn serve
然后浏览器中打开http://192.168.8.132:8080/
就可以看到欢迎界面。
项目文件说明:
vue ui
图形化项目管理:
vue ui
安装插件
Vue CLI 使用了一套基于插件的架构。插件可以修改 webpack 的内部配置,也可以向 vue-cli- service 注入命令。在项目创建的过程中,绝大部分列出的特性都是通过插件来实现的。
在一个已经被创建好的项目中安装一个插件,可以使用 vue add 命令。例如安装路由插件:
vue add router
(然后会提示你现在有代码变的操作)
输入y继续:
然后再输入y,插件添加成功。
(添加插件,可能会对项目进行破坏性地结构上的变更,甚至是文件内容的修改。例如查看main.js和App.vue。所以其实最好把安装之前的版本给备份一下。)
处理资源路径
当你在 JavaScript、CSS 或 *.vue 文件中使用相对路径 (必须以 . 开头) 引用一个静态资源时,该资源将被webpack处理。
转换规则:
-
如果 URL 是一个绝对路径 (例如 /images/foo.png ),它将会被保留不变。
<img alt="logo" src="/assets/pyy.png"> // 会在public文件夹中去找,名字不会被改动:webpack不会对public做任何操作 <img alt="pyy" src="https://www.baidu.com/xxx/pyy.png"> // 图片服务器中的地址访问
-
如果 URL 以 . 开头会作为一个相对模块请求被解释并基于文件系统相对路径。
// 会被webpack编译(打开检查器查看图片src,我们可以看到最终编译的结果是文件的hash) // 关于图片webpack做的操作: 图片都会有批量的处理,把它作为资源、模块来做处理,如果足够小,会把它变成url路径,做字符串的编码。如果足够大,会起一个合适的名字,放到一个合适的地方去。 <img alt="logo" src="./assets/pyy.png">
-
如果 URL 以 @ 开头会作为一个模块请求被解析。Vue CLI 默认会设置一个指向 src 的别名 @ 。
<img alt="logo" src="@/assets/pyy.png"> // @代表src根目录
-
扩展:如果 URL 以 ~ 开头会作为一个模块请求被解析。这意味着甚至可以引用 Node 模块中的资源(node_modules中的)
<img src="~xxx-npm-package/pyy.png">
那么何时使用public文件夹呢?
通过 webpack 的处理并获得如下好处:
-
脚本和样式表会被压缩且打包在一起,从而避免额外的网络请求。
-
文件丢失会直接在编译时报错,而不是到了用户端才产生 404 错误。
-
最终生成的文件名包含了内容哈希,因此不必担心浏览器会缓存它们的老版本。
使用public的场景:
-
需要在构建输出中指定一个固定的文件名字。
-
有非常多的图片,需要动态引用它们的路径。
-
需要考虑兼容性,使用script标签引入。
注意:
如果应用打包上传到服务器之后,没有部署在域名的根部,那么需要配置publicPath前缀:
在项目最外层,加上一个vue.config.js文件,文件中添加如下代码:
module.exports = {
// 判断当前运行环境 是生产环境还是开发环境
// 生产环境时加上/部署服务器中的文件夹名/, 开发环境时就不添加
publicPath: process.env.NODE_ENV === 'production'? '/部署服务器中的文件夹名/': '/'
}
预处理器
如果创建项目时没有选择需要的预处理器(Sass/Less/Stylus),则需手动安装相应loader。
# Sass
yarn add sass-loader node-sass
# Less
yarn add less-loader less
# Stylus
yarn add stylus-loader stylus
安装完成了,就可以使用了。
<style scoped lang='scss'>
$color: green;
h1 {
color: $color;
}
</style>
注意:当 <style>
标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素,其原理是通过使用PostCSS 来实现转换。
<template>
// 给标签添加自定义属性 data-v-f3edg9
<div class="green" data-v-f3edg9>hi</div>
</template>
<style>
// 标签添加的自定义属性和postcss来对应
.green[data-v-f3edg9] {
color: green;
}
</style>
扩展1:
如果我们很多组件都需要使用scss变量,那么怎么办呢,这个时候我们可以把公共的提出去。例如我们上面的$color: green变量给提出去。
在src下新建一个styles文件夹,然后添加一个common.scss文件,把$color: green剪切到这个文件中。
然后装style-resources-loader
插件。
yarn add style-resources-loader
然后在vue.config.js文件中,修改代码为:
const path = require('path')
function addStyleResource(rule) {
rule.use('style-resource')
.loader('style-resources-loader')
.options({
patterns: [
path.resolve(__dirname, './src/styles/common.scss'),
],
})
}
module.exports = {
publicPath: process.env.NODE_ENV === 'production'? '/部署服务器中的文件夹名/': '/',
chainWebpack: config => {
const types = ['vue-modules', 'vue', 'normal-modules', 'normal']
types.forEach(type => addStyleResource(config.module.rule('scss').oneOf(type)))
},
}
然后再重新启动项目。
在程序启动之初,先把这个文件加载了,然后其他文件就都可以使用了。
扩展2:
深度作用选择器: >>>
在非css预处理器中,使用 >>> 操作符可以使 scoped 样式中的一个选择器能够作用得“更深”,例如影响
子组件(必需要写在scoped 样式中)。
<style scoped>
#app >>> p {
color: green
}
</style>
在css预处理器无法正确解析 >>> 。我们使用 /deep/ 或 ::v-deep 操作符。
<style scoped lang="scss">
#app {
// /deep/ p{
// color: green;
// }
::v-deep p{
color: green;
}
}
</style>
路由
安装
如果之前创建项目没有选择router,那么需要安装路由插件。
vue add router
基础使用
在router/index.js,可以配置路由。
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
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.
// 使用webpack+vue提供的异步组件的方式实现懒加载
// 代码将来会分割出去成一个独立的chunk(分片的模式)
// 只有进入到这个路由,才会实时的去下载这个组件,因为打包的时候是单独打包出去的,这样加载时间会变短
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
路由出口
一般在App.vue中,使用路由的总出口 : <router-view></router-view>
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<!-- 路由的出口 -->
<router-view/>
</div>
</template>
动态路由匹配
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 Detail 组件,对于所有 ID 各不相同的商品,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:
{ path: '/detail/:id', component: Detail }
// router/index.js中添加:
{
// :id标示动态路由
path: '/detail/:id',
name: 'Detail',
component: () => import('../views/Detail.vue')
},
路由导航
语法:<router-link :to='' />
使用router-link可以进行路由导航,to是要导航到哪儿去,里面写的是路由的path。
使用:
<li v-for="item of goodsList" :key='item.id'>
<router-link :to='`/detail/${item.id}`'>
{{item.name}} - {{item.price | symbol("$")}}
</router-link>
</li>
获取:
<template>
<div>
<h1>商品详情页</h1>
<p>{{$route.params.id}}</p>
</div>
</template>
<script>
export default {
// 响应路由参数变化
// 如果不这样监听,那么在多个之间切换的时候,组件为了考虑性能,是不会进行销毁重建的,也就是说在下面mounted或者created都不会再触发
watch: {
$route: {
immediate: true,
// deep: true,
handler() {
console.log('通过id去获取详情');
}
}
},
mounted () {
console.log(this.$route.params.id);
},
created () {
console.log('发起请求');
},
}
</script>
通配符
{
// 会匹配所有路径
// 从上往下去匹配,当其他的都匹配不上,代表路由有误,这个时候就可以去加载我们写的404提示页面
// 一般情况下,都会放到最后
path: '*',
component: () => import('../views/404.vue')
}
嵌套路由
实际的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件。
在路由对象中添加children属性,值是一个数组对象。
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue'),
children: [
{
path: 'detail/:id',
name: 'Detail',
component: () => import('../views/Detail.vue')
}
]
},
改造about页面添加嵌套路由出口:
<template>
<div class="about">
<h1>about</h1>
<ul>
<li v-for="item of goodsList" :key='item.id'>
<router-link :to='`/about/detail/${item.id}`'>
{{item.name}} - {{item.price | symbol("$")}}
</router-link>
</li>
</ul>
<!-- 嵌套路由出口 -->
<router-view></router-view>
</div>
</template>
这个时候,(1)访问/detail/:id,就是单独的详情页面,(2)/about是商品列表,(3)/about/detail/:id是商品列表下还有商品详情。
路由跳转
语法:router.push(location, onComplete?, onAbort?)
// 字符串
router.push('/')
// 对象
router.push({ path: `/about/detail/${item.id}` })
// 命名的路由
router.push({
name: 'Detail', // 路由文件中,去匹配 我们书写的name值
params: { id: item.id } // 参数
})
// 带查询参数,最终会变成 /goods?price=100
router.push({ path: 'goods', query: { price: 100 }})
路由懒加载
路由组件的懒加载能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
把组件按组分块:
() => import(/* webpackChunkName: "group-about" */ "../views/About.vue")
路由进阶
路由守卫
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
全局守卫
router.beforeEach((to, from, next) => {
xxx代码
// to: Route: 即将要进入的目标 路由对象
// from: Route: 当前导航正要离开的路由
// next: Function: 一定要调用该方法来 resolve 这个钩子。
})
在router/index.js中,添加
// 全局守卫
// to:到哪儿去 from:从哪儿来 next:放行
router.beforeEach((to, from, next) => {
// 判断路由是否需要守卫
// meta数据
if (to.meta.auth) {
// 是否登录
if (window.isLogin) { // 假如在全局保存了一个变量isLogin,用来判断是否登陆
next() // 放行
} else {
next('/login?redirect='+to.fullPath) // 跳转到登陆,然后登陆成功之后重定向到想去的页面
}
} else {
next()
}
})
路由独享的守卫
和全局守卫beforeEach参数是一致的。
beforeEnter((to, from, next) => {
xxx代码
// to: Route: 即将要进入的目标 路由对象
// from: Route: 当前导航正要离开的路由
// next: Function: 一定要调用该方法来 resolve 这个钩子。
})
例如:
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
children: [
{
path: 'detail/:id',
name: 'Detail',
component: () => import('../views/Detail.vue')
}
],
beforeEnter(to, from, next) {
// 是否登录
if (window.isLogin) { // 假如在全局保存了一个变量isLogin,用来判断是否登陆
next() // 放行
} else {
next('/login?redirect=' + to.fullPath) // 跳转到登陆,然后登陆成功之后重定向到想去的 页面
}
}
},
组件内守卫
可以在路由组件内直接定义以下路由导航守卫(写在组件内的方法):
-
beforeRouteEnter
-
beforeRouteUpdate
-
beforeRouteLeave
// About.vue
beforeRouteEnter(to, from, next) {
if (window.isLogin) {
next();
} else {
next("/login?redirect=" + to.fullPath);
}
}
动态路由
通过router.addRoutes(routes)方式动态添加路由
例如很常见的要求用户必须登录,否则只能去登录页。
修改全局守卫为:
router.beforeEach((to, from, next) => {
// 已登录
if (window.isLogin) {
if (to.path === '/login') {
// 已登录没必要去登录页,重定向至首页
next('/')
} else {
// 去其他页放行
next()
}
} else {
// 没有登录
if (to.path === '/login') {
// 要去登录页就直接放行
next()
} else {
// 否则重定向到登录页
next('/login?redirect=' + to.fullPath)
}
}
})
修改Login.vue,当用户登录成功后动态添加/about
login() {
window.isLogin = true;
// 注意参数需要是数组对象,如果没有登录,那么就不会有这个路由
this.$router.addRoutes([{
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.
// 使用webpack+vue提供的异步组件的方式实现懒加载
// 代码将来会分割出去成一个独立的chunk(分片的模式)
// 只有进入到这个路由,才会实时的去下载这个组件,因为打包的时候是单独打包出去的,这样加载时间会变短
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"),
children: [
{
path: "detail/:id",
name: "Detail",
component: () => import("../views/Detail.vue")
}
],
beforeEnter(to, from, next) {
// 是否登录
if (window.isLogin) {
// 假如在全局保存了一个变量isLogin,用来判断是否登陆
next(); // 放行
} else {
next("/login?redirect=" + to.fullPath); // 跳转到登陆,然后登陆成功之后重定向到想去的 页面
}
}
}]);
// 使用的是带查询参数,所以是获取query
this.$router.push(this.$route.query.redirect);
}
然后,来思考一下,虽然做了权限认证,但是会遇到一个问题,当我刷新的时候,添加的路由没了,又要走登录流程,这个肯定是不合理的,那么怎么来解决呢?自己试一试呢。
组件缓存
keep-alive
利用keepalive做组件缓存,保留组件状态,提高执行效率。
例如:缓存about组件。
在App.vue中,改造代码:
<!-- 缓存组件 -->
<keep-alive include="about" max='5'>
<!-- 注意使用include或则exclude时,去匹配的是组件中的name值,不是路由中的name -->
<!-- include是要缓存的组件,exclude是不缓存的组件,如果是多个,name值之间逗号隔开,例如include="about,detail" -->
<!-- 属性max,是指需要缓存的最大数,如果超出了,就会把最老的缓存t出去,加上最新的缓存, 以达到资源的最优化 -->
<router-view/>
</keep-alive>
由于keep-alive的出现,就有两个特别的钩子:activated、deactivated。
在about组件中去监听:
// 被keep-alive缓存会触发
activated(){
console.log('activated');
},
deactivated(){
console.log('deactivated');
},