React Native開發之必備React基礎

CrazyCodeBoy發表於2019-03-04

為了幫助大家快速上手React Native開發,在這本節中將向大家介紹開發React Native所需要的一些React必備基礎知識

概述

本節課將從React的特點、如何使用React、JSX語法,然後會對元件(Component)以及元件的屬性(props)、狀態(state)、生命週期等方面進行講解。

通過本節課程能學到什麼?

  • 對React有個全面的認識;
  • 熟悉JSX基本語法;
  • 瞭解元件結構;
  • 熟悉元件的生命週期;
  • 學會使用props;
  • 學會使用state;
  • 熟悉自定義元件;

React是什麼?

React 是 Facebook 推出的開源 JavaScript Library,它是一個用於組建使用者介面的JavaScript庫,讓你以更簡單的方式來建立互動式使用者介面,它的出現讓許多革新性的 Web 觀念開始流行起來,例如:Virtual DOM、Component,宣告式渲染等。

宣告式與命令式

指令式程式設計:命令“機器”如何去做事情(how),這樣不管你想要的是什麼(what),它都會按照你的命令實現。
宣告式程式設計:告訴“機器”你想要的是什麼(what),讓機器想出如何去做(how)。
複製程式碼

演示

  1. 當資料改變時,React將高效的更新和渲染需要更新的元件。宣告式檢視使你的程式碼更可預測,更容易除錯。
  2. 構建封裝管理自己的狀態的元件,然後將它們組裝成複雜的使用者介面。由於元件邏輯是用JavaScript編寫的,而不是模板,所以你可以輕鬆地通過您的應用程式傳遞豐富的資料,並保持DOM狀態。
  3. 一次學習隨處可寫,學習React,你不僅可以將它用於Web開發,也可以用於React Native來開發Android和iOS應用。

如何使用?

構建一個新的 React 單頁應用,可以通過Create React App來完成。它可以幫助你配置開發環境,以便你可以使用最新的 JavaScript 特性,還能提供一個友好的開發體驗,併為生產環境優化你的應用。

npm install -g create-react-app
create-react-app my-app
cd my-app
npm start
複製程式碼
"dependencies": {
    "react": "^16.6.3",//是 React 的核心庫
    "react-dom": "^16.6.3",//提供與 DOM 相關的功能
    "react-scripts": "2.1.1"//create-react-app 的一個核心包,一些指令碼和工具的預設配置都整合在裡面
  },
複製程式碼

ReactDOM.render

ReactDOM.render(element, container[, callback])

渲染一個 React 元素到由 container 提供的 DOM 中,並且返回元件的一個 引用(reference) (或者對於 無狀態元件 返回 null )。

JSX

JSX 是一個看起來很像 XML 的 JavaScript 語法擴充套件。 每一個XML標籤都會被JSX轉換工具轉換成純JavaScript程式碼,使用JSX,元件的結構和元件之間的關係看上去更加清晰。 JSX並不是React必須使用的,但React官方建議我們使用 JSX , 因為它能定義簡潔且我們熟知的包含屬性的樹狀結構語法。

Usage:

React.render(//使用JSX
    <div>
        <div>
            <div>content</div>
        </div>
    </div>,
    document.getElementById('example')
);
React.render(//不使用JSX
    React.createElement('div', null,
        React.createElement('div', null,
            React.createElement('div', null, 'content')
        )
    ),
    document.getElementById('example')
);
複製程式碼

createElement

React.createElement(
  type,
  [props],
  [...children]
)
複製程式碼

根據給定的型別建立並返回新的 React element 。引數type既可以是一個html標籤名稱字串(例如'div' 或 'span' ),也可以是一個 React component 型別(一個類或一個函式)。


React.createElement(Hello, {toWhat: 'World'}, 'hello'),
//等價於 <Hello toWhat="World">hello</Hello>,

複製程式碼

HTML標籤 與 React元件 對比

