React(16+版本以上的) 基礎知識介紹

今天,本章節會介紹一些 React 的基礎知識和基本用法。已經入門 React 基礎的同學,可以簡單看看這篇文檔內容。React 零基礎的同學還建議去慕課網學習React入門基礎

另外,下面講到的代碼將全部使用 es6 語法,教程中我會介紹一些用到的 es6 語法,但是不會從頭講解了,推薦閱讀es6入門

hello world

以下是一個最簡單的demo,將一個最簡單的組件渲染到頁面上。

import React from 'react'
import { render } from 'react-dom'

// 定義組件
class Hello extends React.Component {
    render() {
        // return 裏面寫jsx語法
        return (
            <p>hello world</p>
        )
    }
}

// 渲染組件到頁面
render(
    <Hello/>,
    document.getElementById('root')
)

深入一下,這裏import React from 'react'引用的是什麼?

這裏的'react'對應的就是./package.json文件中dependencies中的'react',即在該目錄下用npm install安裝的 react 。npm 安裝的 react 的物理文件是存放在 ./node_modules/react中的,因此引用的東西肯定就在這個文件夾裏面。

打開./node_modules/react/package.json找到"main": "react.js",,這裏的main即指定了入口文件,即./node_modules/react/react.js這個文件。那麼,問題的答案自然就出來了。

jsx 語法

React 裏面寫模板要使用 jsx 語法,它其實和 html 很相似但是又有那麼幾點不一樣。下面簡單介紹一下 jsx 語法的一些特點:

使用一個父節點包裹

jsx 中不能一次性返回零散的多個節點,如果有多個請包涵在一個節點中。例如,

// 三個 <p> 外面必須再包裹一層 <div>
return (
  <div>
    <p>段落1</p>
    <p>段落2</p>
    <p>段落3</p>
  </div>
)

再例如:

// { } 中返回的兩個 <p> 也要用 <div> 包裹
return (
  <div>
    <p>段落1</p>
    {
      true 
      ? <p>true</p>
      : <div>
        <p>false 1</p>
        <p>false 2</p>
      </div>
    }
  </div>
)

註釋

jsx 中用{/* */}的註釋形式

        return (
            // jsx 外面的註釋
            <div>
                {/* jsx 裏面的註釋 */}
                <p>hello world</p>
            </div>
        )

樣式

對應 html 的兩種形式,jsx 的樣式可以這樣寫:
css樣式:<p className="class1">hello world</p>,注意這裏是className,而 html 中是class
內聯樣式:<p style={{display: 'block', fontSize: '20px'}}>hello world</p>,注意這裏的{{...}},還有fontSize的駝峯式寫法

事件

拿 click 事件爲例,要在標籤上綁定 click 事件,可以這樣寫

class Hello extends React.Component {
    render() {
        return (
            <p onClick={this.clickHandler.bind(this)}>hello world</p>
        )
    }

    clickHandler(e) {
        // e 即js中的事件對象,例如 e.preventDefault()
        // 函數執行時 this 即組件本身,因爲上面的 .bind(this)
        console.log(Date.now())
    }
}

注意,onClick是駝峯式寫法,以及.bind(this)的作用

循環

在 jsx 中使用循環,一般會用到Array.prototype.map(來自ES5標準)

class Hello extends React.Component {
    render() {
        const arr = ['a', 'b', 'c']
        return (
            <div>
                {arr.map((item, index) => {
                    return <p key={index}>this is {item}</p>
                })}
            </div>
        )
    }
}

注意,arr.map是包裹在{}中的,key={index}有助於React的渲染優化,jsx中的{}可放一個可執行的 js 程序或者變量

判斷

jsx中使用判斷一般會用到三元表達式(表達式也是放在{}中的),例如:

return (
  <div>
    <p>段落1</p>
    {
      true 
      ? <p>true</p>
      : <p>false</p>
      </div>
    }
  </div>
)

也可以這樣使用:

<p style={{display: true ? 'block' ? 'none'}}>hello world</p>

代碼分離

之前的demo代碼都是在一個文件中,實際開發中不可能是這樣子的,因此這裏就先把組件的代碼給拆分開。我們將使用 es6 的模塊管理規範。

page 層

創建./app/containers/Hello/index.jsx文件,將之前創建組件代碼複製進去

import React from 'react'

class Hello extends React.Component {
    render() {
        return (
             <p>hello world</p>
        )
    }
}

export default Hello

然後./app/index.jsx中代碼就可以這樣寫。

import Hello from './containers/Hello';

render(
    <Hello/>,
    document.getElementById('root')
)

注意,代碼import Hello from './containers/Hello';這裏可以寫成./containers/Hello/index.jsx也可以寫成./containers/Hello/index

subpage 層

如果Hello組件再稍微複雜一點,那麼把代碼都放一塊也會變得複雜,接下來我們再拆分。

創建./app/containers/Hello/subpage目錄,然後在其下創建三個文件Carousel.jsx Recommend.jsx List.jsx,分別寫入相應的代碼(看代碼文件即可),然後./app/containers/Hello/index.js中即可這樣寫

import Carousel from './subpage/Carousel'
import Recommend from './subpage/Recommend'
import List from './subpage/List'

class Hello extends React.Component {
    render() {
        return (
            <div>
                <p>hello world</p>
                <hr/>
                <Carousel/>
                <Recommend/>
                <List/>
            </div>
        )
    }
}

注意,這裏import.jsx後綴省略了。

component 層

