React中JSX的理解

WindrunnerMax發表於2020-12-19

React中JSX的理解

JSX是快速生成react元素的一種語法,實際是React.createElement(component, props, ...children)的語法糖,同時JSX也是Js的語法擴充套件,包含所有Js功能。

描述

JSX發展過程

在之前,FacebookPHP大戶,所以React最開始的靈感就來自於PHP
2004年這個時候,大家都還在用PHP的字串拼接來開發網站。

$str = "<ul>";
foreach ($talks as $talk) {
  $str += "<li>" . $talk->name . "</li>";
}
$str += "</ul>";

這種方式程式碼寫出來不好看不說,還容易造成XSS等安全問題。應對方法是對使用者的任何輸入都進行轉義Escape,但是如果對字串進行多次轉義,那麼反轉義的次數也必須是相同的,否則會無法得到原內容,如果又不小心把HTML標籤給轉義了,那麼HTML標籤會直接顯示給使用者,從而導致很差的使用者體驗。
到了2010年,為了更加高效的編碼,同時也避免轉義HTML標籤的錯誤,Facebook開發了XHPXHP是對PHP的語法擴充,它允許開發者直接在PHP中使用HTML標籤,而不再使用字串。

$content = <ul />;
foreach ($talks as $talk) {
  $content->appendChild(<li>{$talk->name}</li>);
}

這樣的話,所有HTML標籤都使用不同於PHP的語法,我們可以輕易的分辨哪些需要轉義哪些不需要轉義。不久的後來,Facebook的工程師又發現他們還可以建立自定義標籤,而且通過組合自定義標籤有助於構建大型應用。
到了2013年,前端工程師Jordan Walke向他的經理提出了一個大膽的想法:把XHP的擴充功能遷移到Js中,首要任務是需要一個擴充來讓Js支援XML語法,該擴充稱為JSX。因為當時由於Node.jsFacebook已經有很多實踐,所以很快就實現了JSX

const content = (
  <TalkList>
    {talks.map(talk => <Talk talk={talk} />)}
  </TalkList>
);

為何使用JSX

React認為渲染邏輯本質上與其他UI邏輯內在耦合,比如在UI中需要繫結處理事件、在某些時刻狀態發生變化時需要通知到UI,以及需要在UI中展示準備好的資料。
React並沒有採用將標記與邏輯進行分離到不同檔案這種人為地分離方式,而是通過將二者共同存放在稱之為元件的鬆散耦合單元之中,來實現關注點分離。
React不強制要求使用JSX,但是大多數人發現,在JavaScript程式碼中將JSXUI放在一起時,會在視覺上有輔助作用,它還可以使React顯示更多有用的錯誤和警告訊息。
簡單來說,JSX可以很好的描述頁面html結構,很方便的在Js中寫html程式碼,並具有Js的全部功能。

優點

JSX的優點主要體現在以下三點:

  • 快速,JSX執行更快,因為它在編譯為JavaScript程式碼後進行了優化。
  • 安全,與JavaScript相比,JSX是靜態型別的,大多是型別安全的。使用JSX進行開發時,應用程式的質量會變得更高,因為在編譯過程中會發現許多錯誤,它也提供編譯器級別的除錯功能。
  • 簡單,語法簡潔,上手容易。

JSX例項

規則定義

JSX中定義了一些規則以及用法:

  • JSX只能有一個根元素,JSX標籤必須是閉合的,如果沒有內容可以寫成自閉和的形式<div />
  • 可以在JSX通過{}嵌入Js表示式。
  • JSX會被babel轉換成React.createElement的函式呼叫,呼叫後會建立一個描述HTML資訊的Js物件。
  • JSX中的子元素可以為字串字面量。
  • JSX中的子元素可以為JSX元素。
  • JSX中的子元素可以為儲存在陣列中的一組元素。
  • JSX中的子元素可以為Js表示式,可與其他型別子元素混用;可用於展示任意長度的列表。
  • JSX中的子元素可以為函式及函式呼叫。
  • JSX中的子元素如果為boolean/null/undefined將會被忽略,如果使用&&運算子,需要確保前面的是布林值,如果是0/1則會被渲染出來。
  • 在物件屬性中定義React元件,可以使用object的點語法使用該元件。
  • React元素會被轉換為呼叫React.createElement函式,引數是元件,因此React和該元件必須在作用域內。
  • React元素需要大寫字母開頭,或者將元素賦值給大小字母開頭的變數,小寫字母將被認為是HTML標籤。
  • 不能使用表示式作為React元素型別,需要先將其賦值給大寫字母開頭的變數,再把該變數作為元件。

JSX的使用

在示例中我們宣告瞭一個名為name的變數,然後在JSX中使用它,並將它包裹在大括號中。在JSX語法中,可以在大括號內放置任何有效的JavaScript表示式。例如2 + 2user.firstNameformatName(user)都是有效的JavaScript表示式。

const name = "Josh Perez";
const element = <h1>Hello, {name}</h1>;

ReactDOM.render(
  element,
  document.getElementById("root")
);

同樣JSX也是一個表示式,JSX天生就是需要被編譯之後才可以使用的,在編譯之後JSX表示式會被轉為普通JavaScript函式呼叫,並且對其取值後得到JavaScript物件。也就是說,你可以在if語句和for迴圈的程式碼塊中使用JSX,將JSX賦值給變數,把JSX當作引數傳入,以及從函式中返回JSX

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