React 可以渲染 HTML 標籤 (strings) 或 React 元件 (classes)。 要渲染 HTML 標籤,只需在 JSX 裡使用小寫字母開頭的標籤名。

var myDivElement = <div className="foo" />;
React.render(myDivElement, document.root);
複製程式碼

要渲染 React 元件,只需建立一個大寫字母開頭的本地變數。

var MyComponent = ...;
var myElement = <MyComponent someProperty={true} />;
React.render(myElement, document.body);
複製程式碼

提示:

  • React 的 JSX 里約定分別使用首字母大、小寫來區分本地元件的類和 HTML 標籤。
  • 由於 JSX 就是 JavaScript,一些識別符號像 class 和 for 不建議作為 XML 屬性名。作為替代, React DOM 使用 className 和 htmlFor 來做對應的屬性。

JavaScript 表示式

屬性表示式

要使用 JavaScript 表示式作為屬性值,只需把這個表示式用一對大括號 {} 包起來,不要用引號 ""

// 輸入 (JSX):
var person = <Person name={window.isLoggedIn ? window.name : ''} />;
// 輸出 (JS):
var person = React.createElement(
  Person,
  {name: window.isLoggedIn ? window.name : ''}
);
複製程式碼

子節點表示式

同樣地,JavaScript 表示式可用於描述子結點:

// 輸入 (JSX):
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
// 輸出 (JS):
var content = React.createElement(
  Container,
  null,
  window.isLoggedIn ? React.createElement(Nav) : React.createElement(Login)
);
複製程式碼

註釋

JSX 裡新增註釋很容易,它們只是 JS 表示式而已。你只需要在一個標籤的子節點內(非最外層)用 {} 包圍要註釋的部分。

class ReactDemo extends Component {
  render() {
    return (
      <View style={styles.container}>
        {/*標籤子節點的註釋*/}
        <Text style={styles.welcome}
          //textAlign='right'
          textShadowColor='yellow'
          /*color='red'
          textShadowRadius='1'*/
          >
          React Native!
        </Text>
      </View>
    );
  }
}
複製程式碼

心得:在標籤節點以外註釋,和通常的註釋是一樣的,多行用“/**/” 單行用“//”;

JSX延展屬性

不要試圖去修改元件的屬性

不推薦做法:

  var component = <Component />;
  component.props.foo = x; // 不推薦
  component.props.bar = y; // 不推薦
複製程式碼

這樣修改元件的屬性,會導致React不會對元件的屬性型別(propTypes)進行的檢查。從而引發一些預料之外的問題。

推薦做法:

var component = <Component foo={x} bar={y} />;
複製程式碼

延展屬性(Spread Attributes)

你可以使用 JSX 的新特性 - 延展屬性:

  var props = {};
  props.foo = x;
  props.bar = y;
  var component = <Component {...props} />;
複製程式碼

傳入物件的屬性會被複制到元件內。

它能被多次使用,也可以和其它屬性一起用。注意順序很重要,後面的會覆蓋掉前面的。

  var props = { foo: 'default' };
  var component = <Component {...props} foo={'override'} />;
  console.log(component.props.foo); // 'override'
複製程式碼

上文出現的...標記被叫做延展操作符(spread operator)已經被 ES6 陣列 支援。

www.devio.org/2018/09/09/…

Component

React 允許將程式碼封裝成元件(component),然後像插入普通 HTML 標籤一樣,在網頁中插入這個元件。

class Hello extends React.Component{
    render() {
        return <h1>Hello {this.props.name}</h1>;
    }
}
ReactDOM.render(
  <Hello name="John" />,
  document.getElementById('example')
);
複製程式碼

上面程式碼中,變數 HelloMessage 就是一個元件類。模板插入 <HelloMessage />時,會自動生成 HelloMessage 的一個例項。所有元件類都必須有自己的 render 方法,用於輸出元件。

注意

  • 元件類的第一個字母必須大寫
  • 元件類只能包含一個頂層標籤? 演示

元件的屬性(props)

