React 導讀(二)
來自專欄小擼伴讀系列
前言
在上篇文章React 導讀(一)https://zhuanlan.zhihu.com/p/33514953中學習到了寫第一個 Web 組件,這篇文章將繼續之前的目錄,開始新的知識點補充:
1. [x] React 如何編寫 Hello World!
2. [x] React 中三個最基礎、最重要的東西
3. [x] React 中的 JSX
4. [x] 你的第一個 Web 組件
5. [ ] React 中最開始需要關注的生命周期
6. [ ] React 一個組件集合的簡單交互
7. [ ] React 開始一個項目的一點建議
8. [ ] React 簡單的項目結構組織
五、React 中最開始需要關注的生命周期
其實在學習 React 之前,就應該了解目前前端推薦的是組件化開發的方式,React 是讓組件化更加簡單的庫。那麼組件開發必不可少的就是生命周期,說直白一點就是運行組件的過程是 React 來做,運行過程中需要有一些代碼鉤子來讓我們去調用,在組件執行的某一個地方去執行我們自己寫的代碼。這裡先介紹擁有的生命周期鉤子,下面的方法 `constructor` 和 `render` 不屬於生命周期,我按功能分類了一下,也就是學習的時候不一定要按部就班,應該以學習之後能真正寫一些東西為目標:
(1) 與組件掛載相關的方法,包括構造函數
- constructor
- componentWillMount
- componentDidMount
- componentWillUnmount
最常用的生命周期應該是最後 2 個,`constructor` 和 `componentWillMount` 目前先理解成能滿足的功能大體相同,如果這裡解釋太複雜不太好。
對於最開始關注的是:`this.state` 的初始化以及 `ajax ` 在哪裡請求。
`this.state` 在 constructor 進行初始化,`ajax` 推薦在 `componentDidMount` 中進行請求。
- `componentDidMount` 就是在組件已經掛載到 DOM 中後的鉤子,可以理解為 jQuery 中提供的 `ready` 方法。
- `componentWillUnmount` 是在組件即將被卸載前一刻的鉤子,一般用於取消 `componentDidMount` 中訂閱的事件等作用,清理一些不要的變數等,避免內存泄漏。
下面通過一個簡單的例子說明一下:
先有一個 `foods.json` 文件來模擬請求的後台數據。
[ { "id": 1, "name": "香蕉" }, { "id": 2, "name": "蘋果" }, { "id": 3, "name": "獼猴桃" }]
// 1. 掛載相關的生命周期class CycleMount extends React.Component { constructor() { super(); this.state = { foods: [] }; console.log(1. constructor 執行了...); } componentDidMount() { // 這裡使用原生的 fetch API 進行 ajax 請求,你也可以使用 $.ajax 進行請求,原理是一樣的,重點是關注 setState 的地方 fetch(/mock/foods.json, { method: GET, headers: new Headers({ Accept: application/json }) } ).then(dataResult => { if(dataResult.status === 200) { return dataResult.json(); } else { return []; } }).then(data => { // 這裡的 data 就是 foods.json 裡面的數據 // 調用 setState 來更新 render 裡面調用的 this.state 的值 this.setState({ foods: data }); }); console.log(2. componentDidMount 執行了...); } render() { // foods 是一個數組,map 方法是數組自帶的方法,可以查詢相關 api const foodItems = this.state.foods.map(food => { return (<li key={food.id}>{food.name}</li>); }); // 這裡是返回的最終組件結構 return ( <ul> {foodItems} </ul> ); }}
上面有了完整的注釋,也能看到基本上項目中可能會將代碼寫到何處,我也打了兩個日誌,來識別到底是誰先執行,結果可以自己運行一下,執行順序就是我標記的1,2。
好了,基本的學習了,可以自己動手試試訂閱一個事件,然後在卸載的時候取消這個事件。
(2) 優化相關
- shouldComponentUpdate
這個方法比較重要,但是我這裡不會介紹得太過於複雜,太複雜只會讓重要的部分不那麼突出。這個方法的返回值是一個 `boolean` 類型,分別代碼的意義:
`true` 組件應該更新,執行 `render` 方法以及相關生命周期;
`false` 組件狀態沒有更新,不執行 `render` 等方法,意味著網頁界面不會改變。
那麼它直觀上的作用是能夠通過返回值來決定界面是否改變,實際的意義就是當我們知道當前 `oldState = this.state` 的值和新的 `newState = this.state` 值完全相等的時候(或者是新傳入的 props)就不用再浪費性能去重新渲染組件了。
API 上的定義是這樣的:
/* nextProps: 新的 props, nextState: 新的 state */shouldComponentUpdate(nextProps, nextState): boolean
舉個例子來直觀說明一下:
class ComponentOptimize extends React.Component { state = { count: 0 }; shouldComponentUpdate(nextProps, nextState) { // 當 count > 10 的時候就不能再重新渲染組件了 if(nextState.count > 10) { return false; } return true; } handleUpdateCount() { console.log(我點了一下哦!); this.setState(prevState => { return { count: prevState.count + 1 }; }); } render() { return ( <div onClick={this.handleUpdateCount.bind(this)} stylex={{cursor: pointer}}> <h1>{this.state.count}</h1> </div> ); }}
> 你會發現 10 過後界面就沒有再更新過了,這樣應該很直觀了。
(3) Props 相關的生命周期
- componentWillReceiveProps
這個主要是在組件的 props 傳入新的值後被調用,不管是不是傳的一樣的值或者 `shouldComponentUpdate` 返回了 `false`,看下例子吧:
class Cat extends React.Component { componentWillReceiveProps(nextProps) { console.log(改一次我執行一次!); } shouldComponentUpdate(nextProps, nextState) { // 改的名字一樣的時候 return this.props.name !== nextProps.name; } render() { console.log(貓改了一次名字!); return ( <h1>我有新名字了!{this.props.name}</h1> ); }}class App extends React.Component { state = { catName: 嚕嚕 // 我家貓咪~ }; handleChangeCatName() { const catNames = [嚕嚕, 小白, 小黃, 小黑, 皮卡丘]; const catIndex = this.getSomeOneIndex(); this.setState({ catName: catNames[catIndex] }); } getSomeOneIndex() { return Math.floor(Math.random() * 5 + 0); } render() { return ( <div> {/* 給 Cat 傳新的名字 */} <Cat name={this.state.catName} /> <button onClick={this.handleChangeCatName.bind(this)}>點我給貓咪取新名字!</button> </div> ); }}
最後肯定每次點擊按鈕都會輸出這句的結果 `console.log(改一次我執行一次!);`。
(4) 更新組件相關
- componentWillUpdate
- componentDidUpdate
因為都是講解 API,所以國際慣例的先看下 API 的定義吧:
// 組件更新前執行componentWillUpdate(nextProps, nextState)// 組件更新後執行componentDidUpdate(prevProps, prevState)
可以從定義中看出,它們都接受了兩個參數:props && state,不過看變數前綴能夠聯想點什麼。
暫時想不到什麼實際項目的例子,隨便假設點內容吧。不過這裡需要注意的地方是:
- 這兩個方法裡面都不要調用 setState!
- 第一次初始化組件 render 的時候不會執行
- shouldComponentUpdate 返回 false 不會執行
第一條的原因:容易形成一個遞歸的調用,不作就不會死...所以盡量不要在這裡調~目前還沒有碰到需要在這裡調的需求。
第二條的原因:額,說好的更新才調,初始化不調用是符合邏輯的。
第三條的原因:額,這 2 個鉤子是與組件更新相關的,所以也符合邏輯的,組件是否更新就是靠 `shouldComponentUpdate` 返回值。
在上面 `Cat` 的例子中加入下面的代碼可以看下結果:
componentWillUpdate() { console.log(componentWillUpdate 執行了!)}componentDidUpdate() { console.log(componentDidUpdate 執行了!)}
(5)組件錯誤
- componentDidCatch
就是在組件發生異常的時候可能會被調用的鉤子,需要注意的有下面的地方:
- 只能在父級組件捕獲子組件的異常;
- 如果異常被 `try...catch` 包裹父級組件的鉤子就不會執行了。
看個例子吧:
class Cat extends React.Component { componentWillReceiveProps(nextProps) { // 這裡手動拋一個異常,觸發我們的鉤子 componentDidCatch throw new Error(miao miao~); } render() { let miao = this.props.name; return ( <div> {miao} </div> ); }}class App extends React.Component { state = { catName: 嚕嚕, isError: false, }; handleChangeCatName() { const catNames = [嚕嚕, 小白, 小黃, 小黑, 皮卡丘]; const catIndex = this.getSomeOneIndex(); this.setState({ catName: catNames[catIndex] }); } getSomeOneIndex() { return Math.floor(Math.random() * 5 + 0); } componentDidCatch(error, info) { console.log(error, info); if(error) { // 如果有錯誤信息,就重新渲染一下組件,可能是更好的交互 this.setState({ isError: true }); } } render() { return ( <div> <Cat name={this.state.catName} /> {!this.state.isError ? <button onClick={this.handleChangeCatName.bind(this)}>點我給貓咪取新名字!</button> : <p>不要奴才給我取名字了!</p> } </div> ); }}
(6) 渲染相關
- render
額...這個不想寫了,先睡覺吧~應該寫了這麼多個小例子也差不多了~可以動手試試哦!還不清楚的可以 Google 一下,你就知道。
你會發現我們大多數代碼都是圍繞 state 和 props 展開的,所以只要掌握好了之前說的三個重要概念,基本可以說得上你會了 JS 你就會了 React ? 到目前為止,需要記住的內容應該已經差不多了。
生命周期很重要,其實學到這裡也差不多可以上手寫點項目熟練一下了,其他的更多是思維和編程方面的東西,周期的篇幅單獨來一篇吧~其他主題之後再繼續吧!
推薦閱讀:
※[從零開始學化妝]從零開始學化妝超詳細化妝入門教程
※國畫入門教程:《茄子的畫法》
※中文代碼示例[譯]Scala中創建隱式函數
※新編兒童國畫入門教程-瓢蟲畫法
※【伊人教程】瑜伽入門教程集合