了解完畫面渲染以及元件的生命週期後,接著來看看可以被Vue和React轉換成Virtual DOM的HTML的部分要怎麼寫吧!這個部分雖然沒有很困難,但老實說從Vue轉換到React我在一開始的時候,還是稍微有點不習慣,因為用v-for和v-if真的用得太習慣了。那究竟差異在哪呢?就直接給他看下去吧!
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
You’ve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a>
</h3>
</div>
</template>
「Template」是Vue官方推薦來定義元件顯示結構的HTML模板語法,原本就寫Vue的朋友應該都對Vue的Template很熟悉。使用它的時候,可以用大家都很熟悉的HTML寫法搭配template語法達到一些特定條件的畫面呈現方式。雖然看起來是很單純的在寫HTML,不過實際上寫出來的內容卻會被轉換成Vue的渲染函式。
看起來是寫HTML
<template>
<h1 className="greeting">
Hi! I am <i>{{name}}</i>
</h1>
</template>
實際上會被轉換成渲染函式
render() {
return this.$createElement('h1', { class: 'greeting' }, [
'Hi! I am ',
this.$createElement('i', this.name)
]);
}
雖然剛開始接觸Template的時候,需要了解特定用法中,它的語法該怎麼寫,但是當我們熟悉了寫法後,想要透過迴圈的方式寫,或是透過一些state來決定某一段HTML要不要顯示或是該怎麼顯示時,Template所提供的寫法都是讓我們更快達到這個目的的好幫手。
補充一下:如果想要在Vue中使用JSX也可以,但官方是推薦使用vue專屬的Template。
import { useState } from 'react';
export default function Content() {
const [count, setNumber] = useState(1);
const handleAddCount = () => {
setNumber(count + 1);
};
return (
<div>
<p>current count: {count}</p>
<button onClick={handleAddCount}>add</button>
</div>
)
}
JSX和Template一樣是在定義元件顯示的內容,雖然在定義HTML結構的時候,可以像在寫一般HTML一樣,去寫要用哪些HTML標籤,但這其實是JSX提供的語法糖,實際上你還是在寫JavaScript,而那個語法糖最後也會將你像在寫一般HTML的內容轉換成使用React.createElement()的寫法。
看起來是在寫HTML
function Greeting({name}) {
return (
<h1 className="greeting">
Hi! I am <i>{name}</i>
</h1>
);
}
實際上JSX會把看起來是在寫HTML的內容轉換成React.createElement()的寫法。
function Greeting() {
return React.createElement(
'h1',
{ className: 'greeting' },
'Hi! I am ',
React.createElement('i', null, name)
);
}
另外,與Template不同的是因為JSX就是在寫JavaScript,所以並沒有一些需要額外去記的寫法,例如想要用Template用法中的v-if、v-for的效果,可以很單純地透過大家最熟悉的JavaScript的寫法去呈現。還有一個需要特別注意的是因為寫JSX就是在寫JavaScript,所以在寫HTML的class的時候,會需要改寫成className
,Javascript才不會把它誤認是它自己本身就有的class。
接下來也來透過一些Vue Template中常用的寫法來看看用JSX要怎麼寫!
從Vue3開始Template就可以接受多個根元素,所以不用再跟以前一樣一定要包一層沒有任何用途的div在最外層,可以直接寫自己希望的HTML結構。
<template>
<input type="text" >
<button>Click</button>
</template>
寫JSX的時候,則不接受多個根元素,因為React.createElement只會回傳一個React element,所以如果寫多個根元素的話,就無法成功轉換成React.createElement()的寫法。
因為這個緣故,在實際撰寫的時候,也就需要在外層多包一層HTML元素。但是如果是用div包在最外層的話,會讓最後的HTML多了一個層沒有必要的div,想避免這種情況的時候,就可以使用Fragement或空的標<>來包在最外層。
所以可以寫成這樣
export default function RefCom() {
return (
<Fragement>
<h1>title</h1>
<p>description</p>
</Fragement>
)
}
也可以寫成這樣
export default function RefCom() {
return (
<>
<h1>title</h1>
<p>description</p>
</>
)
}
在Template中,不需要嚴格的使用閉合標籤,template會自動幫忙處理這個部分,所以可以這樣寫在template裡面。
<template>
<input type="text">
<img src="a.jpg" alt="a" >
</template>
但是寫jsx的時候,就必需要嚴格的使用閉合標籤,這樣jsx才能正常解析這個內容。
<input type="text" />
<img src="a.jpg" alt="a" />
如果是寫Template的話,我們需要使用v-if和v-else這樣的語法下去讓畫面可以依照state下去切換成正確該顯示成什麼。
<template>
<div>
<p class="special" v-if="isSpecialMode">special mode</p>
<p v-else>normal mode</p>
<button @click="handleModeToggle">toggle</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const isSpecialMode = ref(false);
const handleModeToggle = () => {
isSpecialMode.value = !isSpecialMode.value
};
</script>
如果是寫JSX的話,就需要記得現在自己是在寫JavaScript,所以需要用寫JavaSctipy的思考模式下去呈現,所以可以寫if else、三元運算,也可以用switch、&&或||的寫法來呈現。
import './App.css';
import { useState } from 'react';
function App() {
const [isSpecialMode, setIsSpecialMode] = useState(false);
const handleToggleMode = () => {
setIsSpecialMode(!isSpecialMode);
};
return (
<div className="App">
{
isSpecialMode ? <p className="special">special mode</p> : <p>normal mode</p>
}
<button onClick={handleToggleMode}>Toggle</button>
</div>
);
}
export default App;
如果是寫Template,我們可以使用v-for這個語法下去讓畫面用跑迴圈的方式呈現,以減少重複結構的程式碼出現,重複出現的部分就可以只寫一次。
<template>
<ul>
<li v-for="listItem in list" :key="listItem.id">{{ listItem.name }}</li>
</ul>
</template>
<script setup>
import { ref } from 'vue';
const list = ref([
{
id: 1,
name: 'apple',
},
{
id: 2,
name: 'lemon',
},
{
id: 3,
name: 'orange',
}
]);
</script>
寫JSX的話,一樣也是要轉換成在寫JavaScript的大腦,用JavaScript跑迴圈方式下去寫就可以,主要都是以map的寫法下去寫。
function App() {
const list = [
{
id: 1,
name: 'apple',
},
{
id: 2,
name: 'lemon',
},
{
id: 3,
name: 'orange',
}
];
return (
<div className="App">
<ul>
{
list.map((item) => (
<li key={item.id}>{ item.name }</li>
))
}
</ul>
</div>
);
}
export default App;
如果要把事件或變數綁定在Template上,需要使用v-on及v-bind的寫法,大多時候都會簡寫為@和:。如果是想要把變數帶到畫面上顯示,則是透過{{}}。
<template>
<div>
<!-- 放上變數 -->
<p>{{ count }}</p>
<!-- 綁定事件 -->
<button @click="handleAdd">Add</button>
<!-- 綁定變數 -->
<ChildComponent :msg="msg" />
</div>
</template>
在這個部分寫JSX的寫法也有差異,只要使用JSX的以on開頭的事件名稱即可,綁定變數也只要用以下這樣的寫法就可以。
import './App.css';
import { useState } from 'react';
import ChildComponent from './components/ChildComponent';
function App() {
const [count, setCount] = useState(0);
const handleCountAdd = () => {
setCount(count + 1);
};
return (
<div className="App">
// 放上變數
<p>count: {count}</p>
// 綁定事件
<button onClick={handleCountAdd}>Toggle</button>
// 綁定變數
<ChildComponent count={count} />
</div>
);
}
export default App;
今天快速看了一下Vue及React在HTML部分的寫法,雖然Template和JSX看起來都像在寫單純的HTML,但實際上都不是在寫HTML
,一個是Vue提供的模板語法,一個則是在寫JavaScript,也是因為它們實際上都不是HTML,所以才可以搭配Vue及React的渲染機制產生Virtual DOM,而最終都將被轉換成Virtual DOM物件。如果原本是習慣Vue的Template寫法的人,只要記得JSX其實是在寫JavaScript,並以寫JavaScript的方向下去思考,就會比較快熟悉JSX的寫法。
那Template和JSX的部分就看這裡,明天先休息一下,暫時忘掉React和Vue,來看一下不管寫React還是寫Vue都很重要的一個資料流的design pattern - 單向資料流。
createElement
Writing Markup with JSX