以上介紹的是頁面和複雜頁面的拆分,但那都是頁面層級的,即page層。這裏複雜頁面拆分爲subpage其實沒啥特別的,就是把複雜頁面的代碼拆分一下,會更加符合開放封閉原則。而且,只有複雜頁面纔有必要去拆分,簡單頁面根本沒必要拆分。因此,無論是page還是subpage它都是頁面級別的。

頁面的特點是其獨特性,一個頁面就需要創建一個文件(如果兩個頁面可以共用一個文件,這是設計不合理,得治)。而頁面其中的內容,就不一定是這樣子了。例如,現在的APP每個頁面最上面都會有個 header ,即可以顯示標題,可以返回。每個頁面都有,樣子差不多,難道我們要爲每個頁面都做一個?——當然不是。

創建./app/components/Header/index.jsx文件,簡單寫入一個組件的代碼(見源碼文件),然後在./app/containers/index.jsx中引用

import Header from '../../components/Header'

class Hello extends React.Component {
    render() {
        return (
            <div>
                <Header/>
                {/* 省略其他內容 */}
            </div>
        )
    }
}

Hello 頁面會用到 Header,以後的其他頁面也會用到 Header ,我們把多個頁面都可能用到的功能,封裝到一個組件中,代碼放在./app/components下。

數據傳遞 & 數據變化

props

接着剛纔 Header 的話題往下說,每個頁面都會使用 Header ,但是 Header 上顯示的標題每個頁面肯定是不一樣的。我們需要這樣解決:頁面中引用Header時,這樣寫 <Header title="Hello頁面"/>,即給 Header 組件設置一個 title 屬性。而在 Header 組件中可以這樣取到

    render() {
        return (
             <p>{this.props.title}</p>
        )
    }

在 React 中,父組件給子組件傳遞數據時,就是以上方式,通過給子組件設置 props 的方式,子組件取得 props 中的值即可完成數據傳遞。被傳遞數據的格式可以是任何 js 可識別的數據結構,上面demo是一個字符串。React 中,props 一般只作爲父組件給子組件傳遞數據用,不要試圖去修改自己的 props ,除非你想自找麻煩

props && state

上面提到了 props 不能被自身修改,如果組件內部自身的屬性發生變化,該怎麼辦?—— React 爲我們提供給了 state,先看一個demo:

class Hello extends React.Component {
    constructor(props, context) {
        super(props, context);
        this.state = {
            // 顯示當前時間
            now: Date.now()
        }
    }
    render() {
        return (
            <div>
                <p>hello world {this.state.now}</p>
            </div>
        )
    }
}

還有一點非常重要,React 會實時監聽每個組件的 props 和 state 的值,一旦有變化,會立刻更新組件,將結果重新渲染到頁面上,下面demo演示了state的變化,props也是一樣的

class Hello extends React.Component {
    constructor(props, context) {
        super(props, context);
        this.state = {
            // 顯示當前時間
            now: Date.now()
        }
    }
    render() {
        return (
            <div>
                <p onClick={this.clickHandler.bind(this)}>hello world {this.state.now}</p>
            </div>
        )
    }
    clickHandler() {
        // 設置 state 的值的時候,一定要用 this.setState ,不能直接賦值修改
        this.setState({
            now: Date.now()
        })
    }
}

智能組件 & 木偶組件

這是用 React 做系統設計時的兩個非常重要的概念。雖然在 React 中,所有的單位都叫做“組件”,但是通過以上例子,我們還是將它們分別放在了./app/containers./app/components兩個文件夾中。爲何要分開呢?

  • 智能組件 在日常開發中,我們也簡稱“頁面”。爲何說它“智能”,因爲它只會做一些很聰明的事兒,髒活累活都不幹。它只對數據負責,只需要獲取了數據、定義好數據操作的相關函數,然後將這些數據、函數直接傳遞給具體實現的組件即可。
  • 木偶組件 這裏“木偶”一詞用的特別形象,它總是被人拿線牽着。它從智能組件(或頁面)那裏接受到數據、函數,然後就開始做一些展示工作,它的工作就是把拿到的數據展示給用戶,函數操作開放給用戶。至於數據內容是什麼,函數操作是什麼,它不關心。

以上兩個如果不是理解的很深刻,待把課程學完再回頭看一下這兩句話,相信會理解的。

生命週期

React 詳細的生命週期可參見這裏(這裏面很詳細的-一定要仔細看),也可查閱本文檔一開始的視頻教程。這裏我們重點介紹這個項目開發中常用的幾個生命週期函數(hook),相信你在接下來的 React 開發中,也會常用這些。

以下聲明週期,也沒必要每個都寫demo來解釋,先簡單瞭解一下,後面會根據實際的例子來解釋,這樣會更加易懂。

  • getInitialState

初始化組件 state 數據,但是在 es6 的語法中,我們可以使用以下書寫方式代替

class Hello extends React.Component {
    constructor(props, context) {
        super(props, context);
        // 初始化組件 state 數據
        this.state = {
            now: Date.now()
        }
    }
}
  • render

最常用的hook,返回組件要渲染的模板。

  • comopentDidMount

組件第一次加載時渲染完成的事件,一般在此獲取網絡數據。實際開始項目開發時,會經常用到。

  • shouldComponentUpdate

主要用於性能優化,React 的性能優化也是一個很重要的話題,後面一併講解。

  • componentDidUpdate

組件更新了之後觸發的事件,一般用於清空並更新數據。實際開始項目開發時,會經常用到。

  • componentWillUnmount

組件在銷燬之前觸發的事件,一般用戶存儲一些特殊信息,以及清理setTimeout事件等。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章