iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 10
3
Modern Web

給初入JS框架新手的React.js入門系列 第 10

【React.js入門 - 10】 夾在中間的props: children

  • 分享至 

  • xImage
  •  

(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問


在前面幾篇中,當我們使用React component時,多半是這樣使用的:

<元素名稱/>

然而在一開始的時候,我們提到過像這樣的使用方法也是可以的:

<元素名稱> (其他的東西) </元素名稱>

在以前使用純html語法時,我們也常常用這樣巢狀的方式包住多個元素。那麼我們要怎麼在自製元素中使用包在中間的「其他東西」呢?
在react component中,我們把包在標籤中間的東西,稱為children。

children的使用

children是props之一,所以當使用的children改變時,畫面也會重繪。接續上一篇的程式碼來練習使用children。原本我們在App.js中完成了一個有按鍵的元件,並在index.js使用。

import React from 'react';
function App(props){
  return(
       <button> {props.name} </button> 
  );
}
export default App;

現在,我們試著來在index.js中,用夾在<App>標籤中的內容來設定<button></button>中間的文字。

  1. 請把index.js中的<App/>改成<App></App>,並在兩個標籤中間加入文字。

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';
    import * as serviceWorker from './serviceWorker';
    
    ReactDOM.render(
        <div>
            <App> 在index.js中設定文字 </App>
        </div>,
        document.getElementById('root')
    );
    
    
  2. 把App.js的render函式中button標籤內的文字移除。

    function App(props){
      return(
           <button>  </button> 
      );
    }
    
  3. 在App.js函式中button標籤內使用children。因為children是props之一,所以使用方法為props.children

    function App(props){
      return(
           <button> {props.children} </button> 
      );
    }
    export default App;
    
    

執行結果:

children會用來幹嘛?

自己覺得最常用到的地方是做固定的Layout(EX: nav列)
例如,這是我亂做的破產版FB導覽列:

import React from 'react';

const Layout=(props)=>{
    const navStyle={
        position:"fixed",
        top:"0",
        left:"0",
        width:"100%",
        height:"43px",
        backgroundColor:"rgb(66, 103, 178)",
        display:"flex",
        alignItems:"center"
    };
    const iconStyle={
        marginLeft:"10%",
        height:"25px",
        width:"25px",
        borderRadius:"1px",
        backgroundColor:"white"
    };
    const inputStyle={
        marginLeft:"5px",
        padding:"0px 7px",
        height:"25px",
        width:"28%",
        borderRadius:"2px",
        border:"none",
        backgroundImage:"url('https://cdn1.iconfinder.com/data/icons/hawcons/32/698627-icon-111-search-512.png')",
        backgroundPosition:"97% 50%",
        backgroundSize:"auto 80%",
        backgroundRepeat:"no-repeat"
    };
    return(
      <div>
          <div className="nav-bar" style={navStyle}>
            <div  className="icon" style={iconStyle}>
                <img style={{height:"120%"}} src="https://www.logospng.com/images/48/facebook-logo-fb-sketched-sketch-icon-48978.png" alt="icon"/>
            </div>
            <input placeholder="搜尋" style={inputStyle}/>
          </div>
          <div className="index-container" style={{marginTop:"43px"}}>
              {props.children}
          </div>
      </div>
    );
}
export default Layout;

index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import Layout from "./Layout";
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
    <div>
        <Layout>
            <App>在index.js中設定文字</App>
        </Layout>
    </div>,
    document.getElementById('root')
);
serviceWorker.unregister();


搭配後面會講的state或是react-router-dom去控制children的內容,就能做出最小更動畫面的效果。另外,在設計component時,也能用children把架構拆成兩層,做出相當彈性、直覺又實用的架構(例如: boostrap中的List & ListItem)

預告第21篇: children間的溝通

跟上一篇的最後一樣,我還是先提過一下children間的溝通方法,後面會再整理一次。
children之間或是children和包住children的元素溝通方法相當於在「同一父元件中兩個子元件的溝通」。假設今天我們今天在A元件中,使用了B元件,B元件中的children是C和D元件。那麼當B要傳東西給C時,我們就要把A當作溝通橋梁:

  1. 用A的state來綁定C的props,在A中定義一個改變該state的函式
  2. 把該函式綁在B上
  3. 在B元件呼叫綁定在props上的A元件函式
  4. A元件的函式修改了state
  5. C元件的props就會被改變

C和D之間如果要溝通也是用同樣的方法,後面會有一篇詳細講一次。

小結

children的使用方法就講到這邊,接下來幾篇我們會先從class component開始,然後講state跟react hook的useState。

