上一个项目:仿 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使用起来仍是比较简单的,上手很是的容易,这里就细讲其使用说明了。