一篇包含了react全部基本點的文章

去年,我寫了一本關於學習React.js的小書,原來是大約100頁。 今年我要挑戰本身,把它概括爲一篇文章。react

本文不會涵蓋什麼是React,或者爲何要學習它。 相反,這是面向已經熟悉JavaScript並熟悉DOM API基礎知識的人,對React.js的基礎知識的介紹。程序員

如下全部代碼示例均標示爲參考。 它們純粹是爲了提供概念而寫的例子。 他們大多數能夠寫得更好一些。express

1:組件是React的一切

React是圍繞可重用組件的概念設計的。 您定義小組件,並將它們放在一塊兒造成更大的組件。數組

全部小或小的組件均可重複使用,甚至跨不一樣的項目。瀏覽器

一個React組件(以其最簡單的形式)是一個簡單的JavaScript函數:安全

// Example 1
// https://jscomplete.com/repl?j=Sy3QAdKHW
function Button (props) {
  // Returns a DOM element here. For example:
  return <button type="submit">{props.label}</button>;
}
// To render the Button component to the browser
ReactDOM.render(<Button label="Save" />, mountNode)

用於按鈕標籤的花括號將在下面介紹。 如今沒必要要擔憂他們。 ReactDOM也將在後面解釋,可是若是要測試這個例子和接下來的代碼示例,render函數就是你須要的。微信

ReactDOM.render的第二個參數是React將要接管和控制的目標DOM元素。 在jsComplete REPL中,您就可使用mountNode變量。架構

關於示例1的注意事項有如下幾點:app

  • 組件名稱以大寫字母開頭。 這是必需的,由於咱們將處理HTML元素和React元素的混合。 小寫名稱保留給HTML元素。 事實上,請繼續嘗試將React組件命名爲「button」。 ReactDOM將忽略該函數並呈現常規的空HTML按鈕。
  • 每一個組件都接收一個屬性列表,就像HTML元素同樣。 在React中,這個列表叫作props。建立功能組件,你能夠經過使用任意名稱命名props
  • 在上面的Button組件的返回中,咱們寫出了奇怪的HTML。 這既不是JavaScript也不是HTML,甚至不是React.js。 可是,它很是受歡迎,成爲React應用程序中的默認設置。 它被稱爲JSX,它是一個JavaScript擴展。 JSX也是妥協! 繼續嘗試在上面的函數中的任何其餘HTML元素,並查看它們是如何支持的(例如,返回一個文本輸入元素)。

2: What the flux is JSX?

上面的示例1能夠用純粹的React.js來編寫,而不須要JSX,以下所示:dom

// Example 2 -  React component without JSX
// https://jscomplete.com/repl?j=HyiEwoYB-
function Button (props) {
  return React.createElement(
    "button",
    { type: "submit" },
    props.label
  );
}
// To use Button, you would do something like
ReactDOM.render(
  React.createElement(Button, { label: "Save" }),
  mountNode
);

createElement函數是React頂級API中函數。 您須要學習的這個級別中共有7件事情中的1項。 可見ReactApi多麼簡短。

很像DOM自己有一個document.createElement函數來建立一個由標籤名稱指定的元素,React的createElement函數是一個更高級別的函數,能夠作相似於document.createElement的功能。 但它也能夠用於建立一個表示React組件的元素。 當咱們使用上面的例2中的Button組件時,咱們這裏就是建立了一個React組件。

document.createElement不一樣,React的createElement能夠接受第二個參數以後的動態參數,以表示建立的元素的後代。 因此createElement實際上建立一個樹。

這是一個例子:

/ Example 3 -  React’s createElement API
// https://jscomplete.com/repl?j=r1GNoiFBb
const InputForm = React.createElement(
  "form",
  { target: "_blank", action: "https://google.com/search" },
  React.createElement("div", null, "Enter input and click Search"),
  React.createElement("input", { name: "q", className: "input" }),
  React.createElement(Button, { label: "Search" })
);
// InputForm uses the Button component, so we need that too:
function Button (props) {
  return React.createElement(
    "button",
    { type: "submit" },
    props.label
  );
}
// Then we can use InputForm directly with .render
ReactDOM.render(InputForm, mountNode);