題外話:
自己以前在查鐵人賽文章的時候,不會30篇全看,而是只看自己需要的章節,所以才會把相關的東西都提一下。在安排主題順序的時候猶豫了很久,因為我希望把這系列的重心擺在讓新手step by step了解現代框架中對於元件的操作邏輯,所以步調放的很慢,我覺得自己可能是這次鐵人賽中講React的人裡面講最慢的吧哈哈。可是這樣就變成我每篇都在說這個會用到後面的東西,不知道這樣是不是件壞事就是了。


上一篇
【React.js入門 - 09】 用props綁定函式
下一篇
【React.js入門 - 11】 開始進入class component
系列文
給初入JS框架新手的React.js入門31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
吠吠
iT邦新手 3 級 ‧ 2022-05-09 10:28:28

想請教一下,如果使用了 props.chidren ,要如何在 children 中再 props 進去 children 裡面呢?
example:
App.jsx

exprort default function App () { 
    return <>
        <Card>
            <Button />
        </Card>
    </>
}

Card.jsx

exprort default function Card ({children}) { 
    const length = list.length
    const Item = () => <>{children}</>
    
    return <>
    {
        length.map(el=> {
            <Item key={el.id} data={el}/>
        })
    }
    </>
}

這時候在 Button.jsx 裡面的 data 會獲取不到資料

看更多先前的回應...收起先前的回應...
Andy Chang iT邦研究生 4 級 ‧ 2022-05-09 19:16:27 檢舉

Hi, 抱歉最近有點小忙,我先回答你這個case的解法,再來慢慢解釋原理

import Card from 'someWhere';
import Button from 'someWhere';

export default function App () { 
    return <Card element={Button} />
}

// 你的list應該是從某個地方取得的,我假設它的結構是這樣
const list = [
    { id: 'a' },
    { id: 'b' },
];

export default function Card ({ element }) { 
    const Element = element; //不可省略
    return (
        <>
            {
                list.map(el=> {
                    <Element key={el.id} data={el}/>
                })
            }
        </>
   );
}
吠吠 iT邦新手 3 級 ‧ 2022-05-09 22:54:25 檢舉

非常非常感謝大大的回答,最近在自學 react 的路上都是看松鼠大大的文章XD

Andy Chang iT邦研究生 4 級 ‧ 2022-05-11 22:31:36 檢舉

要說明的部份有點多,我一部份一部份講。

首先,map是javascript array ES6後的原生內建函式,而你先是呼叫了這個函式來取得array長度,也就是length是一個integer

const length = list.length

但在length是integer的情況下,對他呼叫map是不行的,因為integer並沒有原生map函式

length.map(el=> {
    <Item key={el.id} data={el}/>
})
Andy Chang iT邦研究生 4 級 ‧ 2022-05-11 23:32:57 檢舉

接著是為什麼你的data無法傳給Button。在你的程式碼中,你對children包了一層

 const Item = () => <>{children}</>

雖然Babel在編譯的時候是根據大寫來判斷是不是React component,但是在function component內定義的function會被認定為是函式,而不是React component。所以這也是為什麼在我提供的解答中改成了

export default function Card ({ element }) { 
    const Element = element; //不可省略

因為你想做的事情是讓父層去決定Card要使用的React component。

Andy Chang iT邦研究生 4 級 ‧ 2022-05-12 00:16:58 檢舉

然後是為什麼解答不是用children而是用一般的props來接收要用的React component。children的用法比較接近於設計模式中的裝飾者模式,大致上的概念是對於使用children元件來說,該元件並不會去操作、控致children的行為,而是對children進行包裝。例如在我的教學文中,固定的Layout只會去包覆children、加上導覽列。

但是在你的case,我們必須要把資料list和父層傳入的component加工製成新UI,也就是父層傳入的component的行為會受到在Card綁定的props控制。這樣的需求會更接近於「仰賴注入」加上設計模式中的工廠模式(透過同樣的參數去產生不同的元件),所以透過一般的props傳入語意會更明確。react-router的<Route>就是用同樣的方法。

Andy Chang iT邦研究生 4 級 ‧ 2022-05-12 00:22:19 檢舉

最後是要注意Babel必須要看到大寫開頭才會知道要轉成React.createElement(16以前)或是React._jsx(17以後)。

export default function Card ({ element }) {
    // 無法編譯成功的版本
    return <element/>
    
    // 正確的版本
    const Element = element;
    return <Element/>
}

以上是所有的解釋,有點複雜,建議可以去看看Design Pattern,可以幫助理解React在做什麼

我要留言

立即登入留言