React 我不知道的事 & 下半年期許


Posted by J米 on 2020-06-13

這次主要是紀錄這次在做公司專案時,寫前後端串連上所踩到的一些地雷,本專案使用 React + Redux-Saga 。
讓我們再次回顧一下這張經典的 Component Lifecycle 吧!

React Lifecycle

react-lifecycle
這張圖我應該看過不下十次,但每次看還真的每次都有新的觀念刷新我的三觀,這次要講的是 getDerivedStateFromProps ,這個方法沒用過還真不知道。

首先,我們再次想一下,網頁哪時候會 re-render 呢?
...
...
...

沒錯,答案是當 state 改變的時候,也就是說 setState 或是有使用 Redux 你的 Store 拿到 api 資料時,這兩個都會使我們 state 改變。

主要想做一個頁面是依附在另一個 Component 出現之後,當這個 Component 結束後,就要出現,姑且叫 NewFunction,然而,這個頁面需要去打 api 取得資料,再做渲染,於是問題就來啦!你怎麼知道 api 資料回來了呢?

就要透過 getDerivedStateFromProps ,其跨越了 Mounting & Updating 兩個生命週期,換句話說,就是只要沒 Unmount ,只要 state 改變就會進來這個方法。
首先,想邀請各位進入我的情境,假設使用者已經填妥一個報名表單,那現在我要做的事情是呈現出你剛剛報名的資料給使用者做確認,如果使用者確認完資料無誤後,按下按鈕呈現報名成功反饋畫面。

因此我們會有三個檔案:

  1. Provider :主要處理資料流
  2. FormComponent:渲染 Form 頁面
  3. FeedbackComponent:渲染 Feedback 頁面

1.Provider

import React, {Component} from 'react';
import is from 'is_js';
import {connect} from 'react-redux';
import {getData,sendData} from 'Actions';

const Provider = Content => {
  @connect(
    state => ({
      // dataFromApi 是 getData Call 完後存在 store,再從 store 以 props 拿,同理 sendDataResponse 也是
      dataFromApi: state.entities.dataFromApi,
      sendDataResponse: state.entities.sendDataResponse
    }),
    dispatch => ({
      getData: () => dispatch(getData()),
      sendData: (params) => dispatch(sendData(params)) 
    })
  )

  class ProviderFactory extends Component {
    constructor(props){
      super(props);
      this.state = {
        dataFromApi: null,
        sendDataResponse: null,
        showForm : false,
        showFeedback : false
      }
    }

    componentDidMount() {
      // store 沒有再 call api,避免重複 call api
      if (is.empty(this.props.dataFromApi)) {
        this.props.getData()
      }
    }
    // 會在 Component 上先渲染出 api 的 data ,如填完後按下送出按鈕,按了會觸發 sendForm
    sendForm = (info) => {
      // 帶 name,phone,address 資訊 
      this.props.sendData(info)
    }

    static getDerivedStateFromProps(nextProps, prevState) {
      // 如果 store 有就先渲染,server side render 利於 SEO
      if (
        is.not.undefined(nextProps.dataFromApi) 
      ) {
        return {
          showForm: true,
          dataFromApi: nextProps.dataFromApi
        }
      }
      if (is.not.undefined(sendDataResponse) 
          && sendDataResponse.status == 'true'
      ) {
         // 代表送出表單正確,顯示出 feedback.js,關閉 form.js
        return {
          sendDataResponse: nextProops.sendDataResponse,
          showFeedback: true,
          showForm: false
        }
      }
      return null; // 都沒有回 null
    }

    render() {
      const {dataFromApi} = this.state
      if (is.undefined(dataFromApi)) {
        return null
      }

      return (
        <Content
          dataFromApi={dataFromApi}
          sendForm={sendForm}
          sendDataResponse={sendDataResponse}
          showForm={showForm}
          showFeedback={showFeedback}
        />
      )
    }
  }

  return ProviderFactory;
}

export default Provider;

2.FormComponent

import React, { Component } from 'react';
import Provider from 'Providers/Provider';
import is from 'is_js';

class FormComponent extends Component {
  constructor() {
    super();
    this.state = {
      showForm: false
    };
  }
  componentDidMount() {
    this.setState({ showForm: this.props.showForm });
  }
  redirectApplyPage() {
    windown.location = '/apply';
  }

  render() {
    const {
      dataFromApi,
      sendForm
    } = this.props; // 拿 provider 資料
    const { showForm } = this.state;
    if (!showForm) {
      return null;
    }
    return (
      <div className="form-wrapper">
        請問剛剛以下報名資訊是否正確呢?
        <div className="form-name">
          {dataFromApi.name}
        </div>
        <div className="form-phone">
          {dataFromApi.phone}
        </div>
        <div className="form-address">
          {dataFromApi.address}
        </div>
        <button onClick={this.redirectApplyPage}>
            重新報名
        </button>
        <button onClick={this.sendForm}>
            資訊確認,報名!
        </button>
      </div>
    );
  }
}
const FormComponent = FormComponentContainer(Content);
export default FormComponent;

