【譯】一份通俗易懂的React.js基礎指南-2018

原文連接:tylermcginnis.com/reactjs-tut… by Tyler McGinnishtml

React.js學習指南

這篇文章最初發表於2015年1月,但最近被更新爲React 16.3以及它所包含的全部優勢。react

React.js基礎:

組件是React的構建快。若是你擁有Angular背景,組件很是相似於Direactives.若是你來自於不一樣的背景,它們本質上是小工具或者模塊。你能夠認爲組件就是由HTML,CSS,JS和組件內部一些特定的數據組成的集合。它們擁有着你須要的一切東西,被包裹在一個美妙的組合包中。這些組件要麼用純JavaScript定義,要麼能夠在React團隊所稱的「JSX」中定義。若是你決定使用JSX(你最願意的方式,很標準的——也是咱們將使用到的工具),你須要一些編譯工具將JSX轉換成JavaScript,咱們以後再說這個。web

React之因此可以如此方便地構建用戶界面,是由於數據既能夠來自組件的父組件,也可以包含在組件自己中。在咱們進入到代碼以前,咱們要確保對組件高度理解。ajax

上圖是我Twitter帳戶的圖片。若是咱們使用React重構這個頁面,咱們將把不一樣的部分拆分紅不一樣的組件(敲黑板)。請注意,組件能夠在其內部嵌套組件。咱們能夠命名左邊的組件(粉紅框部分)爲UserInfo組件。在UserInfo組件中有另外一個組件(橙色塊),能夠將其定義爲UserImages組件。這中父/子關係的工做方式是:UserInfo組件(父組件)存放着其自己和UserImages組件(子組件)的「狀態」數據。若是咱們想要在自組件中使用任何父級組件的數據,咱們會將數據做爲屬性傳遞給自組件。在這個例子中,咱們傳遞全部用戶的圖片給UserImages組件(這些圖片都存放在UserInfo組件中)。咱們將詳細的在代碼中討論,可是我但願你可以明白上面👆的圖片發生了些什麼。這種父/子結構使得咱們管理數據的方式更加方便,由於咱們不須要精確地知道咱們的數據到底存放在哪的而且咱們不該該在其它地方操縱這些數據。算法

下面要討論的話題都是關於React基礎方面的東西。若是你瞭解它們以及它們的原理,閱讀完本教程後,你將更上一層樓。npm

JSX - 容許咱們使用HTML語法相似的寫法,可以被轉換成輕量級的JavaScript對象。

Virtual DOM - 實際DOM的JavaScript表示。

React.Component - 建立一個新組件的方式。

render(方法)- 爲特定的組件描述UI的樣子。

ReactDOM.render = 將React組件渲染成DOM節點。

state - 組件內部的數據存儲(對象)。

construtor (this.state) - 在組件中建立內部狀態(state)的方式。

setState - 組件內部更新狀態(state)的工具方法並會從新渲染UI。

props - 父組件中傳遞給子組件的數據。

propTypes - 容許你控制傳遞給子組件的屬性(props)的存在或者類型。

defaultProps - 容許你爲本身的組件設置默認的屬性(props)。

Component LifeCycle(生命週期)
    - componentDidMount - 組件被掛載時執行
    - componentWillUnmount - 組件註銷前執行
    - getDerivedStateFromProps - 當組件被掛載而且props發生變化時執行。用於更新組件中的狀態(state)當props改變時。

Events
    - onClick
    - onSubmit
    - onChange
    ...
複製代碼

我知道這看起來不少,可是你很快就會看到,在使用React構建健壯應用時,這每個部分都是很是重要的(當我說我但願這是一個全面的指南時,我也不是在開玩笑)。數組

在這一點上,你應該高度理解React是如何工做的。如今,讓咱們看一些代碼。安全


建立你的第一個組件(JSX,Virtual DOM,render,ReactDOM.render)

讓咱們繼續建立咱們的第一個組件吧!app

