上一個項目:仿 CNODE 社區 剛完成,感受有點意猶未盡,對於 登陸 這一塊老師並無展開,我先是用了 localStorage 本身瞎搞,跑通以後想了下,vuex 不是專門作全局狀態管理的麼?那麼用 vuex 作登陸是最合適不過的呀。因而又搜了些別人用 vuex 作登陸狀態管理的案例,算是搞明白了。javascript
如今選擇了若愚老師的這個項目,主要是鞏固一下對 vue 的認識,同時對 vuex 作個更詳細的瞭解。css
本項目作一款多人共享博客,包含首頁、用戶文章列表、登陸、註冊、我的管理、編輯、發佈等功能。html
測試帳號: hunger10086vue
測試密碼: 123456java
項目連接:GitHubwebpack
預覽連接:Git Pagesios
實現功能:git
使用 Vue.js 技術棧:vue-cli / vue2 / axios / vue-router /vuex / es6 / webpack / element-uies6
博客主要記錄項目完成過程當中學習到的知識點,其餘的就一筆帶過了。github
老套路了..使用 vue-cli 建立項目骨架
在 vue 項目中使用 less: <style scoped lang='less'></style>
若是要在某個組件中引入 less 文件,則在 style 中寫入 @import '../assets/base.less';
便可(記得 npm 裝上 less 和 less-loader 哦)
ElementUI 的有很詳細的安裝使用文檔
主要步驟:
1.安裝 cnpm i element-ui
2.引入
import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css';
3.掛載到 Vue Vue.use(ElementUI)
而後就能夠在組件中使用 element-ui 了。
另外,若愚老師前期還對 axios 底層請求作了進一步的定製和封裝,其中一些技巧很值得學習。
1.先把 axios 請求封裝成了輸入參數更簡潔明瞭、報錯信息更「人性化」的 Promise 對象。
// /helpers/request.js axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded' axios.defaults.baseURL = 'http://blog-server.hunger-valley.com' axios.defaults.withCredentials = true export default function request(url, type = 'GET', data = {}) { return new Promise((resolve, reject) => { let option = { url, method: type } if(type.toLowerCase() === 'get') { option.params = data }else { option.data = data } axios(option).then(res => { console.log(res.data) if(res.data.status === 'ok') { resolve(res.data) }else{ Message.error(res.data.msg) reject(res.data) } }).catch(err => { Message.error('網絡異常') reject({ msg: '網絡異常' }) }) }) }
2.再把獲取數據的 API 進行封裝,使其更易用:
import request from '@/helpers/request' const URL = { REGISTER: '/auth/register', LOGIN: '/auth/login', LOGOUT: '/auth/logout', GET_INFO: '/auth' } export default { // 註冊 register({username, password}) { return request(URL.REGISTER, 'POST', { username, password }) }, // 登陸 login({username, password}) { return request(URL.LOGIN, 'POST', { username, password }) }, // 登出 logout() { return request(URL.LOGOUT) }, // 獲取信息 getInfo() { return request(URL.GET_INFO) } }
這樣子處理的話,登陸請求就能夠不使用 axios('http://blog-server.hunger-valley.com/auth/login','POST',{username,password})
那麼繁瑣了,直接 auth.login({username,password})
就完事了~
能夠查看此 commit
使用 grid 進行佈局。
關於 grid 佈局以前有了解過,grid 經過在頁面上劃分 columns 和 rows ,而後把內容分別放進不一樣區域來創建佈局,也寫過 demo,但真正在項目中使用仍是第一次。關於 grid 的教程能夠參考這裏
如在項目中的使用:
#app { display: grid; // 分紅三列,左右列寬度分別是頁面的12%,中間內容寬度自適應 grid-template-columns: 12% auto 12%; // 分紅三行,上下行高度自適應,中間內容佔滿剩餘寬度 grid-template-rows: auto 1fr auto; // 劃分區域 grid-template-areas: "header header header" ". main . " "footer footer footer"; #header{ grid-area: header; padding-left: 12%; padding-right: 12%; } #main{ grid-area: main; } #footer{ grid-area: footer; padding-left: 12%; padding-right: 12%; } }
能夠查看此 commit
在完成項目的過程當中接觸到了 async/await :
async/await 是異步編程的一種解決方案。
async 聲明一個函數爲異步函數,這個函數返回的是一個 Promise 對象;
await 用於等待一個 async 函數的返回值(注意到 await 不只僅用於等 Promise 對象,它能夠等任意表達式的結果,因此,await 後面實際是能夠接普通函數調用或者直接量的。)
以項目中用戶註冊爲例:
// 聲明 register 爲一個異步函數,他會返回一個 Promise 對象 async register({commit},{username,password}){ // 用戶註冊成功後後會返回的一個 Promise 對象,其中包含了用戶的信息,let res 就是異步 auth.register 獲取的結果 let res = await auth.register({username,password}) commit('setUser',{user:res.data}) commit('setLogin',{isLogin:true}) // 把 res.data 返回出去,使用 register() 後就能夠用 then 來處理這個結果 return res.data },
對於 async/await,我參考了 邊城 在 segmentfault 中的這邊文章。
如何在項目中使用 vuex 管理狀態?(以登陸爲例)
因爲使用單一狀態樹,應用的全部狀態會集中到一個比較大的對象。當應用變得很是複雜時,store 對象就有可能變得至關臃腫。
爲了解決以上問題,Vuex 容許咱們將 store 分割成模塊(module)。每一個模塊擁有本身的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行一樣方式的分割
在這裏針對不一樣的內容狀態管理劃分到不一樣的文件,保持可讀性:
store | │ index.js // 引入子模塊 │ └─modules auth.js // 負責用戶註冊、登陸狀態 blog.js // 負責用戶獲取、發佈、修改博客等
把負責用戶註冊登陸的 state 寫在了 auth.js:
// auth.js import auth from '@/api/auth' const state = { // 先定義一個默認的用戶狀態 user:null, //登陸狀態 isLogin:false } const getters = { // 獲取 state 數據 user:state => state.user, isLogin:state => state.isLogin } const mutations = { // 更新用戶數據 setUser(state,payload){ state.user = payload.user }, // 更新用戶登陸狀態 setLogin(state,payload){ state.isLogin = payload.isLogin } } const actions = { ... // 檢測用戶是否登陸 async checkLogin({commit,state}){ // 先從本地store的state去看用戶是否登陸,若是登陸了 就返回true if(state.isLogin) return true let res = await auth.getInfo() commit('setLogin',{isLogin:res.isLogin}) // 若是本地沒有這個狀態,就發ajax請求去服務器,服務器會返回一個isLogin的響應,根據這個值來肯定是否登陸 if(!res.isLogin) return false commit('setUser',{user:res.data}) // 最後的 return true 是爲了在實例中then拿到這個true,方便作下一步處理 return true }, // 用戶登陸 {commit} 是默認參數,至關於 context.commit,使用了 ES6 的參數結構 login({commit},{username,password}){ // 調用底層接口,返回的是一個 Promise 對象 return auth.login({username,password}) .then(res => { // 把經過 axios 獲取回來的用戶數據提交 mutation,更新到 state: commit -> setUser -> state commit('setUser',{user:res.data}) commit('setLogin',{isLogin:true}) }) }, ... } export default { state, getters, mutations, actions }
// index.js import Vue from 'vue' import Vuex from 'vuex' import auth from './modules/auth' Vue.use(Vuex) export default new Vuex.Store({ modules:{ auth, } })
在 Login.vue 中,咱們要作的事情就是:點擊按鈕後,調用 auth.js 中的 login()
方法,完成登陸,更新 state 中的數據,並給須要的組件更新狀態(如 header.vue)
首先映射 login 方法到此組件,這樣此組件就能夠經過 this.login
來調用這個 auth.js 中的方法了:
//Login.vue import {mapActions} from 'vuex' export default { name:'Login', methods:{ ...mapActions([ 'login' ]), }, }
接着設置點擊事件,點擊按鈕會執行 onLogin,調用 this.login ,發送 axios 請求 auth.login({username,password})
,成功註冊後 commit mutation,更新 state 數據,跳轉到首頁:
<el-button size="medium" @click="onLogin">當即登陸</el-button>
//Login.vue methods:{ ...mapActions([ 'login' ]), onLogin(){ this.login({username:this.username,password:this.password}) .then(()=>{ console.log(`${this.username},${this.password}`) this.$router.push({path:'/'}) }) } },
在 Header 中,登陸和未登陸,他的樣式在兩種狀態下是不同的:
未登陸的時候,header 會顯示提示登陸和註冊的按鈕;登陸以後,header 會顯示用戶頭像及其餘操做選項。
而這兩種狀態的切換,就要依靠咱們的 state 了,首先引入映射 {mapGetters,mapActions}
,在頁面還未渲染的時候檢查 state 中用戶登陸狀態,用戶已登陸,則返回 isLogin = true
,獲取用戶信息,渲染到頁面上;用戶未登陸,則返回 isLogin = false
。
<header :class="{login:isLogin,'no-login':!isLogin}">
// Header.vue import {mapGetters,mapActions} from 'vuex' export default { name:'Header', // 把 store 中 getter 屬性映射到此組件 computed:{ ...mapGetters([ 'isLogin', 'user' ]) }, //在頁面沒有渲染以前檢查用戶是否登陸 created(){ this.checkLogin() }, methods:{ // 把 auth.js 中的 checkLogin 方法映射到此組件 ...mapActions([ 'checkLogin' ]), },
這就是 vuex 在在項目中管理登陸狀態了。
添加路由元信息。
項目中有一些頁面,好比添加文章、編輯文章等等,都須要先確認用戶是否登陸才能操做,不然將會自動跳轉到登陸頁。
路由元信息作的就是這樣一件事情,咱們給某段路由添加一個 meta 字段 meta:{ requiresAuth:true }
,這段路由路由匹配到的全部路由記錄會暴露爲 $route 對象 (還有在導航守衛中的路由對象) 的 $route.matched 數組。經過遍歷 $route.matched 來檢查路由記錄中的 meta 字段,對訪問的路徑作一個狀態檢查,從而肯定是否容許訪問。
const router = new Router({ routes: [ ... { path: '/Create', component: () =>import ('@/pages/Create/Create'), // 路由添加 meta 字段 meta:{ requiresAuth:true } }, ... ] }) router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.requiresAuth)) { // 若是 store.dispatch('checkLogin') 返回的結果 isLogin 爲 false,則說明用戶沒有登陸,就會跳轉到 /login store.dispatch('checkLogin').then(isLogin=>{ if (!isLogin) { next({ path: '/login', query: { redirect: to.fullPath } }) } else { next() } }) } else { next() // 確保必定要調用 next() } })
小技巧 - 按需加載,節約性能:
按需加載的適用場景,好比說「訪問某個路由的時候再去加載對應的組件」,用戶不必定會訪問全部的路由,因此不必把全部路由對應的組件都先在開始的加載完;更典型的例子是「某些用戶他們的權限只能訪問某些頁面」,因此不必把他們沒權限訪問的頁面的代碼也加載。
// before import Index from '@/pages/Index/Index' const router = new Router({ routes: [ { path: '/', component: Index }, ... ] }) // after const router = new Router({ routes: [ { path: '/', component: () =>import ('@/pages/Index/Index') }, ... ] })
使用 marked.js 對 markdown 內容進行轉換:
文章詳情頁(Detail)中,經過服務器返回的文章內容是 markdown 格式的,先用 marked.js 庫處理一下,再使用 v-html 渲染到頁面中。
1.安裝 cnpm install marked
2.在組件中引入 import marked from 'marked'
3.把內容進行轉換:
computed:{ markdown(){ return marked(this.rawContent) //this.rawContent 是從服務器獲取的文章正文 } },
4.在頁面中引入: <section class="article" v-html='markdown'></section>
最後介紹一款小插件,能夠在使用 vue 開發的時候更好地調試和 debug。
vue-devtools是一款基於chrome遊覽器的插件,用於調試vue應用,這能夠極大地提升咱們的調試效率。
GitHub 文檔:vue-devtools
Chrome 插件下載地址:Get the Chrome Extension
當咱們添加完vue-devtools擴展程序以後,咱們在調試vue應用的時候,chrome開發者工具中會看一個vue的一欄,點擊以後就能夠看見當前頁面vue對象的一些信息。vue-devtools使用起來仍是比較簡單的,上手很是的容易,這裏就細講其使用說明了。