天天看點

React JSX文法與元件JSX基礎介紹渲染React元素元件與屬性

JSX基礎介紹

先看看一個最簡單的例子:

const element = <h1>Hello, world!</h1>;           

上面這段有趣的例子既不是标準的JavaScript也不是HTML,它就是我們接下來要介紹的JSX的文法,是一種JavaScript的擴充。在React中使用JSX描述一個UI是什麼樣子的,就好像HTML告訴浏覽器我們看到的頁面是什麼樣子。最開始接觸JSX時會感覺它很像一種模闆語言,但是除了提供模闆能力之外,他擁有JavaScript所有的能力。

JSX用于産生React的元件,JSX最大的特色就是就是在JavaScript中嵌入和HTML表達式。我們先看下面這個例子:

function formatName(user) {
  //将參數合并成一個srting
  return user.firstName + ' ' + user.lastName;
}

//建立user對象
const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

//建立element對象,并用JSX文法辨別為一個html内容。
//h1标簽中會包含方法計算之後的内容
const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);           
測試代碼

這個例子将JSX文法分成了很多部分,element就是一個HTML的JSX表達式,HTML标簽最好使用一組()括号包裹起來以避免分号導緻的問題(分号可能會在編譯時成為HTML内容的一部分)。ReactDOM是一個react工具,用于提供Dom渲染功能。ReactDOM.render 方法接受2個參數,一個是要渲染的JSX元素,另外一個是Dom對象,render會在這個Dom對象中添加由JSX定義的HTML。

JSX是一種豐富的表達式,他可以随意嵌套JavaScript和HTML使用,例如if、for等等,比如:

function getGreeting(user) {
  // 使用if來判斷輸入參數,根據判斷結果來輸出HTML内容
  if (user) {
    return <h1>Hello, {formatName(user)}</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}           

源生的HTML可以任意指定屬性,同樣在JSX中也有這個能力,例如:

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

也可以直接用 />表示一個HTML标簽的閉環:

const element = <img src={user.avatarUrl} />;           

當然也可以同時聲明父元素和子元素:

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);           

需要注意的是:由于JSX更像JavaScript,在使用JSX文法時建議使用駝峰規範來命名。例如将标簽上的"class"命名為"className"。

JSX天生具備防止注入攻擊的能力。ReactDom在渲染之前會轉義所有嵌入JSX中的值,是以他能確定沒有任何特殊的内容被注入到最終的HTML代碼中。在渲染之前,所有的東西都會轉換成string類型,這将能有效的防止XSS攻擊。

JSX對象

首先需要強調的是,JSX對象就是一個JavaScript對象,所有的JSX表達式最終都會轉義成JavaScript。有兩種方法可以建立JSX對象:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);           

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);           

上面2種建立JSX對象的方法結果都是一樣的。使用

React.createElement()

 方法的好處是它會執行一些檢查,以幫助我們編寫無錯誤的代碼。最終通過轉義,他會輸出這樣一個結構:

const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world'
  }
};           

官方将這個對象的結構稱為React元素。React通過這個對象來控制浏覽器對dom的渲染,最終顯示我們想要的内容。

渲染React元素

前一小節提到的React元素是React的基本單元,React會由一個一個的基本單元組成,最終建構成一個有效的體系(元件化)。每一個元素用來描述想在螢幕上展示什麼。

和Dom結構不同的是, React元素是一個純粹的對象并且比建立一個Dom花費的資源更少。React會全局維護所有的元素,并在合适的時候更新到浏覽器的Dom,這就是虛拟Dom管理機制。

将一個元素渲染成為Dom

從一個簡單的div标簽開始:

<div id="root"></div>           

這是一個“根元素”,我們将通過ReactDom來管理他的所有子元素。一個RreactDom.render方法隻能用來渲染一個Dom元素。如果想同時對多個元素進行渲染,可以使用互不關聯的RreactDom.render方法來對不同的Dom元素進行操作。

下面的例子将一個JSX元素渲染到Dom中,完成後,會在頁面中顯示Hello world:

const element = <h1>Hello, world</h1>;
ReactDOM.render(
  element,
  document.getElementById('root')
);           

更新已被渲染的元素

React元素是

不可變對象

,一旦建立,将不再能夠修改,包括其屬性和子元素。更新UI的方法就是建立一個新的元素并用ReactDom.render()再次渲染他。如下面的例子:

//建立一個tick方法,用于執行重複方法
function tick() {
  //建立一個JSX對象
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);           

上面代碼中建立了一個tick方法,并使用setInterval讓這個方法每1秒執行一次。tick中建立了一個用于顯示時間的JSX對象,然後将其渲染到#root節點中。運作代碼可以看到例子實作了一個時鐘功能,每秒都會調用ReactDom.render動态修改時鐘的數字。

需要強調的是:重複使用ReactDom.render方法來多次渲染Dom并不是React推崇的方法。後續的内容中會介紹更合理的方法。

React隻執行必要的更新

