python全栈-vue框架
python全栈-vue框架
文章目录
- 前言
- 环境配置
- Vue基础知识
- v-html
- v-bind
- {{}}使用 JavaScript 表达式
- v-if
- v-show
- `v-if` vs. `v-show`
- v-for
- (item,index)
- v-on
- 函数传参
- 事件修饰符
- 数组更新
- v-model
- v-model的修饰符
- 计算属性
- 侦听器
- 动态设置标签属性值
- 生命周期函数
- 组件
- 创建组件
- 引入组件
- 组件的复用
- props 父传子
- 自定义事件 子传父
- 插槽
- 后备内容
- 具名插槽
- 作用域插槽
- KeepAlive
- 异步加载组件
- 处理边界情况
- 路由
- 起步
- router-view
- router-link页面跳转
- 动态匹配路由
- 嵌套路由
- 编程式导航
- 命名路由
- 重定向和别名
- HTML5 History 模式
- 路由进阶
- 导航守卫
- 路由元信息
- 路由懒加载
- vue项目练习
- Axios网络请求
- 网络请求的封装
- 跨域处理
- Vuex
- Vuex进阶
- Mutation
- Actions
- 插件
- 项目打包
vue是前端三大框架之一
前言
开发环境:Node
使用npm管理vue的插件
由于npm是国外的,使用cnpm速度更快,自己安装cnpm
在终端安装cnpm:npm install -g cnpm --registry=https://registry.npmmirror.com
构建工具webpack
环境配置
安装vue CLI工具:npm install -g @vue/cli
创建一个vue项目:vue create myproject
初始化的时候选择babel,还有一个含web的
linter不要勾选。然后回车。
不要保存配置future projects。如果想要保存配置需要命名,方便下一次安装。
- 运行vue项目:npm run serve
需要去pycharm里面找到刚刚创建的项目,然后打开package.json,找到scripts那个数据,找到serve一行,在最左边有一个运行。点击之后,就会在终端返回两个本地网址:一个是local,一个是network
访问local后面的网址就行,这个就是我们运行的项目了。是实时更新的,我们编写代码的时候,它会不断的重新执行,这样我们就可以看到我们编写的效果了。不用一直重新运行。
也算是个好消息吧,vue只是需要一个运行的平台,不一定要使用vscode。本人一直坚持的pycharm,还能接着使用。pycharm支持python,html,css,js,现在新增vue框架。
项目里面public不要管,img是存放图片的。别乱动。
剩下的都是配置文件,不用操心。
Vue基础知识
其实不用记笔记了,只需要去官网看文档就行。模板语法 | Vue.js
但是,总觉得还是有个笔记比较好。有关知识点的信息,就不写了。
我们需要在app.vue里面学习vue的知识
可以移除hello world.vue文件了,同时在div id是app的容器里面把容器内的内容清空。
现在我们可以在这个div容器里面编写我们的代码了。
在app.vue里面有三个部分,template,script,style
其中template就是我们的html模板部分,
所谓的模板语法就是template+js部分。
<template>
<div id="app">
<h3>模板语法</h3>
<p>{{ msg }}</p>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
msg: 'Hello World',
}
},
components: {}
}
</script>
就是我们的数据/文本,可以不写在html结构里面,我们在template里面使用两个花括号,在花括号内部写变量名。
然后在js里面,写一个data方法,在写一个返回值,返回值是以键值对的形式。
这样,template里面的msg就获得了js里面给他赋的值。
v-html
就是我们想要插入html标签,但是直接写进入,会被当成文本。需要给盛放标签的容器加一个属性v-html,然后这个标签就可以成功插入了
<p v-html="aaa"></p>
aaa:'<h2>Hello World</h2>'
在vue里面以“v-”开头的属性都是指令
v-bind
动态修改属性的值
<div v-bind:class="aaa">123456</div>
aaa:'box1'
在v-bind后面加上冒号,再跟着属性,任意属性都可以,这里是class,可以是其他的。
属性的值,必须是一个对象。比如aaa,它的值是box1.
好处:我们可以随时修改属性的值。在前端显示的时候,会动态修改。
下面就是前端显示的内容。上面是我们设计的vue框架。
<div class="box1">123456</div>
由于v-bind太常用了,官方优化了一下。
<div :class="aaa">123456</div>
在属性前面加冒号,就是动态属性了。可以不写v-bind了。
{{}}使用 JavaScript 表达式
在双层花括号里面,只能写单个结果的表达式。
比如aaa,或者aaa+1,它可以进行简单的计算,也可以盛放三目表达式,或一个js语句。前提是这个js语句只会返回一个结果。
判断的依据是,能不能单独放在return后面。
v-if
可以根据后面的属性值决定当前标签是否显示,就像display一样。
当v-if后面的对象的值为真就显示,对象的值是假就不显示,
<div :class="aaa" v-if="flag">123456</div>
data() {
return {
msg: 'Hello World',
aaa:'box1',
flag: false
}
},
还有一个细节,在js里面,每一个键值对之间要使用逗号隔开。必须有。
- v-if的标签后面还可以跟一个含v-else的标签。
当v-if的值为假,v-else的标签就会显示出来。当v-if的值为真,v-else就不会被显示。
一个 v-else
元素必须跟在一个 v-if
或者 v-else-if
元素后面,否则它将不会被识别。
<div :class="aaa" v-if="flag">123456</div>
<p v-else>654321</p>
<template>
与v-if
因为v-if的机制,可以隐藏或显示一个标签,或者容器。这个容器里面可以放很多内容。我们可以一次显示出来。
现在又想把容器里面的子标签。和这个容器是同一级别。
就是容器隐藏的时候,子标签都隐藏,当容器显示的时候,子标签要和容器是同级的。那么任何一个html标签都无法做到。
所以vue提供了template标签,这个标签也可以当成一个容器。里面可以嵌套很多子标签。当这个容器显示的时候,子标签和这个容器是同级的。
<h3>模板语法</h3>
<p>{{ msg }}</p>
<div :class="aaa" v-if="flag">123456</div>
<template v-else>
<p>a</p>
<p>b</p>
<p>c</p>
<p>d</p>
</template>
就是template里面的p标签和外面的div标签是同级的。在网页中的结构是同级的。
v-show
和v-if的效果是一样的,都是元素的显示和隐藏。
<div :class="aaa" v-show="flag">123456</div>
但是底层的原理是不一样的,v-if是把整个标签移除,或者添加标签。当移除标签的时候,在网页中是看不到这个标签的。
v-show是基于css的样式display的值。就是这个标签会在网页中显示。同时显示display这个属性。
v-if
vs. v-show
v-if
是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
v-if
也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
相比之下,v-show
简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display
属性会被切换。
总的来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show
较好;如果在运行时绑定条件很少改变,则 v-if
会更合适。
v-for
这是列表渲染。
首先,我们要拿到一个列表数据。比如result,值是一个列表,外面是中括号,里面的数据都是json格式的。以逗号隔开每个数据。
result:[
{
id: 1,
text:'AAAAAA'
},
{
id: 2,
text:'BBBBBB'
},
{
id: 3,
text:'CCCCCC'
}
]
然后是我们拿到这个数据生成列表。
<ul>
<li v-for="item in result">{{item.text}}</li>
</ul>
在v-for后面写一个迭代器。就是python里面的for循环迭代器。
这里是item从result里面拿数据。item代表的是数据项,这个数据项里面还有更小的数据,比如id,text。
后面的花括号,就是我们从列表项取出来的数据。如果直接写item,就是整个json格式的数据项展示在页面上。
如果是使用的item.text就是从数据项里面取出text数据的值。比如AAAAAA。
同样的,也可以拿数据项里面的id的值。页面上显示的是键值对的值。
一般来说,会有报错的。
<li v-for="item in result" :key="item.id">{{item.text}}</li>
与v-for搭配的是key属性。因为key属性保证了数据项的唯一。在网页渲染的时候保证不会出错。
key是一个动态属性,因为要随着下标的变化而变化。
(item,index)
在v-for的迭代器里面,item实际上是两个值。
<li v-for="(item,index) in result" :key="index">{{item.text}}--{{index}}</li>
index就是当前数据项的下标。从0开始。
因为整个下标也是唯一的,有些时候,这个下标也可以作为key。
v-on
事件触发的槽函数,需要在与data函数同级的,methods函数里面。
就是事件的槽函数必须在methods里面
<button v-on:click="btnclick"></button>
<p v-if="flag">看不见我</p>
methods:{
btnclick(){
this.flag = !this.flag;
}
}
通过取反操作,可以让一个标签显示或消失
所有data里面的元素,通过this使用
由于这个绑定指令的高频使用,官方也有一个简写方式@
<button @click="btnclick"></button>
就是@和’v-on:‘是等价的
函数传参
btnclick(e){
this.flag = !this.flag;
console.log(e)
}
就是一切函数默认有一个event对象作为参数。
通过这个方法可以直接获取我们点击的列表项。
<ul>
<li @click="liclick(item.text)" v-for="(item,index) in result">{{item.text}}</li>
</ul>
liclick(text){
console.log(text)
}
在调用函数的时候可以把列表项的参数传进去,比如这里可以把列表项的文本信息拿到。
<li @click="liclick(item.text,$event)" v-for="(item,index) in result">{{item.text}}</li>
liclick(text,e){
console.log(text,e)
}
既可以获取列表项的参数,同时还可以获取整个列表项。
事件修饰符
就是阻止浏览器默认事件,比如a标签的跳转行为。
<a @click.prevent href="http://www.baidu.com">点击跳转</a>
就是在事件触发后面加一个后缀。也可以再触发一个槽函数。既阻止了页面跳转,又可以触发函数
<a @click.prevent="btnclick" href="http://www.baidu.com">点击跳转</a>
下面是官方提供的其他修饰符。应该叫事件的附加条件。
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>
数组更新
btnclick(){
this.result.push({
id:1004,
text:'DDD'
})
},
我们可以动态的给之前创建的数组添加元素。
官方:Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
替换一个数组
变更方法,顾名思义,就是会对调用它们的原数组进行变更。相对地,也有一些不可变 (immutable) 方法,例如 filter()
,concat()
和 slice()
,这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的
- 所有可以改变原始数组的方法比如push,都可以使用。就是实时更新数组。
像是数组合并concat,他们返回的是一个新数组。原数组没有发生改变,就不会实时更新。但是我们通过替换的方式,把原数组替换。
btnclick(){
this.result = this.result.concat([{id:1005,text: 'EEE'}])
},
反正修改原数组才可以更新页面。
v-model
<form>
<input type="text" v-model='names'>
<p>{{names}}</p>
</form>
给输入框添加v-model指令。
后面写上变量名。这个变量名需要在data里面声明一下,就是给个空字符串占位。
我们在输入框输入的内容,可以在p标签里面显示。
常见的输入框,文本框都可以使用。
v-model的修饰符
因为v-model可以实时获取用户输入的内容,可能用户输入的内容很多。触发事件的频率太高,需要使用防抖操作。
官方提供了修饰符。
- lazy 当用户输入完内容,输入框失去焦点才获取内容。
<input v-model.lazy="msg" />
- number 用户输入自动转换为数字
<input v-model.number="age" />
- trim 自动去除用户输入内容中两端的空格
<input v-model.trim="msg" />
计算属性
<p>{{names.split('').reverse().join('')}}</p>
官方的本意是放一些简单的变量,方便我们使用。它确实可以在插值的时候放一些特别复杂的计算式。
但是官方不推荐使用特别复杂的计算式。推出了新概念,计算属性。
<p>{{names.split('').reverse().join('')}}</p>
<p>{{names1}}</p>
computed:{
names1(){
return this.names.split('').reverse().join('')
}
},
这两个p标签的效果是一样的。需要把之前的那一大段内容写在computed方法里面,这个方法和data同级。
也可以在methods里面构造这个方法,但是methods是事件。调用的时候需要有括号执行。
<p>{{names1()}}</p>
methods:{
names1(){
return this.names.split('').reverse().join('')
}
}
- 像这样调用一个函数也会获得和计算属性相同的结果
就是方法在执行的时候,会重新求值。消耗性能资源很多
侦听器
需要在与data同级的条件下,调用watch方法
在watch里面,你想监听哪个属性,就用哪个属性构建方法。
就是我们在data创建变量,在html中使用这个变量。如果想要监听这个变量的值,就需要在watch里面构建方法。
在watch构造的方法,有两个参数,第一个参数是新属性值,第二个参数是发生变化之前的属性值。
都可以打印出来,前面的参数是当前属性的值。后面的参数是变化之前的值
watch:{
names:function(newval,oldval){
console.log(newval,oldval);
}
},
这里监听的是之前创建的输入框里面的值,names。newval就是输入框内的文本。
动态设置标签属性值
<p :class="{'boxx':true}">aaaaa</p>
就是把class属性变成动态的。注意属性值的结构,一个花括号,然后是引号包住的属性值,再后面是一个冒号,冒号后面是布尔值。布尔值为真的时候,这个属性值就显示在页面上,也就是这个标签有这个属性。布尔值为假,就没有这个属性值
<p :class="['boxx','aaa1']">aaaaa</p>
可以接受一个列表作为class属性对象。就是一次性添加多个属性值。
可以是数组嵌套对象
<p :class="[{'boxx':true},'boxx','aaa1']">aaaaa</p>
生命周期函数
我们在组件的方法里面有四个常用的生命周期函数:
- created 页面创建
- mounted 页面渲染
- undated 页面更新
- destoryed 页面销毁
常用的是页面渲染mounted
页面渲染时自动执行该方法
组件
创建组件
在components目录里面创建以vue结尾的文件。
在新建文件的template里面创建一个div元素。这个div是这个组件的唯一根元素。不能有同级的div元素。
<script setup>
</script>
<template>
<div>
</div>
</template>
<style scoped>
</style>
引入组件
在主文件里面的script里面,写import 组件名 from 组件路径 这个是组件引入。
同时在主文件里面的components方法里面写组件名称,这一步的操作叫组件注入。
然后我们就可以在主文件的template标签里面,写<组件名/> 就可以把组件放入了
正常的编译器,你只需要把第三步实现,编译器会自动把组件引入和注入。编译器自动执行前两步操作。
组件的复用
一个组件可以在文件中重复使用。而且每个组件的互相独立,不会有冲突。
比如,我们在组件里面放一个按钮和p标签显示按钮的点击次数。
我们在文件里面添加三次这个组件。我们在点击每一个组件的按钮,只会增加按钮所在组件的点击次数。不会影响其他的组件点击次数。
<script>
export default {
data() {
return {
count: 0
}
}
}
</script>
<template>
<div>
<p>这是组件{{ count }}</p>
<button @click="count+=1">按钮</button>
</div>
</template>
data必须是函数,使用的变量count也必须是以函数的形式返回,才可以保住组件变量的唯一性。
data() {
count: 0 错了
}
不可以直接声明使用,必须是函数
props 父传子
组件之间进行数据交互的操作
就是文件给组件传参,还有组件给文件传值
<hello :num="1" />
在文件里面给组件传参。
<p>{{num}}</p>
props: {
num:{
type: Number,
default: 0
}
}
需要在组件里面的script标签里面,设置props方法与data同级。然后把文件里面传给组件的参数写一下,以对象的方式创建属性type指定参数的类型,default指定参数的默认值。然后这个num参数就可以在组件里面使用了。
我们给哪个组件传参,哪个组件里面的num就显示什么参数。没有传参的组件就显示default默认值
<hello :title="'你好'" />
传递字符串作为参数的时候,字符串需要使用引号包裹
- props验证规则
就是我们在组件的props方法里面给参数指定的type类型,一旦文件里面传参和组件参数的类型不一致会有报错
type的类型就是:Number数字,string字符串
自定义事件 子传父
组件往文件传参数
vue组件:
<button @click="sendclick">发送按钮</button>
<script>
export default {
data() {
return {
count: 0,
msg: "sendclicked",
}
},
methods: {
sendclick(){
this.$emit('onclick',this.msg) // 第一个参数是父级调用的方法名称,第二个参数是我们给父级的数据
}
}
}
</script>
注意methods里面的方法,this指针和$emit。第一个参数是父级调用的方法名称,第二个参数是我们给父级的数据
父级文件调用组件,并接收参数
<hello :title="'你好'" @onclick="onclick" />
methods:{
onclick(data){
this.names = data
}
},
在父级里面写触发事件,@后面的是组件给他的方法名,后面的值是在父组件里面自定义的方法。可以是同名的方式。
还需要在父级组件里面的methods接收参数,其中data就是从子级组件传过来的内容。这里是把传过来的内容赋值给父级的变量names了
插槽
就是把之前的组件引入的单标签<hello/>,变成闭合的双标签<hello></hello>
并且在这两个标签里面写其他的标签
在组件中使用slot标签,接收主文件传过去的html结构
<template>
<div>
<slot></slot>
<p>这是插槽</p>
</div>
</template>
这是主文件调用的时候。在组件里面写html标签。
<hello>
<div>显示文本</div>
</hello>
我们还可以动态设置插槽的内容
<hello>
<div>{{msg}}</div>
</hello>
msg变量是在主文件中声明的。不是在组件里面声明的
后备内容
<slot>没有传内容</slot>
就是组件里面的slot标签写个文本内容。如果父级文件调用的时候没有传东西过来,就显示这个内容在页面上。如果有内容传过来就显示内容。
具名插槽
在文件中引入组件的时候。需要使用template标签包裹内容。并在template里面使用属性v-slot,属性值是组件里面slot的name属性值。
因为组件里面可以有多个slot标签。还可以选择哪一个slot接收文件的内容。
<template v-slot:header>
<div>{{msg}}</div>
</template>
下面是插槽标签。可以使用多个slot标签。并且使用name属性。
<slot>没有传内容</slot>
<slot name="header">没有传内容1</slot>
<slot name="footer">没有传内容2</slot>
用这个方法就可以实现文件和组件的互通了
作用域插槽
也叫数据回传
- 在组件里面声明一个变量,通过slot标签传给文件。键ddd值demo
<slot :ddd="demo"></slot>
- 在文件中接收参数
<template v-slot="d11">{{d11.ddd}}</template>
KeepAlive
在使用component标签进行组件的切换的时候,如果一个组件有多个状态。我们在多次调用组件的时候,希望记录组件上一次使用的状态,比如打开的第二个标签等等。
默认是不保存组件的状态,需要在文件中给可能发生动态切换的组件外部套一层元素KeepAlive。
KeepAlive可以保存组件的状态。作为一种缓存。
<script>
export default {
data(){
return {
msg:0,
}
},
methods:{
btnclick(){
this.msg+=1
}
}
}
</script>
<template>
<div>
<p>Hello</p>
<button @click="btnclick">加数字</button>
<p>{{msg}}</p>
</div>
</template>
这是vue子组件。其中msg有一个数值。第一次打开的时候msg是0,但是我们点击按钮之后msg一定不是0.但是我们每次切换组件的时候,msg都会刷新,现在不想刷新了。
<template>
<div id="app">
<h3>动态切换</h3>
<button @click="btnclick">切换内容</button>
<component :is="sonvue"></component>
</div>
</template>
<script>
import Hello from "@/components/hello.vue";
import Hello1 from "@/components/hello1.vue";
export default {
name: 'App',
data() {
return {
sonvue:Hello,
}
},
components: {
Hello,
Hello1
},
methods:{
btnclick(){
if(this.sonvue===Hello){
this.sonvue=Hello1;
}else{
this.sonvue=Hello;
}
}
},
}
</script>
在父文件里面,需要引入import两个子组件。还有组件的components注入。以及一开始就给sonvue赋值指向哪个子组件。这里在data里面一开始给sonvue赋值hello组件。通过点击事件把sonvue重新赋值。
<keep-alive>
<component :is="sonvue"></component>
</keep-alive>
只需要给调用组件的标签套一个KeepAlive标签,就可以缓存子组件的状态了。
异步加载组件
如果我们设计了很多组件,在用户使用的时候,不一定一开始就要全部加载出来。比如上面的动态组件,在一个标签里面可以含有两个组件。默认加载第一个子组件,但是第二个子组件不一定会被用户使用。如果我们加载了第二个子组件可能造成性能浪费。
所以有一个新方法,只有用户使用到子组件的时候才会加载新的子组件。
// import Hello1 from "@/components/hello1.vue";
const Hello1 = () => import("@/components/hello1.vue");
上面是我们使用component默认加载子组件的方式。下面是异步加载子组件的方式。
下面这种方法只有组件被使用才加载。
处理边界情况
使用美元符号直接操作dom元素(不推荐)
通过$root直接获取主文件的变量
子组件通过$parent.msg可以直接获取父组件的变量值。
因为破坏了层级之间的关系,不推荐使用。
路由
由官方维护
在使用路由之前,都是通过a标签的超链接进行跳转。每一次跳转都需要重新加载新的页面
有了路由之后,页面不再发生跳转,只有视图的切换。用户使用的时候,速度更快。
现在官方文件的安装教程都是vue-router4,适配的是vue3.特别新。但是企业里面可能不会这么快的使用最新版本。所以需要切换到vuerouter3的文件。
- 项目里面安装
需要在终端里面输入cd 项目名,进入项目,然后在安装vue路由router
在项目里面直接使用npm install vue-router
安装路由
- 在vue文件里面自动安装
<script src="/path/to/vue.js"></script>
<script src="/path/to/vue-router.js"></script>
如果在一个模块化工程中使用它,必须要通过 Vue.use()
明确地安装路由功能:(在main.js里面)
import VueRouter from 'vue-router'
Vue.use(VueRouter)
起步
因为是优化页面跳转,所以在src里面创建一个pages目录,然后创建多个vue文件。
然后在main.js里面配置路由
- 实例化路由对象
const router = new VueRouter({
})
- 定义路由
// 引入页面
import home from './pages/home.vue'
import user from './pages/user.vue'
// 定义路由
const routes = [
{
path: '/',
component: home,
},
{
path: '/user',
component: user,
}
]
定义了两个页面,path是网页的路由,component是页面。
然后把两个路由添加到路由对象里面去。
const router = new VueRouter({
routes
})
- 挂载路由
new Vue({
router,
render: h => h(App),
}).$mount('#app')
把第一步创建的router对象,写入vue对象里面。
现在还无法显示页面,因为没有路由出口。
router-view
需要在app.vue里面添加路由入口。
因为app.vue是vue默认打开的vue页面。
router-view是其他页面加载的位置。
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
在浏览器输入http://localhost:8080/#/打开的就是首页
http://localhost:8080/#/user就是打开第二个页面。
router-link页面跳转
<template>
<div id="app">
<router-link to="/">首页</router-link>|
<router-link to="/user">用户中心</router-link>
<router-view></router-view>
</div>
</template>
router-link类似标签a超链接的跳转效果。不过这个是vue路由自带的,官方适配
to后面是url路径
动态匹配路由
识别用户参数,显示用户信息
因为是动态路由了,要改main.js的匹配规则
const routes = [
{
path: '/',
component: home,
},
{
path: '/user/:userid',
component: user,
}
]
我们先试着跳转一个用户页面
<router-link to="/user/1001">用户中心</router-link>
这里是直接通过路由跳转,跳转到一个userid是1001的用户。
<p>{{$route.params.userid}}</p>
我们在用户页面,获取信息。前面的两个单词都是前缀。后面的是从路由获取的参数userid
也可以在路由里面填写多个参数,path: ‘/user/:userid/:id’,
<router-link to="/user/1001/12">用户中心</router-link>
这里传递了两个参数,
<p>{{$route.params.userid}}</p>
<p>{{$route.params.id}}</p>
然后在用户页面接收了
嵌套路由
就是在路由里面嵌套路由
下面是一个新闻页面含有两个子页面运动和乒乓,以及盛放视图的标签view
<template>
<div>
<p>新闻</p>
<router-link to="/news/sports">体育</router-link>|
<router-link to="/news/pingpang">娱乐</router-link>
<router-view></router-view>
</div>
</template>
在main.js里面创建一个路由news,然后使用children后面跟一个数组。
{
path: '/news',
component: news,
children: [
{
path: 'Sports',
component: sports,
},
{
path: 'Pingpang',
component: pingpang,
}
]
}
编程式导航
用事件去跳转页面$router.push('/')
这里是跳转到首页
$router.replace.('/')
和push的效果是一样的
- push和replace的区别
push可以缓存页面,可以让浏览器前进后退。
replace是页面覆盖。
感觉页面有必要缓存就用push,没用就用replace
命名路由
就是把路由赋值给一个变量,然后访问这个变量
只需要在main.js里面的各种路由里面指定一个name属性
const routes = [
{
path: '/',
name: 'home',
component: home,
},
{
path: '/user/:userid/:id',
name: 'user',
component: user,
}
]
然后在routerlink里面把跳转方式改成name:路由变量
<template>
<div id="app">
<router-link :to="{name:'home'}">首页</router-link>|
<router-link :to="{name:'user',params:{userid:'101',id:'10'}}">用户中心</router-link>|
<router-link :to="{name:'news'}">新闻</router-link>
<router-view></router-view>
</div>
</template>
记住第二个用户跳转,里面的参数传递方式。
router.push({ name: 'user', params: { userId: 123 } })
这是通过push跳转
重定向和别名
- 重定向redirect
访问a页面,默认跳转b页面
还是在routes里面的路由,添加一个属性redirect,在这个属性后面写重定向的新页面。后续在访问的时候,自动打开redirect定义的路由
{
path: '/news',
name: 'news',
component: news,
redirect:'/news/Sports',
children: [
{
path: 'Sports',
name: 'Sports',
component: sports,
},
{
path: 'Pingpang',
name: 'Pingpang',
component: pingpang,
}
]
}
比如新闻页面,打开新闻应该默认跳转到具体的新闻页面。而不是空的新闻页面
- 别名alias
就是给一个页面添加多个路由
{
path: '/',
name: 'home',
alias: '/home',
component: home,
},
访问根路由是首页,访问home也是首页。
HTML5 History 模式
const router = new VueRouter({
mode: 'history',
})
需要在实例化路由的时候,添加mode参数
如果是history模式,在访问网页的时候,网址里面就没有#了。
还可以是hash,hash默认就是默认有#。
如果没有#,用户直接输入网址,有可能进不来。还要有其他的配置。
路由进阶
导航守卫
最常用的是前置守卫
下面一共有三个模式的守卫
一个前置,一个后置,一个解析守卫
前置守卫就是在跳转新页面之前进行一个验证,解析守卫和前置的效果是一样的。后置守卫是在页面跳转之后的验证。
由于后置守卫是在页面跳转之后,就没必要验证了,所以只有前两个参数,没有next
- 全局前置守卫
router.beforeEach((to, from, next) => {
// ...
})
每个守卫方法接收三个参数:
-
to: Route
: 即将要进入的目标 路由对象 -
from: Route
: 当前导航正要离开的路由 -
next 是否允许页面跳转,如果有这个参数,页面可以随意跳转,如果没有就不能跳转(老师让这么理解的
-
全局后置守卫
router.afterEach((to, from) => {
// ...
})
- 路由独享的守卫
可以在路由配置上直接定义 beforeEnter
守卫:
只给一个路由页面配置守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
- 组件内守卫
之前的几个守卫只能在路由配置的文件里面设置。
现在的是可以在某一个页面的vue组件里面设置守卫。
路由差不多就是页面,页面就是组件。
下面分别是进入,有参数更新,离开。他们都有三个参数,from,to,next
beforeRouteEnter
beforeRouteUpdate
(2.2 新增)beforeRouteLeave
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
路由元信息
定义路由的时候可以配置 meta
字段,然后在守卫里面可以验证信息,决定页面是否跳转。
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { islogin: true }
}
]
}
]
})
就是我们可以在定义路由的时候,设置meta属性,比如这里有一个属性islogin,他的属性值是true。
然后我们去设置一个守卫,这里就是给用户界面加一个meta验证,如果触发这个验证,就要有对应的方法去验证。然后进行页面跳转。
router.beforeEach((to, from, next) => {
// ...
console.log(from)
console.log(to)
if (to.meta.islogin === true) {
// islogin是需要进行操作,验证用户是否已经登录,不然打不开用户详情的页面
console.log('需要验证')
// 假设一个验证字段
const token1 = true
if (token1){ // 验证成功就进入用户详情页面
next()
}else {
next('/login') // 如果验证没通过,就去登录界面
}
}
next()
})
- 应用场景,订单页面,用户信息页面,都需要基于用户登录之后,才可以进入。不然都要跳转到登录界面,完成登录验证。
路由懒加载
就跟子页面一样,不是所有的内容一次性加载完。为了让用户体验更好,或者优化网页的性能,给用不上的页面设置成懒加载,只有用到的时候,才会加载。
就跟组件里面的异步加载组件一样。不要一次性加载完所有路由。
推荐是除了首页的路由,其他的路由页面都使用懒加载。
{
path: '/',
name: 'home',
alias: '/home',
component: home
},
{
path: '/user/:userid/:id',
name: 'user',
component:() => user,
meta: { islogin: true }
},
只需要component:user,在冒号和别名之间加上圆括号和小于等于号就行component:() => user,
主要是影响用户的第一次页面的加载速度
vue项目练习
到此为止,我们就可以在创建vue项目的时候把路由选上了,因为我们已经知道了路由的全部知识。
配置一个vue项目,首先要引入CLI工具,然后创建vue项目,最后是vue路由的配置
由于我们在创建项目的时候选上了路由,项目的结构发生了变化,首先是所有的路由配置,被提取到了router目录的index.js文件里面。
- 设计底部导航,需要抽象成一个组件,放在每个页面底部
在这个组件里面的style标签加一个scoped属性。我们在style里面设置的样式,只在这一个组件内生效。
- router-link的精准匹配exact
router-link在跳转页面的时候在浏览器会被翻译成a标签。我们点击这个标签的时候,vue会自动给a标签添加属性。
但是vue的机制有点问题,网址的根路由‘/’会一直含有被点击的属性。这个对我们设计样式很不合适。所以需要在router-link标签添加属性exact,只需要给根路由添加即可,就会精准匹配了。
<router-link exact to="/"></router-link>
<template>
<div>
<div class="navbox">
<ul>
<li><router-link exact to="/"><span class="iconfont icon-shouye"></span><br>首页</router-link></li>
<li><router-link to="/find"><span class="iconfont icon-faxian"></span><br>发现</router-link></li>
<li><router-link to="/car"><span class="iconfont icon-gouwuche"></span><br>购物车</router-link></li>
<li><router-link to="/dingdan"><span class="iconfont icon-dingdan"></span><br>订单</router-link></li>
<li><router-link to="/user"><span class="iconfont icon-geren"></span><br>我的</router-link></li>
</ul>
</div>
</div>
</template>
- 发现页的重定向
因为发现页面含有子页面,并且是三个子页面,他本身不显示。所以要用页面重定向
{
path: '/find',
name: 'find',
component: Find,
redirect: '/find/follow',
children: [
{
path: 'club',
name: 'club',
component:Club
},
{
path: 'cook',
name: 'cook',
component:Cook
},
{
path: 'follow',
name: 'follow',
component:Follow
}
]
}
因为不需要使用图标,顶部导航非常简单
<template>
<div>
<div class="navbox">
<div class="box">
<ul>
<li><router-link to="/find/follow">关注</router-link></li>
<li><router-link to="/find/cook">食谱</router-link></li>
<li><router-link to="/find/club">美食社</router-link></li>
</ul>
</div>
</div>
</div>
</template>
Axios网络请求
不仅适用于Vue,所有的前端框架都可以使用
Axios 是一个基于 promise 网络请求库,作用于node.js
和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http
模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。
- 安装方法
使用 npm:
npm install axios --save
和安装路由的方法一样,需要cd进入项目再安装
我们只需要一个页面测试网络请求,不需要使用路由,可以不安装。
还需要使用import导入axios
- get请求方式
mounted() {
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(response => {
// console.log(response.data);
this.lists = response.data;
})
.catch(err => {
console.error('Error:', err);
});
}
上面那个api失效了,反正有点小问题。使用下面这个吧https://jsonplaceholder.typicode.com/posts
这个api返回一堆数据,主要包含title和body
catch是网络请求失败返回的信息
- post请求
axios.post('https://jsonplaceholder.typicode.com/posts', {
title: 'foo',
body: 'bar',
userId: 1
})
.then(response => {
console.log(response.data);
})
.catch(err => {
console.error('Error:', err);
});
post请求需要在网址后面,跟着一个参数对象,比如这里面的title,body,userId,就是给服务器的参数键值对。
剩下的和get是一样的
- 参数拼接
一般的服务器会把post请求的网址当成字符串处理。我们需要在传参数的时候,把参数转换成字符串。
需要使用querystring,这个是nodejs自带的库。
const querystring = require('querystring');
axios.post('https://jsonplaceholder.typicode.com/posts', querystring.stringify({
title: 'foo',
body: 'bar',
userId: '1'
}))
.then(response => {
console.log(response.data);
})
.catch(err => {
console.error('Error:', err);
});
网络请求的封装
为了保护接口,都会对网络请求部分进行封装
- 拦截器
发送数据的时候,识别到网络的请求方式是post的时候,进行一些参数转换的操作
接收数据的时候,进行一个网络状态码的判断
- api
具体的api操作,设置一个api目录,里面盛放各种小操作和信息。
这里是设置了一个base的js文件,这个文件存放的是api方法
比如baseurl,就是网址的根路由。也就是域名。
然后是banner,就是我们在banner组件里面需要使用的api了。这里是banner的具体路由。后续在使用的时候,就是base.baseurl+base.banner
哦,把方法和api又独立出来了。一切api操作独立成文件。banner请求需要使用api.getbanner方法,这个方法的原理就是刚刚的路径拼接。
其他的api操作,比如登录,就是api.login。原理是一样的。
跨域处理
有两种解决方案:
- 后台解决
使用CORS解决。使用npm安装cors:npm install cors --save
然后在服务器里面添加app.use(cors()),就可以直接解决跨域问题
- 前端解决(只能是开发环境下跨域,用户使用的时候不可以
在项目里面添加vue.config.js文件,名称不要变。
然后去官网找devserver.proxy配置参考 | Vue CLI
module.exports = {
devServer: {
proxy: {
'/api': {
target: '<url>',
changeOrigin: true,
pathRewrite:{
'^/api':""
}
}
}
}
}
以后遇到了跨域就这样解决
Vuex
使用npm安装:npm install vuex --save
也要先进入项目。还需要导入
import Vuex from 'vuex'
Vue.use(Vuex)
还需要像路由一样,注入到底部的new Vue里面和router并列
Vuex 依赖 Promise (opens new window)。如果你支持的浏览器并没有实现 Promise (比如 IE),那么你可以使用一个 polyfill 的库
你可以通过 CDN 将其引入
<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script>
- 构建一个简单的vuex
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
state是状态值,mutations里面的increment是修改状态值的方法
在所有的组件里面都可以读取这个count的值了。方法是$store.state.count
对于整个项目来说,store就是一个全局属性。
通过store.commit(‘increment’) 就可以修改这个全局属性了
- 封装vuex
和路由一样,也要封装到一个独立的文件里面,把上面的代码都拿走。
创建一个store目录下面的js文件,通过export default store;把这个文件打出去
最后把这个js文件通过import导入main.js文件
现在知道了原理,在创建项目的时候,也可以勾选vuex了。
Vuex进阶
除了使用$store还要一个官方提供的方法mapState
首先使用import { mapState } from ‘vuex’ 引入
然后在computed里面创建
computed:{
...mapState(['count','name'])
}
就是三个点,然后参数是一个数组,数组的元素就是我们在上面创建的全局参数。
通过这个方法,我们在组件里面可以直接使用<p>{{ count }}-{{ name }}</p>
Mutation
是修改状态的,action是有异步的时候使用(可选项
mutation是修改状态的唯一方法,也就是store.commit加方法。this.$store.commit(‘increment’)
组件可以传参.在组件里面可以传一个参数给这个方法,然后可以让状态值的增加大小随着组件的参数而改变。只能改变大小
mutations: {
increment (state,num) {
state.count+=num
}
}
- 优化mutation的调用方式
在刚刚的mapState旁边增加一个mapMutations。
在methods里面…mapMutations([‘increment’,‘decrement’])
导入这两个方法。然后就可以使用this直接调用了。this.increment(10)
Actions
刚刚的mutation只能是同步操作,mutation不能有异步操作。所以actions来解决这个问题。
action可以有异步,也可以没有异步。
和mutation都需要在vuex文件里面。和mutation同级。第一个参数必须是context,就和mutation的state一样。
还有调用的时候,不是通过commit,而是dispatch调用
this.$store.dispatch(‘increment1’,10)
- 优化调用 mapActions
和前面的一样
插件
- 一个是awesome
是官方维护的插件,托管在GitHub上面。
vuejs/awesome-vue: 🎉 A curated list of awesome things related to Vue.js
- 一个是element ul
使用vue add element 就可以加载了。进入项目再加载
然后是选择,全引用和按需加载
组件 | Element
在官网中有一个完整组件列表和引入方式
就是把所有组件都引入,引入不是使用,不会占用资源
把这js文件列表复制,替换我们项目的element.js文件。想要哪个用哪个。
- 使用开关组件
组件 | Element
<el-switch
v-model="value"
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch>
<script>
export default {
data() {
return {
value: true
}
}
};
</script>
里面还有一个默认值,true就是开,false就是关
- 表格效果
组件 | Element
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="date" label="日期" width="180"></el-table-column>
<el-table-column prop="name" label="姓名" width="180"></el-table-column>
<el-table-column prop="address" label="地址"></el-table-column>
</el-table>
<script>
export default {
data() {
return {
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}]
}
}
}
</script>
项目打包
在我们运行项目的地方,下一行就是打包命令,直接运行,dist目录就是打包好的项目
默认打包,必须把项目放在服务器根目录
或者去vue的配置文件vue.config.js,使用下面的代码,下面的第一个是你想放在服务器的目录
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/my-project/'
: '/'
}
如果打算将项目部署到 https://<USERNAME>.github.io/<REPO>/
上 (即仓库地址为 https://github.com/<USERNAME>/<REPO>
),可将 publicPath
设为 "/<REPO>/"
。