爲了建立React組件,你將使用到ES6中的類。dom

import React from 'react'
import ReactDOM from 'react-dom'

class HelloWorld extends React.Component {
    render() {
        return (
            <div>Hello World!</div>
        )
    }
}

ReactDOM.render(<HelloWorld />, document.getElementById('root')); 複製代碼

注意到咱們的類中只有惟一的一個方法就是render。每一個組件都須要有一個render方法。使用render方法的理由是它能描述組件的UI(user interface)。所以在這個例子中Hello world會被渲染出來展現在屏幕上。如今咱們看看ReactDOM是作什麼的。ReactDOM.render方法有兩個參數。第一個參數是你想要渲染的組件,第二個參數是你渲染組件的地方(哪一個DOM 節點中)。(注意咱們使用的是ReactDOM.render而不是React.render。這個改變出如今React.14中爲了使React更加模塊化。當你認爲React能夠呈現比DOM元素更多的內容時,這是有意義的)。在上面的例子中,咱們告訴React獲取HelloWorld組件並在IDroot的元素中渲染。正如咱們前面提到的React中的父/子關係,你一般只須要在應用中用到一次ReactDOM.render方法,由於經過渲染最父級的組件,全部的自組件也都會被渲染。

此時你可能以爲在Javascript中寫「HTML」有些奇怪。在你開始學習web開發時,就有人告訴你應該將邏輯層和視圖層分開,AKA也就是保持JavaScript與HTML的鬆耦合。這種模式很強大,但它也有一些缺點。因爲篇幅緣由,這裏將再也不詳述,你能夠查看這篇文檔一探究竟。隨着你對React的瞭解愈來愈多,這種不安應該會很快消退。在render方法中寫的「HTML」並非真正的HTML而是React所稱的「JSX」。JSX容許咱們很容易地編寫相似HTML的語法,這些語法(最終)被轉換爲輕量級JavaScript對象。而後,React能夠獲取這些JavaScript對象,並將其建立成「虛擬DOM」或實際DOM的JavaScript表示。這建立了共贏的局面,你可使用JavaScript的強大功能來訪問模板。

看看下面的例子,這就是你的JSX最終被編譯的樣子:

class HelloWorld extends React.Component {
       render() {
           return React.createElement('div', null, 'Hello world');
       }
   }
複製代碼

如今,您能夠放棄JSX -> JS轉換階段,像上面的代碼同樣編寫您的React組件,但正如你所能想象的,這將是至關棘手的。我不知道有誰沒有使用JSX。有關JSX編譯的更多信息,你能夠查看React Elements vs React Components

到目前爲止,咱們尚未真正強調咱們將要進入的這個新的虛擬DOM範式的重要性。React團隊採用這種方法的緣由是,由於虛擬DOM是實際DOM的JavaScript表示,React可以持續跟蹤當前虛擬DOM(在一些數據更改以後計算)和前一個虛擬DOM(在一些數據更改以前計算)的不一樣。React將新舊虛擬DOM之間的變化隔離開來,而後只更新須要更改的實際DOM。一般UI有許多狀態,這使得管理狀態變得困難。每次狀態改變都會從新渲染Virtual DOM, React讓你更容易思考應用程序處於什麼狀態。過程是這樣的:

一些用戶事件改變了應用程序的狀態 -> 從新渲染Virtual DOM -> 使用Diff算法計算出新Virtual DOM和前一個Virtual DOM間的差別 -> 只更新實際DOM中必要的變化

由於存在從JSXJS的轉換過程,因此在開發過程當中須要設置某種類型的轉換階段。在本系列的第2部分中,我將介紹WebpackBabel來進行這種轉換。

讓咱們回顧一下咱們的「最重要的React的部分」清單,看看咱們如今到哪兒了:


JSX - 容許咱們使用HTML語法相似的寫法,可以被轉換成輕量級的JavaScript對象。

