vue @input带参数_Vue 全家桶开发的一些小技巧和注意事项
前言
用 vue 全家桶開發一年多了,踩過不少坑,也解決了很多的問題,把其中的一些點記錄下來,希望能幫到大家。以下內容基于最新版的 vue + vuex + vue-router + axios + less + elementUI,vue 腳手架是 vue-cli3。
css 的 scoped 屬性
vue 為了防止 css 污染,當組件的 標簽有 scoped 屬性時,它的 css 只作用于當前組件中的元素。實現原理很簡單,給當前組件中的每個標簽都加上唯一的自定義屬性:data-v-唯一的屬性,然后 css 選擇器都加上屬性選擇器.article-title[data-v-唯一的屬性],這樣這個 css 只會匹配到當前頁面的這個元素。
注意:每個組件的最外層的標簽會帶上父組件的data-v-屬性,也就是這個標簽會被父組件的樣式匹配到,所以父組件盡量不要使用標簽選擇器,這個標簽不要使用父組件中的 id 或者 class。
在父組件想修改子組件的 css(修改 elementUI 組件的樣式),我們可以借助深度作用選擇器 >>>
div >>> .el-input{width: 100px;
}
/* sass/less的話可能無法識別,這時候需要使用 /deep/ 選擇器。*/
div /deep/ .el-input{
width: 100px;
}
深度作用選擇器會去掉后面元素的屬性選擇器[data-v-],即上面代碼會編譯成:div[data-v-12345667] .el-input{}。就可以匹配到子組件的元素,從而覆蓋樣式。
父子組件的生命周期鉤子函數執行先后順序
組件的生命周期鉤子函數是到了某個生命周期點就會觸發,而不是在這個鉤子函數中進行生命周期,比如說 DOM 加載好了,就會觸發mounted 鉤子函數,所以在created 里面寫一個延遲定時器,mounted 鉤子不會等定時器執行。
各個周期鉤子函數觸發的時間點參考(圖來源于網絡)
關于父子組件的生命周期:不同的鉤子函數有不同的表現。父組件的虛擬 DOM 先初始化好了(beforeMount),才會去初始化子組件的虛擬 DOM (beforeMount),而 mounted 事件,等價于 window.onload,子組件 DOM 沒加載好,父組件 DOM 永遠不可能加載好。所以基本生命周期鉤子函數執行順序是:父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
父子組件的 update 和 beforeUpdate 執行先后順序:數據修改+虛擬 DOM 準備好會觸發 beforeUpdate,換句話說 beforeUpdate 等價于 beforeMount,而 update 等價于 mounted。所以先后順序是:父 beforeUpdate -> 子 beforeUpdate -> 子 update -> 父 update。
同理beforeDestory和destoryed的先后順序是:父 beforeDestory -> 子 beforeDestory -> 子 destoryed -> 父 destoryed。
生命周期鉤子函數其實也可以寫成數組的形式:mounted: [mounted1, mounted2],同一個生命周期可以觸發多個函數,這也是mixin(混入)的原理,mixin里面也可以寫生命周期鉤子,最終會和組件里面的生命周期鉤子函數一起變成數組形式,mixin里面的鉤子函數會先執行。
異步請求數據在哪個鉤子函數中執行比較好
生命周期鉤子函數的中異步會放入事件隊列,而不會在這個鉤子函數中執行。也就是說你在 created 和 mounted 中請求數據是一樣的,都不會立即更新數據,所以不會導致虛擬 DOM 重新加載,也不影響頁面中靜態的部分加載。生命周期鉤子函數中的異步賦值,vue 會在一遍流程走完之后執行update。另外,給數據賦值然后更新 DOM 也是異步的,偵聽到數據變化,Vue 將開啟一個隊列,并緩沖在同一事件循環中發生的所有數據變更,去掉重復賦值然后更新。
生命周期鉤子函數中的異步行為測試:
export default {data(){
return {
list:[],
}
},
methods:{
getData(){
//生成指定范圍的隨機整數
const randomNum = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
//生成固定長度的非空數組
const randomArr = length => Array.from({ length }, (item, index) => index * 2);
const time = randomNum(100,3000);//模擬請求時間
console.log('getData start');
return new Promise(resolve => {
setTimeout(() => {
const arr = randomArr(10);
resolve(arr);
},time)
})
}
},
async created(){
console.log('created');
this.list = await this.getData();
console.log('getData end');
},
beforeMount() {
console.log('beforeMount');
},
mounted(){
console.log('mounted');
},
updated(){
console.log('updated');
}
}
結果如下圖,所以在 created 中和 mounted 中請求數據,數據的更新時間是一樣的,在 created 中發起請求,可以更早的請求到數據。并且使用服務端渲染 SSR 的時候, mounted 鉤子不會加載。
父組件監聽子組件的生命周期
可以寫自定義事件,然后在子組件的生命周期函數中觸發這個自定義事件,但是不優雅,我們可以使用 hook:
復制代碼
從 A 頁面切換到 B 頁面,A 頁面中有一個定時器,到了 B 頁面用不上,需要在離開 A 頁面的時候清除掉,辦法很簡單,在 A 頁面的生命周期鉤子函數beforeDestory或者路由鉤子函數beforeRouteLeave里面清除掉就行,但是問題來了,怎么拿到定時器呢?把定時器寫到 data 里面,可行但是不優雅,我們有如下寫法:
//在初始化定時器之后this.$once('hook:beforeDestory',()=>{
clearInterval(timer);
})
is 屬性的妙用
由于 HTML 標簽的限制,tr 標簽里面只能有 th, td 標簽,而寫自定義標簽則會被解析到 tr 標簽外層,所以這時候我們可以用 is 屬性
復制代碼
最近有個頁面有大量的 SVG 圖標,我將每一個 SVG 都寫成了一個組件。由于 SVG 組件名稱又各不相同,所以需要動態標簽來表示:
arr: [ { id: 1, name: 'first' }, { id: 2, name: 'second' }, { id: 3, name: 'third' }, ]
item.name
item.name
給事件傳額外參數
原生 DOM 事件綁定的函數的第一個參數都會是事件對象event,但是有時候我們想給這個函數傳其他的參數,直接傳會覆蓋掉event,我們可以這么寫,變量$event就代表事件對象。
如果要傳的變量不是事件對象呢?在使用 elementUI 的時候碰到這么一個情況,在表格中使用了下拉菜單組件,代碼如下:
下拉菜單黃金糕獅子頭
復制代碼
下拉菜單事件 command 函數自帶一個參數,為下拉選中的值,這個時候我們想把表格數據傳過去,如果@command="handleCommand(row)"這樣寫,就會覆蓋掉自帶的參數,該怎么辦呢?這時候我們可以借助箭頭函數:@command="command => handleCommand(row,command)",完美解決傳參問題。
順便說一下,elementUI 的表格可以用變量$index代表當前的列數,和$event一樣的使用:
編輯復制代碼
經掘友指點,默認參數有多個的時候,可以這樣寫:@current-change="(...defaultArgs) => treeclick(ortherArgs, ...defaultArgs)"
v-slot 語法
v-slot 的用法(slot 語法已經廢棄):相當于在組件中留一個空位,使用該組件的時候可以傳一些標簽過去,插入到對應的空位。可以有多個空位,取不同的名字即可,默認是 default。同時還可以將一些數據傳過去,簡寫是#。
Here might be a page title
A paragraph for the main content.
And another one.
Here's some contact info
總結:
其他名稱的 slot(非 default)僅能用于 template 標簽。
插槽里面的標簽拿不到傳給子標簽的數據(插槽相當于孫子組件):
插槽可以使用解構語法v-slot="{ user }"。
子組件修改父組件傳過來的值
v-model在使用的時候很像雙向綁定的,但是 Vue 是單項數據流,v-model 只是語法糖而已:父組件用v-bind將值傳給子組件,子組件通過 change/input 事件觸發修改父組件的值。
復制代碼
v-model 不僅僅能在 input 上用,在組件上也能使用。
vue 組件間傳遞數據是單向的,即數據總是由父組件傳遞到子組件,子組件在其內部可以有自己維護的數據,但它無權修改父組件傳遞給它的數據,我們也可以參照v-model語法糖進行修改父組件的值,但是每次都這樣寫太麻煩了,vue 提供了一個修飾符.sync,用法如下:
父組件通過 ref 訪問到子組件
雖然 vue 提供 $parent 和 $children來訪問父/子組件,但是組件的父組件/子組件存在很多不確定性,例如組件被復用,他的父組件有多種情況。我們可以通過 ref 訪問到子組件的數據和方法。
復制代碼
注意:
ref 必須等 DOM 加載好了才可以訪問
雖然 mounted 生命周期 DOM 已經加載好了,但是為了以防萬一,我們可以使用 $nextTick 函數
背景圖、css 的 @import 使用路徑別名
在用 Webpack 處理打包時,可將某一目錄配置一個別名,代碼中就能使用與別名的相對路徑引用資源
import tool from '@/utils/test'; // Webpack 能正確識別并打包。但是在 css 文件,如 less, sass, stylus 中,使用 @import "@/style/theme" 的語法引用相對 @ 的目錄確會報錯。解決辦法是是在引用路徑的字符串最前面添加上 ~ 符號。
css module 中:@import "~@/style/theme.less"
css 屬性中:background: url("~@/assets/xxx.jpg")
html 標簽中:
vue-router 的 hash 模式和 history 模式
我們先來看一個完整的 URL:https://www.baidu.com/blog/guide/vuePlugin.html#vue-router。其中 https://www.baidu.com 是網站根目錄,/blog/guide/是子目錄,vuePlugin.html是子目錄下的文件(如果只有目錄,沒有指定文件,會默認請求index.html文件),而#vue-router就是哈希值。
vue 是單頁應用,打包之后只有一個 index.html,將他部署到服務器上之后,訪問對應文件的目錄就是訪問這個文件。
hash 模式:網址后面跟著 hash 值,hash 值對應每一個 router 的名稱,hash 值改變意味著router 改變,監聽 onhashchange 事件,來替換頁面內容。
history 模式:網址后面跟著‘假的目錄名’,其值就是 router 的名稱,而瀏覽器會去請求這個目錄的文件(并不存在,會 404),所以 history 模式需要服務器配合,配置 404 頁面重定向到到我們的 index.html,然后 vue-router 會根據目錄的名稱來替換頁面內容。
優缺點:
hash 模式的 # 號很丑,使用的是 onhashchange 事件切換路由,兼容性會好一點,不需要服務器配合
history 模式好看點,但是本地開發、網站上線,都需要服務器額外配置,并且還需要自己寫 404 頁面,使用的是 HTML5 的 history API,兼容性差一點。
兩者的配置區別在于:
const router = new VueRouter({mode: 'history', //"hash"模式是默認的,無需配置
base: '/',//默認配置
routes: [...]
})
vue-cli3 的 vue.config.js 配置:
module.exports = {publicPath: "./", // hash模式打包用
// publicPath: "/", // history模式打包用
devServer: {
open: true,
port: 88,
// historyApiFallback: true, //history模式本地開發用
}
}
如果是網站部署在根目錄,router 的 base 就不用填。如果整個單頁應用服務在 /app/ 下,然后 base 就應該設為 "/app/",同時打包配置(vue.config.js)的 publicPath 也應該設置成/app/。
vue-cli3生成新項目的時候會有選擇路由的模式,選擇history模式就會幫你都配置好。
vue-router的鉤子函數
鉤子函數分三種:組件內鉤子,全局鉤子,路由獨享鉤子。
APP.vue沒有組件內鉤子函數,因為APP.vue是頁面的入口,這個組件是必定會加載的,而使用組件內鉤子函數可以阻止組件加載。
全局鉤子主要用于路由鑒權,但是消耗很大。組件內的鉤子beforeRouteLeave主要用于用戶離開前的提示(比如說有未保存的文章),這個鉤子有一些坑:hash模式下,瀏覽器的后退按鈕無法觸發這個鉤子函數。同時我們還可以監聽用戶的關閉當前窗口/瀏覽器事件:
window.onbeforeunload = e => "確定離開當前頁面,你的修改將不會被保存!";為了防止惡意網站,用戶關閉窗口/瀏覽器事件是不可阻止的,只能提示,而且不同的瀏覽器兼容性也不同。
Vuex 持久化存儲
Vuex 中的數據,刷新頁面之后就會丟失。要實現持久化存儲需要借助本地存儲(cookie 和 storage 等),一般是登錄之后返回的數據(角色,權限,token 等)需要存儲到 Vuex,所以我們可以在登錄頁將數據存儲到本地,而在主頁面(除了登錄頁,其他所有頁面的入口)進入之前(beforeCreate 或者路由鉤子 beforeRouteEnter)讀取出來,并提交到 Vuex 就好了。這樣即使刷新,也會觸發主頁面的進入鉤子函數,會被提交到 Vuex。
beforeRouteEnter (to, from, next) {const token = localStorage.getItem('token');
let right = localStorage.getItem('right');
try{
right = JSON.parse(right);
}catch{
next(vm => {
//彈窗采用elementUI
vm.$alert('獲取權限失敗').finally(() => {
vm.$router.repalce({name:'login'})
})
})
}
if(!right || !token){
next({name:'login',replace:true})
}else{
next(vm => {
//這里面的事件會在mounted之后觸發
vm.$store.commit('setToken',token);
vm.$store.commit('setRight',right);
})
}
}
beforeRouteEnter的回調會在mounted鉤子之后觸發,這就比較蛋疼了。而主頁面的mounted會在所有子組件的mounted之后觸發,所以我們可以這樣寫。
import store from '^/store';//將實例化的store引入進來beforeRouteEnter (to, from, next) {
const token = localStorage.getItem('token');
if(!token){
next({name:'login',replace:true})
}else{
store.commit('setToken',token);
next();
}
}
要想實現數據修改之后仍能持久化存儲,我們可以先把數據存到localstorage,然后監聽window.onstorage事件,數據有修改提交到Vuex。
mutations 里面觸發 action
mutations 是同步修改 state 的值,假如另一個值是異步獲取(action)的,依賴于這個同步的值的修改,需要在 mutations 里面賦值之前觸發 action 里面的事件,我們可以給實例化的 Vuex 命名,在 mutations 里面拿到 store 對象。
const store = new Vuex.Store({state: {
age: 18,
name: 'zhangsan',
},
mutations: {
setAge(state, val) {
// 假如age變化了之后,name也要跟著變化
// 需要在每次給age賦值的時候,同步觸發action里面的getName
state.age = val;
store.dispatch('getName');
},
setName(state, val) {
state.name = val;
},
},
actions: {
getName({ commit }) {
const name = fetch('name'); //從接口異步獲取
commit('setName', name);
},
},
});
Vue.observable進行組件通信
如果項目很小,不需要用到 vuex ,可以用Vue.observable來模擬一個:
//store.jsimport Vue from 'vue';
const store = Vue.observable({ name: '張三', age: 20 });
const mutations = {
setAge(age) {
store.age = age;
},
setName(name) {
store.name = name;
},
};
export { store, mutations };
axios 的 qs 插件
get 請求的數據放在 url 里面,類似于http://www.baidu.com?a=1&b=2,其中a=1&b=2就是 get 的參數,而對于 post 請求,參數放到 body 里面,常用的數據格式有表單數據和 json 數據,兩者的差異就是數據格式不同,表單數據編碼格式和 get 一樣,只不過是放在 body 里面,而 json 數據則是 json 字符串
qs 基本使用:
import qs from 'qs'; //qs是axios里面自帶的,所以直接引入就可以了const data = qs.stringify({
username: this.formData.username,
oldPassword: this.formData.oldPassword,
newPassword: this.formData.newPassword1,
});
this.$http.post('/changePassword.php', data);
qs.parse()是將 URL 解析成對象的形式,qs.stringify()是將對象 序列化成 URL 的形式,以&進行拼接。而對于不同的數據格式,axios 會自動設置對應的content-type,不需要手動設置。
表單數據(不帶文件)的 content-type 是application/x-www-form-urlencoded
表單數據(帶文件)的 content-type 是multipart/form-data
json 數據的 content-type 是application/json
碰到過一次接口需要我用表單傳一個數組。假設數據是arr = [1,2,3]如果直接使用 qs.stringify(),則數據會變成arr[]=1&arr[]=2&arr[]=3,很容易看出來,多了一個[],讓接口把參數名改成arr[]就能用,但是這樣不好。不過可以發現,表單傳數組的本質就是同名參數傳多次,這時候我們也可以這樣:
const data = new FormData();arr.forEach(item => {
data.append('arr', item);
});
測試一下,完美解決,但是事情到這里還沒完,翻一下qs 官方文檔,qs 轉換支持第二個參數,完美解決我們的問題。
const data = qs.stringify(arr, { arrayFormat: 'repeat' }); // arr=1&arr=2&arr=3elementUI 的一些總結
表單驗證同步寫法,避免多層嵌套函數
if (!valid) return
按需引入之后級聯菜單高度撐滿屏幕。解決辦法:加一句全局樣式
height: 250px;
}
級聯菜單的數據是按需獲取的沒法回顯。解決辦法:根據已有的路徑數據去請求樹數據,然后給級聯菜單加一個v-if,等數據都請求好了再顯示出來。比如說省市縣三級聯動數據,已知用戶選擇的是廣東省-深圳市-南山區,那么分別去請求所有省、廣東省、深圳市的數據,然后將數據拼成一個 tree ,綁定到級聯菜單,然后設置v-if="true"。
表格高度自適應,可以給表格外層加一個 div ,然后給這個 div 計算高度(或者彈性盒子自適應高度),表格屬性
/* less寫法 */
.table-wrap{
height: calc(~"100vh - 200px");
/* 部分版本這樣寫會失效,需要加上下面一句 */
/deep/ .el-table{
height: 100% !important;
}
}
使用多個 upload 組件,需要將這些文件一起上傳到服務器。可以通過this.$refs.poster.uploadFiles拿到文件對象。然后自己手動組裝成表單數據。
tips: 建議上傳尺寸250*140
methods:{
//選擇圖片之后替換舊圖片和顯示略縮圖
changePhoto(file, fileList) {
//創建的Blob URL可直接預覽圖片
this.previewUrl = window.URL.createObjectURL(file.raw);
if (fileList.length > 1) {
fileList.shift();
}
},
revokeUrl(e) {
//圖片加載完成之后銷毀Blob URL
if (e.target.src.startsWith("blob:")) window.URL.revokeObjectURL(e.target.src);
},
//提交表單數據
async submitData() {
const template = this.$refs.template.uploadFiles[0], //模板文件
poster = this.$refs.poster.uploadFiles[0], //海報文件
formData = new FormData();
if (!template) return this.$message.warning("必須選擇模板文件");
if (!poster) return this.$message.warning("必須選擇海報文件");
formData.append("zip", template.raw);
formData.append("poster", poster.raw);
const res = await this.$http.post('url', formData);
},
}
使用VueI18n國際化,需要將elementUI的語言包和項目中的語言包合并成一個。
import zhLocale from './locales/zh.js';/* 引入本地簡體中文語言包 */
import zhTWLocale from './locales/zh-TW.js';/* 引入本地繁體中文語言包 */
import enLocale from './locales/en.js';/* 引入本地英語語言包 */
import zhElemment from 'element-ui/lib/locale/lang/zh-CN'//引入elementUI簡體中文語言包
import zhTWElemment from 'element-ui/lib/locale/lang/zh-TW'//引入elementUI繁體中文語言包
import enElemment from 'element-ui/lib/locale/lang/en'//引入elementUI英語語言包
Vue.use(VueI18n);
const messages = {//語言包
zh: Object.assign(zhLocale, zhElemment),//本地語言包加入elementUI的語言包
'zh-TW': Object.assign(zhTWLocale, zhTWElemment),//本地語言包加入elementUI的語言包
en: Object.assign(enLocale, enElemment)//本地語言包加入elementUI的語言包
};
const i18n = new VueI18n({
locale: "zh", //zh默認是簡體中文
messages
});
Vue.use(ElementUI, {
i18n: (key, value) => i18n.t(key, value)
})
最后
有寫錯的,或者有什么問題,歡迎大家評論
作者:沉末_ 鏈接:https://juejin.im/post/5d8c6a97e51d45782c23fa69
推薦閱讀:
1、GitHub 上能挖礦的神仙技巧 - 如何發現優秀開源項目
2.?9 種你或許不知道的 Vue 好用小技巧
3.?Vue + TypeScript + Element 項目實戰及踩坑記
4.?重磅:硬核前端面試開源項目匯總(進大廠必備)
總結
以上是生活随笔為你收集整理的vue @input带参数_Vue 全家桶开发的一些小技巧和注意事项的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python将txt转json_Pyth
- 下一篇: Joi验证模块的使用