iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 7
1
Modern Web

I Want To Know React系列 第 7

I Want To Know React - Component 內部原理 & 使用技巧

回顧 React component

上一篇中,我們介紹了 React component 是什麼,並解釋了它的好處與語法,一起來複習一下吧!

React component 代表自定義元件的藍圖。可以接受名為 props 的參數,並會回傳 React element。

在語法方面,React component 有兩種宣告方式:Function component 與 Class component。在使用方面,其語法則與使用一般 JSX element 的方式大同小異。

就來看個範例重新回憶:

// function Welcome is a React Component
function WelcomeWithFn(props) {
  return <h1>Hello, {props.name}</h1>;
}

// it is equal to
class WelcomeWithClass extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

// can produce React Element with JSX tag
const elementWithFn = <WelcomeWithFn name="Sara" />;

const elementWithClass = <WelcomeWithClass name="Sara" />;

但 React 內部到底是怎麼處理 React component 的呢?接下來,就讓我們深入了解 React component 的內部原理吧!

React Component 內部原理

React component 寫成 JSX 後也是 React element

初探 JSX 篇所介紹,JSX 語法在執行後其實就是 React element 這種型別的 JavaScript object。對於寫成 JSX 的 React component 來說也是如此。

以下面這個範例來說:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

console.log(<Welcome name="Sara" />);
// {$$typeof: Symbol(react.element), type: ƒ Welcome(props), props: {name: "Sara"}, key: null, ref: null, …}

範例中,Welcome 是一個自定義的 React component,緊接著,用 <Welcome /> JSX 語法來產出對應的物件。當使用 console.log<Welcome /> 物件的內容印出來後,可以發現它的型別(React 定義的 $$typeof)也是 React element。

使用 React component 的 JSX 時不會直接執行 function / class 內容

當使用 React component 產出對應的 React element 時並不會執行 component function / class 的內容。React 會等到 render 時才去呼叫 component 的 render 函式並產出內部的 React element。這讓使用 React component 的成本十分便宜。

舉例來說,接續上面的 Welcome React Component 的例子,眼尖的讀者可以發現 console.log 產出的 <Welcome /> React element 物件的 type 是 Welcome function,而 Welcome 內回傳的 <h1> 則尚未被執行出來放到 React Element 物件中。

因此,我們不需要擔心抽取 React Component 會造成效能負擔。

Render React Component 內部執行步驟

有了以上的知識後,我們以更清楚 React component 的特性了,以下將用範例的方式更深入的介紹 render React component 內部實際運作步驟:

  1. 首先,開發者編寫了一段 React 程式:

    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
    const element = <Welcome name="Henry" />
    ReactDOM.render(element, document.getElementById('root'));
    
  2. 接著,在 bundle 時期 Babel 會先把 JSX 轉譯成 React.createElement()

    <Welcome /> 的 type Welcome 函式是 React.createElement 的第一個參數,而 attribute name="Henry" 會轉成 props { name: 'Henry' }

    function Welcome(props) {
    	return React.createElement('h1', null, `Hello ${props.name}`);
    }
    
    const element = React.createElement(Welcome, { name: 'Henry' }, null);
    ReactDOM.render(element, document.getElementById('root'));
    
  3. 執行時期,當執行到 React.createElement(Welcome, ...) 時會產出對應的 React element。

    該 React element 的 element.type 會是 Welcome 函式。需要注意的是此時尚未執行 Welcome 函式中的內容:

    const element = React.createElement(Welcome, { name: 'Henry' }, null);
    // {$$typeof: Symbol(react.element), type: f Welcome(props), key: null, ref: null, props: {…}, …}
    
  4. 接著執行 ReactDOM.render(),並帶入 Welcome React element 與 container 參數:

    ReactDOM.render(element, document.getElementById('root'));
    
  5. Render 時,React 才去呼叫 Welcome() 建置出 Welcome 內部的 React element h1

    function Welcome(props) {
    	return React.createElement('h1', null, `Hello ${props.name}`);
      // {$$typeof: Symbol(react.element), type: "h1", key: null, ref: null, props: {…}, …}
    }
    
  6. React 會把 Virtual DOM snapshot 產出來,跟上一個的 Virtual DOM snapshot 做比較

  7. 因為這是第一次產出 Virtual DOM snapshot,並沒有上一刻 Virtual DOM snapshot 可以做比較,所以 React 就會把所有的內容都 render 到畫面上

開發技巧

組合 Component

如上個段落所提及,React component 寫成 JSX 後其實會是 React element,而 React component 的回傳值可以是任何的 React element,也就是說,在 React component 中回傳其他 React component 產生的 React element 是完全合法的。

這就代表,我們可以用 React component 來組合 React component。舉例來說:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));

讀者也可以 到 CodePen 上試試

App 是一個 React component,它會回傳了另一個 React component 所產生的 React element <Welcome name="Henry" /><Welcome name="Cahal" />,最終再將 <App/> React element 傳給 React 把所有的內容 render 到畫面上。

這個小範例也是一個典型 React App 的縮影。通常一個 React App 只會有一個根 React component App 代表整個網站的內容,所有更小的 React component 都會是 App 下面的 children。最後,只需要把 React element <App /> 帶給 React render 即可產出整個網頁。

適時拆分 Component

上一篇使用 React component 的好處 的段落中,我們了解 React component 在可以帶給來重用性、關注點分離等優點,因此不用怕將一個大的 React component 拆成多個小的 React component。

通常遇到以下幾個狀況時會是拆分 component 的好時機:

  • 當要重複利用一個元件時
  • 當想要把一個獨立的元件封裝起來,不受其他元件影響時
  • 當一個 component 太大,造成不相關邏輯強烈耦合,或者難以閱讀時

總結來說,其實只要能夠將元件分割為一個合理的獨立區塊,那這個元件就會有拆分為 component 的價值,這也是 React 所提倡的,以 component 為單位的關注點分離方式。

當然,太瑣碎的 component 也可能會造成維護上的不易,所以實作上還是應該以易維護性作為拆分 component 的最高準則。

小結

在這章節中,我們了解了 React component 的以下幾個特性:

  • React component 寫成 JSX 後也是 React element
  • 使用 React component 的 JSX 時不會直接執行 function / class 內容

並也了解了 React 在 render component 時的內部運作步驟。

除此之外,還學到了以下兩個 component 開發的技巧:

  • 組合 component
  • 適時拆分 Component

下一篇中將會介紹 React component 的狀態(State)。

參考資料


上一篇
I Want To Know React - 初探 Component & Props
下一篇
I Want To Know React - 初探 State
系列文
I Want To Know React30

尚未有邦友留言

立即登入留言