本節目錄
Axios 是一個基於 promise 的 HTTP 庫,可以用在瀏覽器和 node.js 中,promise是es6里面的用法。
axios能做的事情:官網地址
從瀏覽器中創建 XMLHttpRequests
從 node.js 創建 http 請求
支持 Promise API
攔截請求和響應
轉換請求數據和響應數據
取消請求
自動轉換 JSON 數據
客戶端支持防御 XSRF
我們先看一下promise對象,看文檔。
axios是基於promise對象的,axios的用法類似於jQuery的鏈式用法,就是.then().catch().then().catch()連着用等等,每個方法都相當於給你返回了一個promise對象,接着可以調用這個對象的其他方法
好,那么首先現在安裝一下axios,兩種方式:
直接下載安裝:
$ npm install axios //在咱們模塊化開發的項目中,一定要使用這個來下載
cdn的方式引入:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script> //這個做簡單的演示的時候可以用
其實用法官網寫的很詳細了,這里你可以去直接看官網,我這里就簡單看一下get請求和post請求的使用方法:
get請求的寫法:
// 為給定 ID 的 user 創建請求 axios.get('/user?ID=12345') //get方法then方法等都給你返回了一個promise對象,接着可以調用這個對象的方法 .then(function (response) { //response請求成功接收的數據 console.log(response); }) .catch(function (error) { //error請求錯誤捕獲的異常 console.log(error); }); // 可選地,上面的請求可以這樣做 axios.get('/user', { //請求的查詢參數還可以這樣寫 params: { ID: 12345 } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
post請求的寫法:
axios.post('/user', { //后面這個大括號里面寫請求體里面的請求數據,就是jQuery的ajax里面的data屬性 firstName: 'Fred', lastName: 'Flintstone' }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
好,接下來在們上篇博客最后通過webpack做的那個項目來演示一下axios的使用,首先在我們的項目目錄下執行下載安裝axios的指令:
npm install axios -S //為什么要下載項目下呢,因為將來打包項目推送到線上的時候,打包我們的項目,全局的那些環境你是打包不上的,你線上環境里面可能沒有我們下載的這些環境或者說工具,所以直接在我們的項目環境中下載,打包的時候一同打包給線上
那么接下來axios這個東西怎么用呢,我們是不是可能在好多個組件中都要發送請求啊,首先看一下我們開發時的結構:
那這個怎么玩呢,原型鏈技術:
vue對象實例在我們的mian.js里面引用的,那么我們就在main.js里面引入axios,並且封裝給vue實例,那么其他組件就能用了(繼承),在mian.js里面加上下面兩句:
import Axios from 'axios' Vue.prototype.$http = Axios; //Vue.prototype.名字(這個名字隨便起,一般是叫$http或者$https,那么一看就明白,你這是在往后端發送請求)
看mian.js代碼:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router'
//引入axios,封裝axios,注意不用使用Vue.use()昂,人家官網也沒說讓你用,用use的一般是在vue的基礎上封裝的一些框架或者模塊,但是這個axios不是vue的,是一個新的基於es6的http庫,在哪個語言里面都可以用這個庫,它是一個獨立的內容,vue里面一般不用jQuery的ajax,人間jQuery也是前端的一個框架可以這么說,人家vue也是前端的框架,兩者做的事情差不多,人家不想用jQuery的 import Axios from 'axios' Vue.prototype.$http = Axios; //Vue.prototype.名字(這個名字隨便起,一般是叫$http或者$https,那么一看就明白,你這是在往后端發送請求) import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); import '../static/global/index.css' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, template: '<App/>' })
然后我們啟動我們的項目,執行npm run dev,然后在我們的組件中使用一下axios,我在我們之前創建的那個Course.vue組件中使用了一下,也算是寫一下前面布置的那個練習吧,大家看看代碼,看看你能看懂不:
<template> <!--<div>--> <!--這是Course頁面--> <!--</div>--> <div> <div class="categorylist"> <span v-for="(item,index) in categoryList" :key="item.id" :class="{active:currentIndex===index}" @click="clickCategoryHandler(index,item.id)">{{ item.name }}</span> </div> <div class="course"> <ul> <li v-for="(course,index) in courseList" :key="course.id"> <h3>{{ course.name }}</h3> <!-- 這里就拿個name的值演示一下效果 --> </li> </ul> </div> </div> </template> <script> export default { name: 'Course', data(){ return{ categoryList:[],//用來接收請求回來的數據,一般都和我們后端返回回來的數據的數據類型對應好,所以我寫了個空列表(空數組),那么在使用這個數據屬性的地方就變成了你請求回來的數據 currentIndex:0, //為了讓我們上面渲染的第一個span標簽有個默認樣式 courseList:[], //存放免費課程頁里面的課程分類標簽頁對應的數據 courseId:0, //用來記錄用戶點擊的哪個課程標簽,根據這個id動態的往后端請求數據 } }, //發送請求獲取這個組件的數據,我們隨便找個網站,找個數據接口昂,比如這里用的是https://www.luffycity.com/courses這個網址里面的組件的數據接口,我們訪問這個網址,你在瀏覽器調試台的network的地方能看到,它發送了兩個請求,一個是請求了這個網址https://www.luffycity.com/api/v1/course_sub/category/list/,一個是請求了這個網址https://www.luffycity.com/api/v1/courses/?sub_category=0&ordering= //好,那么我們就通過axios來請求一下這兩個接口的數據,自己寫一個getCategoryList方法,封裝請求邏輯,在created鈎子函數中使用一下,直接在created里面寫請求邏輯也是可以的,只不過結構不太清晰而已 //我們發現,我們要請求的這兩個url'https://www.luffycity.com/api/v1/courses/?sub_category=0&ordering='和https://www.luffycity.com/api/v1/course_sub/category/list/,你會發現這些url的前面部分https://www.luffycity.com/api/v1/都是一樣的,能不能做一個公用的部分存起來啊,省去了很多的代碼,好,axios也提供了一個對應的方法defaults.baseURl,怎么用這個東西呢,在我們引入axios的方法配置,也就是在我們的main.js里面配置,看我們的main.js內容 methods:{ //獲取標題欄的數據 getCategoryList(){ // this.$https.get('https://www.luffycity.com/api/v1/course_sub/category/list/'),配置了公用的url,所以我們下面就寫這個公用url的后面的部分就可以了 // var _this = this; 可以先將this保存下來,下面的方法中也可以不寫成箭頭函數了,直接使用_this變量就是我們的當前組件對象 this.$https.get('course_sub/category/list/') .then((response)=>{ //注意我們寫箭頭函數,因為寫普通函數的話,里面使用this的時候,this指向的當前調用該方法的對象,就是axios,而我們像讓this指向定義當前對象的父類,也就是我們的當前組件對象,所以要改變一下this的指向,不用箭頭函數也能搞定,把this先保存一下,賦值給一個變量 // console.log(response); //響應數據的對象 // console.log(response.data.data); //響應回來的數據 // console.log(response.data.error_no); //響應數據的狀態 if (response.data.error_no === 0){ this.categoryList = response.data.data; let obj = { //因為我們的免費課程里面的子組件的標簽頁最前面還有個'全部'功能,所以把它加上 id:0, name:'全部', category:0, hide:false, }; this.categoryList.unshift(obj);//數組頭部追加元素 //數據刪除任意一個指定的元素,按照索引來刪除對應元素,splice(2,2,'a')方法還記得嗎,三個參數,第一個2是從索引2開始刪,第二個2,是刪除幾個,第三個'a',第三個參數可以不寫,就成了單純的刪除操作,是刪除元素的地方替換成'a',突然想起來了,就在這里提一嘴 } }) .catch((error)=>{ console.log('獲取數據失敗,錯誤是:',error);//后端返回的錯誤 }) }, //獲取標題欄中免費課程的標簽頁列表數據 getCourseList(){ // this.$https.get('courses/?sub_category=0&ordering=') 通過this.courseId的值來改變sub_category=0的這個0,來發送動態的請求,獲取不同課程標簽對應的不同的數據,別忘了用反引號,模板字符串,鍵盤上tab鍵上面那個鍵 this.$https.get(`courses/?sub_category=${this.courseId}&ordering=`) .then((response)=>{ // console.log(response); // console.log(response.data); // console.log(response.data.data); if (response.data.error_no === 0){ this.courseList = response.data.data; } }) .catch((error)=>{ console.log('標題欄中免費課程的標簽頁列表數據獲取失敗,錯誤是:',error) }) }, //點擊span標簽變css樣式的,並且發送另外一個這個標簽對應的請求,請求的url是https://www.luffycity.com/api/v1/courses/?sub_category=0&ordering=,這些url的數據是我們點擊免費課程那個組件的時候發送的請求,返回給我們的,然后我們渲染到這個我們這里點擊的每個span標簽的 clickCategoryHandler(val,course_id){ this.currentIndex = val; this.courseId = course_id; //將用戶點擊的課程標簽的id賦值給我們設置的這個數據屬性,將來通過這個數據屬性來動態的發送請求,執行下面的方法來發送請求 this.getCourseList(); //根據用戶點擊的課程標簽傳過來的courseid的值來動態獲取對應的數據 } }, //免費課程頁面剛加載完就發送了下面的兩個請求,拿到了數據,渲染在頁面上了,所以我們通過一個created鈎子函數搞一下 created(){ this.getCategoryList(); this.getCourseList(); } } </script> <style scoped> span{ padding: 0 20px; } span.active{ color:blue; } </style>
里面涉及到了mian.js文件的一個配置,我把main.js的代碼也放到這里吧:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import Axios from 'axios'
//看這里,看這里!!! Vue.prototype.$https = Axios; //Vue.prototype.名字(這個名字隨便起,一般是叫$http或者$https,那么一看就明白,你這是在往后端發送請求) //給Axios設置一個將來發送請求時的公用的url,那么將來通過axios請求網址時,就只需要寫這個公用url的后面的部分 Axios.defaults.baseURL = 'https://www.luffycity.com/api/v1/'; //axios官網上有 //引入element-ui import ElementUI from 'element-ui'; //引入element-ui的css樣式,這個需要單獨引入 import 'element-ui/lib/theme-chalk/index.css'; //給Vue添加一下這個工具 Vue.use(ElementUI); import '../static/global/index.css' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, template: '<App/>' })
vuex官網地址:https://vuex.vuejs.org/zh/guide/
vuex是什么呢?官方說法是這樣的:Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
其實vuex簡單來講就是一個臨時數據倉庫,能夠幫我們保存各個組件中的數據,也就是每個組件都能使用這個倉庫里面的數據,只要修改了數據倉庫里面的數據,使用了倉庫中這個數據的組件中的這個數據都會跟着變化,非常適合各組件的一個傳值,之前我們使用組件之間的傳值都是用的父子組件傳值或者平行組件傳值的方式,這個vuex幫我們做了一個更方便的數據倉庫來實現組件傳值和各組件的數據共享,但是小型項目一般用它比較合適,大型項目還是用組件傳值的方式,因為這個倉庫對於大型項目來講,比較笨重,共享數據量太大了也會對頁面性能有所影響,所以我們今天學的這個vuex不是說必須要用的,還是看需求和業務場景的。
來看一個圖:
通過上面這個圖我相信大家應該很清楚vuex是做什么的,好,既然涉及到組件往里面存值、取值、修改值的操作,那么我們就來看看怎么完成這些操作。
vuex就相當於一個倉庫(store),首先我們說一下vuex的五大悍將:
state:狀態,也就是共享數據
mutations:更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似於事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是我們實際進行狀態更改的地方,並且它會接受 state 作為第一個參數,但是所有修改數據的操作通過mutations都是同步(串行)操作,目的應該是為了保證數據安全,大家應該能夠發現,多個組件可能都會執行修改一個共享數據的操作(異步操作,定時器,ajax都是異步的),那么異步操作共享數據,就容易出現數據混亂的問題,所以凡是通過mutations的數據操作都變成了同步狀態。
actions:Action 類似於 mutation,不同在於:Action 提交的是 mutation,也就是提交的是mutations中的函數(方法),而不是直接變更狀態(共享數據)。Action 可以包含任意異步操作,也就是說異步操作可以同時提交給actions,通過dispatch方法,但是action提交數據的操作必須經過mutations,通過commit方法提交給mutations,又變成了同步,為了數據安全可靠不混亂。也就是說操作數據的方式有兩個: 1.操作數據--> commit -->mutations(同步) 2.操作數據--> dispatch -->actions(異步)--> commit -->mutations(同步),如果將異步任務直接交給mutations,就會出現數據混亂不可靠的問題。
getters:相當於store的計算屬性
modules:模塊
getters和modules不常用,我們后面的項目也用不到,所以這里就不做詳細的解釋了,我們只要學習前三個悍將(state、mutations、actions),這些悍將都是寫在我們創建的store對象里面的屬性,具體他們幾個怎么搞,先看圖:
首先下載安裝,項目目錄下在終端執行一下下面的指令:
npm i vuex -S
由於vuex這個倉庫所有的組件都要使用,所以我們需要將它掛載到我們的全局vue實例里面,vue實例我們是在main.js里面引入並創建的,所以我們看main.js代碼如下:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import Axios from 'axios' Vue.prototype.$https = Axios; Axios.defaults.baseURL = 'https://www.luffycity.com/api/v1/'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); import '../static/global/index.css' Vue.config.productionTip = false; //引入vuex import Vuex from 'vuex' Vue.use(Vuex); //必須use一下 //創建vuex的store實例,這個實例就是我們的vuex倉庫 const store = new Vuex.Store({ //這里面使用vuex的五大悍將 state:{ num:1, //state狀態有個num=1,也就是有個共享數據num=1,組件中使用它 }, mutations:{ //mutations里面的函數第一個參數就是上面這個state屬性,其他的參數是調用這個這函數時給傳的參數 setNum(state,val){ // console.log(val); state.num += val; }, }, //此時還沒有用到actions異步提交倉庫的一些數據操作的任務 actions:{ } }); /* eslint-disable no-new */ new Vue({ el: '#app', router, store, // 必須將創建的vuex.Store對象掛載到vue實例中,那么相當於給我們的vue對象添加了一個$store屬性,將來組件中通過this.$store就能調用這個對象,this雖然是組件對象,但是組件對象都是相當於繼承了vue對象,還記得router嗎,使用router的時候有兩個對象,通過vue對象調用,this.$router,this.$route,和它類似的用法 components: { App }, template: '<App/>' });
然后我們創建兩個組件,一個叫做Home.vue,一個叫做Son.vue,這個Son組件是Home組件的子組件,我們就玩一下這兩個組件對vuex倉庫的一個共享數據的一系列操作,下面完成了組件從倉庫中取值(必須通過組件的計算屬性完成這個事情),還有通過mutations來完成的一個同步方式修改數據的操作,看代碼:
Home.vue文件的代碼如下:
<template> <div> 這是Home頁面 <h1>我是父組件中的 {{ myNum }}</h1> <Son></Son> </div> </template> <script> //引入子組件 import Son from '../son/Son' export default { name: 'Home', data() { return { } }, //掛載Son子組件 components:{ Son, }, //組件中想操作store中的數據,必須通過計算屬性來監聽vuex的store中的state里面的數據,組件中才能獲取到並使用store中的這個數據 computed:{ //在模板語法中使用{{ myNum }},就能用myNum對應函數里面的返回值 myNum:function () { // console.log(this);//還記得這個this指的是誰嗎,就是當前的組件對象,組件是不是都相當於繼承於我們的vue對象,而store又掛載到了我們的vue實例上,那么通過組件對象.$store就能獲得咱們的vuex倉庫store return this.$store.state.num //獲取到我們store中的state里面的數據 } }, } </script> <style> </style>
Son.vue文件代碼如下:我們在子組件中來個button按鈕,點擊一下這個按鈕,就給store倉庫中的數據num加1,然后Home組件中使用了這個num地方的num值也自動跟着發生改變,實現了一個父子組件傳值的效果。
<template> <div> <h2>我是子組件 {{ sonNum }}</h2> <button @click="addNum">同步修改num+1</button> </div> </template> <script> export default { name:'Son', data(){ return{ } }, //Son子組件中使用一下store中的數據 computed:{ sonNum:function () { console.log(this); return this.$store.state.num } }, methods:{ //給button綁定了一個點擊事件,這個事件做的事情是點擊一下,給store中的state中的num + 1 addNum(){ //不能直接修改store中的數據,這是vuex限定死的,必須通過mutations里面的方法來修改 this.$store.commit('setNum',1) //注意,commit里面的第一個參數是我們在vuex的store對象中的mutations里面定義的方法,第二個參數1就是要傳給mutations中的setNum函數的數據,也就是第二個參數val }, } } </script> <style></style>
然后我們看頁面效果:
這就是我們直接通過mutations中的函數完成的一個同步修改數據的操作,
下面我們再來一個通過mutations中的函數完成做一個異步修改數據的操作,看一下上面說的會造成數據不可靠的問題的效果:
首先看main.js的內容如下:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import Axios from 'axios' Vue.prototype.$https = Axios; Axios.defaults.baseURL = 'https://www.luffycity.com/api/v1/'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); import '../static/global/index.css' Vue.config.productionTip = false; //引入vuex import Vuex from 'vuex' Vue.use(Vuex); //必須use一下 //創建vuex的store實例,這個實例就是我們的vuex倉庫 const store = new Vuex.Store({ //這里面使用vuex的五大悍將 state:{ num:1, //state狀態有個num=1,也就是有個共享數據num=1,組件中使用它 }, mutations:{//異步數據操作要執行的對應的mutations中的函數 setNumAsync(state,val){ setTimeout(()=>{ //還記得嗎,定時器是個異步任務,你頁面隨便操作,它自己玩自己的 state.num += val; },1000) } }, actions:{ } }); /* eslint-disable no-new */ new Vue({ el: '#app', router, store, // 必須將創建的vuex.Store對象掛載到vue實例中,那么相當於給我們的vue對象添加了一個$store屬性,將來組件中通過this.$store就能調用這個對象,this雖然是組件對象,但是組件對象都是相當於繼承了vue對象,還記得router嗎,使用router的時候有兩個對象,通過vue對象調用,this.$router,this.$route,和它類似的用法 components: { App }, template: '<App/>' });
Home.vue中的代碼沒有變化,還是之前的代碼,不用改,所以我們直接看Son.vue文件中的代碼如下:
<template> <div> <h2>我是子組件 {{ sonNum }}</h2> <button @click="addNumAsync">異步修改num+5</button> </div> </template> <script> // <button @click="addNum">同步修改num+1</button> export default { name:'Son', data(){ return{ } }, //Son子組件中使用一下store中的數據 computed:{ sonNum:function () { console.log(this); return this.$store.state.num } }, methods:{ //給button綁定了一個點擊事件,這個事件做的事情是點擊一下,給store中的state中的num + 1 addNum(){ //不能直接修改store中的數據,這是vuex限定死的,必須通過mutations里面的方法來修改 this.$store.commit('setNum',1) //注意,commit里面的第一個參數是我們在vuex的store對象中的mutations里面定義的方法,第二個參數1就是要傳給mutations中的setNum函數的數據,也就是第二個參數val }, //異步提交數據加1操作 addNumAsync(){ this.$store.commit('setNum',5) //每次都加5 } } } </script> <style></style>
效果如下:
所以,我們提交異步任務操作數據的時候,必須遵循人家vuex說的,異步的任務先提交給actions,再有actions提交給mutations,那么將來,同步的數據操作,我們也是先給actions,再給mutations,在mutations中進行數據操作,看代碼,將異步和同步操作放到actions中:
mian.js文件代碼如下:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import Axios from 'axios' Vue.prototype.$https = Axios; Axios.defaults.baseURL = 'https://www.luffycity.com/api/v1/'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); import '../static/global/index.css' Vue.config.productionTip = false; //引入vuex import Vuex from 'vuex' Vue.use(Vuex); //必須use一下 //創建vuex的store實例,這個實例就是我們的vuex倉庫 const store = new Vuex.Store({ //這里面使用vuex的五大悍將 state:{ num:1, //state狀態有個num=1,也就是有個共享數據num=1,組件中使用它 }, mutations:{ //mutations里面的函數第一個參數就是上面這個state屬性,其他的參數是調用這個這函數時給傳的參數 // setNum(state,val){ // // console.log(val); // state.num += val; // }, muNum(state,val){ // console.log(val); state.num += val; }, //在actions中調用這個方法,還是mutations中這個方法來進行數據操作 muNumAsync(state,val){ state.num += val; } }, actions:{ //同步數據操作,我們也通過actions來提交給mutations setNum(context,val){ context.commit('muNum',val); }, //異步數據操作要執行的對應的actions中的函數,actions中的函數在調用mutations中的函數,actions中的函數的第一個參數是我們的store對象 setNumAsync(context,val){ setTimeout(()=>{ //還記得嗎,定時器是個異步任務,你頁面隨便操作,它自己玩自己的 context.commit('muNumAsync',val); },1000) } } }); /* eslint-disable no-new */ new Vue({ el: '#app', router, store, components: { App }, template: '<App/>' });
Home.vue組件沒有改動,所以我只把改動的Son.vue組件的代碼拿出來了,看代碼:
<template> <div> <h2>我是子組件 {{ sonNum }}</h2> <button @click="addNum">同步修改num+1</button> <button @click="addNumAsync">異步修改num+5</button> </div> </template> <script> // <button @click="addNum">同步修改num+1</button> export default { name:'Son', data(){ return{ } }, //Son子組件中使用一下store中的數據 computed:{ sonNum:function () { console.log(this); return this.$store.state.num } }, methods:{ //給button綁定了一個點擊事件,這個事件做的事情是點擊一下,給store中的state中的num + 1 addNum(){ //同步的數據操作,也通過dispatch方法調用actions中的函數,將數據操作提交給actions中的函數 this.$store.dispatch('setNum',1) }, //異步提交數據加1操作 addNumAsync(){ //為了必須通過actions來提交數據操作,這里面我們就不能直接commit了,而是用dispatch方法調用actions中的函數,將數據操作提交給actions中的函數 // this.$store.commit('setNum',5) this.$store.dispatch('setNumAsync',5) //每次都加5,setNumAsync是actions中的函數 } } } </script> <style></style>
然后看頁面效果:
然后看一下上面代碼的邏輯圖:
vuex用起來比較麻煩,但是對於組件傳值來說方便了一些,哈哈,也沒太大感覺昂,沒關系,用熟練了就好了,現在再回去看一下vuex的那個圖,應該就清晰了。好,這就是我們講到的vuex倉庫store,我們使用vuex來搞一搞異步操作,完成的事情就是點擊對應課程,展示課程詳細信息:
先看目錄結構:
然后我們直接上代碼了
Coures.vue代碼如下:
<template> <!--<div>--> <!--這是Course頁面--> <!--</div>--> <div> <div class="categorylist"> <span v-for="(item,index) in categoryList" :key="item.id" :class="{active:currentIndex===index}" @click="clickCategoryHandler(index,item.id)">{{ item.name }}</span> </div> <div class="course"> <ul> <li v-for="(course,index) in courseList" :key="course.id"> <h3 @click="showDetail(course.id)">{{ course.name }}</h3> <!-- 點擊它,跳轉到對應課程的詳情數據頁面 --> </li> </ul> </div> </div> </template> <script> export default { name: 'Course', data(){ return{ categoryList:[],//用來接收請求回來的數據,一般都和我們后端返回回來的數據的數據類型對應好,所以我寫了個空列表(空數組),那么在使用這個數據屬性的地方就變成了你請求回來的數據 currentIndex:0, //為了讓我們上面渲染的第一個span標簽有個默認樣式 courseList:[], //存放免費課程頁里面的課程分類標簽頁對應的數據 courseId:0, //用來記錄用戶點擊的哪個課程標簽,根據這個id動態的往后端請求數據 } }, methods:{ getCategoryList(){ this.$https.get('course_sub/category/list/') .then((response)=>{ if (response.data.error_no === 0){ this.categoryList = response.data.data; let obj = { id:0, name:'全部', category:0, hide:false, }; this.categoryList.unshift(obj);//數組頭部追加元素 } }) .catch((error)=>{ console.log('獲取數據失敗,錯誤是:',error);//后端返回的錯誤 }) }, //獲取標題欄中免費課程的標簽頁列表數據 getCourseList(){ this.$https.get(`courses/?sub_category=${this.courseId}&ordering=`) .then((response)=>{ if (response.data.error_no === 0){ this.courseList = response.data.data; } }) .catch((error)=>{ console.log('標題欄中免費課程的標簽頁列表數據獲取失敗,錯誤是:',error) }) }, //點擊span變色,獲取對應的數據 clickCategoryHandler(val,course_id){ this.currentIndex = val; this.courseId = course_id; this.getCourseList(); }, //第一步:查看課程詳細信息,獲取詳細信息數據,第二步要配置路由了 showDetail(val){ //跳轉路由,加載對應組件 this.$router.push({ name:'coursedetail', //傳的params參數 params:{ cid:val, } }); } }, created(){ this.getCategoryList(); this.getCourseList(); } } </script> <style scoped> span{ padding: 0 20px; } span.active{ color:blue; } </style>
路由配置信息index.js文件代碼如下:
import Vue from 'vue' import Router from 'vue-router' // import HelloWorld from '@/components/HelloWorld' //@還記得嗎,表示src文件夾的根路徑 //引入組件 import Course from '@/components/Course/Course' import Home from '@/components/Home/Home' import CourseDetail from '@/components/CourseDetail/CourseDetail' // console.log(Course); //給Vue添加vue-router功能,使用別人提供的功能都要用Vue來use一下 Vue.use(Router) //創建路由對象 export default new Router({ mode:'history', //去掉瀏覽器地址欄的#號 //配置路由信息 routes: [ { path: '/', // redirect:'Home' //直接跳轉Home名字對應的path路徑上 redirect:'/home' }, { path: '/home', name: 'Home', component: Home }, { path: '/course', name: 'Course', component: Course }, //第二步配置路由! { path: '/course/:cid/payment_info/', //動態params的動態路由 name: 'coursedetail', component: CourseDetail } ] })
課程詳細信息插件CoureDetail.vue文件代碼如下:
<template> <div> 我是課程詳情組件 <h3> {{ courseDetailShow }} <!-- 簡單展示了詳細信息數據中的一個name數據 --> </h3> </div> </template> <script> export default { name:'coursedetail', data(){ return{} }, //第三步,調用actions中的函數,將courseid作為參數給他 created(){ let cid = this.$route.params.cid; this.$store.dispatch('getDetailAsync',cid); }, //第七步:在模板中使用vuex倉庫中修改后的數據 computed:{ courseDetailShow(){ console.log(this.$store.state.detailList); return this.$store.state.detailList; } } } </script> <style></style>
mian.js代碼如下:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import Axios from 'axios' Vue.prototype.$https = Axios; Axios.defaults.baseURL = 'https://www.luffycity.com/api/v1/'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); import '../static/global/index.css' Vue.config.productionTip = false; //引入vuex import Vuex from 'vuex' Vue.use(Vuex); //必須use一下 //創建vuex的store實例,這個實例就是我們的vuex倉庫 const store = new Vuex.Store({ //這里面使用vuex的五大悍將 state: { num: 1, detailList:'' }, mutations: { muNum(state, val) { // console.log(val); state.num += val; }, muNumAsync(state, val) { state.num += val; }, //第六步:修改state中的數據 getCourseDetail(state, val) { // state.detailList = val; state.detailList = val.data.name; //這里我只是簡單的獲取了一下詳情信息中的name屬性的數據 console.log('?????',state.detailList) } }, actions: { setNum(context, val) { context.commit('muNum', val); }, setNumAsync(context, val) { setTimeout(() => { context.commit('muNumAsync', val); }, 1000) }, //第四步:提交獲取課程詳細信息的數據操作 getDetailAsync(context, val) { Axios.get(`course/${val}/payment_info/`) .then((response) => { // console.log(response.data); //第五步:調用mutations中的方法 context.commit('getCourseDetail', response.data); }) .catch((error) => { console.log(error); }); } } }); /* eslint-disable no-new */ new Vue({ el: '#app', router, store, // 必須將創建的vuex.Store對象掛載到vue實例中,那么相當於給我們的vue對象添加了一個$store屬性,將來組件中通過this.$store就能調用這個對象,this雖然是組件對象,但是組件對象都是相當於繼承了vue對象,還記得router嗎,使用router的時候有兩個對象,通過vue對象調用,this.$router,this.$route,和它類似的用法 components: {App}, template: '<App/>' });
代碼流程圖如下:
再補充一點:
將Home組件組成全局組件,在main.js中做,看main.js內容:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import Axios from 'axios' Vue.prototype.$https = Axios; Axios.defaults.baseURL = 'https://www.luffycity.com/api/v1/'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); import '../static/global/index.css' Vue.config.productionTip = false; //引入vuex import Vuex from 'vuex' //將Home組件做成全局組件的方法,看這里看這里!!!! import Home from './components/Home/Home' Vue.component(Home.name,Home); Vue.use(Vuex); //必須use一下 //創建vuex的store實例,這個實例就是我們的vuex倉庫 const store = new Vuex.Store({ //這里面使用vuex的五大悍將 state: { num: 1, detailList:'' }, mutations: { muNum(state, val) { // console.log(val); state.num += val; }, muNumAsync(state, val) { state.num += val; }, //第六步:修改state中的數據 getCourseDetail(state, val) { // state.detailList = val; state.detailList = val.data.name; console.log('?????',state.detailList) } }, actions: { setNum(context, val) { context.commit('muNum', val); }, setNumAsync(context, val) { setTimeout(() => { context.commit('muNumAsync', val); }, 1000) }, //第四步:提交獲取課程詳細信息的數據操作 getDetailAsync(context, val) { Axios.get(`course/${val}/payment_info/`) .then((response) => { // console.log(response.data); //第五步:調用mutations中的方法 context.commit('getCourseDetail', response.data); }) .catch((error) => { console.log(error); }); } } }); /* eslint-disable no-new */ new Vue({ el: '#app', router, store, // 必須將創建的vuex.Store對象掛載到vue實例中,那么相當於給我們的vue對象添加了一個$store屬性,將來組件中通過this.$store就能調用這個對象,this雖然是組件對象,但是組件對象都是相當於繼承了vue對象,還記得router嗎,使用router的時候有兩個對象,通過vue對象調用,this.$router,this.$route,和它類似的用法 components: {App}, template: '<App/>' });
在Course.vue組件中使用這個組件
<template> <!--<div>--> <!--這是Course頁面--> <!--</div>--> <div> <div class="categorylist"> <span v-for="(item,index) in categoryList" :key="item.id" :class="{active:currentIndex===index}" @click="clickCategoryHandler(index,item.id)">{{ item.name }}</span> </div> <div class="course"> <ul> <li v-for="(course,index) in courseList" :key="course.id"> <h3 @click="showDetail(course.id)">{{ course.name }}</h3> <!-- 點擊它,跳轉到對應課程的詳情數據頁面 --> </li> </ul> </div> <div> <!-- 看這里看這里,全局組件不需要掛載,直接在template中使用 --> <Home></Home> </div> </div> </template> <script> export default { name: 'Course', data(){ return{ categoryList:[],//用來接收請求回來的數據,一般都和我們后端返回回來的數據的數據類型對應好,所以我寫了個空列表(空數組),那么在使用這個數據屬性的地方就變成了你請求回來的數據 currentIndex:0, //為了讓我們上面渲染的第一個span標簽有個默認樣式 courseList:[], //存放免費課程頁里面的課程分類標簽頁對應的數據 courseId:0, //用來記錄用戶點擊的哪個課程標簽,根據這個id動態的往后端請求數據 } }, methods:{ getCategoryList(){ this.$https.get('course_sub/category/list/') .then((response)=>{ if (response.data.error_no === 0){ this.categoryList = response.data.data; let obj = { id:0, name:'全部', category:0, hide:false, }; this.categoryList.unshift(obj);//數組頭部追加元素 } }) .catch((error)=>{ console.log('獲取數據失敗,錯誤是:',error);//后端返回的錯誤 }) }, //獲取標題欄中免費課程的標簽頁列表數據 getCourseList(){ this.$https.get(`courses/?sub_category=${this.courseId}&ordering=`) .then((response)=>{ if (response.data.error_no === 0){ this.courseList = response.data.data; } }) .catch((error)=>{ console.log('標題欄中免費課程的標簽頁列表數據獲取失敗,錯誤是:',error) }) }, //點擊span變色,獲取對應的數據 clickCategoryHandler(val,course_id){ this.currentIndex = val; this.courseId = course_id; this.getCourseList(); }, //第一步:查看課程詳細信息,獲取詳細信息數據,第二步要配置路由了 showDetail(val){ //跳轉路由,加載對應組件 this.$router.push({ name:'coursedetail', //傳的params參數 params:{ cid:val, } }); } }, created(){ this.getCategoryList(); this.getCourseList(); } } </script> <style scoped> span{ padding: 0 20px; } span.active{ color:blue; } </style>
最后給大家說一些做單頁面應用的問題:
通過vue這種框架做的單頁面應用,如果沒有做vue服務器渲染(類似於django的模板渲染),SEO很難搜索到你的網站,爬蟲也不能直接在你的頁面上爬出數據,但是即便是單頁面應用,前端頁面的數據不是前端寫死的,就是從接口獲取的,我們找接口就行了,接口的數據不能完全滿足你,你在爬頁面上的。
那SEO問題怎么辦,nodejs服務器(前端做),,或者后端服務器配合渲染,前端和后端都要做vue,相互渲染來完成頁面解析,讓SEO能搜索到你所有的頁面,有缺陷,如果在公司寫這種單頁面應用,一定要注意SEO的問題,那有這么多問題,怎么還用單頁應用啊,單頁應用可以提高用戶體驗,防止白屏現象,頁面更好維護。
那么SEO是什么呢,我找了一些資料給大家,看看就明白了,頁面越多,百度收錄的機會越大

1.收錄和索引分別指什么呢? 收錄:頁面被百度蜘蛛發現、分析過,認為頁面存在價值,進行收錄處理 索引:百度蜘蛛抓取頁面后,經初步分析后,認為頁面內容對用戶有意義,做建庫處理 簡而言之,百度建的數據庫就好比一個圖書館,這座圖書館並不會收錄所有書(頁面),要根據書本身的質量和價值,來決定是否收錄;並不是所有收錄的頁面,圖書館都會外借(索引)。所以,網站要獲得好的排名,第一步就是提升收錄和索引:只有在這個圖書館里的書足夠多,外借的可能性才會提高,才有可能得到百度的特別推薦(排名首頁)。 2.收錄和索引有什么聯系呢? 收錄和索引是包含和被包含的關系,頁面必須先被收錄才可以被索引,換而言之,就是書必須放進了圖書館,才有可以能被讀者借走。所以,一般情況下,網站的收錄量大於索引量。 3.收錄的價值 (1)收錄是索引的前提 網站上線以后,站點需有穩定的服務器、正確的robots,百度蜘蛛才可以對網站頁面進行抓取。 (2)權重傳遞只發生在收錄頁面 沒有收錄的頁面,不會有權重產生。針對需刪除的頁面,如果已被百度收錄的頁面,需對新舊頁面進行301跳轉處理。做301處理,可為已收錄頁面完成權值權重傳遞和流量切換。 4.索引的價值 (1)只有網頁進入索引庫,才有獲得流量的機會 網頁雖然被建入索引庫,但獲得流量的機會並不同,無效索引很難獲得流量。只有索引以后,用戶才可以通過關鍵字搜索,在結果中找到頁面。 (2)只有進入索引庫,頁面才能被檢索 新聞源站點(新聞源目錄)內的鏈接,必須先被網頁庫建索引,才有機會出現在新聞檢索中 5.如何提升收錄量和索引量 (1)網站更新頻繁 網站內容更新的頻次,很大程度會影響百度蜘蛛抓取網站頁面的周期。更新內容頻繁的網站,蜘蛛抓取的周期更短,頁面收錄提升。 (2)內容原創度高 百度喜歡原創的東西,原創都越高,頁面越容易被收錄。 (3)百度站長平台鏈接提交 鏈接提交百度站長后台是提升網站收錄和索引最簡單粗暴的方式。所以,通常每個網站完成優化后,SEOer們會主動提交站點地圖給百度,讓百度蜘蛛自己來抓。 (4)發外鏈 外鏈能直接吸引百度蜘蛛過來,使蜘蛛訪問網站的頻率提高。優質的外鏈,不僅可以促進頁面的收錄、提升頁面權重,而且對關鍵詞的排名也有一定的影響。 6.如何查詢收錄量和索引量 目前,百度站長后台是可以查詢網站索引量的,但無法查詢網站的收錄情況。不過,在愛站和中國站長等第三方工具,均有提供各個網站收錄查詢的工具,是否准確不置可否。
解決SEO的大概思路,看圖:
還有一點跟大家說一下,以后我們通過腳手架的webpack模塊創建完項目之后,下面這幾個必須要有,沒有的自己npm安裝一下:
直接看代碼吧:
main.js代碼如下:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' Vue.config.productionTip = false; //平行組件傳值,通過bus對象,然后我們將bus對象掛載到Vue對象上,那么其他組件通過this.$bus就能用這個公交車對象了 let bus = new Vue(); Vue.prototype.$bus = bus; /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, template: '<App/>' });
App.vue組件代碼如下:
<template> <div id="app"> <router-view/> </div> </template> <script> export default { name: 'App' } </script> <style> </style>
Home.vue組件代碼如下:
<template> <div> 我是Home組件 <!--<Son title="Jaden"></Son> 傳靜態數據給子組件 --> <Son :title="title" :msg="msg" @handler="handlerClick"></Son> <!-- 傳動態數據給子組件 --> <button @click="busHandler">通過bus對象傳值</button> </div> </template> <script> import Son from './Son' export default { name:'Home', data(){ return{ title:'test1', msg:'test2', } }, components:{ Son, }, methods:{ handlerClick(val){ this.msg = val; }, //平行組件傳值 busHandler(){ this.$bus.$emit('homeClick',2); }, } } </script> <style></style>
Son.vue組件代碼如下,Son.vue組件是Home組件的父級組件,我們除了做了父子、子父組件傳值外還做了平行組件傳值,都在這幾個文件的代碼里面了
<template> <div> 我是Son組件 {{ msg }} -- {{ title }} -- {{ number }} </div> </template> <script> export default { name:'Son', data(){ return{ number:0, } }, props:['msg','title'], created(){ this.$emit('handler',1); this.$bus.$on('homeClick',(val)=>{ this.number = val; }) } } </script> <style></style>
router路由配置信息的index.js內容如下:
import Vue from 'vue' import Router from 'vue-router' import Home from '@/components/Home/Home' Vue.use(Router); export default new Router({ mode:'history', routes: [ { path: '/', redirect:{name:'Home'} }, { path: '/', name: 'Home', component: Home } ] })