Virtual DOM - 實際DOM的JavaScript表示。

React.Component - 建立一個新組件的方式。

render(方法)- 爲特定的組件描述UI的樣子。

ReactDOM.render = 將React組件渲染成DOM節點。

state - 組件內部的數據存儲(對象)。

construtor (this.state) - 在組件中建立內部狀態(state)的方式。

setState - 組件內部更新狀態(state)的工具方法並會從新渲染UI。

props - 父組件中傳遞給子組件的數據。

propTypes - 容許你控制傳遞給子組件的屬性(props)的存在或者類型。

defaultProps - 容許你爲本身的組件設置默認的屬性(props)。

Component LifeCycle(生命週期) - componentDidMount - 組件被掛載時執行 - componentWillUnmount - 組件註銷前執行 - getDerivedStateFromProps - 當組件被掛載而且props發生變化時執行。用於更新組件中的狀態(state)當props改變時。

Events - onClick - onSubmit - onChange ...


咱們的速度很快。全部粗體部分都是咱們已經介紹過了,你至少應該可以解釋這些特定組件如何適應React生態系統了。

添加state到你的組件中

接下來是state。以前咱們提到過管理用戶界面是困難的,由於他們老是有大量的狀態。這是React真正發光的地方。每一個組件都有能力管理它們本身的狀態而且在須要的時候將這些狀態傳遞給他們的子組件。回到前面Twitter帳戶的例子,UserInfo組件(上面的粉紅塊)負責管理用戶信息狀態(或者數據)。若是另一個組件也須要這個state/data,但那個狀態(state)不是UserInfo組件的直接子組件,而後你將建立另外一個組件,它將是UserInfo和另外一個組件(或兩個組件都須要該狀態)的直接父組件,而後將狀態(state)做爲屬性(props)傳遞到子組件中。換句話說,若是你有一個多組件層次結構,那麼公共父組件應該管理狀態,並經過屬性(props)將其傳遞給其子組件。

讓咱們看一個使用其內部狀態的示例組件:

class HelloUser extends React.Component {
    constructor(props) {
        super(props)
        
        this.state = {
            username: 'tylermcginnis'
        }
    }
    
    render() {
        return (
            <div> Hello {this.state.username} </div>
        )
    }
}
複製代碼

咱們在這個例子中引入了一些新的語法。第一個你注意到的是constructor方法。從上面的定義得知,constructor方法是組件中的狀態(state)定義的方式。換句話說,任何你在constructor中設置在this.state上的數據都會成爲組件狀態(state)的一部分。在上面的代碼中咱們告訴組件咱們想要持續跟蹤username的狀態。username如今能夠被使用經過調用this.state.username的方式,咱們在render方法中確實是這樣作的。

關於state咱們須要討論的最後一件事是,咱們的組件須要有能力改變它自身內部的狀態(state)。咱們能夠經過setState來作這件事。還記得咱們前面咱們提到過當數據發生改變的時候都會出發re-rendering從新渲染virtual dom嗎?

通知咱們的應用程序一些數據發生了變化 -> 從新渲染virtual DOM -> 使用diff算法計算前一個virtual DOM和新virtual DOM之間的區別 -> 實際的DOM節點進行必要的更新。

經過咱們的應用程序,數據發生了變化的信號就是setState。不論何時setState被調用,virtual DOM都會被從新渲染,diff算法會跑起來,而且實際的DOM也會進行必要的更新。

做爲旁註,當咱們在下面的代碼中介紹setState時,咱們也會介紹一些清單中事件。一箭雙鵰!

所以在下面的例子中,咱們將有一個輸入框,不管何時進行輸入,它都會自動更新咱們的狀態(state)和改變username的值。

class HelloUser extends React.Component {
    construtor(props) {
        super(props)
        
        this.state = {
            username: 'tylermcginnis'
        }
        
        this.handleChange = this.handleChange.bind(this)
    }
    handleChange (e) {
        this.setState({
            username: e.target.value
        })
    }
    render() {
        return (
            <div> Hello {this.state.username} <br /> Change Name: <input type="text" value={this.state.username} onChange={this.handleChange} /> </div> ) } } 複製代碼

