(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問
有了JSX之後,工程師還是不滿足。因為過去像這樣的過程不斷的重複:
let menuItemWording=[
"Like的發問",
"Like的回答",
"Like的文章",
"Like的留言"
];
let menuItem = menuItemWording.map((wording) => new MenuItem(wording));
let menuInstance = new Menu(menuItem);
但這樣不就等於是我們在自製HTML元素嗎? 既然現在HTML標籤元素可以被運算,那我們自製的元素能不能也用和HTML標籤元素一樣的方法使用呢?
於是,function component就誕生了。
在React中,我們只要遵循以下規則,就能讓function變成React的元件,並且在JSX中以<我的元件/>
或是<我的元件></我的元件>
方式使用。
例如,前五天的MenuItem以原生JS長這樣:
function MenuItem(wording){
let menuItem = document.createElement('li');
menuItem.setAttribute('class',"menu-item");
menuItem.textContent = wording;
this.getDOMItem = () => menuItem;
}
在React中,我們可以這樣寫:
import React from 'react';
function MenuItem(){
return <li>文字</li>;
}
export default MenuItem;
而回到前一篇的src/index.js
,我們就能以<MenuItem/>
或是<MenuItem></MenuItem>
的方式使用MenuItem
import React from 'react';
import ReactDOM from 'react-dom';
// 引入剛剛撰寫的元件
import MenuItem from './component/MenuItem';
let menuItemWording=[
"Like的發問",
"Like的回答",
"Like的文章",
"Like的留言"
];
let menuItemArr = menuItemWording.map((wording) => <MenuItem/>);
ReactDOM.render(
<div>{menuItemArr}</div>,
document.getElementById('root')
);
執行結果:
是不是很方便呢?
這裡React的console會跳warning,提示你應該要為menuItem加上key,我們會在後面和效能處理的文章一起說明
看到這裡,你應該會有個想法:
不對啊,你這裡menuItem裡的文字是鑲死的,但我需要動態符合array裡的內容啊?
當初React的設計者也有想到這點。他們想到,在過去使用原生HTML元素的時候,我們常常會用 像id、onclick這些 attribute(屬性) 來設定元素,例如以下就是在設定button被點擊後在幹嘛:
<button onclick="doSomething()">按我啊</button>
如果我們自製的元件也能夠這樣該有多好啊 !
於是,React的設計者就讓所有寫在自製元件標籤上的「屬性」,和其他從外部控制元件的參數包成一個物件、傳入元件中,並稱為props。
props要怎麼用呢? 以我們的menuItem為例,我們在menuItem上把wording綁在一個稱為text
的屬性上:
import React from 'react';
import ReactDOM from 'react-dom';
import MenuItem from './component/MenuItem';
let menuItemWording=[
"Like的發問",
"Like的回答",
"Like的文章",
"Like的留言"
];
let menuItemArr = menuItemWording.map((wording) => <MenuItem text={wording}/>);
ReactDOM.render(
<div>{menuItemArr}</div>,
document.getElementById('root')
);
此時,React會把MenuItem標籤上屬性整理成一個物件,傳入function component的參數中。
<MenuItem text={wording}/>
也就是function component會收到:
props = {
text: "wording對應的文字"
}
所以,我們如果要在menuItem中使用,就會是這樣:
import React from 'react';
const menuItemStyle = {
marginBottom: "7px",
paddingLeft: "26px",
listStyle: "none"
};
function MenuItem(props){ //加入props到參數
// 使用props中的text
return <li style={menuItemStyle}>{props.text}</li>;
}
export default MenuItem;
執行結果:
接著,我們就能來試試看把Menu也改成React的樣子囉!
import React from 'react';
const menuContainerStyle = {
position: "relative",
width: "300px",
padding: "14px",
fontFamily: "Microsoft JhengHei",
paddingBottom: "7px",
backgroundColor: "white",
border: "1px solid #E5E5E5",
};
const menuTitleStyle = {
marginBottom: "7px",
fontWeight: "bold",
color: "#00a0e9",
cursor: "pointer",
};
const menuBtnStyle = {
position: "absolute",
right: "7px",
top: "33px",
backgroundColor: "transparent",
border: "none",
color: "#00a0e9",
outline: "none"
}
const menuStyle = {
display: "block" //這裡先讓它顯示
};
function Menu(props){
return (
<div style={menuContainerStyle}>
<p style={menuTitleStyle}>{props.title}</p>
<button style={menuBtnStyle}>V</button>
<ul style={menuStyle}>{props.menuItems}</ul>
</div>
);
}
export default Menu;
import React from 'react';
import ReactDOM from 'react-dom';
import MenuItem from './component/MenuItem';
// 引入新的Menu
import Menu from './component/Menu';
let menuItemWording=[
"Like的發問",
"Like的回答",
"Like的文章",
"Like的留言"
];
let menuItemArr = menuItemWording.map((wording) => <MenuItem text={wording}/>);
ReactDOM.render(
<Menu title={"Andy Chang的Like"} menuItems={menuItemArr}/>,
document.getElementById('root')
);
但是這樣還是不夠直覺,因為明明我們最後的結構是
但是現在卻是讓menuItem變成一個attribute的感覺,有沒有辦法能讓menuItem在視覺上夾在menu中間呢?
設計React的工程師也想到了這點,於是,他們把「夾在標籤中間」的內容也全部包成一個物件,稱為children,然後再丟進props裡面。
也就是剛剛的程式碼可以改成這樣:
//上方省略
let menuItemArr = menuItemWording.map((wording) => <MenuItem text={wording}/>);
ReactDOM.render(
<Menu title={"Andy Chang的Like"}>{menuItemArr}</Menu>,
document.getElementById('root')
);
在Menu中,中間的東西會在props的「children屬性」中
//上方省略
function Menu(props){ //修改ul中的內容
return (
<div style={menuContainerStyle}>
<p style={menuTitleStyle}>{props.title}</p>
<button style={menuBtnStyle}>V</button>
<ul style={menuStyle}>{props.children}</ul>
</div>
);
}
// 下方省略
是不是直覺多了呢?
如果有仔細看剛剛範例中的程式碼,你會發現Menu開關的功能並沒有被加進去。這是因為在React元件中,以內部控制元件必須要用特殊的API。如果你直接用前面的原生JS code去實作,會發現有一些問題、或是不照你的預期。我們會在下一篇來講解如何在function component使用這個API。