關於以上例子要注意的幾點:

  • InputForm不是React組件; 它只是一個React元素。 這就是爲何咱們直接在ReactDOM.render調用中使用它,而不是使用<InputForm />
  • 咱們能夠嵌套React.createElement調用,由於它都是JavaScript。
  • React.createElement的第二個參數能夠是null,也能夠是一個空對象,當元素不須要attributes和props時。
  • 咱們能夠將HTML元素與React組件混合使用。 您能夠將HTML元素視爲內置的React組件。
  • React的API嘗試儘量接近DOM API,所以咱們爲輸入元素使用className而不是類。 私覺得,咱們都但願React的API將成爲DOM API自己的一部分。 由於,你知道的,這有太多的好處了。

上面的代碼是您在引入React庫時瞭解的內容。 瀏覽器不處理任何JSX業務。 然而,咱們人類喜歡看HTML而且使用HTML而不是這些createElement調用(想象一下使用document.createElement構建一個網站,我相信你能夠的!)。 這就是爲何存在JSX的緣由。 咱們能夠用很是相似於HTML的語法編寫它,而不是用React.createElement調用上面的表單:

// Example 4 - JSX (compare with Example 3)
// https://jscomplete.com/repl?j=SJWy3otHW
const InputForm =
  <form target="_blank" action="https://google.com/search">
    <div>Enter input and click Search</div>
    <input name="q" className="input" />
    <Button label="Search" />
  </form>;
// InputForm "still" uses the Button component, so we need that too.
// Either JSX or normal form would do
function Button (props) {
  // Returns a DOM element here. For example:
  return <button type="submit">{props.label}</button>;
}
// Then we can use InputForm directly with .render
ReactDOM.render(InputForm, mountNode);

關於上面的例子注意如下幾點

  • 它不是HTML。 例如,咱們仍然在使用className而不是類。
  • 咱們仍然在考慮將以上HTML做爲JavaScript。 看看我在末尾添加了分號。

咱們上面寫的(例4)是JSX。 然而,咱們在瀏覽器的執行版本是它的編譯版本(示例3)。 爲了實現這一點,咱們須要使用預處理器將JSX版本轉換爲React.createElement版本。

那就是JSX。 這是一個折中,容許咱們以相似於HTML的語法編寫咱們的React組件,這是一個很好的共識。

上面標題中的「Flux」一詞被選爲韻腳(...),但它也是Facebook流行的很是受歡迎的應用程序架構的名稱。 最着名的實現是Redux。

JSX,順便說一下,能夠本身在其餘地方使用。 這不是隻有在React中才可使用的。

3: 您能夠在JSX中的任何位置使用JavaScript表達式

在JSX部分中,您能夠在一對花括號內使用任何JavaScript表達式。

// Example 5 -  Using JavaScript expressions in JSX
// https://jscomplete.com/repl?j=SkNN3oYSW
const RandomValue = () => 
  <div>
    { Math.floor(Math.random() * 100) }
  </div>;
// To use it:
ReactDOM.render(<RandomValue />, mountNode);

任何JavaScript表達式均可以放在那些花括號內。 這至關於JavaScript模板文字中的$ {}插值語法。

這是JSX中惟一的約束:只有表達式。 因此,你不能使用常規的if語句,可是三元表達式是能夠的。

JavaScript變量也是表達式,因此當組件接收到props列表(RandomValue組件沒有,props是可選的)時,能夠在花括號內使用這些props。 咱們在上面的Button組件中這樣作了(示例1)。

JavaScript對象也是表達式。 有時候,咱們在一個花括號裏面使用一個JavaScript對象,這使得它看起來像雙花括號,但它實際上只是一個大括號內的一個對象。 一個用例是將CSS樣式對象傳遞給React中的style屬性:

// Example 6 - An object passed to the special React style prop
// https://jscomplete.com/repl?j=S1Kw2sFHb
const ErrorDisplay = ({message}) =>
  <div style={ { color: 'red', backgroundColor: 'yellow' } }>
    {message}
  </div>;
// Use it:
ReactDOM.render(
  <ErrorDisplay 
    message="These aren't the droids you're looking for" 
  />,
  mountNode
);

請注意,我如何僅解析props參數中的message的。 這是JavaScript。 還要注意上面的style屬性是一個特殊的屬性(再次,它不是HTML,它更接近於DOM API)。 咱們使用一個對象做爲style屬性的值。 該對象定義了樣式,就像咱們使用JavaScript同樣(由於確實就是)。

甚至能夠在JSX中使用React元素,由於這也是一個表達式。 記住,一個React元素就是一個函數調用:

const MaybeError = ({errorMessage}) =>
  <div>
    {errorMessage && <ErrorDisplay message={errorMessage} />}
  </div>;
  
