[Vue] Vue指令-渲染畫面
TL;DR
Vue指令大致分為三類:雙向綁定、畫面渲染、事件觸發。
這邊主要介紹渲染畫面使用的指令。
v-text & {{}}
v-text
與{{}}
(mustache)的使用上基本上是相同的,都是為了渲染純文字使用的。v-text
需要搭配html標籤使用。
都是直接在裡面插入data內的變數。
使用方法如下:
<p>{{name}}在{{position}}吃早餐</p>
<p><span v-text="name"></span>在<span v-text="position"></span>吃早餐</p>
使用v-text
的好處是可以將span
修改成strong
的標籤,就可以改為粗體。斜體也是一樣的。
如果今天data的值與一個input雙向綁定,則畫面也會同步修改。
<p>{{name}}在{{position}}吃早餐</p>
<p><span v-text="name"></span>在<span v-text="position"></span>吃早餐</p>
<input type="text" v-model="name">
<input type="text" v-model="position">
修改input的內容也會導致畫面更動
單次綁定(v-once)
如果只希望畫面在初始化那次要渲染data的內容,但是之後data變動時畫面不希望更動。就可以使用v-once
指令。
使用方法:
<p>{{name}}在{{position}}吃早餐</p>
<p><span v-text="name"></span>在<span v-text="position"></span>吃早餐</p>
<p v-once><span v-text="name"></span>在<span v-text="position"></span>吃早餐</p>
v-once不需要帶入任何參數。接著不管如何變更data內的資料,畫面都不會更動。
渲染出{{}}
如果希望可以在畫面中顯示出{{}}
,不希望被判讀成mustache將內容轉譯出來,可以使用v-pre
的指令:
<p v-pre>加上v-pre的{{}}不會被轉譯</p>
{{}} 進階技巧
其實{{}}
內可以放入任何的表達式。我們可以利用表達式撰寫靈活的程式碼:
<div id="app">
<div class="p-5">
<h3>進階技巧:表達式</h3>
<p>{{name}}在{{position}}吃早餐</p>
<p>{{`${name}在${position}吃早餐`}}</p>
<p>{{`${name}在${position}吃${30+50+100}元的早餐`}}</p>
<p>{{text.split('').reverse().join('')}}</p>
<p>可以綁定methods: {{say('杰倫')}}</p>
<p>{{ 87+87 }}</p>
<hr>
<h3>進階技巧:顯示資料狀態</h3>
<p>{{products}}</p>
</div>
</div>
-
樣板字面值
(Line:4 ~ Line:7)
因為在{{}}
內是撰寫表達式,所以我們可以撰寫一個樣板字面值。使用樣板字面值我們可以直接使用$
去取得變數內容。另外因為${}
內也是表達式,所以也可以直接做計算。 -
js原生方法
(Line:7)
只要是表達是都可以撰寫在{{}}
內,所以我們可以使用js原生的方法,將字串拆解,反轉,再組合。結果會輸出 "餐早吃店餐早在明小"。 -
調用methods內的方法
(Line:8)
在前面有事先定義一個方法:methods: {
say(name) {
return `${name}在${this.position}吃早餐`
}
}所以我們在
{{}}
內可以呼叫這個function,會將我們所傳入的字串杰倫
組合成新的字串後return,並且渲染出來。 -
數值計算
(Line:9) 既然都可以撰寫表達式了,數值的計算也是一個表達式。會將計算結果回傳出來並且渲染。 -
顯示資料狀態
開發過程中雖然可以透過套件去觀察data的內容,但是我們也可以直接將data的值透過{{}}
顯示在畫面上。開發結束後再將其移除。
v-html
為了防止XSS攻擊,在data內的資料透過v-text
or {{}}
的方式都只會渲染為字串。
如果要將資料以html標籤的方式解 析,則需要使用v-html
的指令:
<div id="app">
<div class="p-5">
{{rawHtml}}
<p v-text="rawHtml"></p>
<p v-html="rawHtml"></p>
</div>
</div>
Line:5 正確解析為html的格式。
官方文件有提到:
在你的站点上动态渲染任意的 HTML 是非常危险的,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要将用户提供的内容作为插值
可以的話最好不要使用v-html指令
v-for
當我們需要渲染多筆資料的時候,就會使用v-for
。v-for
可以帶入兩個參數:第一個是內容本身,第二個是索引值(從0開始)。如果不需要索引值可以只帶入一個參數。
另外我們也可以使用:key="`${item.id}-list`"
的方式,來組合成一組字串。
這邊的key不是我們在v-for內自定義的名稱key。而是vue定義的。
我們每次使用v-for的時候都需要加上v-bind:key="唯一值"
,用以區別不同的元件。
這是因為vue的運作機制的關係,有相同父元素的子元 素必須有獨特的 key。重複的 key 會造成渲染錯誤。
雖然理論上來說,索引值也會是唯一值。但是因為vue在渲染v-for的元素時是採用就地更新的方式,如果使用索引值當作key在元素需要調整順序時會有效能上的問題。所以本身是不建議利用索引值當作:key
內的值的。
但是如果元素的排序並不會更動,就不會造成效能上的問題。
簡單來說,可以的話還是使用id之類不會變動的值當作唯一值是最好的
陣列
v-for
使用時會依序取出陣列內的內容,以以下範例來說就是每個品項的物件。item
代表物件本身,是可以自行命名的,第二個參數可以選 用,用法類似forEach。
利用v-for
渲染多筆陣列內的資料:
- html(#app)
- data
<div id="app">
<div class="p-5">
<h3>呈現多筆資料於畫面上 v-for</h3>
<p>菜單</p>
<ul>
<li v-for="(item,key) in products" v-bind:key="item.name">
{{key+1}} - {{item.name}} / {{item.price}} 元
</li>
</ul>
</div>
</div>
data() {
return {
name: '小明',
products: [
{
name: '蛋餅',
price: 30,
vegan: false
},
{
name: '飯糰',
price: 35,
vegan: false
},
{
name: '小籠包',
price: 60,
vegan: false
},
{
name: '蘿蔔糕',
price: 30,
vegan: true
},
],
productsObj: {
chineseOmelette: {
name: '蛋餅',
price: 30,
vegan: false
},
riceBall: {
name: '飯糰',
price: 35,
vegan: false
},
soupDumpling: {
name: '小籠包',
price: 60,
vegan: false
},
radishCake: {
name: '蘿蔔糕',
price: 30,
vegan: true
}
},
}
},
......
key是item的索引值,值預設從0開始,我們希望從1開始,可以設定為{{key + 1}}
。
物件
使用v-for
迴圈將物件渲染到畫面上時
item
內容會是屬性的值(value)
key
則會是屬性名稱。
- html(#app)
- data
<div id="app">
<div class="p-5">
<h3>呈現多筆資料於畫面上 v-for</h3>
<h4>物件回圈</h4>
<p>菜單</p>
<ul>
<li v-for="(item,key) in productsObj" v-bind:key="item.name">
{{key}} - {{item.name}} / {{item.price}} 元
</li>
</ul>
</div>
</div>
data() {
return {
name: '小明',
products: [
{
name: '蛋餅',
price: 30,
vegan: false
},
{
name: '飯糰',
price: 35,
vegan: false
},
{
name: '小籠包',
price: 60,
vegan: false
},
{
name: '蘿蔔糕',
price: 30,
vegan: true
},
],
productsObj: {
chineseOmelette: {
name: '蛋餅',
price: 30,
vegan: false
},
riceBall: {
name: '飯糰',
price: 35,
vegan: false
},
soupDumpling: {
name: '小籠包',
price: 60,
vegan: false
},
radishCake: {
name: '蘿蔔糕',
price: 30,
vegan: true
}
},
}
},
......
因為這邊的key是屬性名稱,所以沒有辦法+1(應該說這邊如果+1則會變成字串的相加)
純數值
v-for
也可以用在純數值上,item會依序從1開始到指定的數字。
也可以使用巢狀迴圈:
<div id="app">
<div class="p-5">
<ul>
<li v-for="item in 5" :key="item">
{{item}}:<span v-for="item in 10" :key="item">{{item}}</span>
</li>
</ul>
</div>
</div>
template
有時候想要回圈渲染的內容,受限於html標籤沒有辦法直接使用一組v-for就將內容給渲染出來。這時候可以使用template標籤來一次渲染包含多個元素的區塊。
- html(#app)
- data
<div id="app">
<div class="p-5">
<ul>
<h3>進階技巧:在 template 標籤使用 v-for</h3>
<table class="table">
<tbody>
<template v-for="(item,key) in products" :key="item.name">
<tr >
<th rowspan="2">{{key}}</th>
<td colspan="2">
{{item.name}}
</td>
</tr>
<tr>
<td>
{{item.price}}元
</td>
<td>
{{item.vegan?"素食":"非素食"}}
</td>
</tr>
</template>
</tbody>
</table>
</ul>
</div>
</div>
data() {
return {
name: '小明',
products: [
{
name: '蛋餅',
price: 30,
vegan: false
},
{
name: '飯糰',
price: 35,
vegan: false
},
{
name: '小籠包',
price: 60,
vegan: false
},
{
name: '蘿蔔糕',
price: 30,
vegan: true
},
],
productsObj: {
chineseOmelette: {
name: '蛋餅',
price: 30,
vegan: false
},
riceBall: {
name: '飯糰',
price: 35,
vegan: false
},
soupDumpling: {
name: '小籠包',
price: 60,
vegan: false
},
radishCake: {
name: '蘿蔔糕',
price: 30,
vegan: true
}
},
}
},
......
另外這邊使用了三元運算子,判斷vegan
內的布林值,並且回傳出不同的字串。
v-if / v-else
v-if
指令可以讓我們判斷要不要顯示該節點,另外可以使用v-else
去判斷例外狀況:
- html(#app)
- data
<div id="app">
<div class="p-5">
<h3>v-if</h3>
<p v-if="isFull">小明 飽了</p>
<p v-else>小明 還沒吃飽</p>
<button type="button" v-on:click="change('isFull')">狀態切換</button>
</div>
</div>
const App = {
data() {
return {
name: '小明',
isFull: true,
link: '小明',
products: [
{
name: '蛋餅',
price: 30,
vegan: false
},
{
name: '飯 糰',
price: 35,
vegan: false
},
{
name: '小籠包',
price: 60,
vegan: false
},
{
name: '蘿蔔糕',
price: 30,
vegan: true
},
],
productsObj: {
chineseOmelette: {
name: '蛋餅',
price: 30,
vegan: false
},
riceBall: {
name: '飯糰',
price: 35,
vegan: false
},
soupDumpling: {
name: '小籠包',
price: 60,
vegan: false
},
radishCake: {
name: '蘿蔔糕',
price: 30,
vegan: true
}
},
}
},
methods: {
change: function (key) {
this[key] = !this[key];
},
},
};
Vue.createApp(App).mount('#app');
官方文件建議v-if
與v-for
不要撰寫在同一個元素上。因為優先級會不明顯。同時使用時v-if會優先執行。
如果真的有需要同時使用,可以利用template
來使用v-for
,內容再搭配v-if
判斷
<template v-for="">
<p v-if></p>
</template>
v-else-if
透過onclick切換link的內容,顯示目前套用active的是哪個按鈕。並且根據link內容切換要渲染哪個段落。
- html(#app)
- data
<div id="app">
<div class="p-5">
<h3>v-else-if</h3>
<nav class="nav nav-pills nav-fill">
<a class="nav-link" href="#" v-bind:class="{ 'active': link === '小明' }"
v-on:click="link = '小明'">小明</a>
<a class="nav-link" href="#" v-bind:class="{ 'active': link === '小美' }"
v-on:click="link = '小美'">小美</a>
<a class="nav-link" href="#" v-bind:class="{ 'active': link === '杰倫' }"
v-on:click="link = '杰倫'">杰倫</a>
</nav>
<div>
<div v-if="link === '小明'">小明吃早餐</div>
<div v-else-if="link === '小美'">小美去百貨公司</div>
<div v-else>杰倫去幫助人</div>
</div>
</div>
</div>
const App = {
data() {
return {
name: '小明',
isFull: true,
link: '小明',
products: [
{
name: '蛋餅',
price: 30,
vegan: false
},
{
name: '飯糰',
price: 35,
vegan: false
},
{
name: '小籠包',
price: 60,
vegan: false
},
{
name: '蘿蔔糕',
price: 30,
vegan: true
},
],
productsObj: {
chineseOmelette: {
name: '蛋餅',
price: 30,
vegan: false
},
riceBall: {
name: '飯糰',
price: 35,
vegan: false
},
soupDumpling: {
name: '小籠包',
price: 60,
vegan: false
},
radishCake: {
name: '蘿蔔糕',
price: 30,
vegan: true
}
},
}
},
methods: {
change: function (key) {
this[key] = !this[key];
},
},
};
Vue.createApp(App).mount('#app');
v-if
/ v-else-if
/ v-else
是有順序性的。如果把 Line:15 & Line:16 互換,會沒有辦法正確顯示
v-show
v-show
與v-if
使用方法類似,但是差異在讓節點消失的原理不同。
v-show
是利用display:none
讓節點消失;v-if
則是移除整個節點。
<h3>v-if 與 v-show</h3>
<p v-show="isFull">小明 飽了</p>
<p v-if="isFull">小明 飽了</p>
<button type="button" v-on:click="change('isFull')">狀態切換</button>
-
顯示時
-
不顯示時