今天,本章節會介紹一些 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
事件等。