// The MaybeError component uses the ErrorDisplay component:
const ErrorDisplay = ({message}) =>
  <div style={ { color: 'red', backgroundColor: 'yellow' } }>
    {message}
  </div>;
// Now we can use the MaybeError component:
ReactDOM.render(
  <MaybeError
    errorMessage={Math.random() > 0.5 ? 'Not good' : ''}
  />,
  mountNode
);

上面的MaybeError組件將只顯示ErrorDisplay組件,若是有一個errorMessage字符串傳遞給它和一個空的div。 React將{true}{false}{undefined}{null}視爲沒有呈現任何內容的有效元素子元素。

您還可使用JSX內的集合上的全部JavaScript方法(map,reduce,filter,concat等)。 再次聲明緣由是由於它們返回的是表達式:

// Example 8 - Using an array map inside {}
// https://jscomplete.com/repl?j=SJ29aiYH-
const Doubler = ({value=[1, 2, 3]}) =>
  <div>
    {value.map(e => e * 2)}
  </div>;
// Use it
ReactDOM.render(<Doubler />, mountNode);

請注意,我是如何給valueprops默認值的,由於它全是Javascript。 還要注意,我在div中輸出了一個數組表達式,這在React中是可行的。 它將把每個雙倍的值放在一個文本節點中。

4: 您可使用JavaScript類編寫React組件

簡單的功能組件很是適合簡單的需求,但有時咱們須要更多的功能。 React支持經過JavaScript類語法建立組件。 這是使用類語法編寫的Button組件(在示例1中):

// Example 9 - Creating components using JavaScript classes
// https://jscomplete.com/repl?j=ryjk0iKHb
class Button extends React.Component {
  render() {
    return <button>{this.props.label}</button>;
  }
}
// Use it (same syntax)
ReactDOM.render(<Button label="Save" />, mountNode);

類語法很簡單。 定義一個擴展了React.Component基類的類(須要學習的另外一個頂級的React API)。 該類定義一個惟一實例函數render(),該render函數返回虛擬DOM對象。 每次咱們使用上面的基於Button類的組件(例如,經過執行<Button ... />),React將從這個基於類的組件中實例化一個對象,並在DOM樹中使用該對象。

這就是爲何咱們在上面的渲染輸出中在JSX中使用this.props.label的緣由。 由於每一個組件都得到一個稱爲props的特殊實例屬性,該實例屬性在實例化時保存傳遞給該組件的全部值。

// Example 10 -  Customizing a component instance
// https://jscomplete.com/repl?j=rko7RsKS-
class Button extends React.Component {
  constructor(props) {
    super(props);
    this.id = Date.now();
  }
  render() {
    return <button id={this.id}>{this.props.label}</button>;
  }
}
// Use it
ReactDOM.render(<Button label="Save" />, mountNode);

咱們還能夠定義類屬性函數,並在咱們想使用的地方使用,包括返回的JSX輸出內:

// Example 11 — Using class properties
// https://jscomplete.com/repl?j=H1YDCoFSb
class Button extends React.Component {
  clickCounter = 0;
handleClick = () => {
    console.log(`Clicked: ${++this.clickCounter}`);
  };
  
  render() {
    return (
      <button id={this.id} onClick={this.handleClick}>
        {this.props.label}
      </button>
    );
  }
}
// Use it
ReactDOM.render(<Button label="Save" />, mountNode);

關於例子11有幾點須要注意

  • handleClick函數是使用JavaScript中新建的類字段語法編寫的。這種語法仍然屬於stage-2,,但因爲不少緣由,它是訪問組件安裝實例(因爲箭頭功能)的最佳選擇。 可是,您須要使用像Babel這樣的編譯器來配置它來理解stage-2,(或類字段語法)來獲取上面的代碼。 jsComplete REPL具備預配置。
  • 咱們還使用相同的類字段語法定義了ClickCounter實例變量。 這容許咱們徹底跳過使用類構造函數調用。
  • 當咱們將handleClick函數指定爲特殊的onClick,React屬性的值時,咱們沒有調用它。 咱們把handleClick函數引用傳遞給出去了。 在這個屬性裏面調用函數是使用React最多見的錯誤之一。
// Wrong:
onClick={this.handleClick()}
// Right:
onClick={this.handleClick}

5: React的事件中,兩個最重要的區別

在React元素中處理事件時,與DOM API的方式有兩個很是重要的區別:

  • 全部React元素屬性(包括事件)使用camelCase命名,而不是小寫。 它是onClick,而不是onclick
  • 咱們傳遞一個實際的JavaScript函數引用做爲事件處理程序,而不是一個字符串。 它是onClick = {handleClick},而不是onClick =「handleClick」