注意到咱們介紹了一些東西。第一個是handleChange方法。每當用戶在輸入框中鍵入時,就會調用這個方法。當handleChange被調用時,它會調用setState來從新定義咱們的用戶名將其賦值爲任何在輸入框中輸入的值(e.target.value)。記住,每次setState被調用時,React都會建立一個新的virtual DOM,執行diff算法,而且更新實際的DOM節點。

如今來看一下咱們的render方法。咱們添加了一個包含輸入域的新行。它的類型顯然是text。這個值將是咱們的用戶名(username)的值,它最初是在getInitialState方法中定義的,將在handleChange方法中更新。請注意,這裏有一個你可能從未見過的新屬性,onChangeonChange是一個React事件,它會在每次輸入框中的值發生變化時調用你指定的任何方法,在這裏咱們指定的方法是handleChange

上述代碼的處理過程大體是這樣的。

用戶在輸入框中鍵入 -> handleChange調用 -> 組件的狀態被設置爲一個新值 -> React從新渲染虛擬DOM -> React執行diff算法計算出差別 -> 實際DOM節點被更新。

以後咱們會講到props,咱們將看到一些更高級處理state的用例。

咱們到這裏了!若是你不能很好的解釋上面列舉條目中加粗的部分,你最好重讀一遍。真正學習React的一個技巧是,不要讓被動的閱讀給你一種虛假的安全感,覺得你知道發生了什麼。你應該在編輯器中建立本身的組件,而不僅是看我作了些什麼。這是你真正開始學習如何使用React的惟一方法。這適用於本教程和後續教程。


從父組件中接收狀態(props,propTypes,getDefaultProps)

咱們已經討論過幾回props了,由於沒有它們真的很難作不少事情。前面的定義指出,props是父組件傳遞給子組件的數據。這容許咱們的React體系結構保持至關直接的狀態。處理須要使用特定數據的頂級父組件中的狀態,若是你的子組件也須要該數據,則將該數據做爲props傳遞下去。

這裏有一個使用props很是簡單的例子:

class HelloUser extends React.Component {
 render() {
   return (
     <div> Hello, {this.props.name}</div>
   )
 }
}

ReactDOM.render(<HelloUser name="Tyler"/>, document.getElementById('root')); 複製代碼

注意,在第9行,咱們有一個名爲name的屬性,其值爲「Tyler」。如今在咱們的組件中,咱們可使用{this.props.name}來獲取「Tyler」。

讓咱們看一個更高級的例子。咱們如今有兩個組件。一個parent,一個child。父組件將跟蹤狀態並將該狀態的一部分做爲props傳遞給子進程。讓咱們首先看看父組件。

父組件

class FriendsContainer extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      name: 'Tyler McGinnis',
      friends: ['Jake Lingwall', 'Sarah Drasner', 'Merrick Christensen']
    }
  }
  render() {
    return (
      <div> <h3> Name: {this.state.name} </h3> <ShowList names={this.state.friends} /> </div> ) } } 複製代碼

在這個組件中,沒什麼特別的:咱們有一個初始狀態,咱們將初始狀態的一部分傳遞給另外一個組件。大多數新代碼未來自這個子組件,因此讓咱們更仔細地研究一下。

子組件:

class ShowList extends React.Component {
  render() {
    return (
      <div> <h3> Friends </h3> <ul> {this.props.names.map((friend) => <li>{friend}</li>)} </ul> </div>
    )
  }
}
複製代碼

請記住,render方法返回的代碼是實際DOM的表示形式。若是你不熟悉Array.prototype.map,這段代碼看起來有點陌生。map所作的就是建立一個新數組,對數組中的每一個項目調用回調函數,並將每一個項目調用回調函數的結果填充到新數組中。例如,

