皮卡丘就這麼跑進我的文章裡。。。
目前我的專案長這樣~
我一樣用電子名片這個專案來學習 React props 的運作原理,今天也會將我預先儲存在 json 裡的資料(假裝它是從 API 或是後端抓來的資料)更新到我的電子名片上。
props 讓我們可以從 parent component 傳內容到 child component 中,props 讓 component 之間可以互相溝通,靈活地傳遞資訊。當一個 component 有所更動,它的狀態(States)也會有所改變,而 child component 會自動更新透過 props 去接受 parent component 裡面被更新的值。
我們現在的 parent component 裡面的內容
 class Card extends React.Component{
    render (){
      return(
         <div className="card">
            <Photo />
            <Bio />
            <Updates />
         </div>
      )
    }
  }
  
  
還記得,我們是在這個最主要的 Card 類別組件裡面,把整個我們之前拆解的 sub components 重新整合回來放在這邊。
我們可以透過 props 在這個 components 裡面,給予新的內容。
舉個例子,假設現在我要做出一隻皮卡丘,於是我們有定義顏色的 Color component,告訴機器我要黃色的皮卡丘;也有定義表情的 Expressive component與設定它的動作的 Action component,不過這個階段還不確定我要讓皮卡丘有什麼表情跟動作,我可不可以在最後製作前再告訴機器我要什麼樣的皮卡丘,答案是可以的。
Pikachu component 作為 parent component ,它就像是製作的機器,裡面接收了所有 sub components 所定義的資訊,目前的資訊是:
做出一隻 黃色皮卡丘
可能長這樣:(最普通皮卡丘xD)
但在按下機器,製作皮卡丘之前,我想要讓他看起來受到驚嚇然後雙手很僵硬。我們可以在 Pikachu component 裡面直接賦予 <Expressive /> 和 <Action /> 你想要設定的值,而 child components 會自動抓取這個在 parent component 裡新增的值。我們只需要在<Expressive /> 裡面設定「驚訝的表情」;動作的部分在  <Action /> 裡設定「僵硬」。
最後,機器就會幫我們做出 受到驚嚇,雙手僵硬的黃色皮卡丘
like this
回到我們現有的專案,我們可以在 Card component 裡面,設定在 Bio component 裡面已經定義好的 name 和 location。
作法如下
  class Card extends React.Component{
    render (){
      return(
         <div className="card">
            <Photo />
            <Bio name={"Annie Tsai"} location={"New York"}/>
            <Updates />
         </div>
      )
    }
並且在 Bio 的類別屬性裡,呼叫我們在 parent component 裡面設定的值。
呼叫的方式:
{this.props.<className>}
所以我們在 className="name" 和 className="location" 裡面,加上呼叫 props 的程式。
  class Bio extends React.Component{
    render (){
      return(
        <div className="bio">
          <h1 className="name">{this.props.name}</h1>
          <h2 className="location">{this.props.location}</h2>
          <div className="occupation">
            <p>Front End Developer</p>
          </div>
        </div>
      )
    }
  }
  
現在我們重新整理頁面,得到的結果會是:
大致了解 props 的運作之後,我現在要透過呼叫 json 裡的資料,render 到 DOM 上。
我有一個 person 變數,裡面存放著 json 格式的資料
var person = {
  name: 'Ya-Yun Tsai',
  location: 'Taichung, Taiwan',
  occupation: {
    title: 'Marketing Intern',
    employer: '@voicetube'
  },
  photo: './images/me.png',
  updates: [
    {
     platform: 'twitter',
     status: 'I\'m happy, hope you\'re happy too!'
    },
    {
     platform: 'twitter',
     status: 'All about learning is just trying to become a better version of ourselves.'
    },
    {
     platform: 'twitter',
     status: 'Feel excited to join this competition!!!!!'
    },
    {
     platform: 'facebook',
     status: 'If you\’re working on something that you think is going to get accomplished in this lifetime then you’re not thinking big enough'
    }
  ]
}
我們可以透過 {variable name + name} 來取得這個物件裡的某個值,例如我要取得我的名字,可以透過
{person.name}
要取得 location 則是
{person.location}
現在,我們就把剛剛在 Card component 裡面寫死的值,轉換成呼叫 person 裡面儲存的值。
在更新頁面之後,我遇到一個 bug
這是一個 decoder 的 bug
我點擊它顯示錯誤的網址,它告訴我 bug 詳細資訊
Objects are not valid as a React child (found: object with keys {title, employer}).
這是因為在 occupation 物件裡面有兩個 keys,分別為 title 和 employer,我在取出 occupation 的值的時候,我寫了 {person.occupation},它並不知道我到底是要 title 還是 employer 裡面的值,因此出現了 not valid 的錯誤訊息。
只要把 {person.occupation} 清楚定義,bug 就解決囉~
{person.occupation.title}
現在可以看到  名字、地點、職業 都是從變數 person 裡面取出來的值

可是老師我有問題!!!!(舉手)
如果想要呼叫 occupation 裡面的title 跟 employer 該怎麼做?
在 Card component 裡面,維持 occupation={person.occupation}
<Bio name={person.name} location={person.location} occupation={person.occupation} />
        
接著到 Bio component 這個 child component 裡面,設定取出更細瑣的值:
<div className="occupation">
    <p>{this.props.occupation.title} {this.props.occupation.employer}</p>
</div>    
椰~~~現在同時看到 occupation 裡面的title 跟 employer 的資料了!!

updates 裡的資料最後一個階段,我們要來 render updates 裡的資料了!!那兩行 Updates 一直在那邊真的很礙眼。
同樣的,我們可以先在 Card component 裡面,通知它我要取出變數裡 updates 的值了
<Updates updates={person.updates} />
因為在 updates 裡面有 4 個 instance,所以我們要用 map 方法,寫一個迴圈去幫我們取值。
到 Updates component 裡面,新增一個 updates(),我們要把 mapping 的邏輯寫在這個方法裡面,再把結果 return 回去。把每一個 this.props.updates.map 的結果,塞進 <li></li>,還記得剛剛提到有 4 個 instance,所以我們應該會回收 4 次的 <li></li>,裡面放的是 status 的內容。
  class Updates extends React.Component{
    updates(){
        return this.props.updates.map(function(updateInfo){
            return(
              <li className="update">
                  {updateInfo.status}
              </li>
            )
        });
    }
    render (){
      return(
        <div className="updates">
           <ul>
                {this.updates()}
           </ul>
        </div>
      )
    }
  }
  
我們順利取得 status 的內容~

在 React 官方文件裡提及,當我們使用 map() 這個方法時,它希望我們可以在迴圈的產出項目裡面,設定專屬於每個項目的 key,以便在之後資料集有所更動時,它可以追蹤資料的狀態,是被修改過或者是被刪除等等。
可以想成是寶寶剛出生我們都要幫他們取名字,想像醫院裡的媽媽們是資料集,他們生出來的都是寶寶(就像是我們這邊 loop 出來的 <li></li>),而護士就像是 React,他們必須知道寶寶的名字,才能更方便照顧他們。因此我們要分別給每個寶寶專屬的 key。
在 <li> 裡面加上 key index
<li className="update" key={index}>
  {updateInfo.status}
</li>
記得 function 裡面要多傳一個 index 參數進來
function(updateInfo, index)
最最後一個步驟,我們的 platform 還沒引入進來,會加這個 platform 只是模仿它是從不同來源的資訊,我們可以把它加到 className 裡
   <li className={"update " + updateInfo.platform} key={index}>
      {updateInfo.status}
   </li>
我們可以看到 platform 的資料被我加到 className 裡了~

來張完整的電子名片 <3
椰~花了三篇文章介紹拆解 components 還有 props 的運作原理,很有成就感!
完整專案:Github