我們可以通過this.props.xx的形式獲取元件物件的屬性,物件的屬性可以任意定義,但要避免與JavaScript關鍵字衝突。

遍歷物件的屬性:

this.props.children會返回元件物件的所有屬性。 React 提供一個工具方法 React.Children 來處理 this.props.children 。我們可以用 React.Children.mapReact.Children.forEach 來遍歷子節點。

React.Children.map

React.Children.map(children, function[(thisArg)])
複製程式碼

在包含在 children 裡的每個子級上呼叫函式,呼叫的函式的 this 設定為 thisArg 。如果 children 是一個巢狀的物件或陣列,它將被遍歷。如果 children 是 null 或 undefined ,返回 null 或 undefined 而不是一個空陣列。

React.Children.forEach

React.Children.forEach(children, function[(thisArg)])
複製程式碼

Usage:

class NotesList extends React.Component{
    render(){
        return (
            <ol>
                {
                    React.Children.map(this.props.children,(child)=> {
                        return <h1>{child}</h1>;
                    })
                }
            </ol>
        );
    }
}
ReactDOM.render(<NotesList>
    <span>hello</span>
    <span>world</span>
</NotesList>, document.getElementById('root'));
複製程式碼

演示

[PropTypes]

元件的屬性可以接受任意值,字串、物件、函式等等都可以。有時,我們需要一種機制,驗證別人使用元件時,提供的引數是否符合要求。 元件類的PropTypes屬性,就是用來驗證元件例項的屬性是否符合要求。

React.PropTypes 從 React v15.5開始被移入了prop-types,使用時需要留意;

import  PropTypes from 'prop-types'
class MyTitle extends React.Component{
    static propTypes={
        title: PropTypes.string.isRequired,
    };
    render() {
        return <h1> title:{this.props.title} </h1>;
    }
}
ReactDOM.render(<MyTitle />, document.getElementById('root'));
複製程式碼

上面的Mytitle元件有一個title屬性。PropTypes 告訴 React,這個 title 屬性是必須的,而且它的值必須是字串。現在,我們設定 title 屬性的值是一個數值。

var data = 123;
ReactDOM.render(
  <MyTitle title={data} />,
  document.body
);
複製程式碼

這樣一來,title屬性就通不過驗證了。控制檯會顯示一行錯誤資訊。

Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.

更多的PropTypes設定,可以檢視官方文件

預設屬性

此外,可以通過defaultProps用來設定元件屬性的預設值。

class MyTitle extends React.Component{
    static defaultProps={
        shortName:'MyTitle'
    };
    render() {
        return <h1> {this.props.shortName}</h1>;
    }
}
ReactDOM.render(<MyTitle/>, document.getElementById('root'));
複製程式碼

上面程式碼會輸出"MyTitle"

ref 屬性(獲取真實的DOM節點)

元件並不是真實的 DOM 節點,而是存在於記憶體之中的一種資料結構,叫做虛擬 DOM (virtual DOM)。只有當它插入文件以後,才會變成真實的 DOM 。根據 React 的設計,所有的 DOM 變動,都先在虛擬 DOM 上發生,然後再將實際發生變動的部分,反映在真實 DOM上,這種演算法叫做 DOM diff ,它可以極大提高網頁的效能表現。

但是,有時需要從元件獲取真實 DOM 的節點,這時就要用到 ref 屬性。

class Alert extends React.Component {
    showAlert(message) {
        alert(`Debug:${message}`);
    }

    render() {
        return null;
    }
}

class MyTitle extends React.Component {
    onClick = () => {
        this.refs.alert.showAlert('MyTitle');
    };

    render() {
        return <div>
            <h1 onClick={this.onClick}>Click me</h1>
            <Alert ref='alert'/>
        </div>;
    }
}

ReactDOM.render(<MyTitle/>, document.getElementById('root'));
複製程式碼