使用本身的對象將DOM事件對象包裝起來,以優化事件處理的性能。 可是在事件處理程序中,咱們仍然能夠訪問DOM事件對象上可用的全部方法。 React將包裝的事件對象傳遞給每一個句柄調用。 例如,爲了防止表單從默認提交操做中,您能夠執行如下操做:

// Example 12 - Working with wrapped events
// https://jscomplete.com/repl?j=HkIhRoKBb
class Form extends React.Component {
  handleSubmit = (event) => {
    event.preventDefault();
    console.log('Form submitted');
  };
  
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <button type="submit">Submit</button>
      </form>
    );
  }
}
// Use it
ReactDOM.render(<Form />, mountNode);

6: 每個React組件都有故事

如下僅適用於類組件(擴展爲React.Component的組件)。 函數組件有一個略有不一樣的故事。

  1. 首先,咱們爲React定義一個模板,以從組件建立元素。
  2. 而後,咱們指示React在某處使用它。 例如,在另外一個組件的render調用中,或者使用ReactDOM.render。
  3. 而後,React實例化一個元素,並給出一組咱們可使用this.props訪問的props。 那些props正是咱們在上面的步驟2中傳遞的。
  4. 因爲它都是JavaScript,因此構造方法將被調用(若是已經定義的話)。 這是咱們要說的第一個:組件生命週期方法。
  5. 而後React計算render方法(虛擬DOM節點)的輸出。
  6. 因爲這是React渲染元素的第一次,React將與瀏覽器進行通訊(表明咱們使用DOM API)來顯示元素。 這個過程一般被稱爲掛載。
  7. 而後,React調用另外一個生命週期方法,稱爲componentDidMount。 咱們可使用這種方法作一些事情,例如,在DOM上作一些咱們如今知道在瀏覽器中支持處理的東西。 在今生命週期方法以前,咱們處理的DOM所有是虛擬的。
  8. 一些組件故事在這裏結束。 出於各類緣由,其餘組件能夠從瀏覽器DOM中解除掛載。 在後一種狀況發生以前,React調用另外一個生命週期方法componentWillUnmount
  9. 任何已掛載元件的狀態可能會改變。 該元素的父代可能會從新呈現。 在任一種狀況下,安裝的元件可能會接收不一樣的props。 這裏的魔法發生了,咱們如今開始須要React了! 在此以前,咱們徹底不須要作任何事情
  10. 這個組件的故事繼續下去,但在以前,咱們須要瞭解我所說的這個狀態。

7: React組件有一個私有狀態

如下也僅適用於類組件。 有沒有人提到有些人把只作展示的組件叫作啞吧?

狀態類字段是任何React類組件中的特殊字段。 React監視每一個組件狀態以進行更改。 可是對於React要有效地執行這些操做,咱們必須經過另外一個須要學習的React API函數來更改state字段,this.setState

// Example 13 -  the setState API
// https://jscomplete.com/repl?j=H1fek2KH-
class CounterButton extends React.Component {
  state = {
    clickCounter: 0,
    currentTimestamp: new Date(),
  };
  
  handleClick = () => {
    this.setState((prevState) => {
     return { clickCounter: prevState.clickCounter + 1 };
    });
  };
  
  componentDidMount() {
   setInterval(() => {
     this.setState({ currentTimestamp: new Date() })
    }, 1000);
  }
  
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>Click</button>
        <p>Clicked: {this.state.clickCounter}</p>
        <p>Time: {this.state.currentTimestamp.toLocaleString()}</p>
      </div>
    );
  }
}
// Use it
ReactDOM.render(<CounterButton />, mountNode);

這是瞭解state最重要的例子。 它將完善您對React交互方式的基礎知識。 在這個例子以後,還有一些你須要學習的小事情,可是從這一點來看,它主要是你和你的JavaScript技能。

咱們來看一下實例13,從類字段開始。 它有兩個。 特殊狀態字段被初始化爲一個對象,該對象包含起始值爲0的clickCounter,以及起始值爲new Date()currentTimestamp

第二個類字段是一個handleClick函數,咱們傳遞給render方法中的button元素的onClick事件。 handleClick方法使用setState修改此組件實例狀態。 注意到這一點。

咱們在componentDidMount生命週期方法內部啓動的間隔定時器中修改狀態。 它每秒鐘打勾並執行調用this.setState

