文章目录
😹 作者: gh-xiaohe
😻 gh-xiaohe的博客
😽 觉得博主文章写的不错的话,希望大家三连(✌关注,✌点赞,✌评论),多多支持一下!!!
🚏 Vue中的ajax请求
🚀 解决开发环境 Ajax 跨域问题
总结:
🚬 准备好测试的服务器
server1.js
const express = require('express') const app = express() app.use((request,response,next)=>{ console.log('有人请求服务器1了'); // console.log('请求来自于',request.get('Host')); // console.log('请求的地址',request.url); next() }) app.get('/students',(request,response)=>{ const students = [ {id:'001',name:'tom',age:18}, {id:'002',name:'jerry',age:19}, {id:'003',name:'tony',age:120}, ] response.send(students) }) app.listen(5000,(err)=>{ if(!err) console.log('服务器1启动成功了,请求学生信息地址为:http://localhost:5000/students'); })
server2.js
const express = require('express') const app = express() app.use((request,response,next)=>{ console.log('有人请求服务器2了'); next() }) app.get('/cars',(request,response)=>{ const cars = [ {id:'001',name:'奔驰',price:199}, {id:'002',name:'马自达',price:109}, {id:'003',name:'捷达',price:120}, ] response.send(cars) }) app.listen(5001,(err)=>{ if(!err) console.log('服务器2启动成功了,请求汽车信息地址为:http://localhost:5001/cars'); })
启动
- node server1.js
- node server2.js
🚬 准备访问5000服务器 存在跨域问题
安装 axios
- npm i axios
- 引入 import axios from ‘axios’
app.vue
<template> <div> <button @click="getStudents">获取学生信息</button> </div> </template> <script> import axios from 'axios' export default { name:'App', methods: { getStudents(){ axios.get('http://localhost:5000/students').then( // 成功 response => { console.log('请求成功了',response.data) }, // 失败 error => { console.log('请求失败了',error.message) } ) } }, } </script>
解决跨域
- cors 不用前端人员做任何的事情
- 写服务器(后端人员)的人,在服务器里面,给你返回响应的时候,加几个特殊的响应头
- 不是随便的配置的,造成的后果,任何人都可以找你这台服务器要数据
- jsonp 开发时使用微乎其微
- 借助了 srcipt 便签里面 src 属性 不受同源策略限制
- 前端人员需要写特殊的写法,后端人员也需要配合你,并且只能解决 get请求
- 代理服务器
- 和所处的位置(前端)是一样的,协议名、主机名、端口号都保持一致
- 服务器和服务器之间传递不用ajax,使用的是传统的http请求
- nginx 代理服务器
- vue-cli 开启一个代理服务器
🚬 配置代理方式一:
细节:
- 代理服务器不是所有的请求,都转发给5000(不能灵活的配置走不走代理)
- 代理服务器本事就有的数据,就不转发给5000
- 不能配置多个代理
vue.config.js
// 开启代理服务器 devServer: { proxy: 'http://localhost:5000' },
app.vue
端口改成8080
<template> <div> <button @click="getStudents">获取学生信息</button> </div> </template> <script> import axios from 'axios' export default { name:'App', methods: { getStudents(){ axios.get('http://localhost:8080/students').then( // 成功 response => { console.log('请求成功了',response.data) }, // 失败 error => { console.log('请求失败了',error.message) } ) } }, } </script>
🚬 配置代理方式二:
开启代理服务器(方式二) devServer: { proxy: { '/atguigu': { // 请求前缀 target: 'http://localhost:5000', // 请求地址 pathRewrite:{'^/atguigu':''}, // 重写路径 key-value key正则的匹配条件 把以atguigu 开头的变成 空字符串 // ws: true, //用于支持websocket 默认true // changeOrigin: true //用于控制请求头中的host值 默认true }, '/demo': { target: 'http://localhost:5001', pathRewrite:{'^/demo':''}, // ws: true, //用于支持websocket // changeOrigin: true //用于控制请求头中的host值 } } }
app.vue
<template> <div> <button @click="getStudents">获取学生信息</button><br> <button @click="getCars">获取汽车信息</button> </div> </template> <script> import axios from 'axios' export default { name:'App', methods: { getStudents(){ axios.get('http://localhost:8080/atguigu/students').then( response => { console.log('请求成功了',response.data) }, error => { console.log('请求失败了',error.message) } ) }, getCars(){ axios.get('http://localhost:8080/demo/cars').then( response => { console.log('请求成功了',response.data) }, error => { console.log('请求失败了',error.message) } ) } }, } </script>
🚄 2、github用户搜索案例
项目接口:https://api.github.com/search/users?q=xxx
🚬 1、静态编写
① 基础
app.vue
<template> <div id="app"> <div class="container"> <!--头部的搜索--> <section class="jumbotron"> <h3 class="jumbotron-heading">Search Github Users</h3> <div> <input type="text" placeholder="enter the name you search"/> <button>Search</button> </div> </section> <!--List--> <div class="row"> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> </div> </div> </div> </template> <script> export default { name:'App', } </script> <style> .album { min-height: 50rem; /* Can be removed; just added for demo purposes */ padding-top: 3rem; padding-bottom: 3rem; background-color: #f7f7f7; } .card { float: left; width: 33.333%; padding: .75rem; margin-bottom: 2rem; border: 1px solid #efefef; text-align: center; } .card > img { margin-bottom: .75rem; border-radius: 100px; } .card-text { font-size: 85%; } </style>
把bootstop的样式引入
- 方式一:src下创建文件夹assets(静态资源)/css 把 bootstrap.css 存在里面
- 引入:
- ① main.js 中引入 不推荐
- 里面用到了第三方的样式,这些资源还不去使用,目前不用,不推荐assets方式
- ② 根组件app.vue引入 import ‘./assets/css/bootstrao.css’
- 此方式,脚手架会做一个非常严格的检查,有引入不存在的资源就会报错,没有使用也不可以
- 方式二:在public下建立一个css文件夹 把 bootstrap 放入里面,在index.html页面中引入
- 相对路径
② 拆组件
app.vue
- 样式都是控制列表区的
<template> <div class="container"> <Search/> <List/> </div> </template> <script> import Search from './components/Search' import List from './components/List' export default { name:'App', components:{Search,List} } </script>
List.vue
<template> <!--List--> <div class="row"> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> </div> </template> <script> export default { name:'List' } </script> <style scoped> .album { min-height: 50rem; /* Can be removed; just added for demo purposes */ padding-top: 3rem; padding-bottom: 3rem; background-color: #f7f7f7; } .card { float: left; width: 33.333%; padding: .75rem; margin-bottom: 2rem; border: 1px solid #efefef; text-align: center; } .card > img { margin-bottom: .75rem; border-radius: 100px; } .card-text { font-size: 85%; } </style>
Search.vue
- 使用的是 bootstop 中的样式
<template> <!--头部的搜索--> <section class="jumbotron"> <h3 class="jumbotron-heading">Search Github Users</h3> <div> <input type="text" placeholder="enter the name you search"/> <button>Search</button> </div> </section> </template> <script> export default { name:'Search' } </script>
🚬 2、展示动态的数据和交互
Search.vue
- 获取用户输入
- 发送请求
- 把数据通过全局事件总线的方式传递给list
<template> <section class="jumbotron"> <h3 class="jumbotron-heading">Search Github Users</h3> <div> <input type="text" placeholder="enter the name you search" v-model="keyWord"/> <button @click="searchUsers">Search</button> </div> </section> </template> <script> import axios from 'axios' export default { name:'Search', data() { return { keyWord:'' } }, methods: { searchUsers(){ //请求前更新List的数据 this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false}) // 魔板字符串 axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then( response => { console.log('请求成功了',respense.data) //请求成功后更新List的数据 this.$bus.$emit('updateListData',respense.data.items) }, error => { console.log('请求失败了',error.message) } ) } }, } </script>
main.js安装全局事件总线//创建vm new Vue({ el:'#app', render: h => h(App), beforeCreate() { Vue.prototype.$bus = this }, })
List.vue
- List接收数据 Search 传输数据
- avatar_url 用户的头像地址 展示
- html_url 每一个人的github主页 点击时实现跳转
- login 用户的登录名 展示
<script> export default { name:'List', data() { return { users:[] } }, mounted() { this.$bus.$on('updateListData',(dataObj)=>{ console.log('我是List组件,收到数据:',dataObj) this.dataObj = dataObj // 存数据 }) }, } </script>>
<!--替换div中的数据--> <template> <div class="row"> <!-- 展示用户列表 --> <div class="card" v-for="user in info.users" :key="user.login"> <a :href="user.html_url" target="_blank"> <img :src="user.avatar_url" style='width: 100px'/> </a> <p class="card-text">{{user.login}}</p> </div> </div> </template>
🚬 3、完善功能
- 一上来使用有欢迎词 list组件
- 搜索没有加载出来时,限制正在加载中 list组件
- error
List.vue
<template> <div class="row"> <!-- 展示用户列表 --> <div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login"> <a :href="user.html_url" target="_blank"> <img :src="user.avatar_url" style='width: 100px'/> </a> <p class="card-text">{{user.login}}</p> </div> <!-- 展示欢迎词 --> <h1 v-show="info.isFirst">欢迎使用!</h1> <!-- 展示加载中 --> <h1 v-show="info.isLoading">加载中....</h1> <!-- 展示错误信息 --> <h1 v-show="info.errMsg">{{info.errMsg}}</h1> </div> </template> <script> export default { name:'List', data() { return { info:{ isFirst:true, // 是否为初次展示 isLoading:false, // 是否处于加载中 errMsg:'', // 存储错误信息 users:[] } } }, mounted() { this.$bus.$on('updateListData',(dataObj)=>{ this.info = {...this.info,...dataObj}// 通过字面量的形式去合并对象,重名后面为主 }) }, } </script>
Search.vue
<script> import axios from 'axios' export default { name:'Search', data() { return { keyWord:'' } }, methods: { searchUsers(){ //请求前更新List的数据 this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false}) axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then( response => { console.log('请求成功了') //请求成功后更新List的数据 this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items}) }, error => { //请求后更新List的数据 this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]}) } ) } }, } </script>
🚬 4、完整数据
App.vue
<template> <div class="container"> <Search/> <List/> </div> </template> <script> import Search from './components/Search' import List from './components/List' export default { name:'App', components:{Search,List} } </script>
List.vue
<template> <div class="row"> <!-- 展示用户列表 --> <div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login"> <a :href="user.html_url" target="_blank"> <img :src="user.avatar_url" style='width: 100px'/> </a> <p class="card-text">{{user.login}}</p> </div> <!-- 展示欢迎词 --> <h1 v-show="info.isFirst">欢迎使用!</h1> <!-- 展示加载中 --> <h1 v-show="info.isLoading">加载中....</h1> <!-- 展示错误信息 --> <h1 v-show="info.errMsg">{{info.errMsg}}</h1> </div> </template> <script> export default { name:'List', data() { return { info:{ isFirst:true, // 是否为初次展示 isLoading:false, // 是否处于加载中 errMsg:'', // 存储错误信息 users:[] } } }, mounted() { this.$bus.$on('updateListData',(dataObj)=>{ this.info = {...this.info,...dataObj} }) }, } </script> <style scoped> .album { min-height: 50rem; /* Can be removed; just added for demo purposes */ padding-top: 3rem; padding-bottom: 3rem; background-color: #f7f7f7; } .card { float: left; width: 33.333%; padding: .75rem; margin-bottom: 2rem; border: 1px solid #efefef; text-align: center; } .card > img { margin-bottom: .75rem; border-radius: 100px; } .card-text { font-size: 85%; } </style>
Search.vue
<template> <section class="jumbotron"> <h3 class="jumbotron-heading">Search Github Users</h3> <div> <input type="text" placeholder="enter the name you search" v-model="keyWord"/> <button @click="searchUsers">Search</button> </div> </section> </template> <script> import axios from 'axios' export default { name:'Search', data() { return { keyWord:'' } }, methods: { searchUsers(){ //请求前更新List的数据 this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false}) axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then( response => { console.log('请求成功了') //请求成功后更新List的数据 this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items}) }, error => { //请求后更新List的数据 this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]}) } ) } }, } </script>
main.js
//引入Vue import Vue from 'vue' //引入App import App from './App.vue' //关闭Vue的生产提示 Vue.config.productionTip = false //创建vm new Vue({ el:'#app', render: h => h(App), beforeCreate() { Vue.prototype.$bus = this }, })
🚒 3、vue 项目中常用的 2 个 Ajax 库
🚬 axios 强力推荐
通用的 Ajax 请求库, 官方推荐,使用广泛
🚬 vue-resource(插件库)
vue 插件库, vue1.x 使用广泛, 官方已不维护。
安装
- npm i vue-resource
引入插件
- import VueResource from ‘vue-resource’
使用插件
- vue.use(VueResource )
vm 和 vc 身上都多了 $http:(…)
Search.vue<script> export default { methods: { searchUsers(){ this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then( response => { console.log('请求成功了') //请求成功后更新List的数据 this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items}) }, error => { //请求后更新List的数据 this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]}) } ) } }, } </script>
🚤 4、Vue插槽solt
总结:
🚬 效果
① 基础
Category.vue
<template> <div class="category"> <h3>xxxxxx分类</h3> <ul> <li>xxxx</li> <li>xxxx</li> <li>xxxx</li> <li>xxxx</li> </ul> </div> </template> <script> export default { name:'Category' } </script> <style scoped> .category{ background-color: skyblue; width: 200px; height: 300px; } h3{ text-align: center; background-color: orange; } video{ width: 100%; } img{ width: 100%; } </style>
app.vue
<template> <div class="container"> <Category/> <Category/> <Category/> </div> </template> <script> import Category from './components/Category' export default { name:'App', components:{Category} } </script> <style scoped> .container{ display: flex; justify-content: space-around; } </style>
② 实现Category.vue
<template> <div class="category"> <h3>{{title}}分类</h3> <ul> <ul> <li v-for="(item,index) in listData" :key="index">{{item}}</li> </ul> </ul> </div> </template> <script> export default { name:'Category', props:['listData','title'] } </script>
app.vue
<template> <div class="container"> <Category title="美食" :listData="foods"/> <Category title="游戏" :listData="games"/> <Category title="电影" :listData="films"/> </div> </template> <script> import Category from './components/Category' export default { name:'App', components:{Category}, data() { return { foods:['火锅','烧烤','小龙虾','牛排'], games:['红色警戒','穿越火线','劲舞团','超级玛丽'], films:['《教父》','《拆弹专家》','《你好,李焕英》','《流浪地球》'] } }, } </script>
🚬 默认插槽
- 改需求,如下效果
app.vue
<template> <div class="container"> <Category title="美食" > <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> </Category> <Category title="游戏" > <ul> <li v-for="(g,index) in games" :key="index">{{g}}</li> </ul> </Category> <Category title="电影"> <!--controls 控制 (可以控制播放)--> <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> </Category> </div> </template>
Category.vue
<template> <div class="category"> <h3>{{title}}分类</h3> <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> </div> </template> props:['title']
ok 实现
🚬 作用域插槽
- 改需求,如下效果
① 基础
Category.vue
<template> <div class="category"> <h3>{{title}}分类</h3> <slot>我是默认的一些内容</slot> </div> </template> <script> import Category from './components/Category' export default { name:'App', components:{Category} </script>
app.vue
<template> <div class="container"> <Category title="游戏"> <ul> <li v-for="(g,index) in atguigu.games" :key="index">{{g}}</li> </ul> </Category> <Category title="游戏"> <ul> <li v-for="(g,index) in atguigu.games" :key="index">{{g}}</li> </ul> </Category> <Category title="游戏"> <ul> <li v-for="(g,index) in atguigu.games" :key="index">{{g}}</li> </ul> </Category> </div> </template> <script> export default { name:'Category', props:['title'], data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'] } }, } </script>
② 数据不在app组件中 在 Category 组件中
- 插槽也就不需要了
Category.vue
<template> <div class="category"> <h3>{{title}}分类</h3> <ul> <li v-for="(g,index) in atguigu.games" :key="index">{{g}}</li> </ul> </div> </template> <script> export default { name:'Category', props:['title'], data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'], } }, } </script>
app.vue
<template> <div class="container"> <Category title="游戏"></Category> <Category title="游戏"></Category> <Category title="游戏"></Category> </div> </template> <script> export default { name:'Category', props:['title'] } </script>
同样实现
③ 需求:数据是一样的,数据的结构根据使用者的结构来定义
games 的作用域,games 在 Category 中,app中如何使用
插槽提供了便捷的方式,插槽绑定 games,就传给了插槽的使用者
使用者如何拿到,使用template标签 属性scope=“名字随意”,就可以收到数据
第一次使用数据是无序列表
第二次使用数据是有序列表
第二次使用数据每一个都是 h4 标题
Category.vue
<template> <div class="category"> <h3>{{title}}分类</h3> <slot :games="games" msg="hello">我是默认的一些内容</slot> </div> </template> <script> export default { name:'Category', props:['title'], data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'], } }, } </script>
app.vue
<template> <div class="container"> <Category title="游戏"> <template scope="atguigu"> <ul> <li v-for="(g,index) in atguigu.games" :key="index">{{g}}</li> </ul> </template> </Category> <Category title="游戏"> <template scope="{games}"> <ol> <li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li> </ol> </template> </Category> <Category title="游戏"> <!-- 结构赋值 --> <template slot-scope="{games}"> <h4 v-for="(g,index) in games" :key="index">{{g}}</h4> </template> </Category> </div> </template> <script> import Category from './components/Category' export default { name:'App', components:{Category}, } </script>
ok 实现
🚏 Vue中vuex 重要
总结:
理解:
全局事件总线
- 此时组件太多了,而且数据共享
vuex 共享
🚀 1、Vuex
🚬 1、什么时候使用Vuex
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
🚬 2、案例纯粹的vue
① 基础结构
Count.vue
<template> <div> <h1>当前求和为:????</h1> <select > <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button>+</button> <button>-</button> <button>当前求和为奇数再加</button> <button>等一等再加</button> </div> </template> <script> export default { name:'Count', } </script> <style lang="css"> button{ margin-left: 5px; } </style>
app.vue
<template> <div> <Count/> </div> </template> <script> import Count from './components/Count' export default { name:'App', components:{Count}, } </script>
② Count.vue
<template> <div> <h1>当前求和为:{{sum}}</h1> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementOdd">当前求和为奇数再加</button> <button @click="incrementWait">等一等再加</button> </div> </template> <script> export default { name:'Count', data() { return { n:1, //用户选择的数字 sum:0 //当前的和 } }, methods: { increment(){ this.sum += this.n }, decrement(){ this.sum -= this.n }, incrementOdd(){ if(this.sum % 2){ this.sum += this.n } }, incrementWait(){ setTimeout(()=>{ this.sum += this.n },500) }, }, } </script>
🚬 3、Vuex 工作原理图
🚬 4、搭建Vuex环境
🚭 步骤
- 1、npm i vuex
- vue3成为默认版本的同时,vuex也更新到了4版本
- npm i vuex 安装的是vuex4
- vuex的4版本,只能在vue3中使用
- vue2中,要用vuex的3版本 npm i vuex@3
- vue3中,要用vuex的4版本
- 2、Vue.use(Vuex)
- 3、store 管理
- Actions
- Mutations
- State
- 4、vc 看的见 store
🚭 创建store两种方式:
方式一:src下创建个文件夹vuex,创建一个store.js
方式二:文件夹交store 里面有个index.js 官网推荐
//该文件用于创建Vuex中最为核心的store import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) // 准备actions——用于响应组件中的动作 const actions = { } // 准备mutations——用于操作数据(state) const mutations = { } // 准备state——用于存储数据 const state = { } // 创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
🚭 引入Store
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import vueResource from 'vue-resource'
//引入store
import store from './store/index'
//关闭Vue的生产提示
Vue.config.productionTip = false
//使用插件
Vue.use(vueResource)
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store,
beforeCreate() {
Vue.prototype.$bus = this
}
})
注意:引入vuex的位置
- 要在index.js中,如果在main.js中不会报错
- 还需要引入vue,并且要在最上方
- 成功
🚬 5、vuex实现案例(vuex的基本使用)
- 简单易懂
// 1、准备state——用于存储数据 const state = { sum:0 //当前的和 } // 2、准备actions——用于响应组件中的动作 const actions = { jia(context,value){ console.log('actions中的jia被调用了') context.commit('JIA',value) } } // 3、准备mutations——用于操作数据(state) const mutations = { JIA(state,value){ console.log('mutations中的JIA被调用了') state.sum += value } } // 引用 <h1>当前求和为:{{$store.state.sum}}</h1> this.$store.dispatch('jiaWait',this.n) // 如果 Actions 中没有业务逻辑时,可以直接调用Mutations this.$store.commit('JIA',this.n) // 和 Mutations 对话 名字是大写的 // 拓展:Actions可以有多个,一个处理不过来 jiaOdd(context,value){ console.log('actions中的jiaOdd被调用了') console.log('处理了一些事情--jiaOdd') context.dispatch('demo1',value) }, demo1(context,value){ console.log('处理了一些事情--demo1') context.dispatch('demo2',value) }, demo2(context,value){ console.log('处理了一些事情--demo2') if(context.state.sum % 2){ context.commit('JIA',value) } },
Count.vue
<template> <div> <h1>当前求和为:{{$store.state.sum}}</h1> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementOdd">当前求和为奇数再加</button> <button @click="incrementWait">等一等再加</button> </div> </template> <script> export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, methods: { increment(){ this.$store.commit('JIA',this.n) }, decrement(){ this.$store.commit('JIAN',this.n) }, incrementOdd(){ this.$store.dispatch('jiaOdd',this.n) }, incrementWait(){ this.$store.dispatch('jiaWait',this.n) }, }, mounted() { console.log('Count',this) }, } </script> <style lang="css"> button{ margin-left: 5px; } </style>
index.js
- 开发中推荐
- actions 中小写
- mutations 中大写
//该文件用于创建Vuex中最为核心的store import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) //准备actions——用于响应组件中的动作 const actions = { // context 上下文 // 此时没有意义,拿过来就转发 /*jia(context,value){ console.log('actions中的jia被调用了') context.commit('JIA',value) }, jian(context,value){ console.log('actions中的jian被调用了') context.commit('JIAN',value) },*/ jiaOdd(context,value){ console.log('actions中的jiaOdd被调用了') if(context.state.sum % 2){ context.commit('JIA',value) } }, jiaWait(context,value){ console.log('actions中的jiaWait被调用了') setTimeout(()=>{ context.commit('JIA',value) },500) } } //准备mutations——用于操作数据(state) const mutations = { JIA(state,value){ console.log('mutations中的JIA被调用了') state.sum += value }, JIAN(state,value){ console.log('mutations中的JIAN被调用了') state.sum -= value } } //准备state——用于存储数据 const state = { sum:0 //当前的和 } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
🚬 6、Vuex开发者工具使用
🚭 问题一:
问什么在Actions 中给的是 context(上下文),明明只有一个commint操作,为啥不直接给一个commint就可以了
- 如果给你commint 那么就没有退路可严,只能commint向下操作
- 但是Actions可以有多个,一个业务逻辑完成不了,通过上下文可以继续向下调用
// 拓展:Actions可以有多个,一个处理不过来 jiaOdd(context,value){ console.log('actions中的jiaOdd被调用了') console.log('处理了一些事情--jiaOdd') context.dispatch('demo1',value) }, demo1(context,value){ console.log('处理了一些事情--demo1') context.dispatch('demo2',value) }, demo2(context,value){ console.log('处理了一些事情--demo2') if(context.state.sum % 2){ context.commit('JIA',value) } },
🚭 问题二:
context.state.sum += value // 同样可以实现,但是开发者失效了,不建议(开发者工具捕获的是Mutations中数据的变化)
jiaOdd(context,value){ console.log('actions中的jiaOdd被调用了') if(context.state.sum % 2){ //context.commit('JIA',value) context.state.sum += value // 同样可以实现,但是开发者失效了,不建议(开发者工具捕获的是Mutations中数据的变化) } },
🚭 问题三:
为什么要把业务逻辑写在 actions(书写业务逻辑)中,可以写在方法中,同样可以实现,看组件就非常的直观
- 原因:比如不是加操作,而是发票的报销,传递的是发票号,非常非常复杂的发票报销判断逻辑,
- 难不成所有的人的都要写复杂的逻辑判断,自己发请求去链接服务器验证发票的真伪,非常冗余,可复用性高
🚄 2、Store中的配置项
🚬 1、Store中的配置项getters
① 让sum的值放大十倍展示
- 日后的逻辑可能很复杂,而且可能不止使用一个getters
<template> <div> <h1>当前求和为:{{$store.state.sum}}</h1> <h3>当前求和放大10倍为:{{$store.state.sum * 10}}</h3> </div> </template>
- 计算属性实现
- 计算属性不能跨组件使用,只能自己组件使用
<template> <div> <h1>当前求和为:{{$store.state.sum}}</h1> <h3>当前求和放大10倍为:{{dahe}</h3> </div> </template> <script> export default { name:'Count', data() {}, computed: { dahe(){ return this.$store.state.sum * 10 } } } </script>
- getters
store/index.js
//准备getters——用于将state中的数据进行加工 const getters = { bigSum(state){ return state.sum*10 } } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, getters })
Count.vue
<template> <div> <h1>当前求和为:{{$store.state.sum}}</h1> <h3>当前求和放大10倍为:{{$store.getters.bigSum}}</h3> </div> </template>
🚬 2、Store中的配置项_mapState与mapGetters
① 需求
- 展示我自己在自学,学习前端,学习方式,和学习内容是State中的数据
store/index.js
//准备state——用于存储数据 const state = { sum: 0, //当前的和 school: '自学', subject: '前端' }
Count.vue
<template> <div> <h1>当前求和为:{{$store.state.sum}}</h1> <h3>当前求和放大10倍为:{{$store.state.sum * 10}}</h3> <h3>我自己在{{$store.state.school}},学习{{$store.state.subject}}</h3> </div> </template>
简化想要实现的效果
- 把$store.state 省略不写
<template> <div> <h1>当前求和为:{{sum}}</h1> <h3>当前求和放大10倍为:{{bigSum}}</h3> <h3>我自己在{{school}},学习{{subject}}</h3> </div> </template>
② 实现
- 计算属性,就自己组件使用
<script> computed:{ //靠程序员自己亲自去写计算属性 /* 是在 state 中取数据 */ sum(){ return this.$store.state.sum }, school(){ return this.$store.state.school }, subject(){ return this.$store.state.subject }, /* 是在 getters 中取数据 */ bigSum(){ return this.$store.getters.bigSum } }, } </script>
- 使用Vuex中的==…mapState== 和 …mapGetters来实现
- 需要引入
<script> import {mapState,mapGetters} from 'vuex' // obj对象里面 ...obj1一个对象什么意思,把obj1里面的每一组key-value都展开放入对象obj中 computed:{ /* 是在 state 中取数据 */ //借助mapState生成计算属性,从state中读取数据。(对象写法) // ...mapState({he:'sum',xuexiao:'school',xueke:'subject'}), //借助mapState生成计算属性,从state中读取数据。(数组写法) ...mapState(['sum','school','subject']), /* ******************************************************************** */ /* 是在 getters 中取数据 */ //借助mapGetters生成计算属性,从getters中读取数据。(对象写法) // ...mapGetters({bigSum:'bigSum'}) //借助mapGetters生成计算属性,从getters中读取数据。(数组写法) ...mapGetters(['bigSum']) }, } </script>
🚬 3、Store中的配置项_mapActions与mapMutations.mp4
优化:actions
- 需要引入
Count.vue
<template> <div> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementOdd">当前求和为奇数再加</button> <button @click="incrementWait">等一等再加</button> </div> </template> <script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex' methods: { //程序员亲自写方法 /* commit */ increment(){ this.$store.commit('JIA',this.n) }, decrement(){ this.$store.commit('JIAN',this.n) }, /* dispatch */ ncrementOdd(){ this.$store.dispatch('jiaOdd',this.n) }, incrementWait(){ this.$store.dispatch('jiaWait',this.n) }, }, } </script>
优化
<template> <div> <button @click="increment(n)">+</button> <button @click="decrement(n)">-</button> <button @click="incrementOdd(n)">当前求和为奇数再加</button> <button @click="incrementWait(n)">等一等再加</button> </div> </template> <script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex' methods: { /* commit */ //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法) ...mapMutations({increment:'JIA',decrement:'JIAN'}), //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法) // ...mapMutations(['JIA','JIAN']),// 名称要一致 /* dispatch */ //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法) ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法) // ...mapActions(['jiaOdd','jiaWait'])// 名称要一致 }, } </script>
🚬 4、多组件共享数据
在写一个组件Person展示人员的信息
- 需要在vuex中展示的信息sum、persons
- count 组件读取sum、persons,进行展示
- person 组件 读取sum、persons,进行展示
- 添加一个人信息
① 基础
store/index.js
//准备state——用于存储数据 const state = { sum:0, //当前的和 school:'尚硅谷', subject:'前端', personList:[ {id:'001',name:'张三'} ] }
Person
<template> <div> <h1>人员列表</h1> <input type="text" placeholder="请输入名字" v-model="name"> <button>添加</button> <ul> <li v-for="p in personList" :key="p.id">{{p.name}}</li> </ul> </div> </template> <script> export default { name:'Person', computed:{ personList(){ return this.$store.state.personList } } } </script>
② 实现多组件共享数据
store/index.js
//准备mutations——用于操作数据(state) const mutations = { ADD_PERSON(state, value) { console.log('mutations中的ADD_PERSON被调用了') state.personList.unshift(value) } }
person.vue
<template> <div> <h1>人员列表</h1> <h3 style="color:red">Count组件求和为:{{sum}}</h3> <input type="text" placeholder="请输入名字" v-model="name"> <button @click="add">添加</button> <ul> <li v-for="p in personList" :key="p.id">{{p.name}}</li> </ul> </div> </template> <script> import {nanoid} from 'nanoid' export default { name:'Person', data() { return { name:'' } }, computed:{ personList(){ return this.$store.state.personList }, sum(){ return this.$store.state.sum } }, methods: { add(){ const personObj = {id:nanoid(),name:this.name} this.$store.commit('ADD_PERSON',personObj) this.name = '' // 清空输入框 } }, } </script>
count.vue
<template> <div> <h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3> </div> </template> <script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex' export default { name:'Count', data() { }, computed:{ ...mapState(['sum','school','subject','personList']), ...mapGetters(['bigSum']) }, } </script>
person.vue
<template> <div> <h3 style="color:red">Count组件求和为:{{sum}}</h3> </div> </template> <script> import {nanoid} from 'nanoid' export default { name:'Person', data() { }, computed:{ sum(){ return this.$store.state.sum } } } </script>
(VUE入门.assets/image-20220808220828347.png)]
🚒 3、Vuex的模块编码 + 命名空间
- action、mutations、state、getters中如果有多个模块(订单、人员、求和),都放在一个显得臃肿,更容易造成git中的版本控制冲突
- 把不同分类的action、mutations、state、getters放在不同的位置
🚬 ① 原始臃肿的写法方式
store/index.js
//准备actions——用于响应组件中的动作 const actions = { jiaOdd(context, value) { console.log('actions中的jiaOdd被调用了') if (context.state.sum % 2) { context.commit('JIA', value) } }, jiaWait(context, value) { console.log('actions中的jiaWait被调用了') setTimeout(() => { context.commit('JIA', value) }, 500) } } //准备mutations——用于操作数据(state) const mutations = { JIA(state, value) { console.log('mutations中的JIA被调用了') state.sum += value }, JIAN(state, value) { console.log('mutations中的JIAN被调用了') state.sum -= value }, ADD_PERSON(state, value) { console.log('mutations中的ADD_PERSON被调用了') state.personList.unshift(value) } } //准备state——用于存储数据 const state = { sum: 0, //当前的和 school: '自学', subject: '前端', personList: [ { id: '001', name: '张三' } ] } //准备getters——用于将state中的数据进行加工 const getters = { bigSum(state) { return state.sum * 10 } }
🚬 ② 改进: store/index.js
// 求和相关的配置 const countOptions = { // 书写完后countAbout 名才你能被 computed 中的 ...mapState 认识 // ...mapState('countAbout',['sum','school','subject']), 不书写 countAbout 不被认识 namespaced:true, actions:{ jiaOdd(context, value) { console.log('actions中的jiaOdd被调用了') if (context.state.sum % 2) { context.commit('JIA', value) } }, jiaWait(context, value) { console.log('actions中的jiaWait被调用了') setTimeout(() => { context.commit('JIA', value) }, 500) } }, mutations:{ JIA(state, value) { console.log('mutations中的JIA被调用了') state.sum += value }, JIAN(state, value) { console.log('mutations中的JIAN被调用了') state.sum -= value } }, state:{ sum: 0, //当前的和 school: '自学', subject: '前端', }, getters:{ bigSum(state) { return state.sum * 10 } }, } // 人员管理相关的配置 const personOptions = { namespaced:true, actions:{}, mutations:{ ADD_PERSON(state, value) { console.log('mutations中的ADD_PERSON被调用了') state.personList.unshift(value) } }, state:{ personList: [ { id: '001', name: '张三' } ] }, getters:{}, } //创建并暴露store export default new Vuex.Store({ modules:{ countAbout:countOptions, personAbout:personOptions } })
Count.vue
① 非简写
<template> <div> <h1>当前求和为:{{countAbout.sum}}</h1> <h3>当前求和放大10倍为:{{countAbout.bigSum}}</h3> <h3>我在{{countAbout.school}},学习{{countAbout.subject}}</h3> <h3 style="color:red">Person组件的总人数是:{{personAbout.personList.length}}</h3> </div> </template> <script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex' export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, computed:{ // 进行了模块化编码 state中有数据countOptions和 personOptions。(数组写法) ...mapState('countAbout','personAbout'), } } </script>
② 简写
- 开启命名空间:namespaced:true,
- …mapState、…mapMutations、…的简写方式
<template> <div> <h1>当前求和为:{{sum}}</h1> <h3>当前求和放大10倍为:{{bigSum}}</h3> <h3>我在{{school}},学习{{subject}}</h3> <h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment(n)">+</button> <button @click="decrement(n)">-</button> <button @click="incrementOdd(n)">当前求和为奇数再加</button> <button @click="incrementWait(n)">等一等再加</button> </div> </template> <script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex' export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, computed:{ // 进行了模块化编码 state中有数据countOptions和 personOptions。(数组写法) ...mapState('countAbout',['sum','school','subject']), ...mapState('personAbout',['personList']), //借助mapGetters生成计算属性,从getters中读取数据。(数组写法) ...mapGetters('countAbout') }, methods: { //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法) ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}), //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法) ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) } } </script>
🚬 ③ 改进:把store/index.js拆分
store/count.js
//求和相关的配置 export default { namespaced:true, actions:{ jiaOdd(context,value){ console.log('actions中的jiaOdd被调用了') if(context.state.sum % 2){ context.commit('JIA',value) } }, jiaWait(context,value){ console.log('actions中的jiaWait被调用了') setTimeout(()=>{ context.commit('JIA',value) },500) } }, mutations:{ JIA(state,value){ console.log('mutations中的JIA被调用了') state.sum += value }, JIAN(state,value){ console.log('mutations中的JIAN被调用了') state.sum -= value }, }, state:{ sum:0, //当前的和 school:'尚硅谷', subject:'前端', }, getters:{ bigSum(state){ return state.sum*10 } }, }
store/person.js
//人员管理相关的配置 export default { namespaced:true, actions:{ addPersonWang(context,value){ if(value.name.indexOf('王') === 0){ context.commit('ADD_PERSON',value) }else{ alert('添加的人必须姓王!') } } }, mutations:{ ADD_PERSON(state,value){ console.log('mutations中的ADD_PERSON被调用了') state.personList.unshift(value) } }, state:{ personList:[ {id:'001',name:'张三'} ] }, getters:{ firstPersonName(state){ return state.personList[0].name } }, }
store/index.js 引入
import countOptions from './count' import personOptions from './person'
🚬 练习Actions调用Backend API 后端API
store/count.js
//人员管理相关的配置 import axios from 'axios' import { nanoid } from 'nanoid' export default { namespaced:true, actions:{ addPersonWang(context,value){ if(value.name.indexOf('王') === 0){ context.commit('ADD_PERSON',value) }else{ alert('添加的人必须姓王!') } }, addPersonServer(context){ axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then( response => { context.commit('ADD_PERSON',{id:nanoid(),name:response.data}) }, error => { alert(error.message) } ) } } }
- 之后正常引用
🚤 4、Vue路由Route
总结:
理解:
实现什么功能:
SPA(single page web application)应用,单页面应用
🚬 1、基本路由
🚭 路由的基本使用
① 基础
样式都是引入的bootstarp的样式
public/index.html
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
app.vue
<template> <div> <div class="row"> <div class="col-xs-offset-2 col-xs-8"> <div class="page-header"><h2>Vue Router Demo</h2></div> </div> </div> <div class="row"> <div class="col-xs-2 col-xs-offset-2"> <div class="list-group"> <a class="list-group-item active" href="./about.html">About</a> <a class="list-group-item" href="./home.html">Home</a> </div> </div> <div class="col-xs-6"> <div class="panel"> <div class="panel-body"> <h2>我是About的内容</h2> </div> </div> </div> </div> </div> </template> <script> export default { name:'App', } </script>
样式都是引入的bootstarp的样式
public/index.html
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
src/main.js 使用
//引入Vue import Vue from 'vue' //引入App import App from './App.vue' //关闭Vue的生产提示 Vue.config.productionTip = false //创建vm new Vue({ el:'#app', render: h => h(App), })
② 展示app.vue
<template> <div> <div class="row"> <div class="col-xs-offset-2 col-xs-8"> <div class="page-header"><h2>Vue Router Demo</h2></div> </div> </div> <div class="row"> <div class="col-xs-2 col-xs-offset-2"> <div class="list-group"> <a class="list-group-item active" href="./about.html">About</a> <a class="list-group-item" href="./home.html">Home</a> </div> </div> <div class="col-xs-6"> <div class="panel"> <div class="panel-body"> 此处到底展示什么组件,得看用户点击的是哪个导航项 </div> </div> </div> </div> </div> </template>
Home.vue
<template> <h2>我是Home的内容</h2> </template> <script> export default { name:'Home' } </script>
About.vue
<template> <h2>我是About的内容</h2> </template> <script> export default { name:'About' } </script>
③ 使用vue-router
安装 npm i vue-router@3
vue-router 4 只能在 vue 3中使用
vue-router 3 才能在 vue 2中使用
引入vue-router,mian.js
并且引入了路由器
//引入VueRouter import VueRouter from 'vue-router' //引入路由器 import router from './router' //应用插件 Vue.use(VueRouter) //创建vm new Vue({ el:'#app', render: h => h(App), router:router })
使用vue-router需要创建router文件夹/index.js , 创建整个应用的路由器,重要
// 该文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' //引入组件 import About from '../components/About' import Home from '../components/Home' //创建并暴露一个路由器 export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] })
④ 实现单页面应用找到导航区,切换路径
需要路由提供的特殊的标签router-link引入,不是href 而是 to=“/home”,router-view制定位置呈现
router-link 最终转换成的是 a 标签,如果是按钮等等跳转,后面 编程式路由导航
app.vue
<template> <div> <div class="row"> <div class="col-xs-2 col-xs-offset-2"> <div class="list-group"> <!-- 原始html中我们使用a标签实现页面的跳转 --> <!-- <a class="list-group-item active" href="./about.html">About</a> --> <!-- <a class="list-group-item" href="./home.html">Home</a> --> <!-- Vue中借助router-link标签实现路由的切换 --> <!-- active-class该元素被激活时的样式 --> <router-link class="list-group-item" active-class="active" to="/about">About</router-link> <router-link class="list-group-item" active-class="active" to="/home">Home</router-link> </div> </div> <div class="col-xs-6"> <div class="panel"> <div class="panel-body"> <!-- 指定组件的呈现位置 --> <router-view></router-view> </div> </div> </div> </div> </div> </template>
🚭 路由的注意点
1、路由组件和一般组件
- 路由组件:不需写组价标签,靠路由规则,匹配出来,由路由器来进行渲染的组件。 pages放置路由组件
- 一般组件:亲自书写组件,是一般组件。components
2、在频繁的切换时
- 路由组件被销毁了
3、路由组件身上多了两个人,$route 和 $ routers、
- route:配置信息
- routers: 整个应用的路由器只有一个,800个路由规则也只有一个路由器
🚬 2、嵌套(多级)路由
效果
只关注Home中里面的内容Home.vue
- 路径要加上一级路由的路径
//原来 <template> <h2>Home组件内容</h2> </template> // 最新 <template> <div> <h2>Home组件内容</h2> <div> <ul class="nav nav-tabs"> <li> <router-link class="list-group-item" active-class="active" to="/home/news">News</router-link> </li> <li> <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link> </li> </ul> <router-view></router-view> </div> </div> </template> <script> export default { name:'Home', } </script>
Message.vue
<template> <div> <ul> <li> <a href="/message1">message001</a> </li> <li> <a href="/message2">message002</a> </li> <li> <a href="/message/3">message003</a> </li> </ul> </div> </template> <script> export default { name:'Message' } </script>
News.vue
<template> <ul> <li>news001</li> <li>news002</li> <li>news003</li> </ul> </template> <script> export default { name:'News' } </script>
router/index.js
- 一级路由 加 /
- 某一个路由里面的子路由不需要
// 该文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' //引入组件 import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' //创建并暴露一个路由器 export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News, }, { path:'message', component:Message, } ] } ] })
🚬 3、路由传参
🚭 路由接收参数之一:query参数
效果
① 基础Message.vue
- 展示Detail组件把消息带过去,展示的都是Detail组件,但是展示的数据是不同的
<template> <div> <ul> <li v-for="m in messageList" :key="m.id"> <a href="/message1">{{m.title}}</a> </li> </ul> <hr> <router-view></router-view> </div> </template> <script> export default { name:'Message', data() { return { messageList:[ {id:'001',title:'消息001'}, {id:'002',title:'消息002'}, {id:'003',title:'消息003'} ] } }, } </script>
Detail.vue
<template> <ul> <li>消息编号:???</li> <li>消息标题:???</li> </ul> </template> <script> export default { name:'Detail', mounted() { console.log(this.$route) }, } </script>
② 展示
Message.vue
<template> <div> <ul> <li v-for="m in messageList" :key="m.id"> <!-- 跳转路由并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail">{{m.title}}</router-link> </router-link> </li> </ul> <hr> <router-view></router-view> </div> </template>
router/index.js
// 该文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' //引入组件 import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail' //创建并暴露一个路由器 export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News, }, { path:'message', component:Message, children:[ { path:'detail', component:Detail, } ] } ] } ] })
③ 实现Message.vue
- 传递参数
<template> <div> <ul> <li v-for="m in messageList" :key="m.id"> <!-- 跳转路由并携带query参数,to的字符串写法 --> <!-- :绑定 ""引号里面的内容当成js去解析,是模板字符串,还混着${}js变量 --> <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> --> <!-- 跳转路由并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:m.id, title:m.title } }"> {{m.title}} </router-link> </li> </ul> <hr> <router-view></router-view> </div> </template>
Detal.vue
- 接收传递的参数
<template> <ul> <li>消息编号:{{$route.query.id}}</li> <li>消息标题:{{$route.query.title}}</li> </ul> </template>
实现
🚭 命名路由 name
router/index.js
- name 属性
- 作用:在跳转的时候可以简化一些编码
// 该文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' //引入组件 import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail' //创建并暴露一个路由器 export default new VueRouter({ routes:[ { name:'guanyu', path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News, }, { path:'message', component:Message, children:[ { name:'xiangqing', path:'detail', component:Detail, } ] } ] } ] })
Message.vue
// 不配置名字 path <template> <li v-for="m in messageList" :key="m.id"> <!-- 跳转路由并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:m.id, title:m.title } }"> {{m.title}} </router-link> </li> </template> // 路由配置名字 name <template> <li v-for="m in messageList" :key="m.id"> <!-- 跳转路由并携带query参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', query:{ id:m.id, title:m.title } }"> {{m.title}} </router-link> </li> </template>
🚭 路由接收参数之二:params参数
- 如果使用的是params参数,就必须使用name,不能使用path 报错
Message.vue
<template> <div> <ul> <li v-for="m in messageList" :key="m.id"> <!-- 跳转路由并携带params参数,to的字符串写法 --> <!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link> --> <!-- 跳转路由并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:m.id, title:m.title } }"> {{m.title}} </router-link> </li> </ul> <hr> <router-view></router-view> </div> </template>
router/index.js
//创建并暴露一个路由器 export default new VueRouter({ routes:[ { name:'guanyu', path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News, }, { path:'message', component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', component:Detail, } ] } ] } ] })
Detail.vue
<template> <ul> <li>消息编号:{{$route.params.id}}</li> <li>消息标题:{{$route.params.title}}</li> </ul> </template>
🚭 路由props配置
- Detail 如何 读取出来别人传递过来的参数
- props三种写法
router/index.js
//创建并暴露一个路由器 export default new VueRouter({ routes:[ { name:'guanyu', path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News, }, { path:'message', component:Message, children:[ { name:'xiangqing', path:'detail', component:Detail, // props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。 // props:{a:1,b:'hello'} // props的第二种写法,值为布尔值, // 若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。 // props:true //props的第三种写法,值为函数 props($route){ return { id:$route.query.id, title:$route.query.title } } } ] } ] } ] })
第三种写法:简写过程,结构赋值和结构赋值的连续写法
// 1 props($route){ return { id:$route.query.id, title:$route.query.title, } } // 2 结构赋 props({query}){ return {query.id,query.title} } // 3 结构赋值的连续写法 语义化不明确,不是十分推荐 props({query:{id,title}}){ return {id,title} }
Detail.vue
<template> <ul> <li>消息编号:{{$route.params.id}}</li> <li>消息标题:{{$route.params.title}}</li> </ul> </template> -------------------------------------------------------------------- <template> <ul> <li>消息编号:{{id}}</li> <li>消息标题:{{title}}</li> </ul> </template> <script> export default { name:'Detail', props:['id','title'], } </script>
🚭 router-link的replace属性
- 路由对浏览器记录的影响
- 结构:栈的结构 push 压栈
- 指针默认指向最上面的结构 默认 push 模式
- 替换当前栈顶的那一条 replace 模式
app.vue
<!-- replace的简写模式 替换栈顶的数据--> <router-link replace class="list-group-item" active-class="active" to="/about">About</router-link> <!-- replace的非模式 --> <router-link :replace =true class="list-group-item" active-class="active" to="/home">Home</router-link>
🚬 4、编程式路由导航
- 不书写router-link 实现路由的跳转 转换成a标签
- 需求一:实现用按钮来实现路由的跳转
- 需求二:过几秒之后自动实现跳转
Message.vue
<template> <div> <ul> <li v-for="m in messageList" :key="m.id"> <!-- 跳转路由并携带params参数,to的字符串写法 --> <!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link> --> <!-- 跳转路由并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', query:{ id:m.id, title:m.title } }"> {{m.title}} </router-link> <button @click="pushShow(m)">push查看</button> <button @click="replaceShow(m)">replace查看</button> </li> </ul> <hr> <router-view></router-view> </div> </template> <script> export default { name:'Message', data() { return { messageList:[ {id:'001',title:'消息001'}, {id:'002',title:'消息002'}, {id:'003',title:'消息003'} ] } }, methods: { pushShow(m){ this.$router.push({ name:'xiangqing', query:{ id:m.id, title:m.title } }) }, replaceShow(m){ this.$router.replace({ name:'xiangqing', query:{ id:m.id, title:m.title } }) } }, } </script>
Banner.vue
<template> <div class="col-xs-offset-2 col-xs-8"> <div class="page-header"> <h2>Vue Router Demo</h2> <button @click="back">后退</button> <button @click="forward">前进</button> <button @click="test">测试一下go</button> </div> </div> </template> <script> export default { name:'Banner', methods: { back(){ this.$router.back() // 后退 // console.log(this.$router) }, forward(){ this.$router.forward() // 前进 }, test(){ this.$router.go(3) // 前进3步 -4后退四步 } }, } </script>
🚭 缓存路由组件
News.vue
<template> <ul> <li>news001</li> <li>news002</li> <li>news003</li> </ul> </template> <script> export default { name:'News' } </script>
Home.vue
<template> <div> <h2>Home组件内容</h2> <div> <ul class="nav nav-tabs"> <li> <router-link class="list-group-item" active-class="active" to="/home/news" > News </router-link> </li> <li> <router-link class="list-group-item" active-class="active" to="/home/message" >Message</router-link > </li> </ul> <!-- 缓存多个路由组件 --> <!-- <keep-alive :include="['News','Message']"> --> <!-- 缓存一个路由组件 --> <!-- include 包含哪个组件,不书写全部组件 组件名 --> <keep-alive include="News"> <router-view></router-view> </keep-alive> </div> </div> </template>
🚬 5、两个新的声明周期钩子
- 案例:在news组件上面展示一串数据,颜色透明度发生改变
- 生命周期中书写过
News.vue
<template> <ul> <li :style="{opacity}">欢迎学习Vue</li> <li>news001 <input type="text"></li> <li>news002 <input type="text"></li> <li>news003 <input type="text"></li> </ul> </template> <script> export default { name:'News', data() { return { opacity:1 } }, beforeDestroy() { console.log('News组件即将被销毁了') clearInterval(this.timer) }, mounted(){ this.timer = setInterval(() => { console.log('@') this.opacity -= 0.01 if(this.opacity <= 0) this.opacity = 1 },16) }, } </script>
问题:news组件没有被销毁,定期器一直调用,引出两个新的生命周期钩子,路由组件独有
- activated,激活
- deactivated,取消激活(失活)
- News组件出现在你面前激活,消失在你面前失活
<template> <ul> <li :style="{opacity}">欢迎学习Vue</li> <li>news001 <input type="text"></li> <li>news002 <input type="text"></li> <li>news003 <input type="text"></li> </ul> </template> <script> export default { name:'News', data() { return { opacity:1 } }, /* beforeDestroy() { console.log('News组件即将被销毁了') clearInterval(this.timer) }, */ /* mounted(){ this.timer = setInterval(() => { console.log('@') this.opacity -= 0.01 if(this.opacity <= 0) this.opacity = 1 },16) }, */ activated() { console.log('News组件被激活了') this.timer = setInterval(() => { console.log('@') this.opacity -= 0.01 if(this.opacity <= 0) this.opacity = 1 },16) }, deactivated() { console.log('News组件失活了') clearInterval(this.timer) }, } </script>
🚬 6、路由守卫
🚭 全局守卫
-
meta 标识是否需要进行权限的校验,路由元信息
-
meta:{isAuth:true,title:‘新闻’}
- isAuth:true 是否授权
- title:‘新闻’ 路由对应的title标题,点击时切换
-
保护路由的权限
- 设置权限
router/index.js// 该文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' //引入组件 import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail' //创建并暴露一个路由器 const router = new VueRouter({ routes:[ { name:'guanyu', path:'/about', component:About, meta:{title:'关于'} }, { name:'zhuye', path:'/home', component:Home, meta:{title:'主页'}, children:[ { name:'xinwen', path:'news', component:News, meta:{isAuth:true,title:'新闻'} }, { name:'xiaoxi', path:'message', component:Message, meta:{isAuth:true,title:'消息'}, children:[ { name:'xiangqing', path:'detail', component:Detail, meta:{isAuth:true,title:'详情'}, //props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。 // props:{a:1,b:'hello'} //props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。 // props:true //props的第三种写法,值为函数 props($route){ return { id:$route.query.id, title:$route.query.title, a:1, b:'hello' } } } ] } ] } ] }) //全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用 router.beforeEach((to,from,next)=>{ // 在每一次路由切换之前调用函数 // to 去哪 from 来自于哪 next 放行 console.log('前置路由守卫',to,from) //方式一: 路径 // if(to.path === '/home/news' || to.path === '/home/messgae'){ //方式二: 名字 // if(to.name === 'xinwen' || to.name === 'xiaoxi'){ // 方式三:推荐 if(to.meta.isAuth){ // 判断是否需要鉴权 if(localStorage.getItem('school')==='atguigu'){ next() }else{ alert('学校名不对,无权限查看!') } }else{ next() } }) // 全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用 // 没有 next router.afterEach((to,from)=>{ console.log('后置路由守卫',to,from) document.title = to.meta.title || '硅谷系统' }) export default router
🚭 独享路由守卫
- 某一个路由单独响应的路由守卫
- 没有后置路路由守卫
对新闻路由进行限制
router/index.js
//创建并暴露一个路由器 const router = new VueRouter({ routes:[ { name:'guanyu', path:'/about', component:About, meta:{title:'关于'} }, { name:'zhuye', path:'/home', component:Home, meta:{title:'主页'}, children:[ { name:'xinwen', path:'news', component:News, meta:{isAuth:true,title:'新闻'}, beforeEnter: (to, from, next) => { console.log('独享路由守卫',to,from) if(to.meta.isAuth){ //判断是否需要鉴权 if(localStorage.getItem('school')==='atguigu'){ next() }else{ alert('学校名不对,无权限查看!') } }else{ next() } } }, { name:'xiaoxi', path:'message', component:Message, meta:{isAuth:true,title:'消息'}, children:[ { name:'xiangqing', path:'detail', component:Detail, meta:{isAuth:true,title:'详情'}, props($route){ return { id:$route.query.id, title:$route.query.title, a:1, b:'hello' } } } ] } ] } ] }) //全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用 router.afterEach((to,from)=>{ console.log('后置路由守卫',to,from) document.title = to.meta.title || '硅谷系统' }) export default router
🚭 组件内路由守卫
- beforeRouteEnter 当路由进入之前
- beforeRouteLeave 当路由离开之前
about 组件 实现组件内路由守卫
about.vue
<script> export default { name:'About', //通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) { console.log('About--beforeRouteEnter',to,from) if(to.meta.isAuth){ //判断是否需要鉴权 if(localStorage.getItem('school')==='atguigu'){ next() }else{ alert('学校名不对,无权限查看!') } }else{ next() } }, //通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) { console.log('About--beforeRouteLeave',to,from) next() } } </script>
🚬 7、路由器的两种工作模式
router/index.js
const router = new VueRouter({ mode:'history', // 两个值 一个 history 一个 hash routes:[ ] })
🚭 hash模式
- http://localhost:8080/#/home/message
- 路径中带 # 号
- 从# 号开始到结尾,路径值都叫hash值
- 特点:不会随着http请求发给服务器
🚭 history模式
- http://localhost:8080/home/message
区别:
- hash 兼容性好
- hash 在部署服务器时,没有问题,后面的路径当做hash值,不会带给服务器
- history 兼容性略差
- history 在部署服务器时,单页面点击可用,刷新输错,原因:路径后面当做资源,没有这个资源
history 模式解决404问题
- 需要后端工程师的帮助
connect-history-api-fallback 在node JS中解决 history 模式404 的问题
服务器中 server
安装:npm i connect-history-api-fallback
引入:const history = require(‘connect-history-api-fallback’);
应用:在app之前
app.use(history()) app.use(express.static(__dirname+'/static'))
解决
🚏 Vue UI 组件库
🚀 1、移动端常用 UI 组件库
1、Vant https://youzan.github.io/vant
2、Cube UI https://didi.github.io/cube-ui
3、Mint UI http://mint-ui.github.io
4、NutUI https://nutui.jd.com/#/
🚄 2、PC 端常用 UI 组件库
1、Element UI https://element.eleme.cn
2、IView UI https://www.iviewui.com
- 高度定制化的UI,不适合适用于UI组件库
完整引入 ElementUI组件库
ElementUI 中的组件全不都注册成全局组件,所有的样式,所有的组件
main.js
//完整引入 //引入ElementUI组件库 import ElementUI from 'element-ui'; //引入ElementUI全部样式 import 'element-ui/lib/theme-chalk/index.css'; //关闭Vue的生产提示 Vue.config.productionTip = false //应用ElementUI Vue.use(ElementUI); //创建vm new Vue({ el:'#app', render: h => h(App), })
按需引入 ElementUI组件库
安装npm install babel-plugin-component -D // -D 开发依赖
将 .babelrc 修改为 新版本名字为 babel.config.js
module.exports = { presets: [ '@vue/cli-plugin-babel/preset', ["@babel/preset-env", { "modules": false }], ], plugins:[ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] }
main.js//按需引入 import { Button,Row,DatePicker } from 'element-ui'; //关闭Vue的生产提示 Vue.config.productionTip = false //应用ElementUI Vue.component(Button.name, Button); // Vue.component(Row.name, Row); Vue.component(DatePicker.name, DatePicker); //创建vm new Vue({ el:'#app', render: h => h(App), })