上面程式碼中,元件 MyTitle 的子節點有一個Alert元件,為了呼叫這個元件提供的方法,這時就必須獲取真實的 DOM 節點,虛擬 DOM 是拿不到使用者輸入的。為了做到這一點,我們在使用這個元件的時候必須為其設定一個ref屬性,然後 this.refs.[refName] 就會返回這個真實的 DOM 節點。

需要注意的是,由於 this.refs.[refName] 屬性獲取的是真實 DOM ,所以必須等到虛擬 DOM 插入文件以後,才能使用這個屬性,否則會報錯。上面程式碼中,通過為元件指定 Click 事件的回撥函式,確保了只有等到真實 DOM 發生 Click 事件之後,才會讀取 this.refs.[refName] 屬性。

React 元件支援很多事件,除了 Click 事件以外,還有 KeyDown 、Copy、Scroll 等,完整的事件清單請檢視官方文件

心得:ref屬性在開發中使用頻率很高,使用它你可以獲取到任何你想要獲取的元件的物件,有了這個物件你就可以靈活地做很多事情,比如:讀寫物件的變數,甚至呼叫物件的函式。

state

上文講到了props,元件會根據props的變化來進行渲染,但元件無法改變自身的props,那麼元件為了實現互動,可以使用元件的 state 。state 是元件私有的,可以通過state={}方式初始化,通過呼叫 this.setState() 來改變它。當 state 更新之後,元件就會重新渲染自己。

render() 方法依賴於 this.props 和 this.state ,框架會確保渲染出來的 UI 介面總是與輸入( this.props 和 this.state )保持一致。

初始化state

可以通過一下兩種方式來初始化state,在元件的生命週期中僅執行一次,用於設定元件的初始化 state 。

constructor(props){
    super(props);
    this.state={
        name:''
    }
}
//or
state={
    name:''
}
複製程式碼

更新 state

通過this.setState()方法來更新state,呼叫該方法後,React會重新渲染相關的UI。 this.setState({favorite:!this.state.favorite});

Usage:

class FavoriteButton extends React.Component{
   state={
       favorite:false
   };
    handleClick=()=>{
        this.setState({favorite:!this.state.favorite});
    };
    render(){
        const text=this.state.favorite? 'favorite':'un favorite';
        return (
            <h1 onClick={this.handleClick}>
                You {text} this. Click to toggle.
            </h1>
        );
    }
}
複製程式碼

上面程式碼是一個 FavoriteButton 元件,它的通過 state={}初始狀態,也就是一個物件,這個物件可以通過 this.state 屬性讀取。當使用者點選元件,導致狀態變化,this.setState 方法就修改狀態值,每次修改以後,自動呼叫 this.render 方法,再次渲染元件。

心得:由於 this.props 和 this.state 都用於描述元件的特性,可能會產生混淆。一個簡單的區分方法是,this.props 表示那些本元件無法改變的特性,而 this.state 是會隨著使用者互動而產生變化的特性。

元件的生命週期

在iOS中UIViewController提供了(void)viewWillAppear:(BOOL)animated, - (void)viewDidLoad,(void)viewWillDisappear:(BOOL)animated等生命週期方法,在Android中Activity則提供了onCreate(),onStart(),onResume(),onPause(),onStop(),onDestroy()等生命週期方法,這些生命週期方法描述了一個介面從建立到銷燬的一生。

那麼在React 中元件(Component)也是有自己的生命週期方法的。

react-lifecycle-methods-diagram

演示

