一.Vue CLI
1. 安装CLI错误和ESLint规范
- 安装CLI错误可以试试:npm clean cache -force
- ESLint是js一个更严格的标准,如果不想使用可以在config/index.js中更改useEslint: false
- npm run build和npm run dev
2. runtime-compiler和tuntime-only的区别
- vue程序的执行过程:template – ast – render(渲染) – virtual dom – 真实dom
- 使用runtime-only更加高效
- 使用render函数会将createElement中的内容把挂载的内容替换掉
new Vue({
el: '#app',
// components: { App },
// template: '<App/>'
render: function (createElement) {
// eslint-disable-next-line spaced-comment
//1.普通用法:createElement('标签',{标签属性class:'box'},['hello world'])
// return createElement('h2',{class:'box'},['hello world']);
// return createElement('h2',
// {class:'box'},
// ['hello world',createElement('button',['按钮'])])
//2 传入组件对象
return createElement(App)
}
})
- runtime-only的vue文件中的template最终被编译成一个普通的对象,里面已经将template全部渲染成render函数了。vue文件中的template是由vue-template-compiler处理的。
- 总结:
如果在之后的开发中,你依然使用template,就需要选择runtime-compiler
如果之后你使用的是.vue文件开发,那么可以选择runtime-only
3.Vue CLI3创建项目和目录结构
- 使用Vue CLI3创建项目
- 初始化项目:vue create my-project
补充:如果想要删除自己的配置,可以去c盘找C:\Users\admin.vuerc,将自己的配置删掉即可。
- 目录结构详解
4.VueCLI3配置文件的查看和修改
- 修改配置文件的三种方式:
- 启动本地服务 vue ui 使用图形化界面修改配置
- 项目 – node-modules – @vue – cli-service – webpack.config.js
- 在项目的目录下创建自己的配置文件,命名是固定的:vue.config.js
- 箭头函数的使用和this指向
- 箭头函数的使用:
<script>
//箭头函数:也是一种定义函数的方式
//1. 定义函数的方式:function
const aaa = function(){
}
//2. 对象字面量中定义函数
const obj = {
bbb:function () {
},
bbb(){
}
}
//3. es6中的箭头函数
// const ccc = (参数列表) => {...}
//01 无参数无返回值
const ccc = () => {
}
//02 带参数和返回值
const sum = (num1,num2) => {
return num1 + num2
}
//03 放入一个参数
const power = num => { //一个参数可以省略括号
return num * num
}
//04 函数中代码数
//函数中有多行代码
const test = () => {
console.log('hello world');
console.log('hello paopao');
}
//函数中有只有一行代码
const mul = (num1,num2) => num1 * num2 //会把结果返回,可以省略return
// const demo = () => {console.log('hello demo');}
const demo = () => console.log('hello demo');
demo();
console.log(demo()); //undefined 函数没有返回值
</script>
- 箭头函数中this的使用:
<script>
//1 什么时候使用箭头函数,一个函数作为参数传入
// setTimeout(function () {
// console.log(this); //window
// },1000)
setTimeout(()=>{
},1000)
//结论:箭头函数中的this引用的就是最近作用域中的this
//2. 箭头函数中的this是如何查找的? --- 向外层作用域中,一层层查找this,直到有this的定义
const obj = {
//对象中定义函数,一般直接定义,不使用箭头函数
aaa() {
setTimeout(function(){
console.log(this); //window
}, 1000);
setTimeout(() => {
console.log(this); //obj对象
}, 1000);
}
}
obj.aaa()
</script>
二. 路由 Vue-Router
1. 路由
- 什么是路由?
路由就是通过互联的网络把信息从源地址传输到目的地在的活动。
- 路由提供了两种机制:路由和传送
路由是决定数据包从来源到目的地的路径
传送将输入端的数据转移到合适的输出端 - 路由表
路由表本质上就是一个映射表,决定了数据包的指向
- 前端渲染和后端渲染
-
01 网页发展阶段:前期
后端(服务端)渲染 jsp / php
后端路由:后端处理URL和页面的映射关系
-
02 前后端分离阶段
后端只负责提供数据,不负责任何界面的内容
前端渲染:浏览器中显示的网页中的大部分内容,都是由前端写的js代码在浏览器中执行,最终渲染出来的网页。
-
前端路由阶段
SPA页面:simple page web application,单页面富应用,整个网页只有一个html页面。
核心:改变url,但是页面不进行整体刷新。
2.url的hash和html5的history
- url的hash
URL 的hash也就是锚点(#),本质上是改变window.location的href属性
我们可以通过直接赋值location.hash来改变href,但是页面不发生刷新。 - html5的history模式
- history.pushState
我们可以在浏览器使用history.pushState({ },’ ‘,’…’)来改变链接,但是不刷新页面,它是一个栈结构,可以通过history.back( ) 【出栈】回到上一个链接。 - replaceState({ },’ ‘,’…’)同样可用来改变链接,但是它是无法回退上一个链接的,已经替换了链接。
- history.go
适用于history.pushState,因为我们需要回退。history.back()等价于history.go(-1),history.forward()等价于history.go(1),等同于浏览器界面的前进后退。
3. 认识vue-router
- 目前前端流行的三大框架,都有自己的路由实现,vue-router是vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。
- vue-router是基于路由和组件的
路由用来设定访问路径,将路径和组件映射起来
在vue-router的单页面应用中,页面的路径的改变就是组件的切换
01 安装和使用vue-router
- 之前vue cli3创建项目已经选择了加入路由,那么创建好的项目已经带有路由了。
- 配置:src–router–index.js
//配置路由相关信息
import VueRouter from 'vue-router'
import Vue from 'vue'
//1.通过vue.use(插件),安装插件
Vue.use(VueRouter)
const routes = [
//配置映射关系
]
//2.创建VueRouter对象
const router = new VueRouter({
//配置路径和组件之间的映射关系
routes // ES6增强写法,原:routes:routes
})
//3. 将router对象传入到vue实例中
export default router
在main.js中使用:默认引用会自己去找router文件夹下的index.js文件
02 路由映射配置
- 1.创建路由组件
- 2.配置路由映射:组件和路径的映射关系
const routes = [
//配置映射关系 --- 一个映射关系就是一个对象
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
},
]
- 3.使用路由,通过<router-link>和<router-view>(相当于占位,决定组件展示的位置)
- <router-link>是一个vue-router已经内置的组件,它会被渲染成一个<a>标签。
- <router-view>会根据当前的路径,动态渲染出不同的组件。
- 网页的其他内容,比如顶部的标题/导航,或者底部的一些版权信息等会和<router-view>处于同一个等级。
- 在路由切换的时候,切换的是<router-view>挂载的组件,其他内容不会发生改变。
03 路由的默认值和修改为history模式
- 默认情况下,应该进入网站的首页,我们希望<router-view>渲染首页的内容,而不是需要用户手动点击才显示。
如何让路径默认跳转到首页,并且<router-view>渲染首页组件呢?
- 我们需要多配置一个映射
path配置的是根路径:/
redirect是重定向,也就是我们将根路径重定向到/home的路径下
- 默认情况下,路径改变使用的URL的hash,如果希望使用HTML5的history模式,需配置mode。
const router = new VueRouter({
routes, // 映射关系
mode:'history' //默认是hash,改成html5的history模式
})
04 router-link的其他属性补充
- 默认<router-link>是渲染为<a>标签,我们可以使用target=" "指定想要渲染成为的标签。
- replace,默认是使用history.pushState,可以在浏览器前进或后退,如果不希望通过这种方式返回,就可以使用replace属性,这个时候必须点击<router-link>才能切换组件页面。
- active-class:处于活跃状态时,会自动加上一个router-link-class,我们可以使用这个class来添加样式,如果想要更改这个class名字,可以active-class:自定义类名,这是处于选中的组件链接的router-link-class就会变成你定义的class名字。
<router-link to="/home" tag="button" replace active-class="active">首页</router-link>
<router-link to="/about" tag="button" replace active-class="active">关于</router-link>
- 如果希望同时给很多个<router-link>都设置active-class,我们可以直接在创建路由对象时指定:linkActiveClass:‘active’
const router = new VueRouter({
...
linkActiveClass:'active'
})
但其实我们很少需要去修改一个类的名字。
05 通过代码跳转路由
我们可以不使用<router-link>来实现跳转,而是通过代码来实现:
<template>
<div id="app">
<button @click="homeClick">首页</button>
<button @click="aboutClick">关于</button>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
homeClick(){
//通过代码方式修改路径
// history.pushState() --- 不要这么写!会绕过vue-router
this.$router.push('/home') //所有组件都有$router属性
// this.$router.replace('/home') //replace不可返回
console.log("homeClick");
},
aboutClick(){
this.$router.push('/about')
// this.$router.replace('/about')
console.log("aboutClick");
}
},
}
</script>
- 虽然可以跳转,但是浏览器报错:解决方法
三. Vue-Router使用
1. 动态路由的使用
- 在某些情况下,一个页面的path路径可能是不确定的,eg:user/aaa,除了固定的链接之外,还希望在后面拼接用户的id,这种path和component的匹配关系,称为动态路由(也是路由传递数据的一种方式)
- 配置:
//在index.js中配置映射关系
{
path:'/user/:userId',
component:User
}
//在App.vue中我们在跳转路径处拼接用户id
<template>
<div id="app">
<h2>我是app组件</h2>
...
<router-link v-bind:to="'/user/'+userId">用户</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
userId:'zhangsan'
}
},
} ...
</script>
//上面主要是实现链接的拼接
//User.vue,跳转到用户界面后,我们希望在界面显示用户id
<template>
<div>
<h2>我是用户界面</h2>
<p>我是用户的相关信息,heihei</p>
<h2>{{userId}}</h2>
<h2>{{$route.params.userId}}</h2> //$route.params.userId可以直接获取
</div>
</template>
<script>
export default {
name:'User',
computed: {
userId(){
//写在这里必须加this.名称userId是index.js中自己设置的
return this.$route.params.userId; //返回处于活跃状态的路由
}
},
}
</script>
$router拿到的是我们在index.js中定义的router对象
$route是哪个路由处于活跃状态,获取的就是哪个
2. vue-router懒加载
- 路由的懒加载
- 懒加载:用到时才加载
- 官方解释:
当打包构建应用时,js包会变得非常大,影响页面加载
如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样会更加高效
- 打包文件的解析
- npm run build打包项目:打包文件含有.map文件
- 路由懒加载的使用
- 我们使用第三种方式来编写:
打包项目时会发现js文件夹中多了三个js文件,分别对应我们的三个组件,这样用户请求时就不用一次性加载所有组件,效率会提高。
3.嵌套路由的使用
- 实现步骤:
- 创建对应的子组件,并在路由映射中配置对应的子组件
//index.js
{
path:'/home',
// component:() => import("../components/Home")
component:Home,
children:[
{
path:'',
redirect:'news' //默认显示
},
{
path:'news', //不可以加/
component:HomeNews
},
{
path:'message',
component:HomeMessage
}
]
},
- 在组件内部使用<router-view>标签
//Home.vue
<template>
<div>
<h2>我是首页</h2>
<p>我是首页内容,哈哈哈</p>
<router-link to="/home/news">新闻</router-link>
<router-link to="/home/message">消息</router-link>
<router-view></router-view>
</div>
</template>
4.路由-参数传递
1.传递参数主要有两种类型:params和query
- params的类型:(之前动态路由的例子)
配置路由格式:/router/:id
传递的方式:在path后拼接对应的值
传递后形成的路径:/router/xxx- 可以使用$route.params.id获取对应的值
- query的类型:
配置路由格式:/router,普通配置
传递的方式:对象中使用query的key作为传递方式
传递后形成的路径:/router?id=xxx
//index.js 配置映射关系
{
path:'/profile',
component:Profile
}
01 直接在router-link中加入query参数
//App.vue
<router-link :to="{path:'/profile',query:{name:'paopao',age:18,height:1.88}}">档案</router-link>
02 不使用route-link,使用代码进行跳转
//App.vue
<button @click="userClick">用户</button>
<button @click="profileClick">档案</button>
//添加点击事件
userClick(){
this.$router.push('/user/'+this.userId)
},
profileClick(){
this.$router.push({
path:'/profile',
query:{
name:'paopao',
age:18,
height:1.88
}
})
}
query是一个对象,我们在路径后面跟上query查询的内容,并且同样我们可以在Profile.vue中获取传递的参数:
//Profile.vue
<template>
<div>
<h2>我是Profile组件</h2>
<h2>{{$route.query.name}}</h2>
<h2>{{$route.query.age}}</h2>
<h2>{{$route.query.height}}</h2>
</div>
</template>
- router和route的由来
- 区别:
* $router为VueRouter实例,想要导航到不同的URL,则使用$router.push方法。
* $route为当前router跳转对象,里面可以获取name,path,query,params等 - 它们都是vue的属性,所以我们可以到处使用它们,并且vue还有两个全局组件RouterView和RouterLink,所以我们才能使用这两个组件。
5.vue-router全局导航守卫
- 需求:根据不同的页面修改网页的标题
- 我们可以在每个组件中添加生命周期方法,修改title,但是这样每个组件的代码会显得冗余。
created() {
console.log('created');
document.title='用户'
},
- 使用全局导航守卫
- 导航钩子beforeEach 三个参数解析:
to:即将要进入的目标的路由对象
from:当前导航即将离开的路由对象
next:调用该方法后,才能进入下一个钩子
//index.js
//前置钩子
router.beforeEach((to,from,next) => {
//从from跳到to
document.title = to.matched[0].meta.title //实现点击子路由也显示标题
// console.log(to);
next() //下一步,必须调用
})
每一个组件都有自己的meta(元数据:描述数据的数据)和title,to可以获取meta,然后获取里面的title动态修改标题。
to是指去往的那个组件,我们可以给他添加一个meta属性
{
path:'/profile',
component:Profile,
meta:{
title:'档案'
},
}
- 补充:
- 后置钩子afterEach,不需要主动调用next()函数
//前置守卫(guard)
router.beforeEach((to,from,next) => {
//从from跳到to
document.title = to.matched[0].meta.title //实现点击子路由也显示标题
// console.log(to);
console.log('++++');
next() //下一步,必须调用
})
//后置钩子(hook)
router.afterEach((to,from) => {
console.log('-----------');
})
上面使用的导航守卫,被称之为全局守卫,还有其他一些类型的守卫:官网
* 路由独享的守卫
* 组件内的守卫
5.keep-alive
- keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
- router-view也是一个组件,如果直接被包在keep-alive里面,所有路径匹配到的视图组件都会被缓存。
//Home.vue
<script>
export default {
name:"Home",
data() {
return {
message:'你好啊',
path:'/home/news' //保存路径
}
},
created() {
console.log('created');
// document.title='首页'
},
destroyed() {
console.log('destroyed');
},
//1. activated和deactivated这两个函数。只有组件被保存了状态,使用了keep-alive时才有效
activated() {
console.log('activated');
this.$router.push(this.path) //会将上一次保存的path push进来
},
deactivated() {
console.log('deactivated');
},
//2. 记录上一次离开状态,使用组件内的导航守卫
beforeRouteLeave (to, from, next) {
console.log(this.$route.path);
this.path = this.$route.path;
next()
}
}
</script>
//App.vue
<keep-alive>
<router-view></router-view>
</keep-alive>
- keep-alive属性介绍
include - 字符串或正则表达式,只有匹配的组件会被缓存
exclude - 字符或正则表达式,任何匹配的组件都不会被缓存
四.Tabbar的封装案例
- 有一些路径名字不好找,我们可以给路径起别名:
//webpack.base.config.js
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'@': resolve('src'),
'assets': resolve('src/assets'),
'components': resolve('src/components'),
'views': resolve('src/views'),
}
},
- 把导航的每一个模块都单独封装成组件,并且配置它们的路由,我们可以根据path来跳转到对应的component。
//router/index.js
import Vue from 'vue'
import Router from 'vue-router'
const Home = () => import('../views/home/Home')
const Category = () => import('../views/category/Category')
const Cart = () => import('../views/cart/Cart')
const Profile = () => import('../views/profile/Profile')
//1. 安装插件
Vue.use(Router)
//2. 创建默认路由
const routes = [
{
path:'',
redirect:'/home'
},
{
path:'/home',
component:Home
},
{
path:'/category',
component:Category
},
{
path:'/cart',
component:Cart
},
{
path:'/profile',
component:Profile
}
]
//3. 导出router
export default new Router({
routes,
mode:'history'
})
- 这是用来承载四个导航组件的导航条,使用了插槽,这样我们可以动态控制加入几个导航组件,并且设置了导航条的样式。
<template>
<div id="tab-bar">
<slot></slot>
</div>
</template>
<script>
export default {
name:'TabBar'
}
</script>
<style scoped>
#tab-bar {
display: flex;
background-color: #f6f6f6;
position: fixed;
left: 0;
right: 0;
bottom: 0;
box-shadow: 0 -1px 1px rgba(100,100,100,.2);
}
</style>
- 这里是要放在导航条里的单独的导航模块,利用插槽实现我们可以动态添加文字和图标,需要注意的就是计算属性添加,true或者false的判断是根据当前路由是否为处于活跃的路由。
还有属性以及方法的添加,最好在插槽外面多套一个div,将方法和属性添加在div上,否则自己写的内容替换插槽的时候,会把设置在插槽上的属性替换掉。
<template>
<div class="tab-bar-item" @click="itemClick">
<div v-if="!isActive"><slot name="item-icon"></slot></div>
<div v-else><slot name="item-icon-active"></slot></div>
<div :style="activeStyle">
<!-- 会被自己传的内容替换,所以class不要直接加在插槽上,无效 -->
<slot name="item-text"></slot>
</div>
</div>
</template>
<script>
export default {
name:'TabBarItem',
props:{
path:String,
activeColor:{
type:String,
default:'black'
}
},
data() {
return {
// isActive:true
}
},
computed: {
isActive(){
return this.$route.path.indexOf(this.path) !==-1 //判断处于活跃的路由有无我item的path
},
activeStyle(){
return this.isActive ? {color:this.activeColor} : {}
}
},
methods: {
itemClick(){
this.$router.replace(this.path)
}
},
}
</script>
<style>
.tab-bar-item {
flex: 1;
text-align: center;
height: 49px;
font-size: 14px;
}
.tab-bar-item img {
height: 24px;
width: 24px;
margin-top: 3px;
margin-bottom: 2px;
vertical-align: middle; /* 去掉图片默认距离下3px */
}
</style>
- 最后我们把整个导航条封装成独立的组件,这样就可以直接在App.vue中使用模板,不会让App.vue中的代码太杂。
- 注意:使用别名找图标,因为是写在模板中的src,所以引用别名时前面还要加~
<template>
<tab-bar>
//使用别名找图标,因为是写在模板中的src,所以引用别名时前面还要加~
<tab-bar-item path="/home" activeColor="deeppink">
<img slot="item-icon" src="~assets/img/tabbar/shouye1.svg" alt="">
<img slot="item-icon-active" src="~assets/img/tabbar/shouye.svg" alt="">
<div slot="item-text">
首页
</div>
</tab-bar-item>
<tab-bar-item path="/category" activeColor="deeppink">
<img slot="item-icon" src="../assets/img/tabbar/category1.svg" alt="">
<img slot="item-icon-active" src="../assets/img/tabbar/category.svg" alt="">
<div slot="item-text">
分类
</div>
</tab-bar-item>
<tab-bar-item path="/cart" activeColor="deeppink">
<img slot="item-icon" src="../assets/img/tabbar/gouwuche1.svg" alt="">
<img slot="item-icon-active" src="../assets/img/tabbar/gouwuche.svg" alt="">
<div slot="item-text">
购物车
</div>
</tab-bar-item>
<tab-bar-item path="/profile" activeColor="deeppink">
<img slot="item-icon" src="../assets/img/tabbar/zhanghao1.svg" alt="">
<img slot="item-icon-active" src="../assets/img/tabbar/zhanghao.svg" alt="">
<div slot="item-text">
我的
</div>
</tab-bar-item>
</tab-bar>
</template>
<script>
//直接替换App.vue的位置
//import TabBar from './tabbar/TabBar'
//import TabBarItem from './tabbar/TabBarItem'
//引用别名的路径
import TabBar from 'components/tabbar/TabBar'
import TabBarItem from 'components/tabbar/TabBarItem'
export default {
name:'MainTabBar',
components:{
TabBar,
TabBarItem
}
}
</script>
<style>
</style>
- App.vue
<template>
<div id="app">
<router-view></router-view>
<main-tab-bar></main-tab-bar>
</div>
</template>
<script>
import MainTabBar from './components/MainTabBar'
export default {
name: 'App',
components:{
MainTabBar
}
}
</script>
<style>
@import "./assets/css/base.css";
</style>