const friends = ['Jake Lingwall', 'Sarah Drasner', 'Merrick Christensen'];
const listItems = friends.map((friend) => {
  return "<li> " + friend + "</li>";
});

console.log(listItems);
// ["<li> Jake Lingwall</li>", "<li> Sarah Drasner</li>", "<li> Merrick Christensen</li>"];
複製代碼

注意,咱們建立了一個新數組,並將<li> </li>添加到原始數組中的每一項。

map的偉大之處在於它很是適合於React(JavaScript中的內置方法)。所以,在上面的子組件中,咱們映射名稱,將每一個名稱包裝在一對<li>標記中,並將其保存到listItems變量中。而後,咱們的render方法返回一個無序列表,其中包含咱們全部的朋友。

在咱們中止討論props以前,讓咱們再看一個例子。重要的是要理解,不管數據位於何處,都是你但願操做這些數據的。這樣能夠簡化對數據的推理。對於特定數據塊的全部getter/setter方法都將始終位於定義該數據的相同組件中。若是你須要在數據所在位置以外操做一些數據,能夠將getter/setter方法做爲props傳遞到該組件。讓咱們看一個這樣的例子。

class FriendsContainer extends React.Component {
    construtor(props) {
        super(props)
        
        this.state = {
            name: 'Tyler McGinnis',
            friends: [
                'Jake Lingwall',
                'Sarah Drasner',
                'Merrick Christensen'
            ],
        }
        
        this.addFriend = this.addFriend.bind(this)
    }
    
    addFriend(friend) {
        this.setState((state) => ({
            friends: state.friends.concat([friend])
        }))
    }
    
    render() {
        return (
            <div>
                <h3> Name: {this.state.name} </h3>
                <AddFriend addNew={this.addFriend} />
                <ShowList names={this.state.friends} />
            </div>
        )
    }
}
複製代碼

🚨.注意,在addFriend方法中,咱們引入了一種調用setState的新方法。咱們不是傳遞一個對象,而是傳遞一個函數,而後這個函數傳遞狀態(state)。當你根據之前的狀態設置組件的新狀態時(正如咱們對friends數組所作的那樣),你但願給setState傳遞一個函數,該函數接收當前狀態(state)並返回數據與新狀態進行合併。

class AddFriend extends React.Component {
    construtor(props) {
        super(props)
        
        this.state = {
            newFriend: ''
        }
        
        this.updateNewFriend = this.updateNewFriend.bind(this)
        this.handleAddNew = this.handleAddNew.bind(this)
    }
    updateNewFriend(e) {
        this.setState({
            newFriend: e.target.value
        })
    }
    handleAddNew() {
        this.props.addNew(this.state.newFriend)
        this.setState({
            newFriend: ''
        })
    }
    render() {
        return (
            <div> <input type="text" value={this.state.newFriend} onChange={this.updateNewFriend} /> <button onClick={this.handleAddNew}> Add Friend </button> </div> ) } } 複製代碼
class ShowList extends React.Component {
  render() {
    return (
      <div> <h3> Friends </h3> <ul> {this.props.names.map((friend) => { return <li> {friend} </li> })} </ul> </div>
    )
  }
}
複製代碼

你會注意到上面的代碼與前面的示例基本相同,只是如今咱們可以向好友列表中添加新名稱。注意我是如何建立一個新的AddFriend組件來管理咱們將要添加的新朋友的。這是由於父組件(FriendContainer)並不關心你添加的新朋友,它只關心做爲一個總體的全部朋友(朋友數組)。可是,由於咱們堅持只操做關心它的組件的數據的規則,因此咱們已經將addFriend方法做爲一個props傳遞到addFriend組件中,並在handleAddNew方法被調用後與新朋友一塊兒調用它。

在這一點上,我建議你在嘗試使用上面的代碼來從新建立相同的功能,你可能會被卡住3-4分鐘。

