底下這個 APP 裡,有兩支 JS 檔案都使用幾乎一樣格式和 UI 的卡片,如何讓這個卡片可以重複利用在不同地方,同時卡片裡面又能帶入各自的內容?
這篇學到
-
props
也能傳遞 HTML 標籤裡的內容
ex.<h1>我是標題,我也可以被傳遞到別的元件</h1>
-
大量的 JSX 可以透過
props.children
方法,就能在別的元件裡讀取。
原本的寫法
類似的卡片框架:上半部有標題、Avatar、開發者名字&連結;下半部是 Icon list。
這個框架在 Popular 和 Results 共重複寫了 2 次,但是兩邊必須帶入各自的資訊。
Popular.js
顯示程式語言排名最高的開發者。
// Popular.js
function ReposGrid ({ repos }) {
return (
<ul className='grid space-around'>
{repos.map((repo, index) => {
const { owner, html_url, stargazers_count, forks, open_issues } = repo
const { login, avatar_url } = owner
return (
<li key={html_url} className='card bg-light'>
{/* 重複的上半部 */}
<h4 className='header-lg center-text'>
#{index + 1}
</h4>
<img
className='avatar'
src={avatar_url}
alt={`Avatar for ${login}`}
/>
<h2 className='center-text'>
<a className='link' href={html_url}>{login}</a>
</h2>
{/* 重複的下半部 */}
<ul className='card-list'>
<li>
<FaUser color='rgb(255, 191, 116)' size={22} />
<a href={`https://github.com/${login}`}>
{login}
</a>
</li>
<li>
<FaStar color='rgb(255, 215, 0)' size={22} />
{stargazers_count.toLocaleString()} stars
</li>
<li>
<FaCodeBranch color='rgb(129, 195, 245)' size={22} />
{forks.toLocaleString()} forks
</li>
<li>
<FaExclamationTriangle color='rgb(241, 138, 147)' size={22} />
{open_issues.toLocaleString()} open issues
</li>
</ul>
</li>
)
})}
</ul>
)
}
Results.js
輸入兩個 Github 使用者,根據專案的星星和 followers 的數目顯示贏家。
// Results.js
export default class Results extends React.Component {
constructor(props) {
super(props);
this.state = {
winner: null,
}
}
render() {
const { winner } = this.state;
return (
<div className='grid space-around container-sm'>
{/* 重複的上半部 */}
<div className='card bg-light'>
<h4 className='header-lg center-text'>
{winner.score === loser.score ? 'Tie' : 'Winner'}
</h4>
<img
className='avatar'
src={winner.profile.avatar_url}
alt={`Avatar for ${winner.profile.login}`}
/>
<h4 className='center-text'>
Score: {winner.score.toLocaleString()}
</h4>
<h2 className='center-text'>
<a className='link' href={winner.profile.html_url}>
{winner.profile.login}
</a>
</h2>
{/* 重複的下半部 */}
<ul className='card-list'>
<li>
<FaUser color='rgb(239, 115, 115)' size={22} />
{winner.profile.name}
</li>
{winner.profile.location && (
<li>
<FaCompass color='rgb(144, 115, 255)' size={22} />
{winner.profile.location}
</li>
)}
{winner.profile.company && (
<li>
<FaBriefcase color='#795548' size={22} />
{winner.profile.company}
</li>
)}
<li>
<FaUsers color='rgb(129, 195, 245)' size={22} />
{winner.profile.followers.toLocaleString()} followers
</li>
<li>
<FaUserFriends color='rgb(64, 183, 95)' size={22} />
{winner.profile.following.toLocaleString()} following
</li>
</ul>
</div>
</div>
)
}
}
建立可重複使用的元件 (Reusable component)
1. 建立 Card component
// Card.js
export default function Card ({ header, subheader, avatar, href, name, children }) {
return(
<div className="card bg-light">
{/* 卡片的上半部 */}
<h4 className='header-lg center-text'>
{header}
</h4>
<img
className='avatar'
src={avatar}
alt={`Avatar for ${name}`}
/>
{subheader && (
<h4 className='center-text'>
{subheader}
</h4>
)}
<h2 className='center-text'>
<a className='link' href={href}>
{name}
</a>
</h2>
{/* 卡片的下半部 */}
{/* <ul className='card-list'> 會透過 props.children 傳遞進來 */}
{children}
</div>
)
}
-
新增 card.js
將整個卡片裡的所有內容
<div className='card bg-light'>
額外拉出來,建立成一個獨立元件<Card>
。 -
動態內容透過 props 傳遞
<h4>
、<img>
、<a>
等標籤裡的內容,因為會置放在兩個不同地方,資訊彼此不同,所以另外把這些動態內容取名 ex.header
、avatar
、href
等,到時內容會透過props
傳遞進來。 -
加上 props.children
使用
prop.children
,會將<ul className='card-list'>
裡的整個 icon 列表從別的元件透過prop.children
傳遞進來。(見下一步驟)
為什麼
<ul className='card-list'>
沒有寫進<Card>
的 template 裡?
因為這個區塊在兩處內容顯示差異比較大,icon 都不一樣,顯示的訊息也不大相同,你也可以嘗試將所有<li>
裡的 icon 列表用props
傳遞,但程式會變的複雜也不好維護。
2. 修改 Popular.js
// Popular.js
import Card from './Card';
function ReposGrid ({ repos }) {
return (
<ul className='grid space-around'>
{repos.map((repo, index) => {
const { owner, html_url, stargazers_count, forks, open_issues } = repo
const { login, avatar_url } = owner
return (
<li key={html_url}>
{/* 置入 Card 並將裡頭內容透過 props 傳遞 */}
<Card
header={`#${index + 1}`}
avatar={avatar_url}
href={html_url}
name={login}
>
{/* card-list 整個移入至 Card 裡面 */}
<ul className='card-list'>
<li>
<FaUser color='rgb(255, 191, 116)' size={22} />
<a href={`https://github.com/${login}`}>
{login}
</a>
</li>
...
</ul>
</Card>
</li>
)
})}
</ul>
)
}
-
Import
<Card>
componentimport Card from './Card';
-
props 傳遞內容
將
Card
上半部裡的開發者內容透過props
傳遞 -
card-list 放在
<Card></Card>
裡面將下半部整個
<ul className='card-list'></ul>
放在<Card>
階層底下,要被<Card></Card>
包起來。
整個 card-list 移入
<Card>
階層底下,到時 card.js 要怎麼接收讀取?
在 card.js 裡使用props.children
,任何包在元件 ex.<Card></Card>
裡的小孩都能讀取。
3. 修改 Results.js
和 Popular.js 一樣的修改流程
// Results.js
import Card from './Card';
export default class Results extends React.Component {
render() {
return (
<div className='grid space-around container-sm'>
{/* 置入 Card 並將裡頭內容透過 props 傳遞 */}
<Card
header={winner.score === loser.score ? 'Tie' : 'Winner'}
subheader={`Score: ${winner.score.toLocaleString()}`}
avatar={winner.profile.avatar_url}
href={winner.profile.html_url}
name={winner.profile.login}
>
{/* card-list 整個移入至 Card 裡面 */}
<ul className='card-list'>
<li>
<FaUser color='rgb(239, 115, 115)' size={22} />
{winner.profile.name}
</li>
...
</ul>
</Card>
</div>
)
}
}
-
Import
<Card>
componentimport Card from './Card';
-
props 傳遞內容
將
Card
上半部裡的贏家內容透過props
傳遞 -
card-list 放在
<Card></Card>
裡面將下半部整個
<ul className='card-list'></ul>
放在<Card>
階層底下,要被<Card></Card>
包起來。
結果
經過改寫,卡片呈現一切正常。
將兩邊卡片的上半部拆成獨立的元件 <Card>
,內容各自透過 props
傳遞;下半部 icon list 差異度較大,所以在既有的 JS 檔案裡保留整個 JSX 架構,但是包在各自引入的 <Card>
元件裡,之後在 card.js 裡用 props.children
就能讀取兩邊各自的 icon list。