在render方法中,咱們使用了正常讀取語法對state兩個屬性的讀取。 沒有特殊的API。

如今,請注意,咱們使用兩種不一樣的方式更新了狀態:

  1. 傳遞返回一個對象的函數。 咱們handleClick函數中實現了這部份內容。
  2. 經過傳遞一個常規對象。 咱們在間隔回調中實現了。

這兩種方式都是能夠接受的,可是當您同時讀取和寫入狀態時,第一個是首選的(咱們這樣作)。 在間隔回調以內,咱們只寫給狀態,而不是讀取它。 當兩難時,始終使用第一個函數參數語法。 它更加安全,由於setState其實是一個異步方法。

咱們如何更新狀態? 咱們返回一個包含咱們要更新的值的對象。 注意在兩次調用setState中,咱們只是從state字段傳遞一個屬性,而不是二者。 這是徹底能夠的,由於setState實際上將您傳遞的內容(函數參數的返回值)與現有狀態合併。 所以,在調用setState時不指定屬性意味着咱們不但願更改該屬性(而不是刪除它)。

8:React是能夠響應的

React從它對狀態變化作出響應的事實(雖然不是反應性的,而是按計劃進行)而得名。 有一個笑話,反應應該被命名爲Schedule!

然而,當任何組件的狀態被更新時,咱們用肉眼看到的是React對該更新作出反應,並自動反映瀏覽器DOM中的更新(若是須要)。

將render函數輸入視爲二者

  1. 從父元素獲得props
  2. 能夠隨時更新的內部私有狀態

當渲染功能的輸入變化時,其輸出可能會改變。

React保留了渲染歷史的記錄,當它看到一個渲染與前一個渲染不一樣時,它將計算它們之間的差別,並將其有效地轉換爲在DOM中執行的實際DOM操做。

9: React是你的代理

您能夠將React視爲咱們聘請的與瀏覽器通訊的代理。 以上面的當前時間戳顯示爲例。 咱們不是手動去瀏覽器並調用DOM API操做來每秒查找和更新p#timestamp元素,而是在組件狀態上更改了一個屬性,而React表明咱們與瀏覽器進行通訊。 我相信這是真正受歡迎的真正緣由。 咱們討厭瀏覽器(domApi很繁瑣),React自願爲咱們作全部對接工做,免費!

10: 每一個React組件都有一個故事(第2部分)

如今咱們知道一個組件的狀態,以及當這個狀態改變了一些魔法的時候,讓咱們來學習關於該過程的最後幾個概念。

  1. 組件可能須要在其狀態更新時從新呈現,或者當其父級決定更改傳遞給組件的props時,該組件可能須要從新呈現
  2. 若是後者發生,React會調用另外一個生命週期方法componentWillReceiveProps
  3. 若是狀態對象或傳入props被更改,則React有一個重要的決定。 組件應該在DOM中更新嗎? 這就是爲何它在這裏調用另外一個重要的生命週期方法,shouldComponentUpdate。 這個方法是一個實際的問題,因此若是你須要本身定製或優化渲染過程,你必須經過返回true或false來回答這個問題。
  4. 若是沒有指定customComponentUpdate,React默認是一個很是聰明的事情,在大多數狀況下實際上足夠好。
  5. 首先,React在此時調用另外一個生命週期方法componentWillUpdate。 而後React將計算新的渲染輸出並將其與最後渲染的輸出進行比較。
  6. 若是渲染的輸出徹底同樣,React什麼都不作。
  7. 若是存在差別,則React會將這些差別映射到瀏覽器內。
  8. 不管如何,因爲更新過程不管如何(即便輸出徹底相同),React會調用最終的生命週期方法componentDidUpdate

生命週期方法其實是艙口。 若是你沒有作任何事情,你能夠建立沒有他們的完整的應用程序。 他們能夠用來很是方便地分析應用程序中發生的狀況,並進一步優化了React更新的性能。

根據以上學到的東西(或其中的一部分,真的),您就能夠開始建立一些有趣的React應用程序。 若是您渴望瞭解更多信息,請訪問咱們的Plactsight的React.js課程入門

翻譯自All the fundamental React.js concepts, jammed into this single Medium article

關注個人公衆號,更多優質文章定時推送

clipboard.png

建立了一個程序員交流微信羣,你們進羣交流IT技術

圖片描述

若是已過時,能夠添加博主微信號15706211347,拉你進羣

相關文章
相關標籤/搜索