為甚麼函數式組件需要引進 React?

為甚麼函數式組件需要引進 React?
作者: Calpa Liu
字數:4612
出版日期:February 26, 2019
更新日期:December 7, 2023
分類: #前端開發
#React.js#JavaScript

在編寫組件時,最令我感到困惑的是,當我在創建純函數型組件時,為何在代碼中未明確調用 React 卻仍需導入 React。經過一番深入研究,我發現這涉及到 JSX 轉譯的相關設定。然而,出於節省時間和提高效率的考慮,我選擇安裝了 babel-plugin-react-require 插件,以實現自動引入 React 的功能。本文將分享我的研究過程和一些反思。

比如說我們有一個 App.js 的文件,這裡相等於 Hello World 的設定。

import React from "react";
const App = () => (
  <div>Hello World!!!</div>
);
export default App;

原因

Imgur

在 Babel 轉譯我們的 App.js 的時候,會把 JSX 語法糖轉換為 React.createElement 方法。

var App = function App() {
  return React.createElement(
    "div",
    null,
    "Hello World!!!"
  );
};

你可以在 Babel REPL 來看到 Babel 是如何轉譯。

自動引入 React

對於 React 系統的前端項目來說,我們能不能直接寫純函數組件,而不需要在頂部引進 React 語句:import React from 'react';

babel-plugin-react-require

答案也是可以的,我們可以透過 babel-plugin-react-require 來自動辨認無狀態組件,然後如果是的話,自動引進 React。

安裝方法

我們可以透過 npm 直接安裝

npm install babel-plugin-react-require --save-dev

然後在 .babelrc 加入 react-require

{
  "plugins": [
    "react-require"
  ]
}

值得注意的是, react-require 需要在 transform-es2015-modules-commonjs 之前引入,因為插件需要 ES2015 模塊化語句來導入 React 到域裡面。

例子

如果我們寫了一個純函數組件,代碼如下:

export default function Component() {
  return (
    <div />
  );
}

babel-plugin-react-require 插件會自動將上面的代碼轉譯為下面的代碼:

import React from 'react';

export default function Component() {
  /* 下面的代碼交給 Babel 繼續編譯 */
  return (
    React.createElement('div')
  );
}

自定義轉譯使用方法

那麼我們可以再想一下,如果用的不是 React,而是其他 React 生態圈裡面的方法的話。比如說 deku,它是一個用來使用純函數和虛擬 DOM 渲染界面的工具庫。它也是 React 生態圈裡面的一份子。

我們能不能改寫轉譯後的方法,不使用 React.createElement 方法?

Imgur

答案是可以的。

@babel/plugin-transform-react-jsx 允許我們在文件的頂部加入 /** @jsx 方法名稱 */,或者是在 .babelrc 全局修改

比如說這樣的代碼:

/** @jsx dom */

var { dom } = require("deku");

var profile = <div>
  <img src="avatar.png" className="profile" />
  <h3>{[user.firstName, user.lastName].join(' ')}</h3>
</div>;

會轉譯為下面的代碼:

/** @jsx dom */

var dom = require("deku").dom;

var profile = dom("div", null,
  dom("img", { src: "avatar.png", className: "profile" }),
  dom("h3", null, [user.firstName, user.lastName].join(" "))
);

React 碎片

React Fragment

那麼 React 16.2.0 新增的 Fragments 呢?我們可以透過 /** @jsxFrag 函數名稱 */來達到轉譯後的修改目的。

輸入:

/** @jsx dom */
/** @jsxFrag DomFrag */

var { dom, DomFrag } = require("deku");

var descriptions = items.map(item => (
  <>
    <dt>{item.name}</dt>
    <dd>{item.value}</dd>
  </>
));

輸出:

/** @jsx dom */
/** @jsxFrag DomFrag */

var { dom, DomFrag } = require("deku");

var descriptions = items.map(item => dom(
  DomFrag,
  null,
  dom("dt", null, item.name),
  dom("dd", null, item.value)
));

你可以打開 Babel REPL - React Fragment 來實時調試代碼。

安裝方法

你需要安裝 @babel/plugin-transform-react-jsx 來轉換 JSX 語法,如果沒有安裝的話,那麼你可以透過 npm 安裝它。

npm install --save-dev @babel/plugin-transform-react-jsx

全局修改

.babelrc 寫入下面的片段:

{
  "plugins": ["@babel/plugin-transform-react-jsx"]
}

我們可以透過全局修改 pragma 來修改 React.createElement,

{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "pragma": "dom", // React.createElement
      "pragmaFrag": "DomFrag", // React.Fragment
      "throwIfNamespace": false, // 如果 XML 名稱空間的標籤名稱被使用的話,拋出異常
      "useBuiltIns": false // 使用 Object.assign 而不是 Babel 內建的擴展工具 (extend)
    }]
  ]
}

參考資料

  1. Why import React from “react” in a functional component?
  2. @babel/plugin-transform-react-jsx
  3. anthonyshort/deku - Github
  4. vslinko/babel-plugin-react-require - Github
感謝您閱讀我的文章。歡迎隨時分享你的想法。
關於 Calpa

Calpa 擅長使用 TypeScript、React.js 和 Vue.js 開發Responsive Web Design網站。

此外,Calpa 積極參與香港和台灣的開源社區,曾在2019年的香港開源大會上擔任講者,提供工作經驗和見解。此外,他也在 GitHub 上公開分享個人博客程式碼,已獲得超過300顆星星和60個分支的支持。

更多前端開發技術文章:傳送門