0%

React with JSX

考慮下面這個變數宣告:

1
const element = <h1>Hello Tainan,台南!</h1>;

這個標籤語法不是一個字串也不是 HTML。

這個語法叫做 JSX。JSX 結合了 JavaScript 的 JS 和 XML 的 X,是一個 JavaScript 的語法擴充。允許我們直接在 Javascript 源代碼中使用基於標籤的語法來定義 React 元素。有時 JSX 會與 HTML 混淆,因為它們看起來很相似。JSX 也可能會讓你想到一些樣板語言(template language),但不一樣的地方是 JSX 允許你使用 JavaScript 所有的功能。

JSX 是創建 React 元素的另一種方式,執行 JSX 會產生 React 元素,因此您不必費力地在復雜的 React.createElement 調用中尋找缺少的逗號。

建議你在寫 React 的時候透過 JSX 語法來描述使用者介面的外觀。

將 React.createElement 改用 JSX:

const hello = React.createElement("h1", { id: "hello" }, "Hello Scott!");

const hello = <h1 id="hello">Hello Scott!</h1>;
1
2
3
4
5
6
7
<div id="react-root"></div>

<script type="text/babel">
const hello = <h1 id="hello">Hello Scott!</h1>;

ReactDOM.render(hello, document.getElementById("react-root"));
</script>

JavaScript 不認識 JSX 語法,必須先經過 Babel 的預處理(compiling)。

但我們通常不會使用變數的方式,而會使用函式組件的方式:

1
2
3
4
5
<script type="text/babel">
const Hello = () => <h1 id="hello">Hello Scott!</h1>;

ReactDOM.render(<Hello />, document.getElementById("react-root"));
</script>

這個模式是 React JSX 的基本使用方式: 函式組件 (function component)。組成的組件樹(component tree)構造整個 React 應用程序。

JSX Tips

JSX 可能看起來很熟悉,並且大多數規則導致的語法類似於 HTML。但是,使用 JSX 時應注意一些事項。

Nested components

JSX 允許我們將組件添加為其他組件的子代。 例如,在 UsersList 內部,我們可以多次渲染另一個名為 User 的組件。

1
2
3
4
5
<UsersList>
<User />
<User />
<User />
</UsersList>
className

由於 class 是 JavaScript 中的保留字,因此使用 className 來定義 class 屬性。

1
<h1 className="fancy">Hello Scott!</h1>
JavaScript expressions

JavaScript 表達式用大括號 { } 括起來,指示變數將在何處被求值並返回其結果值。例如,如果要在元素中顯示 user 屬性的值,則可以使用 JavaScript 表達式插入該值。該變數 user 將被求值並返回其值。

1
<h1>{user}</h1>

除字符串以外的其他類型的值,也應顯示為 JavaScript 表達式:

1
<input type="checkbox" defaultChecked={false} />
Evaluation

大括號之間添加的 JavaScript 將被求值。 這意味著,可以執行字串串聯或加法之類的操作。這也意味著, JavaScript 將會調用在表達式中找到的函數:

1
2
3
4
5
<h1>{"Hello " + user}</h1>

<h1>{user.toLowerCase()}</h1>

<h1>{`Hello ${user.toLowerCase()}`}</h1>
Mapping Arrays with JSX

JSX 是 JavaScript,因此您可以將 JSX 直接合併到 JavaScript 函數中。例如,您可以將陣列映射到 JSX 元素:

1
2
3
4
5
<ul>
{ userData.map((user, i) => (
<li key={i}>{user}</li>
))}
</ul>

可以比對一下使用 React.createElement 與 使用 JSX 的差別:

const hello = React.createElement(
"ul",
null,
userData.map((user, i) =>
React.createElement("li", { key: i }, `Hello ${user}`)
)
);



const hello =
<ul>
{ userData.map((user, i) => (
<li key={i}>{user}</li>
))}
</ul>;

JSX 看起來很乾淨而且有較佳的可讀性,但是無法直接在瀏覽器解譯執行。所有 JSX 必須轉換為 React.createElement 調用。幸運的是,有一個出色的工具可以執行此任務:Babel。

整段由使用 React.createElement 改用 JSX 改寫。

  • 使用 React.createElement
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script type="text/babel">
const userData = [
"Scott",
"Emily",
"Amanda"
];

const UsersList = ({ users = [] }) => (
React.createElement(
"ul",
null,
users.map((user, i) =>
React.createElement("li", { key: i }, `Hello ${user}`)
)
)
);

ReactDOM.render(
React.createElement(UsersList, { users: userData }, null),
document.getElementById("react-root")
);
</script>
  • 使用 JSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script type="text/babel">
const userData = [
"Scott",
"Emily",
"Amanda"
];

const UsersList = ({ users = [] }) => (
<ul>
{ users.map((user, i) => (
<li key={i}>{user}</li>
))}
</ul>
);

ReactDOM.render(
<UsersList users={userData} />,
document.getElementById("react-root")
);
</script>
  • 第 8 行是一個函式組件,這裡改用 JSX。
  • 第 17 行我們使用 JSX 語法調用 UsersList 函式組件,透過 users 屬性傳遞函式引數。這裡的所有屬性將被包在一個物件 { users: “[…]”, others: “…” } 傳遞給函式組件。

有了基礎概念,現在來做一個比較實際的例子。

美味食譜

