[TOC]
>[success] # 登陸/登出以及JWT認證
在本章中將使用一個 **后端的真實服務** ,來結合這個服務做 **前端應用** 的 **登陸** 以及 **登出** ,并且我們使用 **JWT(全稱:Json Web Token)** 來 **進行認證** 。
>[success] ## 后端代碼概覽
這里使用 **[express搭建](http://www.hmoore.net/wangjiachong/code/1272384)** 了一個服務,這個 **服務運行在本地的 3000端口** ,大家想看可以直接進行 **代碼下載** ,下載 **現成的代碼** ,進行運行,下面主要看一下幾個主要用到的 **后端接口** 。
**本地服務代碼下載地址** :
鏈接:https://pan.baidu.com/s/1LcKTiDsoZy_RmEjmfJuKIw
提取碼:cdaq
**運行服務執行指令** :
~~~
npm start
~~~
1. **serve/routes/index.js**
**login登陸接口**
~~~
var express = require('express');
var router = express.Router();
const jwt = require('jsonwebtoken')
// 模擬從數據庫獲取用戶信息
const getPasswordByName = (name) => {
return { password: '123' }
}
router.post('/getUserInfo', function(req, res, next) {
res.status(200).send({
code: 200,
data: {
name: 'Lison'
}
})
});
// 登陸接口
router.post('/login', function(req, res, next) {
// 獲取前端傳過來的userName跟password
const { userName, password } = req.body
// 如果有用戶名
if (userName) {
// 獲取用戶信息(如果有密碼通過這個方法去數據庫查詢該用戶信息,如果沒有密碼,用戶信息就返回一個空字符串)
const userInfo = password ? getPasswordByName(userName) : ''
// 【用戶信息為空】 或者 【密碼為空】 或者 【輸入密碼與用戶信息密碼不對】3項中1項不對就拋出錯誤
if (!userInfo || !password || userInfo.password !== password) {
res.status(401).send({ // 拋出錯誤:用戶名或密碼不對
code: 401,
mes: 'user name or password is wrong',
data: {}
})
} else {
// 成功
res.send({ // 給前端返回一個token,token是通過一個jwt的一個庫來生成的
code: 200,
mes: 'success',
data: {
// JWT生成規則: 可以自己來定義規則
// 參數1:是一個對象傳入的是 【用戶名稱】
// 參數2:我們用來加密的一個自定義的一個字符串,這里可以定義我們的密鑰,這里隨便寫一個abcd
// 參數3:我們可以在里面設置一些信息,這里設置了一個【token過期時間】
token: jwt.sign({ name: userName }, 'abcd', {
expiresIn: '1d' // 1d?=?1天?10000?=?10秒
})
}
})
}
} else { // 如果無用戶名
res.status(401).send({ // 拋出錯誤:用戶名為空
code: 401,
mes: 'user name is empty',
data: {}
})
}
});
module.exports = router;
~~~
2. **serve/routes/users.js**
**授權接口**
~~~
var express = require('express');
var router = express.Router();
const jwt = require('jsonwebtoken')
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
router.get('/getUserInfo', function(req, res, next) {
res.send('success')
})
// 授權接口
router.get('/authorization', (req, res, next) => {
// req.userName從app.js中中間件(攔截器)的token都正確才給添加的用戶名稱
const userName = req.userName
res.send({
code: 200,
mes: 'success',
data: {
token: jwt.sign({ name: userName }, 'abcd', {
expiresIn: '1d' // 1d?=?1天?10000?=?10秒
})
}
})
})
module.exports = router;
~~~
3. **serve/app.js(中間件:類似攔截器)**
**應用核心配置文件**
~~~
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
const jwt = require('jsonwebtoken')
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var dataRouter = require('./routes/data');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// app.all('*', (req, res, next) => {
// res.header('Access-Control-Allow-Origin', '*')
// res.header('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type')
// res.header('Access-Control-Allow-Methods','PUT,POST,GET,DELETE,OPTIONS')
// next()
// })
// 白名單(不需要做token校驗的接口)
const whiteListUrl = {
get: [
],
post: [
'/index/login',
]
}
// 判斷請求是否在白名單中的方法
const hasOneOf = (str, arr) => {
return arr.some(item => item.includes(str))
}
// 中間件(類似攔截器,每次請求時候會走這里)
app.all('*', (req, res, next) => {
// 獲取當前請求方式并且轉換成小寫(post 或者 get等等)
let method = req.method.toLowerCase()
// 獲取當前請求的路徑
let path = req.path
// 有一些接口是不需要token校驗的,所以在這里設置一下白名單,
// 如果請求方式在白名單里面的對應上,并且請求方式里有請求的這個地址就返回true,接口正常執行,不需要token
if(whiteListUrl[method] && hasOneOf(path, whiteListUrl[method])) next() // 白名單走這里
else { // 白名單之外的都需要token校驗
// 在請求的header中取出authorization
const token = req.headers.authorization
// 判斷沒有token就拋出沒有token請登錄
if (!token) res.status(401).send('there is no token, please login')
else {
// 有token就判斷使用JWT提供的方法,校驗token是否正確
// 第一個參數把獲取到的token傳入
// 第二個參數是我們生成token時候傳入的密鑰,當然這個密鑰可以抽離出一個文件每次從文件中獲取密鑰
// 第三個參數是一個回調函數,第一個參數是錯誤信息,第二個參數是從token中解碼出來的信息
jwt.verify(token, 'abcd', (error, decode) => {
if (error) res.send({ // token錯誤
code: 401,
mes: 'token error',
data: {}
})
else { // token正確返回用戶名,繼續往下走
req.userName = decode.name
next()
}
})
}
}
})
app.use('/index', indexRouter);
app.use('/users', usersRouter);
app.use('/data', dataRouter);
// catch 404 and forward to error handler
app.use(function(err, req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
~~~
>[success] ## 登陸以及Token處理
具體操作如下:
>[success] ### 安裝所需依賴
首先 **安裝2個模塊** ,**js-cookie(它是對cookie進行一些操作,可以設置、讀取cookie)** 、**md5(可以對字符串進行md5加密,用在登陸時候提交密碼時候加密密碼)** ,然后我們依次 **執行安裝指令** ,進行安裝
**js-cookie 安裝指令**
~~~
npm install js-cookie --save
~~~
**md5 安裝指令**
~~~
npm install md5
~~~
>[success] ### 登陸頁面邏輯


1. **登陸頁面代碼以及邏輯**
在 **login.vue(登陸頁面)** 里面寫好一個 **簡單的表單** ,大概是下圖這個樣子

**點擊登陸按鈕** 后執行 **vuex** 中的 **actions** 的 **login異步方法**
**src/views/login.vue**
~~~
<template>
<div>
<input type="text" placeholder="請輸入賬號" v-model="userName" />
<input type="password" placeholder="請輸入密碼" v-model="password" />
<button @click="handleSubmit">登陸</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
name: 'login_page',
data(){
return {
userName: 'Lison', // 用戶名
password: '123' // 密碼
}
},
methods: {
// 引入action中的login方法
...mapActions([
'login'
]),
// 登陸
handleSubmit(){
this.login({
userName: this.userName,
password: this.password
}).then(() => {
console.log('成功')
this.$router.push({
name: 'home'
})
}).catch(error => {
console.log(error)
})
}
}
}
</script>
<style>
</style>
~~~
2. **vuex中的代碼展示**
**點擊登陸時候** 調用 **vuex** 中 **actions** 里定義的 **login方法**,這個 **login方法** 中 **調用了后端的登陸接口**
**src/store/module/user.js**
~~~
// 接口引入
import { login, authorization } from '@/api/user'
// 引入業務類方法
import { setToken } from '@/lib/util'
const state = {}
const mutations = {}
const actions = {
/**
* 登陸方法
* @param commit 執行一個mutation方法來修改state
* @param params this.$store.dispatch('login', '我是params')時傳的參數
*/
login({ commit }, { userName, password }){
return new Promise((resolve, reject) => {
// 登陸接口
login({ userName, password }).then(res => {
if(res.code === 200 && res.data.token){
setToken(res.data.token)
resolve()
} else {
reject(new Error('錯誤'))
}
}).catch(error => {
reject(error)
})
})
},
/**
* 校驗token是否失效
* @param commit 執行一個mutation方法來修改state
* @param token token信息
*/
authorization({ commit }, token){
return new Promise((resolve, reject) => {
authorization().then(res => {
if(parseInt(res.code) === 401){
reject(new Error('token error'))
} else {
resolve()
}
}).catch(error => {
reject(error)
})
})
}
}
export default {
state,
mutations,
actions
}
~~~
3. **接口文件**
**src/api/user.js**
~~~
import axios from './index'
// 登陸接口
export const login = ({ userName, password }) => {
return axios.request({
url: '/index/login',
method: 'post',
data: {
userName,
password
}
})
}
// 檢驗token是否有效
export const authorization = () => {
return axios.request({
url:'/users/authorization',
method: 'get' // 這行可以不寫,默認是get
})
}
~~~
**接口成功** 后, **后端會返回一個 token** ,我們需要把 **token** 儲存起來,在 **后續接口調用時,將它添加到我們請求的header中,傳給后端,后端拿到 token 進行驗證**,此時需要用到剛剛下載的 **js-cookie** 在 **util.js** 中 **封裝一個儲存 token 的業務類方法** ,代碼如下:
**src/lib/util.js**
~~~
import Cookie from 'js-cookie'
// 設置title
export const setTitle = (title) => {
window.document.title = title || 'admin' // 默認title
}
/**
* 設置token
* @param {string} token - 登陸成功后,返回的token
* @param {string} tokenName - 儲存到Cookie時的token名字
*/
export const setToken = (token, tokenName = 'token') => {
Cookie.set(tokenName, token)
}
/**
* 獲取token
* @param {string} tokenName - 儲存到Cookie時的token名字
*/
export const getToken = (tokenName = 'token') => {
return Cookie.get(tokenName)
}
~~~
4. **路由配置**
**login接口成功返回token** 后 **需要跳轉到首頁** ,此時需要在 **路由攔截器中做判斷處理** ,如果有 **token** 會調用一個接口,進行 **token是否可用的校驗 ,如果校驗成功跳轉到首頁** ,**如果沒有 token 證明未登錄,跳轉到登錄頁**
**src/router/index.js**
~~~
import Vue from 'vue'
import Router from 'vue-router'
import routes from './router'
import store from '@/store'
import { setTitle, setToken, getToken } from '@/lib/util'
// 注冊路由
Vue.use(Router)
// vue-router實例
const router = new Router({ routes })
// 注冊全局前置守衛
router.beforeEach((to, from, next) => {
// 動態設置title
to.meta && setTitle(to.meta.title)
// 獲取token
const token = getToken()
if(token){ // 已登錄
// 調用接口判斷token是否失效
store.dispatch('authorization', token).then(() => { // token驗證成功
// 如果跳轉的頁面為登陸頁,就強制跳轉到首頁
if(to.name === 'login') next({ name: 'home' })
else next()
}).catch(error => { // token驗證錯誤
// 這里需要清空token,再返回登錄頁,不然會陷入死循環,回到登錄頁還是有token,token失效還是回到登錄頁,如此反復
setToken('') // 這里也可以使用js-cookie提供的clear方法
next({ name: 'login' })
})
} else { // 未登錄
// 如果去的頁面是登陸頁,直接跳到登陸頁
if(to.name === 'login') next()
// 如果不是登陸頁,強行跳轉到登陸頁
else next({ name: 'login' })
}
})
export default router
~~~
5. **axios請求攔截器添加token**
因為在調用 **authorization 校驗接口** 時,需要 **參數 token** , **只需要添加到請求攔截器** 中即可,這樣后續接口的請求 **token都會在請求頭上添加** 。
**src/lib/axios.js**
~~~
import axios from 'axios'
import { baseURL } from '@/config'
import { getToken } from '@/lib/util'
class HttpRequest {
constructor(baseUrl = baseURL){ // baseUrl = baseURL 是ES6的默認值寫法等同于 baseUrl = baseUrl || baseURL
this.baseUrl = baseUrl // this指向創建的實例,當你使用new HttpRequest創建實例時候,它會把this中定義的變量返回給你
this.queue = {} // 創建隊列,每次請求都會向里面添加一個key:value,請求成功后就會去掉這個key:value,直到this.queue中沒有屬性值時,loading關閉
}
/**
* 默認options配置
*/
getInsideConfig(){
const config = {
baseURL: this.baseUrl,
headers: {
//
}
}
return config
}
distroy (url) {
delete this.queue[url]
if (!Object.keys(this.queue).length) {
// Spin.hide()
}
}
/**
* 攔截器
* @param {Object} instance - 通過axios創建的實例
* @param {String} url - 接口地址
*/
interceptors(instance, url){
/**
* 請求攔截器
* @param {Function} config - 請求前的控制
* @param {Function} error - 出現錯誤的時候會提供一個錯誤信息
*/
instance.interceptors.request.use(config => {
// 添加全局的Lodaing...
if(!Object.keys(this.queue).length){
// Spin.show()
}
this.queue[url] = true
// 每次請求都會把token加到請求頭中
config.headers['Authorization'] = getToken()
return config
}, error => {
return Promise.reject(error)
})
/**
* 響應攔截器
* @param {Function} res - 服務端返回的東西
* @param {Function} error - 出現錯誤的時候會提供一個錯誤信息
*/
instance.interceptors.response.use(res => {
this.distroy(url) // 關閉全局的Lodaing...
const { data } = res
return data
}, error => {
this.distroy(url) // 關閉全局的Lodaing...
return Promise.reject(error.response.data)
})
}
request(options){
const instance = axios.create()
options = Object.assign(this.getInsideConfig(), options) // Object.assign會將2個對象合并成1個對象,相同屬性值會被后者覆蓋
this.interceptors(instance, options.url) // 攔截器
return instance(options)
}
}
export default HttpRequest
~~~
>[success] ### 注釋掉mock.js
把 **main.js** 中引入的 **mock.js** 的引入 **注釋掉** ,因為我們要 **請求真實的接口數據** ,如果這個地方引入了 **mock.js** ,它會用 **mock** 對你的 **所有請求行為進行攔截** ,所以需要 **注釋掉**,代碼如下:
**src/main.js**
~~~
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/element.js'
import Bus from './lib/bus'
// 非生產環境時引入 mock
// if(process.env.NODE_ENV !== 'production') require('./mock')
Vue.config.productionTip = false
Vue.prototype.$bus = Bus
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
~~~
>[success] ### 配置代理
因為 **express** 起的 **后端服務URL** 是 **http://localhost:3000/** ,而 **前端服務URL** 是 **http://localhost:8080/** ,兩者的 **端口號不同(后端端口:3000,前端端口:8080)** ,所以調用接口時候會 **產生跨域** ,這時候在 **后端沒有添加header頭** , **前端解決跨域需要配置代理**,首先在 **vue.config.js** 中把 **devServer 中 proxy** 修改成 **要代理的后端服務地址** 如下:
**vue.config.js**
~~~
const path = require('path') // 引入nodejs的path模塊
const resolve = dir => path.join(__dirname, dir) // resolve方法用來加載路徑
const BASE_URL = process.env.NODE_ENV === 'production' ? '/iview-admin/' : '/' // 判斷當前為開發環境還是打包環境, '/'意思是代表指定在域名的根目錄下,如果要指定到iview-admin下就這樣寫'/iview-admin/', production為生產壞境,development為開發環境
module.exports = {
lintOnSave: false, // 取消每次保存時都進行一次' ESLint '檢測
publicPath: BASE_URL, // 項目的基本路徑,vuecli2.0時打包經常靜態文件找不到,就是需要配置這個屬性為'./'
chainWebpack: config => { // 配置Webpack
config.resolve.alias
.set('@', resolve('src')) // 引入文件時候“ @ ”符號就代表src
.set('_c', resolve('src/components')) // 引入組件文件夾中的文件就可以用“ _c ”代替src/components
},
productionSourceMap: false, // 打包時不生成.map文件,會減少打包體積,同時加快打包速度
devServer: { // 跨域有2種解決方案: 1. 在后端的header中配置, 2. 使用devServer來配置代理解決跨域
proxy: 'http://localhost:3000/' // 這里寫需要代理的URL,這里會告訴開發服務器,將任何未知請求匹配不到靜態文件的請求,都代理到這個URL來滿足跨域
}
}
~~~
然后修改一下 **axios配置文件** 中的 **baseURL** 判斷邏輯,如果是 **開發環境 baseURL 就設置為空字符串** ,因為 **不設置為空字符串,baseURL 不會被代理里配置的URL更改**。
**src/config/index.js**
~~~
// 如果當前是生產環境用生產環境地址,如果是開發環境并且在vue.config.js中配置了代理,就用空字符串【''】,如果未配置代理就用開發環境地址
export const baseURL = process.env.NODE_ENV === 'production' ? 'http://production.com' : ''
~~~
**注意:如果 修改了webpack 的配置 ,必須要 重啟前端的服務,才會生效 。**
>[success] ## Token過期處理
一個網站如果 **長時間沒有操作** ,不可能讓它 **登陸完一次,就一輩子不用再登陸了,就一直能用** ,我們會 **設置一個token的過期時間 ,過期之后需要重新登錄** 。例如:用戶 **登陸成功** 后獲取到了 **token** ,**token 過期時間為一天** ,在這一天都在頻繁的使用該網站,到一天了 **token過期** 就 **讓用戶跳出去重新登錄** ,這樣 **用戶體驗不好** ,我們希望 **當用戶長時間使用網站時,我們應該給 token 續命,給它延長使用時間** ,每次 **頁面的跳轉** 都會調用 **authorization接口** ,**authorization接口** 每次都會返回一個 **新的token** ,它會 **重新計時**。
**src/store/module/user.js**
~~~
// 接口引入
import { authorization } from '@/api/user'
// 引入業務類方法
import { setToken } from '@/lib/util'
const state = {}
const mutations = {}
const actions = {
/**
* 校驗token是否失效
* @param commit 執行一個mutation方法來修改state
* @param token token信息
*/
authorization({ commit }, token){
return new Promise((resolve, reject) => {
authorization().then(res => {
if(parseInt(res.code) === 401){
reject(new Error('token error'))
} else {
setToken(res.data.token) // 重新設置token
resolve()
}
}).catch(error => {
reject(error)
})
})
}
}
export default {
state,
mutations,
actions
}
~~~
>[success] ## 退出登陸
在 **首頁** 寫個 **退出按鈕** ,**點擊按鈕** 時候執行 **setToken('')** 把 **cookie清空** ,但是為了 **保持代碼的一致性** ,還是把這個 **登出方法** 寫在 **vuex** 中進行調用, **清空cookie** 后跳轉到 **登錄頁面** 即可。
1. **首頁代碼**
**src/views/Home.vue**
~~~
<template>
<div>
<h1>首頁</h1>
<button @click="handleLogout">退出登錄</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
methods: {
// 引入vuex中的logout方法
...mapActions([
'logout'
]),
// 退出登錄
handleLogout(){
this.logout()
this.$router.push({
name: 'login'
})
}
}
}
</script>
~~~
2. **vuex模塊中代碼**
**src/store/module/user.js**
~~~
// 接口引入
import { login, authorization } from '@/api/user'
// 引入業務類方法
import { setToken } from '@/lib/util'
const state = {}
const mutations = {}
const actions = {
/**
* 登陸方法
* @param commit 執行一個mutation方法來修改state
* @param params this.$store.dispatch('login', '我是params')時傳的參數
*/
login({ commit }, { userName, password }){
return new Promise((resolve, reject) => {
// 登陸接口
login({ userName, password }).then(res => {
if(res.code === 200 && res.data.token){
setToken(res.data.token)
resolve()
} else {
reject(new Error('錯誤'))
}
}).catch(error => {
reject(error)
})
})
},
/**
* 校驗token是否失效
* @param commit 執行一個mutation方法來修改state
* @param token token信息
*/
authorization({ commit }, token){
return new Promise((resolve, reject) => {
authorization().then(res => {
if(parseInt(res.code) === 401){
reject(new Error('token error'))
} else {
resolve()
}
}).catch(error => {
reject(error)
})
})
},
/**
* 登出方法
*/
logout(){
setToken('')
}
}
export default {
state,
mutations,
actions
}
~~~
>[success] ## 總結
1. **安全性要求不高** :上面的方案適合用于 **安全性要求不高的情況** 。
2. **安全性要求高** :如果你的系統 **對安全性要求比較高** ,就不能使用上面的方案, **不能使用 js 取到 token 存入到 cookie 中,不能進行一些判斷 cookie 里的 token 的邏輯** ,需要 **在服務端設置一個開啟 httpOnly,httpOnly設置為 true 后,就只能通過服務端來把 token 設置到 cookie 中了,無法通過 js 腳本來讀取操作這個 cookie了,這樣就能避免一些跨站腳本攻擊。**
- vue 26課
- Vue-cli3.0項目搭建
- Vue-ui 創建cli3.0項目
- Vue-ui 界面詳解
- 項目目錄詳解
- public文件夾
- favicon.ico
- index.html
- src文件夾
- api文件夾
- assets文件夾
- components文件夾
- config文件夾
- directive文件夾
- lib文件夾
- mock文件夾
- mock簡明文檔
- router文件夾
- store文件夾
- views文件夾
- App.vue
- main.js
- .browserslistrc
- .editorconfig
- .eslintrc.js
- .gitignore
- babel.config.js
- package-lock.json
- package.json
- postcss.config.js
- README.en.md
- README.md
- vue.config.js
- Vue Router
- 路由詳解(一)----基礎篇
- 路由詳解(二)----進階篇
- Vuex
- Bus
- Vuex-基礎-state&getter
- Vuex-基礎-mutation&action/module
- Vuex-進階
- Ajax請求
- 解決跨域問題
- 封裝axios
- Mock.js模擬Ajax響應
- 組件封裝
- 從數字漸變組件談第三方JS庫使用
- 從SplitPane組件談Vue中如何【操作】DOM
- 渲染函數和JSX快速掌握
- 遞歸組件的使用
- 登陸/登出以及JWT認證
- 響應式布局
- 可收縮多級菜單的實現
- vue雜項
- vue遞歸組件
- vue-cli3.0多環境打包配置
- Vue+Canvas實現圖片剪切
- vue3系統入門與項目實戰
- Vue語法初探
- 初學編寫 HelloWorld 和 Counter
- 編寫字符串反轉和內容隱藏功能
- 編寫TodoList功能了解循環與雙向綁定
- 組件概念初探,對 TodoList 進行組件代碼拆分
- Vue基礎語法
- Vue 中應用和組件的基礎概念
- 理解 Vue 中的生命周期函數
- 常用模版語法講解
- 數據,方法,計算屬性和偵聽器
- 樣式綁定語法
- 條件渲染
- 列表循環渲染
- 事件綁定
- 表單中雙向綁定指令的使用
- 探索組件的理念
- 組件的定義及復用性,局部組件和全局組件
- 組件間傳值及傳值校驗
- 單向數據流的理解
- Non-Props 屬性是什么
- 父子組件間如何通過事件進行通信
- 組件間雙向綁定高級內容
- 使用匿名插槽和具名插槽解決組件內容傳遞問題
- 作用域插槽
- 動態組件和異步組件
- 基礎語法知識點查缺補漏
- Vue 中的動畫
- 使用 Vue 實現基礎的 CSS 過渡與動畫效果
- 使用 transition 標簽實現單元素組件的過渡和動畫效果
- 組件和元素切換動畫的實現
- 列表動畫
- 狀態動畫
- Vue 中的高級語法
- Mixin 混入的基礎語法
- 開發實現 Vue 中的自定義指令
- Teleport 傳送門功能
- 更加底層的 render 函數
- 插件的定義和使用
- 數據校驗插件開發實例
- Composition API
- Setup 函數的使用
- ref,reactive 響應式引用的用法和原理
- toRef 以及 context 參數
- 使用 Composition API 開發TodoList
- computed方法生成計算屬性
- watch 和 watchEffect 的使用和差異性
- 生命周期函數的新寫法
- Provide,Inject,模版 Ref 的用法
- Vue 項目開發配套工具講解
- VueCLI 的使用和單文件組件
- 使用單文件組件編寫 TodoList
- Vue-Router 路由的理解和使用
- VueX 的語法詳解
- CompositionAPI 中如何使用 VueX
- 使用 axios 發送ajax 請求
- Vue3.0(正式版) + TS
- 你好 Typescript: 進入類型的世界
- 什么是 Typescript
- 為什么要學習 Typescript
- 安裝 Typescript
- 原始數據類型和 Any 類型
- 數組和元組
- Interface- 接口初探
- 函數
- 類型推論 聯合類型和 類型斷言
- class - 類 初次見面
- 類和接口 - 完美搭檔
- 枚舉(Enum)
- 泛型(Generics) 第一部分
- 泛型(Generics) 第二部分 - 約束泛型
- 泛型第三部分 - 泛型在類和接口中的使用
- 類型別名,字面量 和 交叉類型
- 聲明文件
- 內置類型
- 總結