3.FeedbackComponent

import React, { Component } from 'react';
import Provider from 'Providers/Provider';
import is from 'is_js';

class FeedbackComponent extends Component {
  constructor() {
    super();
    this.state = {
      showFeedback: false
    };
  }
  componentDidMount() {
    this.setState({ showFeedback: this.props.showFeedback });
  }
  render() {
    const {
      sendDataResponse
    } = this.props; // 拿 provider 資料
    const { showFeedback } = this.state;
    if (!showForm) {
      return null;
    }
    return (
      <div className="feedback-wrapper">
        報名結果:
        <div className="feedback-status">
          {sendDataResponse.status}
        </div>
        <div className="feedback-group">
          你被分到的組別是{sendDataResponse.group}。
        </div>
      </div>
    );
  }
}
const FeedbackComponent = FeedbackComponentContainer(Content);
export default FeedbackComponent;
  1. Redux 資料流,先定義 Saga & api.js 定義好 action 動作,究竟要打哪個 api url 。
    dispatch 是發出 action ,有點像一個發出 api 的函式,但還沒啟動。
    state 指的是 store,存 api 回來的資料,這裡的 state 跟 Component 的 state 一點關係也沒有,可以想成全域變數,因此我可以用 props.dataFromApi去取得。

  2. 利用 ComponentDidMount 在 mount 時,去看資料有沒有 Call,如果父層沒 call 過就再次 call 一次 this.props.getData(),也可以看到這些 Redux 存在 store 的 action 都用 props 去呼叫,因為它就像全域變數一樣。

  3. 利用 getDerivedStateFromProps 去判斷,究竟資料回來沒,用一些 if / else 去判斷後,如果資料回來後,改變自己這一層的 state ,將 Provider 的 state 傳給 Component 去做渲染。
    這是這一次專案學到的一些小觀念,帶給大家。

6/12 新增觀念也將上述的程式碼改過

  1. 為何使用 getDerivedStateFromProps 不是用 ComponentDidMount
    A. 主要是因為 ComponentDidMount 是在 render 後面才把資料擺上去,所以是 Client Side Render ,不利於 SEO,getDerivedStateFromProps 是在 constructor 後馬上就去查看 store 有無此資料,如果有在 store 或是 api 回來,就先把資料放上去,再 render。
    B. 用 getDerivedStateFromProps ,也可以在 componentDidMount 前就判斷這個 component 要不要 show,不至於跑到 render 時才決定不 show。
    C. prevState & nextProps 蠻便利的,可以做一些開關檢查,或對一些 state 做特別初始值設定。
    D. getDerivedStateFromProps 專門處理 api 相關 state
  2. 其實也可以在 ComponentDidMount 等資料回來,再把資料擺上去,謝謝 Huli 補充,其結果會像是以下這樣。

這是第一次進行 Coding 的線上溝通,發現如果可以面對面溝通,應該可以更瞭解對方的意見,也藉由這次專案更瞭解,我這篇文章也修改了兩個小時左右,可見 React 真是博大精深,希望往後自己與他人在看時,能夠理解我所遇過的地雷,以及如何正確使用 getDerivedStateFromProps。

這一次專案學到的經驗

  1. 把開發步驟列出來
  2. 如何塞測試資料
  3. 先研究會使用的技術有哪些
  4. Redux call api 的概念
  5. scss 有共用 color 或共用 css 可以獨立寫出來
  6. ReactDerivedStateFromProps

接下來,下禮拜要參與 Huli 第四期的導師計畫了,之前要轉職前就有看過 Huli 的文章,一直覺得 Huli 的教學很對我的 Tempo ,快速且明瞭,但這個計畫 Loading 也不小,期許自己必須跟上進度,去年的我連 Git 都不知道,也慢慢從自學過程中,學習 Javascript 、 Python 、Php 去把後端的一些概念一步一步砌起來,去年八月的我,一直想參與這個計畫去轉職軟體工程師,現在的我,已經成功轉職,但不想有著一絲絲小遺憾,更不想基礎功不扎實,或是拖延症纏身。

今年五月,開始有點小怠惰後,透過信件與 Huli 討論自學的心態後,希望 2020 下半年可以矯正一些不好的習慣,達到一些我今年初設立的目標,也希望透過程式導師計畫去認識這個領域的人,一起分享學習的知識,把一些模糊的觀念再次釐清。

去年離開科技業迷惘的我,自己學習寫程式也沒有人可以一起討論,或許透過導師計畫可以遇見過去的我,推過去的我一把,不要害怕,既然做出決定,就要勇敢去執行。

You can’t connect the dots looking forward; you can only connect them looking backwards. So you have to trust that the dots will somehow connect in your future.


#React #lifecycle #2020 goals







Related Posts

如何做出一個好的 NodeJS 模組?

如何做出一個好的 NodeJS 模組?

magick - 非常實用的圖片轉檔工具

magick - 非常實用的圖片轉檔工具

非對稱加密和簽名認證

非對稱加密和簽名認證


Comments