React 入門 5 - State Lifting


Posted by urlun0404 on 2022-10-31

延續前一篇筆記 state 的概念和範例程式,繼續了解另一個跟state有關的重要概念 ─ State Lifting


首先修改上次的程式碼

上次我們是利用瀏覽器提供的 prompt() 方法詢問成績,但為了完成這個動作得複製兩次 Student component 來完成,而前一篇學到state,我想應該可以透過state來記錄並顯示更新前後的成績。

為了記錄成績,理所當然地會先在跟成績有關的 Grade component 加上 state 變數,所以如下範例移除原本跟成績有關的 props、改成用 state 來顯示成績,目前修改的所有程式碼可參考這裡

function Grade(props) {
  const [grade, setGrade] = useState(0);   // 改成用state記錄目前成績
  return (
    <div>
      <h1>{props.subject}</h1>
      <h3>
        Your {props.subject} grade is {grade} point.
      </h3>
    </div>
  );
}

做到這邊會發現有狀況了,因為 state 變數在 Grade component,修改 state 的函式 setState 當然也放在同一層,而原本要修改成績的按鈕卻放在父層的父層 Edit component。

React 文件有提到:

State is private and fully controlled by the component.

也就是,只有擁有這個state的component可以修改state。

為了解決這個狀況,這裡得去了解React資料流特性 ─ top-down data flow,即React可允許並且只允許上下層傳遞資料,這也代表state的控制權不一定要交給顯示這個狀態的component,也可以交由父層component(甚至父層的父層component)來管理,並將state當作props傳遞到原來要顯示state的子層components

Ok,接下來綜合前面所學的props、state、event handling和top-down data flow資料流特性來解決前面的問題。

這篇文章的解法是:直接把原本在 Grade component 的state向上移動到 Edit component,並且在按鈕增加事件去呼叫可以用 setGrades 改變成績 state 的 updateGrades 函式。

function Edit() {
  const [grades, setGrades] = useState({
    Chinese: 0,
    English: 0,
    Math: 0
  });
  const updateGrades = () => {
    let tempGrades = {};
    Object.keys(grades).forEach((subject) => {
      tempGrades[subject] = prompt(`Enter your ${subject} grade:`);
    });
    setGrades(tempGrades);
  };
  return (
    <div>
      <main>
        <Student grades={grades} setGrades={setGrades} />
      </main>
      <button onClick={updateGrades}>Update your Scores</button>
    </div>
  );
}

像這樣把原本只能在子層component處理的state向上提升至父層層級的動作就稱為 State Lifting

因為這次範例運用 state 和 state lifting 省略許多冗餘程式碼、修正過程比較複雜:例如原來有三個科目所以複製三次 <Grade /> ,而這裡運用 state可以儲存物件的方式也把科目和成績存成object,傳遞到 <Student /> 。完整程式碼可參考這裡

不同以往只給一個 props Student(props) ,這次用解構(destructing)的方式 {grades} 取出 grades ,相當於 const {grades} = props;

得到 grades 之後能用迴圈拜訪、傳遞相應的每個科目和成績,然後返回 <Grade /> 渲染在畫面上面,這樣不必複製三次程式碼,以後新增其他科目成績也較方便。

function Student({ grades }) {
  return (
    <div>
      {Object.keys(grades).map((subject) => (
        <Grade subject={subject} grade={grades[subject]} />
      ))}
    </div>
  );
}

最後來提 state lifting 的另一個好處 ─ 資料共享,意思是父層設定好 state 變數後也能將 state 傳遞給其他子層 components ,這樣在不同 components 就能共享同一份資料;或者若把改變狀態的函式傳遞到其他子層,所有同樣使用到這個 state 的 components 就能一併獲得最新、更新後的資料,也就是可確保不同 components 都能得到相同資訊。


References


#frontend #React







Related Posts

什麼是 SPA ?

什麼是 SPA ?

React Navigation 設計根目錄

React Navigation 設計根目錄

[Web] 使用 OpenSSL 建立開發測試用途的自簽憑證(Self-Signed Certificate)和設置Domain Name

[Web] 使用 OpenSSL 建立開發測試用途的自簽憑證(Self-Signed Certificate)和設置Domain Name


Comments