[元件的生命週期分成三個時期:

  • Mounting:建立時
  • Updating:更新時
  • Unmounting:解除安裝時

不安全的方法

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

使用這些生命週期方法通常會導致錯誤和不一致,因此將來會被棄用。在新的React版本中他們被標記為UNSAFE。

static-lifecycle-methods

Mounting(裝載)

constructor()

constructor(props)
複製程式碼

React元件的建構函式將會在裝配之前被呼叫。當為一個React.Component子類定義建構函式時,你應該在任何其他的表示式之前呼叫super(props)。否則,this.props在建構函式中將是未定義,並可能引發異常。

建構函式是初始化狀態的合適位置。若你不初始化狀態且不繫結方法,那你也不需要為你的React元件定義一個建構函式。

static getDerivedStateFromProps()

static getDerivedStateFromProps(nextProps, prevState)
複製程式碼

元件例項化後和接受新屬性時將會呼叫getDerivedStateFromProps。它應該返回一個物件來更新狀態,或者返回null來表明新屬性不需要更新任何狀態。

注意,如果父元件導致了元件的重新渲染,即使屬性沒有更新,這一方法也會被呼叫。如果你只想處理變化,那麼可以通過比較新舊值來完成。

呼叫this.setState() 通常不會觸發 getDerivedStateFromProps()。

render

ReactComponent render()
複製程式碼

render() 方法是必須的。

當被呼叫時,其會檢查this.props 和 this.state並返回以下型別中的一個:

  • React元素。 通常是由 JSX 建立。該元素可能是一個原生DOM元件的表示,如
    ,或者是一個你定義的複合元件。
  • 字串和數字。 這些將被渲染為 DOM 中的 text node。
  • Portals。 由 ReactDOM.createPortal 建立。
  • null。 什麼都不渲染。
  • 布林值。 什麼都不渲染。(通常存在於 return test && 寫法,其中 test 是布林值。)

返回null 或 false時,ReactDOM.findDOMNode(this) 將返回 null。

render()函式應該是純粹的,也就是說該函式不修改元件的 state,每次呼叫都返回相同的結果,不讀寫 DOM 資訊,也不和瀏覽器互動(例如通過使用 setTimeout)。如果需要和瀏覽器互動,在 componentDidMount() 中或者其它生命週期方法中做這件事。保持 render() 純粹,可以使伺服器端渲染更加切實可行,也使元件更容易被理解。

提示:若 shouldComponentUpdate()返回false,render()函式將不會被呼叫。

componentDidMount()

componentDidMount()
複製程式碼

componentDidMount()在元件被裝配後立即呼叫,通常在該方法中進行一些初始化操作。·初始化時需要DOM節點的操作可以放到這裡進行`。若你需要從遠端載入資料,這是一個適合實現網路請求的地方。在該方法裡設定狀態將會觸發重渲。

這一方法是一個發起任何訂閱的好地方。如果你這麼做了,別忘了在componentWillUnmount()退訂。

另外,在這個方法中呼叫setState()將會觸發一次額外的渲染,但是它將在瀏覽器重新整理螢幕之前發生。這保證了即使render()將會呼叫兩次,但使用者不會看到中間狀態。

Updating (更新)

shouldComponentUpdate

shouldComponentUpdate(nextProps, nextState)
複製程式碼

在接收到新的 props 或者 state,將要渲染之前呼叫,以讓React知道當前狀態或屬性的改變是否不影響元件的輸出。

該方法在初始化渲染的時候不會呼叫,在使用 forceUpdate 方法的時候也不會。如果確定新的 props 和 state 不需要重新渲染,則此處應該 返回 false。

心得:重寫次方你可以根據實際情況,來靈活的控制元件當 props 和 state 發生變化時是否要重新渲染元件。

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(prevProps, prevState)
複製程式碼

getSnapshotBeforeUpdate()在最新的渲染輸出提交給DOM前將會立即呼叫。它讓你的元件能在當前的值可能要改變前獲得它們。這一生命週期返回的任何值將會 作為引數被傳遞給componentDidUpdate()。

componentDidUpdate

componentDidUpdate(prevProps, prevState, snapshot)
複製程式碼

在元件的更新已經同步到 DOM 中之後立刻被呼叫。

該方法不會在初始化渲染的時候呼叫。使用該方法可以在元件更新之後操作 DOM 元素。

Unmounting(移除)

componentWillUnmount

componentWillUnmount()
複製程式碼

在元件從 DOM 中移除的時候立刻被呼叫。

在該方法中執行任何必要的清理,比如無效的定時器,或者清除在 componentDidMount 中建立的 DOM 元素。

參考

相關文章