通常可以通過使用引號來將屬性值指定為字串字面量,也可以使用大括號來在屬性值中插入一個JavaScript表示式,在屬性中嵌入JavaScript表示式時,不要在大括號外面加上引號。因為JSX語法上更接近JavaScript而不是HTML,所以React DOM使用camelCase小駝峰命名來定義屬性的名稱,而不使用HTML屬性名稱的命名約定。例如JSX裡的class變成了className,而tabindex則變為tabIndex

const element1 = <div tabIndex="0"></div>;
const element2 = <img src={user.avatarUrl}></img>;

JSX中也可以使用</>來閉合標籤,另外JSX同樣也可以直接定義很多子元素。

const element1 = <img src={user.avatarUrl} />;
const element2 = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

你可以安全地在JSX當中插入使用者輸入內容,React DOM在渲染所有輸入內容之前,預設會進行轉義,這樣可以確保在你的應用中,永遠不會注入那些並非自己明確編寫的內容,所有的內容在渲染之前都被轉換成了字串,可以有效地防止 XSS跨站指令碼攻擊。

const title = response.potentiallyMaliciousInput;
// 直接使用是安全的:
const element = <h1>{title}</h1>;

實際上Babel會把JSX轉譯成一個名為React.createElement()函式呼叫,通過React.createElement()定義的元素與使用JSX生成的元素相同,同樣這就使得JSX天生就是需要編譯的。

const element1 = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
// 等價
const element2 = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

React.createElement()會預先執行一些檢查,以幫助你編寫無錯程式碼,但實際上它建立了一個這樣的物件。這些物件被稱為React 元素,它們描述了你希望在螢幕上看到的內容,React通過讀取這些物件,然後使用它們來構建DOM以及保持隨時更新。

// 注意:這是簡化過的結構
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

實際上,這就是虛擬DOM的一個節點,Virtual DOM是一種程式設計概念,在這個概念裡,UI以一種理想化的,或者說虛擬的表現形式被儲存於記憶體中,並通過如ReactDOM等類庫使之與真實的DOM同步,這一過程叫做協調。這種方式賦予了React宣告式的API,您告訴React希望讓UI是什麼狀態,React就確保DOM匹配該狀態,這樣可以從屬性操作、事件處理和手動DOM更新這些在構建應用程式時必要的操作中解放出來。
與其將Virtual DOM視為一種技術,不如說它是一種模式,人們提到它時經常是要表達不同的東西。在React的世界裡,術語Virtual DOM通常與React元素關聯在一起,因為它們都是代表了使用者介面的物件,而React也使用一個名為fibers的內部物件來存放元件樹的附加資訊,上述二者也被認為是ReactVirtual DOM 實現的一部分,Virtual DOM也為使用diff演算法奠定了基礎。

<div class="root" name="root">
    <p>1</p>
    <div>11</div>
</div>
// 使用Js物件去描述上述節點以及文件
{
    type: "tag",
    tagName: "div",
    attr: {
        className: "root"
        name: "root"
    },
    parent: null,
    children: [{
        type: "tag",
        tagName: "p",
        attr: {},
        parent: {} /* 父節點的引用 */, 
        children: [{
            type: "text",
            tagName: "text",
            parent: {} /* 父節點的引用 */, 
            content: "1"
        }]
    },{
        type: "tag",
        tagName: "div",
        attr: {},
        parent: {} /* 父節點的引用 */, 
        children: [{
            type: "text",
            tagName: "text",
            parent: {} /* 父節點的引用 */, 
            content: "11"
        }]
    }]
}

示例

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <title>JSX示例</title>
</head>

<body>
  <div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">

  class Clock extends React.Component {
    constructor(props) {
      super(props);
      this.state = { date: new Date() };
    }
    componentDidMount() {
      this.timer = setInterval(() => this.tick(), 1000);
    }
    componentWillUnmount() {
      clearInterval(this.timer);
    }
    tick() {
      this.setState({ date: new Date() });
    }
    render() {
      return (
        <div>
          <h1>{this.props.tips}</h1>
          <h2>Now: {this.state.date.toLocaleTimeString()}</h2>
        </div>
      );
    }
  }

  class App extends React.Component{
    constructor(props){
      super(props);
      this.state = { 
        showClock: true,
        tips: "Hello World!"
      }
    }
    updateTips() {
      this.setState((state, props) => ({
        tips: "React update"
      }));
    }
    changeDisplayClock() {
      this.setState((state, props) => ({
        showClock: !this.state.showClock
      }));
    }
    render() {
      return (
        <div>
          {this.state.showClock && <Clock tips={this.state.tips} />}
          <button onClick={() => this.updateTips()}>更新tips</button>
          <button onClick={() => this.changeDisplayClock()}>改變顯隱</button>
        </div>
      );
    }
  }

  var vm = ReactDOM.render(
    <App />,
    document.getElementById("root")
  );
</script>

</html>

每日一題

https://github.com/WindrunnerMax/EveryDay

參考

https://www.zhihu.com/question/265784392
https://juejin.cn/post/6844904127013584904
https://zh-hans.reactjs.org/docs/introducing-jsx.html

相關文章