iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 11
2
Modern Web

從比入門再往前一點開始,一直到深入React.js系列 第 11

【Day.11】React入門 - function component、props、children

(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就誕生了。

function component的語法

在React中,我們只要遵循以下規則,就能讓function變成React的元件,並且在JSX中以<我的元件/>或是<我的元件></我的元件>方式使用。

  • 開頭引入React(React 17前)
  • 函數名稱為大寫
  • 回傳JSX

例如,前五天的MenuItem以原生JS長這樣:

function MenuItem(wording){
    let menuItem = document.createElement('li');
    menuItem.setAttribute('class',"menu-item");
    menuItem.textContent = wording;
    
    this.getDOMItem = () => menuItem;
}

在React中,我們可以這樣寫:

  • src/component/MenuItem.js (請注意這裡的路徑有小變化)
import React from 'react';

function MenuItem(){
    return <li>文字</li>;
}

export default MenuItem;

而回到前一篇的src/index.js,我們就能以<MenuItem/>或是<MenuItem></MenuItem>的方式使用MenuItem

  • src/index.js
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,我們會在後面和效能處理的文章一起說明

props - 以外部參數控制元件

看到這裡,你應該會有個想法:

不對啊,你這裡menuItem裡的文字是鑲死的,但我需要動態符合array裡的內容啊?

當初React的設計者也有想到這點。他們想到,在過去使用原生HTML元素的時候,我們常常會用 像id、onclick這些 attribute(屬性) 來設定元素,例如以下就是在設定button被點擊後在幹嘛:

<button onclick="doSomething()">按我啊</button>

如果我們自製的元件也能夠這樣該有多好啊 !

於是,React的設計者就讓所有寫在自製元件標籤上的「屬性」,和其他從外部控制元件的參數包成一個物件、傳入元件中,並稱為props。

props要怎麼用呢? 以我們的menuItem為例,我們在menuItem上把wording綁在一個稱為text的屬性上:

  • src/index.js
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時
<MenuItem text={wording}/>

也就是function component會收到:

  • MenuItem得到的參數結構
props = {
    text: "wording對應的文字"
}

所以,我們如果要在menuItem中使用,就會是這樣:

  • src/component/MenuItem.js
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的樣子囉!

  • src/component/Menu.js
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;
  • src/index.js
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中間呢?

children - 夾在中間的props

設計React的工程師也想到了這點,於是,他們把「夾在標籤中間」的內容也全部包成一個物件,稱為children,然後再丟進props裡面

也就是剛剛的程式碼可以改成這樣:

  • src/index.js
//上方省略
let menuItemArr = menuItemWording.map((wording) => <MenuItem text={wording}/>);

ReactDOM.render(
    <Menu title={"Andy Chang的Like"}>{menuItemArr}</Menu>,
    document.getElementById('root')
);

在Menu中,中間的東西會在props的「children屬性」中

  • src/component/Menu.js
//上方省略

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。


上一篇
【Day.10】React入門 - JSX語法
下一篇
【Day.12】React入門 - useState與解構賦值後的props
系列文
從比入門再往前一點開始,一直到深入React.js30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言