ReactDom會将目前的元素與之前的元素進行比對,并且隻會更新被改動部分的Dom以避免全局渲染和多次重複渲染。我們可以通過浏覽器工具來驗證最後一個例子——我們使用render方法建立了整個Dom結構,但是僅僅隻有表示時間的文字部分發生了變動。

元件與屬性

元件是React的重要概念,元件能讓我們将整個頁面的UI分解成獨立、可複用、可繼續分割的對象。從概念上來說,元件很像JavaScript的一個方法,他可以接受任意的參數輸入(React中将這些參數稱呼為屬性——Props)并傳回一個用于UI展示的React元素。

使用函數或類聲明元件

在React中既可以使用function來聲明一個元件,也可以使用ES6規範的class關鍵字來聲明一個元件。下面的例子是使用function建立一個元件:

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

例子中使用function聲明了一個名為Welcome的元件,他隻能接收一個參數用于描述元件的元素。在React中,我們将通過function建立的元件命名為“functional”,因為從字面上看它實際上就是一個JavaScript的函數。

下面的例子是使用ES6 class方式聲明一個元件:

//ES6
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

//JavaScript 非官方内容
var Welcome = React.createClass({
   render: function () {
     return <h1>Hello, {this.props.name}</h1>;
   }
});
//           

上面兩種建立元件的方式,從React的角度來說是一樣的。

與使用方法建立元件相比,使用ES6 class的方式建立元件有更多特性,後續篇幅會說明。

渲染一個元件

為了便于說明,我們先用<div>标簽建立一個最簡單的元件:

const element = <div />;           

此時,element即可認為是一個元件,元件中隻有一個div元素。根據這個定義,我們可以使用使用者自定義的元件,比如使用上面的Welcome:

const element = <Welcome name="Sara" />;           

當React發現element中有使用者自定義的元件,它會使用JSX文法解析element并将标簽上的屬性轉換成一個JSX對象,這個對象被稱為“props”。

例如下面這個例子,我們經使用元件在螢幕上輸出"Hello, Sara":

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

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);           

看看發生了什麼:

  1. 使用ReactDOM.render()方法渲染<Welcome name="Sara" />。
  2. React調用Welcome方法,并傳遞了一個參數:{name: 'Sara'}。
  3. 在Welcome元件中合并了參數,并傳回一個<h1>Hello, Sara</h1>。
  4. ReactDom将<h1>Hello, Sara</h1>更新到浏覽器的Dom樹中。

需要注意的是,使用React元件時一定要将元件名稱首字母大寫。例如在html标簽中<div>是一個标準的Dom,但是<Welcome>并不是一個标準的html标簽,而是一個React元件。React通過判斷元件名稱的首字母加以區分。

元件組合

一個元件能夠被其他的元件引用,就像使用普通的html标簽一樣。我們可以把元件抽象成各種抽象功能在任何地方使用,例如一個按鈕、一個彈出框、一個表單。下面的例子展示了React元件的組合使用:

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

function App() {//建立App元件
  return (
    <div>
      <Welcome name="Sara" /> //使用Welcome元件
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

ReactDOM.render( //向Dom渲染App元件
  <App />,
  document.getElementById('root')
);           

在例子中,首先建立了一個Welcome元件,然後在App元件中重複使用它,最後向浏覽器渲染App。App元件中整合使用了Welcome元件。基于元件可以層層封裝,建議在使用React開始新項目時先從封裝一些小的元件開始,比如按鈕、彈出框等,這會對後面開發高層次的業務邏輯時有巨大的幫助。

一個元件隻能傳回一個根元素,不能同時包含2個根元素。是以上面的例子中App元件中增加了一個<div>元素将Welcome元件包裹起來。

抽象提取元件

不必擔心元件的分割粒度太小,開發元件時我們最好是通過多個層次的元件組合實作一個更高層次的元件。

看下面這個例子——封裝一個Comment元件:

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name} 
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}           
嘗試代碼

Comment元件很難維護,因為它内部的代碼都前台和交織在一起,也無法實作代碼複用。現在我們稍微修改元件中的Avator,将其提取成一個元件:

function Avatar(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}           

Avator元件不需知道他是在哪被使用,它隻關心輸入的參數,并使用參數生成一個Dom結構。現在可以像下面這樣聲明一個Comment元件:

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <Avatar user={props.author} />//使用Avatar元件
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}           

我們再将UserInfo也提取成一個元件:

function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} /> //使用Avatar元件
        {props.user.name}
      </div>
    </div>
  );
}           

此時的Comment:

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} /> //使用UserInfo元件
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}           

屬性(props)隻讀

無論是使用函數(function)還是類(class)聲明元件,它都不能通過修改props參數來改變值。例如下面這個sum方法:

function sum(a, b) {
  return a + b;
}

var param1 = 1,
    param2 = 2,
    result = sum(1, 2);           

在第一次計算得到結果之後,無論怎麼修改param1、param2的值,result都不會改變。

React相當的靈活自由,但是它有一條必須遵守的規則:

所有的React元件必須像上面的sum方法這樣保證傳入的屬性(props)參數隻讀。

繼續閱讀