我們需要一些美味食譜的資料,我們將它放在 APEX Page Properties 的 JavaScript > Function and Global Variable Declaration 中。

recipesData
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const recipesData = [
{
name: "烤鮭魚",
ingredients: [
{ name: "鮭魚", amount: 1, measurement: "磅" },
{ name: "松子", amount: 1, measurement: "杯" },
{ name: "生菜", amount: 2, measurement: "杯" },
{ name: "黃色的南瓜", amount: 1, measurement: "中" },
{ name: "橄欖油", amount: 0.5, measurement: "杯" },
{ name: "大蒜", amount: 3, measurement: "瓣" },
],
steps: [
"將烤箱預熱至350度。",
"將橄欖油撒在玻璃烤盤上。",
"加入南瓜,放入烤箱30分鐘。",
"將鮭魚,大蒜和松子添加到菜中.",
"烤15分鐘。",
"從烤箱中取出,加入生菜即可食用。"
]
},
{
name: "魚炸玉米餅",
ingredients: [
{ name: "白鲑", amount: 1, measurement: "磅" },
{ name: "乳酪", amount: 1, measurement: "杯" },
{ name: "捲心萵苣", amount: 2, measurement: "杯" },
{ name: "番茄", amount: 2, measurement: "大" },
{ name: "玉米饼", amount: 3, measurement: "中" }
],
steps: [
"將魚放在烤架上煮至熱。",
"將魚放在3個玉米餅上。",
"上放生菜,番茄和乳酪。"
]
},
{
name: "蕃茄炒蛋",
ingredients: [
{ name: "紅蕃茄", amount: 3, measurement: "個" },
{ name: "雞蛋", amount: 3, measurement: "個" },
{ name: "蕃茄醬", amount: 1, measurement: "大匙" },
{ name: "蔥花", amount: 2, measurement: "大匙" },
{ name: "蒜末", amount: 1, measurement: "小匙" },
{ name: "橄欖油", amount: 2.5, measurement: "大匙" },
{ name: "鹽", amount: 1, measurement: "小匙" }
],
steps: [
"蕃茄洗淨後切塊狀..(去皮否隨個人喜好即可)~蛋打散備用。",
"起油鍋(兩大匙油)~蛋先入鍋炒至5分熟後盛出備用。",
"原鍋再加入油1/2大匙爆香蔥白、蒜末後倒入蕃茄醬拌炒。",
"接著加入水適量(剛好淹蓋住食材即可)煮開後,加入調味料煮片刻。",
"湯汁略為濃縮後倒入5分熟的炒蛋混合拌勻再煮一下即可。",
"起鍋前淋下香油,灑入蔥花拌一下即可盛盤。"
]
},
];

Region Properties 的 Source > Text 的程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<div id="react-root"></div>

<script type="text/babel">
const { render } = ReactDOM;

const Ingredient = ({amount, measurement, name}) =>
<li>
{amount} {measurement} {name}
</li>;

const IngredientsList = ({list}) =>
<ul className="ingredients">
{list.map((ingredient, i) => (
<Ingredient key={i} {...ingredient} />
))}
</ul>;

const Instructions = ({title, steps}) =>
<section className="instructions" style={{paddingLeft: "1em"}}>
<h3>{title}</h3>
{steps.map((s, i) => (
<p key={i}>{s}</p>
))}
</section>;

const Recipe = ({name, ingredients, steps}) =>
<section id={name.toLowerCase().replace(/ /g, "-")} style={{marginBottom: "1em"}}>
<h2>{name}</h2>
<IngredientsList list={ingredients} />
<Instructions title="烹飪說明" steps={steps} />
</section>;

const Menu = ({title, recipes}) =>
<article>
<header>
<h1>{title}</h1>
</header>
<div className="recipes" >
{recipes.map((recipe, i) => (
<Recipe key={i} {...recipe} />
))}
</div>
</article>;

render(
<Menu recipes={recipesData} title="美味食譜" />,
document.getElementById('react-root')
);
</script>

這裡有 5 個 React 組件,組成一個組件樹。Menu 組件是根組件(root component),所有的資料從根組件的 recipes 屬性注入(第 46 行) ,往整個組件樹流動。此外還有一個 title 屬性。這兩個屬性組成了 Menu 函式所需的引數。Menu 參數用解構賦值的語法取得傳入的值。

第 39 ~ 41 行用 JavaScript 陣列的 map 方法加入多個 Recipe 組件,這裡使用了展開運算子 { …recipe } 賦予 recipe 的所有屬性 name、ingredients 與 steps。

這些組件是基於將應用程序的數據作為屬性傳遞給 Menu 組件而構造的。 如果我們更改 recipes 陣列並重新渲染 Menu 組件,整個樹狀組件也將引起連鎖反應,而 React 將盡可能有效地更改此 DOM。

數據使我們的 React 組件活起來。沒有 recipes 資料,我們構建的 recipes 的用戶界面是沒有用的。食譜和配料以及清晰的烹飪步驟說明使這樣的應用程序有價值。

我們構建了一個組件樹:數據能夠作為屬性流過組件樹的層次結構。 屬性(properties)是整體構圖的一半。另一半則是狀態(state)。 React 應用程序的狀態由具有更改能力的數據驅動。數據變動,驅動整個 React 應用程序的狀態的變動。

目前資料是死的,我們要讓資料活起來,帶動 React 應用程序的互動性。React State Management