在咱們繼續將props以前,我想再講兩個關於props的React特性。它們是propTypesdefaultProps。這裏我很少講,由於這二者都很直接。proptype容許你控制展現,或者傳遞給子組件的某些props的類型。使用propTypes,你能夠指定特定的props是不是必需的,或者特定props的類型。

從React 15開始,PropTypes再也不包含在React包中。你須要經過運行npm install prop-types來單獨安裝它。

defaultProps容許爲某些props指定默認(或備份)值,以防這些props從未傳遞到組件中。

我使用propTypes修改了咱們的組件,要求addFriend是一個函數,並將其傳遞給AddFriend組件。我還使用了defaultProps,若是沒有向ShowList組件提供朋友數組,它將默認爲空數組。

import React from 'react'
import PropTypes from 'prop-types'

class AddFriend extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      newFriend: ''
    }
  }
  updateNewFriend(e) {
    this.setState({
      newFriend: e.target.value
    })
  }
  handleAddNew() {
    this.props.addNew(this.state.newFriend)
    this.setState({
      newFriend: ''
    })
  }
  render() {
    return (
      <div> <input type="text" value={this.state.newFriend} onChange={this.updateNewFriend} /> <button onClick={this.handleAddNew}> Add Friend </button> </div> ) } } AddFriend.propTypes: { addNew: PropTypes.func.isRequired } 複製代碼
class ShowList extends React.Component {
  render() {
    return (
      <div> <h3> Friends </h3> <ul> {this.props.names.map((friend) => { return <li> {friend} </li> })} </ul> </div>
    )
  }
}

ShowList.defaultProps = {
  names: []
}
複製代碼

好了,這是此教程的最後一部分。讓咱們看一下檢查一下清單,看看還剩下什麼。

Component LifeCycle
  - componentDidMount — Fired after the component mounted
  - componentWillUnmount — Fired before the component will unmount
  - getDerivedStateFromProps - Fired when the component mounts and
whenever the props change. Used to update the state of a
component when its props change
複製代碼

就快完了!

組件生命週期

你建立的每一個組件都有本身的生命週期事件,這些事件是有用的。例如,若是咱們想在第一次渲染時發出ajax請求並獲取一些數據,咱們應該在哪裏作呢?或者,若是咱們想在props改變的時候處理一些業務邏輯,咱們又該怎麼作呢?不一樣的生命週期事件是這兩個問題的答案。讓咱們把它們分解。

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      name: 'Tyler McGinnis'
    }
  }
  componentDidMount(){
    // 當DOM節點被掛載的時候執行一次
    // 有益於發送AJAX請求
  }
  static getDerivedStateFromProps(nextProps, prevState) {
    // 這個函數返回的對象將被整合到當前的state中
  }
  componentWillUnmount(){
    // 在組件被卸載以前當即執行
    // 有助於清理一些監聽器(listeners)
  }
  render() {
    return (
      <div> Hello, {this.state.name} </div>
    )
  }
}
複製代碼

componentDidMount - 在初始渲染(render)以後調用一次。由於在調用此方法時組件已經被調用,因此若是有須要,你能夠訪問虛擬DOM,經過調用this.getDOMNode()來實現。這是一個生命週期事件,在這個事件中,你能夠發出AJAX請求以獲取一些數據。

componentWillUnmount - 在從DOM卸載組件以前,將當即調用今生命週期方法。這是你能夠作必要清理的地方。

getDerivedStateFromProps - 有時你須要根據傳遞進來的props更新組件的狀態(state)。這是生命週期方法,你能夠這樣作。它將傳遞propsstate,返回的對象將與當前state合併。

若是你能堅持到如今,作得很好。我但願本教程對你有所幫助,而且你如今至少對React感受還算滿意。

爲了更深刻地瞭解React的基本原理,看看咱們的React基礎課程。或者查看官方教程

相關文章
相關標籤/搜索