Vue組件基礎和Ref對象
組件
概念: 組件系統(tǒng)是 Vue 的一個重要概念相恃,Vue允許我們將代碼拆分獨立成一些將小型的可復用模塊肪笋。這些模塊就被成為組件,使用這些組件就可以構建一個大型項目。在Vue中組件分為全局組件和私有組件
組件的創(chuàng)建
- 全局組件
概念: 注冊在Vue構造函數中的組件,在Vue實例任何地方任何組件中都可以使用
語法: Vue.component('組件名', {組件的配置對象})
<div id="app">
<gec-title></gec-title>
</div>
<script>
//全局組件,使用Vue構造函數提供的component API添加在構造函數中,在Vue實例對象的任何地方都可以使用.\
Vue.component('gec-title', {
// 這里的配置選項與Vue實例對象的配置選項(除了沒有el以外)基本相同
// template 模板配置選項,Vue實例配置選項中也有此選項
// 當前組件/vue實例渲染在頁面中的DOM模板
template: `
<div>
<h2>{{12 + 8}}</h2>
我是組件gec-title的模板
</div>
`
})
new Vue({
el: '#app'
})
</script>
- 私有組件
概念: 通過components配置選項注冊在Vue實例對象或其他子組件內部,只能在注冊的父組件內部使用闲昭。
語法: Vue配置選項和組件配置選項都支持components屬性 components: {組件名: { 組件配置選項 }}
<div id="app">
<gec-title></gec-title>
<component-a></component-a>
</div>
<script>
//全局組件,使用Vue構造函數提供的component API添加在構造函數中,在Vue實例對象的任何地方都可以使用.\
Vue.component('gec-title', {
// 這里的配置選項與Vue實例對象的配置選項(除了沒有el以外)基本相同
// template 模板配置選項,Vue實例配置選項中也有此選項
// 當前組件/vue實例渲染在頁面中的DOM模板
template: `
<div>
<h2>{{12 + 8}}</h2>
我是組件gec-title的模板
</div>
`
})
new Vue({
el: '#app',
components: {
// 在Vue實例中注冊的局部組件 'component-a',只能在Vue的實例對象中使用
'component-a': {
template: `
<div>
<h3>我是組件A</h3>
<child-a></child-a>
<gec-title/>
</div>
`,
components: {
// 在組件'component-a'中注冊的局部組件,該組件只能在組件'component-a'中使用
'child-a': {
template: '<h2>我是組件A的子組件childA</h2>'
}
}
}
}
})
</script>
注意: template屬性指定DOM模板結構中必須有且僅有一個根DOM元素!
組件的data配置選項
概念: 組件設計初衷就是將哪些獨立的可復用的代碼塊封裝起來,因為對象是引用數據類型如果直接將組件的data屬性設置為對象的話。同一個組件在復用時會導致多個組件同時讀寫同一個對象找都,嚴重的影響了組件可復用性和獨立性。為了解決這個問題Vue明確規(guī)定組件的data不可以是個對象廊酣,而是一個返回data對象的工廠模式函數能耻。
語法:
// 普通函數
Vue.component('gec-title', {
template: `
<div>
<h2>{{name}}</h2>
</div>
`,
data() {
return {
name: '小明',
age: 18
}
}
})
// 箭頭函數
Vue.component('gec-title', {
template: `
<div>
<h2>{{name}}</h2>
</div>
`,
data: () => ({
name: '小明',
age: 18
})
})
單項數據流
概念: 在Vue中組件之間是單項數據流的。單項數據流規(guī)定子組件不可以直接訪問父組件的數據亡驰,只能通過props屬性讓父組件把數據傳遞給子組件晓猛。并且子組件不可以直接修改父組件傳遞給子組件的數據。
props的使用
概念: 組件可以通過特殊的配置選項props給自身設置自定義屬性凡辱,父組件就可通過props屬性傳值將父組件的數據傳遞給子組件戒职,因為Vue是單項數據流子組件不可以修改props(props是只讀的)
注意:組件的data屬性與props屬性不能同名
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<h2>{{age}}</h2>
<my-label v-bind:a="age" b="hello" :c="age >= 18?'已成年':'未成年'"/>
</div>
<script>
new Vue({
el: '#app',
data: {
age: 15
},
components: {
'my-label': {
// 給當前my-label設置了三個自定義屬性a b c
// 組件組件自身就可以通過 this.props.a
props: ['a','b','c'],
data: () => ({
name: 'my-label'
}),
template: `
<div>
<p>a:{{a}}</p>
<p>b:{}</p>
<p>c:{{c}}</p>
<child-a v-bind:name="name" :rootage="a"></child-a>
</div>
`,
components: {
'child-a': {
props: ['name','rootage'],
template: '<div>我是childA 父組件的name為{{name}} rootAge{{rootage}}</div>'
}
}
}
}
})
</script>
反向傳值
因為Vue是單項數據流的,規(guī)定只允許父組件向子組件傳遞狀態(tài),而不允許子組件直接修改父組件傳遞過來的狀態(tài)透乾。Vue提供了自定義事件API,通過父組件監(jiān)聽子組件自定義事件, 當子組件想要修改父組件的狀態(tài)時會通過 $emit 方法觸發(fā)自定義事件洪燥。父組件對應監(jiān)聽子組件自定義事件的回調函數就會被觸發(fā),這樣父組件自身的方法就會相應的去修改自身的狀態(tài)乳乌。子組件的props就會更新
語法: $emit('自定義事件名', 向父組件回調函數傳遞參數)
注意: $emit只接受兩個參數,參數一觸發(fā)父組件監(jiān)聽的指定事件名,參數二(可選)向父組件監(jiān)聽事件回調函數傳遞的數據
案例: $emit的使用
<div id="father">
<child :zfb="money" @call="callHandel"/>
</div>
<script>
new Vue({
el:'#father',
data: {
money: 1500
},
methods: {
callHandel() {
console.log('兒子給我打電話了,說錢不夠花')
this.money += 500
}
},
components: {
'child': {
props: ['zfb'],
template: `
<div>
父親通過支付寶轉生活費額度:{{zfb}}
<button @click="$emit('call')">向父親打電話</button>
</div>
`
},
}
})
</script>
Vue腳手架安裝使用
- Step1 在系統(tǒng)變量中安裝Vue腳手架工具vue-cli
#全局安裝vue-cli環(huán)境配置
npm install -g @vue/cli
[圖片上傳失敗...(image-f5a03d-1623249421811)]
- Step2訪問到指定目錄,使用vue指令創(chuàng)建新項目
#全局安裝完畢后,以后 vue create 項目名稱 搭建項目
vue create project_name
[圖片上傳失敗...(image-3c5470-1623249421811)]
- Step3執(zhí)行創(chuàng)建vue項目指令時會返回一個vue項目配置詢問,先直接使用默認vue 2.0 模板
? Please pick a preset: (Use arrow keys)
> Default ([Vue 2] babel, eslint) #使用默認vue 2.0 模板
Default (Vue 3 Preview) ([Vue 3] babel, eslint) #使用默認vue 3.0 模板
Manually select features #自定義模板
[圖片上傳失敗...(image-58dc1f-1623249421811)]
- Step4 安裝完畢后
# 訪問新創(chuàng)建的項目目錄
cd project_name
#啟動測試用服務開發(fā)指令
npm run serve
#項目開發(fā)完畢打包指令
npm run build
Vue-cli項目結構
<pre>
webpack-demo
|- /public // 公共目錄,這個目錄中的文件不會被webpack打包而是作為一個靜態(tài)目錄
// 放置在 public 目錄下或通過絕對路徑被引用捧韵。這類資源將會直接被拷貝,而不會經過 webpack 的處理汉操。
|- index.html // vue中 html模板文件
|- /src // 整個項目代碼開發(fā)目錄
|- main.js // 項目的入口文件
|- babel.config.js // webpack babel-loader配置文件
|- package.json // 項目的配置描述文件
|- README.md // 項目的readme文件
+|- vue.config.js // 可自定義Vue webpack相關配置的文件
</pre>
局部關閉 ESlint 校檢
ESlint 是一個js代碼檢測工具,約束開發(fā)人員的代碼風格,如果想具體了解,請查閱其文檔
相關文檔: https://blog.csdn.net/qq_39557024/article/details/107519531
public 文件夾
概念: 任何放置在 public 文件夾的靜態(tài)資源都會被簡單的復制再来,而不經過 webpack。你需要通過絕對路徑來引用它們磷瘤。
注意: 我們推薦將資源作為你的模塊依賴圖的一部分導入芒篷,這樣它們會通過 webpack 的處理并獲得如下好處:
腳本和樣式表會被壓縮且打包在一起搜变,從而避免額外的網絡請求。
文件丟失會直接在編譯時報錯梭伐,而不是到了用戶端才產生 404 錯誤痹雅。
最終生成的文件名包含了內容哈希,因此你不必擔心瀏覽器會緩存它們的老版本糊识。
public 目錄提供的是一個應急手段绩社,當你通過絕對路徑引用它時,留意應用將會部署到哪里赂苗。如果你的應用沒有部署在域名的根部愉耙,那么你需要為你的 URL 配置 publicPath 前綴,在 public/index.html 或其它通過 html-webpack-plugin 用作模板的 HTML 文件中拌滋,你需要通過 <%= BASE_URL %> 設置鏈接前綴:
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" href="<%= BASE_URL %>css/style.css">
在js文件中 使用process.env.BASE_URL作為pubulic文件的前綴
<template>
<div id="app">
// 直接引入靜態(tài)目錄中的文件
<img alt="Vue logo" :src="`${publicPath}imgs/01.jpg`">
// 相對路徑的引入會導致webpack對該文件進行打包
<img alt="Vue logo" src="../public/imgs/01.jpg">
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
// 獲取公共目錄路徑
publicPath: process.env.BASE_URL
}
}
}
</script>
publicPath配置實在項目的根目錄下vue.config.js中設置publicPath選項就好了
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/production-sub-path/' //真實開發(fā)的話,如果你的項目存放在公司域名二級路徑下 只需要將 /production-sub-path/改為 /公司二級路徑/就可以了
: '/'
}
webpack 相關(了解)
介紹: 在vue-cli中簡單的配置webpack方式就是在Vue項目的根目錄下創(chuàng)建vue.config.js文件朴沿。這個通過配置這個文件中 module.exports 公開的對象實現對Vue Webpack進行修改。
- 指定項目靜態(tài)資源模塊的相對路徑
- 指定打包后文件的目錄(默認dist文件)
- assets文件的目錄
- 多頁面應用開發(fā)
- css模塊化 loader等
- 給項目添加Webpack plugin
- 配置當前項目的開發(fā)測試服務器
// vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/production-sub-path/' //真實開發(fā)的話,如果你的項目存放在公司域名二級路徑下 只需要將 /production-sub-path/改為 /公司二級路徑/就可以了
: '/',
devServer: { // 服務器代理,當請求了代理設置的路徑時 會自動跳轉到指定服務器上,解決跨域問題
proxy: {
"/search": {
target: 'http://musicapi.leanapp.cn/',
changeOrigin: true
// 當你請求 /search?123123 時 會代理到 'http://musicapi.leanapp.cn/search?123123'
}
}
}
}
了解Vue-cli src目錄結構
介紹: 在src文件中main.js是整個項目的入口文件败砂,也是實例化Vue對象的地方
因為vue-cli是模塊化開發(fā)赌渣,所以整個項目不適用< script >標簽引入Vue支持而是使用模塊化依賴模式通過import引入Vue對象
import Vue from 'vue'
import App from './App.vue' // 引入單文件App組件
Vue.config.productionTip = false
// 實例化Vue
new Vue({
render: h => h(App)
// render是template字符串模板的替代方案,render是一個函數函數接收一個參數(createElement)
// 這個參數可以將組件生成為一個Vue DOM節(jié)點渲染在頁面上
// 所以上面這句化等價于 template: '<App></App>'
}).$mount('#app')
// 如果 Vue 實例在實例化時沒有收到 el 選項昌犹,則它處于“未掛載”狀態(tài)坚芜,沒有關聯的 DOM 元素。
// 這時可以使用 vm.$mount() 手動地掛載一個未掛載的實例斜姥。
認識單文件組件
我們觀察下面的代碼,在Vue實例中添加了局部組件App,如果說除了Vue實例以外其他組件可能也需要注冊App局部組件的話鸿竖。我們推薦將App抽離出來提供給其他組件復用
new Vue({
el: '#app',
components: {
App: {
props: ['name','age'],
data() {
return {
address: 'bj'
}
},
template: `
<div>
<h2>用戶:{{name}} 年齡:{{age}}</h2>
<p>地址:{{address}}</p>
</div>
`
}
}
})
抽離后
const App = {
props: ['name','age'],
data() {
return {
address: 'bj'
}
},
template: `
<div>
<h2>用戶:{{name}} 年齡:{{age}}</h2>
<p>地址:{{address}}</p>
</div>
`
}
new Vue({
el: '#app',
components: {
App
}
})
我們在開發(fā)中推薦使用模塊化開發(fā)扣汪,建議將每個可復用的組件單獨封裝成一個js模塊询件,通過import引入其他文件中復用。這樣的好處是便于維護更新讓項目的結構更明確专钉。
// 將App組件封裝成一個js模塊
// App.js
export default {
props: ['name','age'],
data() {
return {
address: 'bj'
}
},
template: `
<div>
<h2>用戶:{{name}} 年齡:{{age}}</h2>
<p>地址:{{address}}</p>
</div>
`
}
// 其他組件或實例注冊App組件
import App from './App'
new Vue({
el: '#app',
components: {
App
}
})
Vue為了簡化template字符串模板的開發(fā)(使用字符串寫HTML語法js無法格式化代碼杈笔,編譯器無法對模板進行補全和檢查)闪水,Vue提供了一個.vue文件簡化了js文件創(chuàng)建單文件組件時template字符串模板開發(fā)不便。.vue文件單獨的將template選項抽離出來以HTML的形式進行開發(fā)蒙具。
上面的App.js就可以轉化為:
// App.vue
// template 被抽離出來變成了一個獨立的標簽,內部本來使用字符串模板代碼變成HTML語法
<template>
<div>
<h2>用戶:{{name}} 年齡:{{age}}</h2>
<p>地址:{{address}}</p>
</div>
</template>
<script>
export default {
props: ["name", "age"],
data() {
return {
address: "bj",
}
}
}
</script>
注意:
-
.vue
文件不僅支持template標簽指定組件的模板樣式和script 公開當前模板配置選項,還支持style標簽內置的設置當前組件的樣式,而且style標簽支持使用sass\less\stylus預編譯語言
<template>
<div class="demo">
我是Demo,在vue文件中
<!-- template內部遵循XML語法規(guī)則,所有單一型標簽后面一定要跟一個/,并且template標簽內部有且僅有一個根元素 -->
<input/>
</div>
</template>
<script>
export default {};
</script>
<!--
style有兩個可選屬性
lang="scss" 內部使用sass語法,項目要額外安裝 sass-loader
lang="less" 內部使用less語法,項目要額外安裝 less-loader
lang="stylus" 內部使用less語法,項目要額外安裝 stylus-loader
不設置該屬性則使用css
scoped 樣式私有化,如果設置了該屬性,style標簽內的所有樣式只會對組件自身有效
-->
<style lang="less" scoped>
.demo {
color: green;
}
</style>
- 建議使用.vue創(chuàng)建組件時,組件名與文件名一致并且使用每個單詞首字母大寫的命名方法敦第。例: HelloWorld.vue、DemoComponent.vue
Props驗證
概念:在開發(fā)中我們可以為組件的 prop 指定驗證要求店量,例如你知道的這些類型芜果。如果有一個需求沒有被滿足,則 Vue 會在瀏覽器控制臺中警告你融师。這一模式會在開發(fā)中幫開發(fā)人員捕獲大量的異常右钾。
語法: props可以是一個數組,數組中的每一項都是當前組件的props屬性名。props屬性還是是一個對象為每個指定的props屬性指定其驗證規(guī)則以及默認值
props: {
// 當前組件的propsA屬性必須是Number數據類型或者為空(可忽略)
propA: Number,
// propB與propA等價
// prop為指定數據類型只需要設置該屬性type值為對應數據類型的構造函數
propB: {
type: Number
},
// prop屬性可以設置為必要屬性,渲染該組件時必須給該屬性傳值并且不可為空
propC: {
type: Number,
required: true
},
// prop屬性支持默認值,當沒有給該prop傳遞屬性是,該屬性則會使用默認值
// 注意: 默認值優(yōu)先級小于required
propD: {
type:Number,
default: 15
},
propE: {
type: Object,
// 對象或數組默認值必須從一個工廠函數獲取
default: function () {
return { message: 'hello' }
}
}
}
Props驗證規(guī)則
props: {
// 通過構造函數指定prop數據類型
propA: Number,
propB: Boolean,
propC: String,
propD: Array,
propE: Object,
propF: Symbol,
propG: RegExp,
propH: Date,
propI: Cat, // 可以指定任何構造函數作為prop的type,當前prop接收到的值必須是當前指定構造函數的實例對象
propJ: Function
// prop驗證支持 多個可能的類型
propK: [String, Number],
// 自定義驗證規(guī)則,不滿足驗證條件是return false
propL: {
validator(val) {// val 傳入到 propL屬性的值
if (typeof val === "string") {
if (/fuck/gi.test(val)) {
console.error("組件ComponentA中 屬性 PropL包含敏感詞匯!");
} else {
return true;
}
} else {
console.error("數據必須是字符串");
}
return false;
}
}
}
動態(tài)組件
概念: VUe 提供了一個標簽component,該標簽可以使用 is attribute 來切換不同的組件:
<div id="app">
<component :is="`my-${name}`"></component>
<button @click="name ='a'">a</button>
<button @click="name ='b'">b</button>
</div>
<script>
new Vue({
el: '#app',
data: {
name: 'a'
},
components: {
'my-a': {
template: '<h2>我的組件A</h2>'
},
'my-b': {
template: '<h2 @click="num++">組件B{{num}}</h2>',
data() {
return {num: 7}
}
},
}
})
</script>
在動態(tài)組件上使用 keep-alive
概念: 當在這些組件之間切換的時候舀射,你有時會想保持這些組件的狀態(tài)窘茁,以避免反復重渲染導致的性能問題。
<div id="app">
<!-- 失活的組件將會被緩存脆烟!-->
<keep-alive>
<component :is="`my-${name}`"></component>
</keep-alive>
<button @click="name ='a'">a</button>
<button @click="name ='b'">b</button>
</div>
<script>
new Vue({
el: '#app',
data: {
name: 'a'
},
components: {
'my-a': {
template: '<h2>我的組件A</h2>'
},
'my-b': {
template: '<h2 @click="num++">組件B{{num}}</h2>',
data() {
return {num: 7}
}
},
}
})
</script>
注意: 在上面的案例中如果沒有使用keep-alive緩存失活的組件,那么失活的組件將被丟棄(卸載)山林,組件切換時每次都是掛載一個全新的組件
異步組件(了解)
介紹: 在大型應用中,我們可能需要將應用分割成小一些的代碼塊邢羔,并且只在需要的時候才從服務器加載一個模塊驼抹。為了簡化,Vue 允許你以一個工廠函數的方式定義你的組件拜鹤,這個工廠函數會異步解析你的組件定義框冀。Vue 只有在這個組件需要被渲染的時候才會觸發(fā)該工廠函數,且會把結果緩存起來供未來重渲染敏簿。例如:
Vue.component('async-example', function (resolve, reject) { setTimeout(function () { // 向 `resolve` 回調傳遞組件定義 resolve({ template: '<div>I am async!</div>' }) }, 1000) })
如你所見明也,這個工廠函數會收到一個 resolve 回調,這個回調函數會在你從服務器得到組件定義的時候被調用惯裕。你也可以調用 reject(reason) 來表示加載失敗温数。這里的 setTimeout 是為了演示用的,如何獲取組件取決于你自己蜻势。一個推薦的做法是將異步組件和 webpack 的 code-splitting 功能一起配合使用:
Vue.component('async-webpack-example', function (resolve) { // 這個特殊的 `require` 語法將會告訴 webpack // 自動將你的構建代碼切割成多個包撑刺,這些包 // 會通過 Ajax 請求加載 require(['./my-async-component'], resolve) })
你也可以在工廠函數中返回一個 Promise,所以把 webpack 2 和 ES2015 語法加在一起咙边,我們可以這樣使用動態(tài)導入:
Vue.component( 'async-webpack-example', // 這個動態(tài)導入會返回一個 `Promise` 對象。 () => import('./my-async-component') )
當使用局部注冊的時候次员,你也可以直接提供一個返回 Promise 的函數:
new Vue({ // ... components: { 'my-component': () => import('./my-async-component') } })
如果你是一個 Browserify 用戶同時喜歡使用異步組件败许,很不幸這個工具的作者明確表示異步加載“并不會被 Browserify 支持”,至少官方不會淑蔚。Browserify 社區(qū)已經找到了一些變通方案市殷,這些方案可能會對已存在的復雜應用有幫助。對于其它的場景刹衫,我們推薦直接使用 webpack醋寝,以擁有內置的頭等異步支持。
懶加載狀態(tài) (2.3.0+ 新增)
這里的異步組件工廠函數也可以返回一個如下格式的對象:
const AsyncComponent = () => ({ // 需要加載的組件 (應該是一個 `Promise` 對象) component: import('./MyComponent.vue'), // 異步組件加載時使用的組件 loading: LoadingComponent, // 加載失敗時使用的組件 error: ErrorComponent, // 展示加載時組件的延時時間带迟。默認值是 200 (毫秒) delay: 200, // 如果提供了超時時間且組件加載也超時了音羞, // 則使用加載失敗時使用的組件。默認值是:`Infinity` timeout: 3000 })
Ref
概念: Vue給組件元素提供了一個ref屬性,綁定了ref屬性的組件元素可以在當前vue實例對象中通過$refs訪問其真實DOM節(jié)點(標簽元素)或實例對象(組件)仓犬。
在原生html標簽中使用ref
語法: <div ref="變量名"></div>
當前組件內部 this.$refs.變量名 訪問其真實DOM節(jié)點
<div id="app">
<audio ref="myAudio" src="./藥水歌.mp3" controls></audio>
<button @click="showAudio">show</button>
</div>
<script>
new Vue({
el: '#app',
methods: {
showAudio() {
console.log(this.$refs.myAudio)
}
}
})
</script>
在組件中中使用ref
語法: <MyComponent ref="變量名" />
當前組件內部 this.$refs.變量名 訪問這個子組件的實例對象,可以獲取這個組件實例的屬性和方法(盡量避免使用該模式,因為他會增加組件間的耦合性)
<template>
<Demo ref="myDemo" />
</template>
<script>
import Demo from './components/Demo'
export default {
name: 'App',
methods: {
show() {
// 子組件Demo的方法在父組件中被調用了
this.$refs.myDemo.sayHello()
}
},
components: {
Demo
}
}
</script>
注意:
- vue實例中每個標簽或組件都可以設置ref屬性,綁定了ref屬性的元素可以有任意個
<div id="app">
<audio ref="myAudio" src="./藥水歌.mp3" controls></audio>
<button ref="myBtn" @click="showAudio">show</button>
<p ref="textEl">text</p>
</div>
// 這時可以在組件中通過 this.$refs不同的變量名訪問對應的DOM元素
- ref配合列表渲染(v-for)使用時,$refs返回值為包含列表渲染出來的所有元素的一個數組
<div id="app">
// 與v-for同級的ref 返回值都是列表渲染出來當前元素數組集合
<p v-for="num in arr" :key="num" ref="numList">
//v-for 內部的ref 返回值也是列表渲染出來當前元素數組集合
<span ref="spanList">{{num}}-span</span>
<a>沒用的a</a>
{{num}}
</p>
<button @click="showRefList">click</button>
</div>
<script>
new Vue({
el: '#app',
data: {
arr: [1, 2, 3, 4, 5]
},
methods: {
showRefList() {
console.log(this.$refs.numList,
this.$refs.spanList)
// [p,p,p,p,p] , [span,span,span,span,span]
}
}
})
</script>