基礎知識
爲什麼要學習Vuejs
網友:"到今天這個時代有些人學完了js、html5/dom/bom直接跳過jQ去學vue我覺得完全沒問題,所以不懂jQ的人會vue當然可以,本來前端的基礎就是js而不是jQ。而且vue本身走的就是模塊化的開發方式,就是讓項目更好維護,更好升級,在這些方面絕對比jQ更優秀。jQuery之所以被替代,一是開發方式保守、老舊、效率低,二是因爲html5出了很多新的api,完全可以代替jQ,就連bootstrap重構都已經申明將剔除jQ。退一萬步說,前端最基礎的還是js,只要你js技術過關,不管是學jQ還是vue都會很快。"
- vue.js的作者尤雨溪是中國人,而且在知乎上有帳號,還非常活躍。
尤雨溪畢業於上海復旦附中,在美國完成大學學業,本科畢業於Colgate University,後在Parsons設計學院獲得Design & Technology藝術碩士學位,現任職於紐約Google Creative Lab。
2016年9月3日,在南京的JSConf上,Vue作者尤雨溪正式宣佈加盟阿里巴巴Weex團隊,尤雨溪稱他將以技術顧問的身份加入 Weex 團隊來做 Vue 和 Weex 的 JavaScript runtime 整合,目標是讓大家能用 Vue 的語法跨三端
前後端分離暨Vuejs的優勢
變革
- 解耦,視圖-網關-服務各司其職。自由組合搭配。
- 後端無狀態,不存儲用戶信息,不維護session減輕負擔。安全提升;
- 單頁模式,響應更快;
- 靜態頁面部署到nginx,響應更快,且升級時無感知,不需重啓;
- Reactive編程。響應式,數據改變時,頁面實時響應。
- 高效,將某些運算遷移到前端,類似邊緣計算-分攤中心壓力;
- 簡化代碼,後端API機械式自動生成,前端調用API即可;
- 技術健全,如路由、存儲、生命週期管控、監聽、計算、調試、測試框架,規範成熟;
- 漸進式,自然融入到已經上線的項目,持續增加需求,擴展性強;
- 數據渲染高效;
- 優秀案例;
- 技術革新、推廣、傳承;
開發
- 上手較快,因爲基礎還是JS;
- 熱部署,代碼更新後即時生效而無需構建。傳統開發亦可以通過一定配置進行熱部署,但有時會失效。
- 靈活與API|MQ交互;
- 兼容技術棧,如WebSocket、MQ等;
- 豐富的組件庫,提升效率;
- 調試、測試框架;
- 脫離後端,暫無API時,可使用mock.js框架模擬;
- 語法糖有助於實現複雜功能;
- 專用IDE,如webstorm;
- 文檔詳盡易懂,社區活躍壯大中;
- 開發體驗:規範、生命週期控制,數據綁定,熱部署不需複雜設置,JS與DOM解耦,不用直接操作DOM,代碼複用,MVVM結構,面向對象-後端思維;
運維
- 獨立部署,減輕後端壓力,發佈以及服務崩潰互不影響;
- 鬆散耦合,定位BUG快捷;
- 代碼易讀,易維護,新增需求無壓力,如多客戶端需求、前後端分離需求;
對比
- Thymeleaf、JSP與DOM耦合,前者遵循xml規範,模板只解決了渲染,沒解決麻煩的DOM問題;
什麼是MVVM?MVVM是Model-View-ViewModel的縮寫
要編寫可維護的前端代碼絕非易事。我們已經用MVC模式通過koa實現了後端數據、模板頁面和控制器的分離,但是,對於前端來說,還不夠。
這裏有童鞋會問,不是講Node後端開發嗎?怎麼又回到前端開發了?
對於一個全棧開發工程師來說,懂前端纔會開發出更好的後端程序(不懂前端的後端工程師會設計出非常難用的API),懂後端纔會開發出更好的前端程序。程序設計的基本思想在前後端都是通用的,兩者並無本質的區別。這和“不想當廚子的裁縫不是好司機”是一個道理。
改變JavaScript對象的狀態,會導致DOM結構作出對應的變化!這讓我們的關注點從如何操作DOM變成了如何更新JavaScript對象的狀態,而操作JavaScript對象比DOM簡單
多了!
這就是MVVM的設計思想:關注Model的變化,讓MVVM框架去自動更新DOM的狀態,從而把開發者從操作DOM的繁瑣步驟中解脫出來
! ——廖雪峯
ps:jQuery MVVM框架 JSViews
文件
- Vue中index.html、main.js、App.vue、index.js之前的關係以及加載過程
App.vue中的router-view
<template>
<div id="app">
<p>就是一張da圖片</p>
[外鏈圖片轉存失敗(img-dlDOl0R2-1562114250786)(https://mp.csdn.net/mdeditor/assets/logo.png)]
<!--PS: router-view渲染路由信息於此-->
<router-view/>
</div>
</template>
router/index.js定義了簡單的路由信息
- main.js
入口,加載組件到index.html
組件
概念
- 組件類似自定義元素.Web組件規範
- 在一個大型應用中,有必要將整個應用程序劃分爲組件,以使開發更易管理。假想例子,以便展示組件的結構↓
<div id="app">
<app-nav></app-nav>
<app-view>
<app-sidebar></app-sidebar>
<app-content></app-content>
</app-view>
</div>
- const
常量 - export
文件通過export暴露接口|變量 - h => h(App)
// 演變步驟
render: function (createElement) {
return createElement(App);
}
render (createElement) {
return createElement(App);
}
render (h){
return h(App);
}
It comes from the term “hyperscript”, which is commonly used in many virtual-dom implementations. “Hyperscript” itself stands for “script that generates HTML structures” because HTML is the acronym for “hyper-text markup language”. – by 尤雨溪
export default
- ES6的export
使用export命令定義了模塊的對外接口以後,其他JS文件就可以通過import命令加載這個模塊(文件),沒錯
$refs
持有所有被ref定義的組件
定義組件
- 在不使用.vue 單文件時,我們是通過 Vue 構造函數創建一個 Vue 根實例來啓動vuejs 項目,Vue 構造函數接受一個對象,這個對象有一些配置屬性 el, data, component, template 等,從而對整個應用提供支持。
- new Vue()
new Vue() 相當於一個構造函數,在入口文件 main.js 構造根組件的同時,如果根組件還包含其它子組件,那麼 Vue 會通過引入的選項對象構造其對應的 Vue 實例,最終形成一棵組件樹
。 - export default
- 比較new Vue() & export default
- 全局組件
Vue.component('todo-item', {
template: '<li>這是個待辦項</li>'
})
- .vue文件
可以把html, css, js 寫到一個文件中,從而實現了對一個組件的封裝 - 父子關係
在一個組件中通過 import 引入另一個組件,這個組件就是父組件,被引入的組件就是子組件.
父組件通過props 向子組件傳遞數據,子組件通過自定義事件向父組件傳遞數據.
註冊組件
- 全局註冊
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })
- 局部註冊
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
生命週期
computed
- 計算屬性,如將總價在computed裏計算,從而實時計算商品購物車裏的商品總價
- 對於任何複雜邏輯,你都應當使用計算屬性
- 無變化時,緩存,提高效率;
工具
構建類
- yarn
可以代替npm的包依賴管理工具
VueX
- 在SPA單頁面組件的開發中 Vue的vuex和React的Redux 都統稱爲同一狀態管理,個人的理解是全局狀態管理更合適;簡單的理解就是你在state中定義了一個數據之後,你可以在所在項目中的任何一個組件裏進行獲取、進行修改,並且你的修改可以得到全局的響應變更.
- 管理Token、全局個人偏好
mutations
const storeLogin = new Vuex.Store({
state: {
// 存儲token
Authorization: localStorage.getItem('Authorization') ? localStorage.getItem('Authorization') : ''
},
mutations: {
// 修改token,並將token存入localStorage
changeLogin(state,user) {
console.log('進入changeLogin')
state.Authorization = user.Authorization;
localStorage.setItem('Authorization', user.Authorization);
}
}
});
又如↓
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
mutations下的函數只適合接收一個對象參數,state是默認傳入,不能把state當做形參
rules
先在html引入rules
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" label-position="left">
data裏定義rules
data() {
loginRules: {
// 驗證器 validateUsername 是一種特殊的函數
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
},
},
驗證器也在data裏面
const validateUsername = (rule, value, callback) => {
// validUsername 是引入的js函數
if (!validUsername(value)) {
callback(new Error('請輸入正確的用戶名'))
} else {
callback()
}
}
引入validUsername
import { validUsername } from '@/utils/validate' // 導入js
Element-ui
// 引入ui
import ElementUI from 'element-ui' //element-ui的全部組件
import 'element-ui/lib/theme-chalk/index.css'//element-ui的css
Vue.use(ElementUI) //使用elementUI
cnpm install [email protected] -S
穿梭框踩坑
- 效果
<div style="text-align: left">
<!-- <div style="text-align: center"> -->
<el-transfer
v-model="value4"
style="text-align: left; display: inline-block"
filterable
:render-content="renderFunc"
:titles="['用戶角色', '已選角色']"
:button-texts="['放棄', '選擇']"
:format="{
noChecked: '${total}',
hasChecked: '${checked}/${total}'
}"
:data="roleData"
:props="defaultProps"
@change="handleChange"
>
<!-- ↓等效:render-content-->
<!-- <span slot-scope="{ option }">{{ option.roleId }} - {{ option.roleName }}</span>-->
<el-button slot="left-footer" class="transfer-footer" size="small">操作</el-button>
<el-button slot="right-footer" class="transfer-footer" size="small">操作</el-button>
</el-transfer>
</div>
data() {
return {
// 角色數據
data: [],
// 別名
defaultProps: {
key: 'roleId',
label: 'roleName',
disabled: false
},
// 不要value4無法移動元素
value4: [1],
renderFunc(h, option) {
// 生成元素的顯示名稱
return <span>{ option.roleName }</span>
// return <span>{ option.roleId } - { option.roleName }</span>
},
- 目標框初始值不出來解決了
功能開發
- demo
cc.vue
# 新建cc組件
template>
<div>
中國工農紅軍
<ul>
<li v-for="site in sites" :key="site.name">
{{site.url}}
<a :href="site.url" target="_blank">{{site.name}}</a>
</li>
</ul>
<input type="button" value="點擊我" @click="printText"/>
</div>
</template>
<script>
// import { METHODS } from 'http'
export default {
name: 'Cc',
methods: {
clickTest: function () {
alert('你點擊了按鈕')
},
printText: function () {
console.log('你點擊了按鈕')
}
},
data () {
return {
msg: '書籍是人類進步的階梯',
msg2: 'Apple',
sites: [
{url: 'http://router.vuejs.org/', name: 'Jack'},
{url: 'http://vuex.vuejs.org/', name: 'Tom'},
{url: 'https://github.com/vuejs/awesome-vue', name: 'Jimy'}
]
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
index.js
# 注入組件
import cc from '@/components/cc'
錯誤記錄
簡單
Error in created hook: “TypeError: _admin.menuTree.then is not a function”
因爲導入的函數沒加括弧
// 函數需要括弧,↓對的
menuTree().then(response => {
}
請求頭類型不對
// 組件內部增加下述代碼 - 局部請求頭
import Axios from 'axios'
Axios.defaults.headers.post['Content-Type'] = 'application/json'
# 或者在axios api里加入
export function call(data) {
return request({
headers: {
'Content-Type': 'application/json' // 設置請求頭請求格式爲JSON
},
url: url,
method: 'post',
data
})
}
功能
ajax
- axios
# install axios
cnpm install axios --save-dev
# --save-dev以省掉手動修改package.json文件的步驟
axios發送ajax請求
<script src="/js/axios.min.js"></script>
window.onload=function(){
new Vue({
el:'#app',
data:{
users:{
name:'',
age:''
}
},
methods:{
sendPsot(){
axios.post('post.php', {
name: this.users.name,
age: this.users.age,
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
}
});
}
- 跨域配置
config/index.js
,proxyTable裏增加內容(老式
)
proxyTable: {
// 解決跨域
'/tbapi':{
// target: "http://api.douban.com/v2",
target: "https://suggest.taobao.com",
changeOrigin:true,
pathRewrite:{
'^/tbapi':''
}
}
},
組件js
// 跨域
Axios.defaults.baseURL = '/tbapi'
Axios.defaults.headers.post['Content-Type'] = 'application/json'
mounted() {
//GET
this.$ajax({
method: 'get',
// tbapi會代替localhost
url: '/sug?code=utf-8&q=電冰箱',
// url: '/sug?code=utf-8&q=電冰箱&callback=cb',
}).then(response => {
// response包含config data等
var resData = response.data.result
iceBoxes = resData
resData.forEach(item => {
console.log(item[0])
console.log(item[1])
// console.log('數據序號'+i+'=='+item)
})
}).catch(function (err) {
console.log(err)
})
//POST
this.$ajax({
method: 'post',
url: '/sug?code=utf-8&q=iPhone',
// data: {
// code: 'utf-8',
// q: 'iPhone'
// }
}).then(response => {
// response包含config data等
var resData = response.data.result
resData.forEach(item => {
console.log(item[0])
console.log(item[1])
// console.log('數據序號'+i+'=='+item)
})
}).catch(function (err) {
console.log(err)
});
}
登錄
邏輯
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
// 也就是說,全局store對象擁有原生的dispatch方法,用於請求API
this.$store.dispatch('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
}
源碼解析
上文的$store來自這裏@/store/index.js
,片段↓
const store = new Vuex.Store({
modules,
getters
})
// 導出實例this的store屬性
export default store
store的dispatch,該單詞是發送的意思
axios的配置
// request即service-axios
import request from '@/utils/request'
// ↑request含有攔截器,url改爲合適的baseUrl
export function login(data) {
console.log('axios實例==',request)
return request({
url: '/user/login',
method: 'post',
data
})
}
當index.vue裏的store.dispatch執行請求時,即會找到上面的login函數,由login函數發出調用請求,接着,我們看request.js
裏的代碼
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
// global全局配置
import { baseUrl } from '@/utils/global'
// create an axios instance 沒錯,service就是axios實例,import@/utils/request即注入service-axios實例
const service = axios.create({
baseURL: baseUrl, // url = base url + request url
// baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
↑這是axios的配置,配置了url和超時時間,當執行$store.dispatch時,即會加上baseUrl進行請求。
接收響應↓,也來自request.js
response => {
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
後臺API切換回Mock需改動
// 1 request.js
const service = axios.create({
// baseURL: baseUrl, // url = base url + request url
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
// 2 user.js
// params: data
data
// 3 user.js
// method: 'post',
method: 'get',
Token - 令牌
- vuex的…mapMutations([’
- 前後端屬於不同的域,導致每次ajax請求服務器都會當做新的用戶訪問,導致session丟失。當然也可以通過維護cookie來讓服務端辨識客戶端,如
axios.defaults.withCredentials=true;
- 每次請求被認爲是新客戶端,產生新session問題,注意session的膨脹;
全局變量
引入模板
彈窗
- 新建模板Test.vue
- 使用
import TestMode from './Test' // 導入相對路徑的Test.vue
...
components: { TestMode }, // 註冊組件
<test-mode v-if="testPageVisible" ref="testMode2" @refreshDataList="getList"></test-mode><!-- 使用組件.getList是確定後的執行函數-->
test(row) { // 這裏是父組件
this.testPageVisible = true
this.$nextTick(() => {
this.$refs.testMode2.test(Object.assign({}, row)) // 調用子組件的函數
})
},
methods: {
test() { // 這裏是Test.vue組件·················
this.dialogFormVisible = true
}
}
編輯器
Json編輯器
原版↓很糟糕
兼容
解決IE兼容問題
- promise
# 安裝es6-promise
npm install es6-promise --save-dev
- main.js中引入ES6的polyfill
import Es6Promise from 'es6-promise'
Es6Promise.polyfill();