[TOC]
# 第 1 章
## 1.JSX 的 onClick 事件處理方式和 HTML 的 onclick 有很大的不同:
在 HTML 中直接使用 onclick 存在的問題:
* onclick 添加的事件處理函數是在全局環境下執行的,這污染了全局環境,很容易產生意料不到的后果
* 給很多 DOM 元素添加 onclick 事件,可能會影響網頁的性能,畢竟,網頁的事件處理函數越多,性能就會越低
* 對于使用 onclick 的 DOM 元素,如果要動態地從 DOM 樹中刪掉的話,需要把對應的事件處理器注銷,如果忘了注銷,就可能造成內存泄漏,這樣的 bug 很難被發現
上述問題在 JSX 中都不存在
* onClick 掛載的每個函數,都可以控制在組件范圍內,不會污染全局空間
* 我們在 JSX 中看到一個組件使用了 onClick,但并沒有產生直接使用 onclick 的HTML,而是使用了事件委托(event delegation)的方式處理點擊事件,無論有多少個 onClick 出現,其實最后都只在 DOM 樹上添加了一個事件處理函數,掛在最頂層的 DOM 節點上。所有的點擊事件都被這個事件處理函數捕獲,然后根據具體組件分配給特定函數,使用事件委托的性能當然要比為每個 onClick 都掛載一個事件處理函數要高
* 因為 React 控制了組件的生命周期,在 unmount 的時候自然能夠清除相關的所有事件處理函數,內存泄漏也不再是一個問題
## 2.script 腳本中的 eject(彈射) 命令
執行`npm run eject`就是把潛藏在 react-scripts 中的一系列技術棧配置都 “彈射” 到應用的頂層,然后我們就可以研究這些配置細節,而且可以更靈活地定制應用的配置。
如 config 目錄下的 webpack.config.dev.js 文件,定制 npm start 所做的構造過程
## 3.*UI=render(data)*
React 的理念可歸結為這一公式。用戶看到的界面(UI),應該是一個函數(在這里叫 render)的執行結果,只接受數據(data)作為參數。這個函數是一個純函數,即沒有任何副作用,輸出完全依賴于輸入的函數,兩次函數調用如果輸入相同,得到的結果也絕對相同。如此一來,最終的用戶界面,在 render 函數確定的情況下完全取決于輸入數據
?對于開發者來說,重要的是區分開哪些屬于 data,哪些屬于 render,想要更新用戶界面,要做的就是更新 data,用戶界面自然會作出響應。所以 React 實踐的也是“響應式編程”(Reactive Programming)的思想,React 的名字由此而來。
## 4.Virutal DOM 的簡單描述
DOM 樹是對 HTML 的抽象,Virtual DOM 是對 DOM 樹的抽象。Virtual DOM 不會觸及瀏覽器的部分,只是存在于 JavaScript 空間的樹形結構,每次自上而下渲染 React 組件時,會對比這一次產生的 Virtual DOM 和上一次渲染的 Virtual DOM,對比就會發現差別,然后修改真正的 DOM 樹時就只需要觸及差別中的部分就行
# 第 2 章 設計高質量的 React 組件
## 1.組件的劃分通則
組件的劃分要滿足高內聚(High Cohesion)和低耦合(Low Coupling)的原則
**高內聚** 指的是把邏輯緊密相關的內容放在一個組件中:傳統上,內容由 HTML 表示,交互行為放在 JavaScript 代碼文件中, 樣式放在 CSS 文件中定義,這雖然滿足一個工程模塊的需要,卻要放在三個不同的文件中。React 中,展示內容的 JSX、定義行為的 JavaScript、甚至定義樣式的 CSS,都可以放在一個 JavaScript 文件中,所以 React 天生具有高內聚的特點
**低耦合** 指不同組件之間的依賴關系要盡量弱化,也就是每個組件要盡量獨立。保持整個系統的低耦合度,需要對系統中的功能由充分的認識,然后根據功能點劃分模塊,讓不同的組件去實現不同的功能
## 2.React 中的數據
React 組件的數據分為兩種,porp 和 state,prop 或者 state 改變都可能引發組件的重新渲染,那么設計一個組件的時候,什么時候用 porp,什么時候用 state 呢?
prop 是組件的對外接口,state 是組件的內部狀態,對外用 prop,內部用 state
prop 的類型不限于純數據,也可以是函數,函數類型的 prop 等于讓父組件給了子組件一個回調函數
可以通過回答以下問題來看看你是否對 React 較為熟悉
* 父組件用 prop 傳遞信息給子組件的形式?
* 子組件如何讀取 prop 值?
* 通過類的 propTypes 屬性定義 prop 規格的格式?
關于 propTypes 的使用需要注意一些問題:定義類的 propTypes 屬性,無疑是要占用一些代碼空間,而且類型檢查也是要消耗 CPU 計算資源的。其次,在線上環境做 propTypes 類型檢查沒有什么幫助,即在開發過程中為了避免犯錯我們才使用 propTypes,發布產品代碼時,可以用一種自動的方式將 propTypes 去掉。`babel-react-optimize`具有這個功能,可以通過過 npm 安裝,但是確保只在發布產品代碼的時候使用它。
<br />
state 需要在構造函數中初始化(通過對 state 的賦值),組件的 state 必須是一個 JavaScript 對象。通過`this.state`讀取當前組件的 state,通過`this.setState()`更新 state
**prop 和 state 的對比**
* prop 用于定義外部接口,state 用于記錄內部的狀態
* prop 的賦值在外部世界使用組件時,state 的賦值在組件內部
* 組件不應該改變 prop 的值,而 state 的存在目的就是讓組件來改變的。雖然 React 并沒有辦法阻止你去修改傳入的 props 對象,但是你仍然不該跨越這條紅線,否則最后可能出現不可預料的 bug
# 第 3 章:從 Flux 到 Redux
## Flux 到 Redux

對于 MVC 框架,為了讓數據流可控,Controller 應該是中心,當 View 要傳遞消息給 Model 時,應該調用 Controller 的方法,同樣,當 Model 要更新 View 時,也應該通過 Controller 引發新的渲染

一個 Flux 應用包含四個部分:
* Dispatcher:處理動作分發,維持 Store 之間的依賴關系
* Store:負責存儲數據和處理數據相關邏輯
* Action:驅動 Dispatcher 的 JavaScript 對象
* View:視圖部分,負責顯示用戶界面
MVC 與 Flux 的對比:在 MVC 框架中,系統能夠提供什么樣的服務,通過 Controller 暴露函數來實現。每增加一個功能,Controller 往往就需要增加一個函數;在 Flux 的世界里,新增加功能并不需要 Dispatcher 增加新的函數,要做的就是增加一種新的 Action 類型, Dispatcher 的對外接口并不用改變。
當需要擴充應用所能處理的”請求“時,MVC 方法就需要增加新的 Controller,而對于 Flux 則只是增加新的 Action
Flux 的優點就在于其 “單向數據流” 的管理方式,這種 “限制” 禁絕了數據流混亂的可能
其不足之處在于:
* Store 之間的依賴關系
* 難以進行服務器端渲染
* Store 混雜了邏輯和狀態
> 感興趣的可以搜下使用 Flux 的狀態管理方案
## Redux

Flux 的基本原則是“單向數據流”,Redux 在此基礎上強調三個基本原則:
* 唯一數據源(Single Source of Truth)
* 保持狀態只讀(State is read-only)
* 數據改變只能通過純函數完成(Changes are made with pure functions)
1.唯一數據源:應用的狀態數據應該只存儲在唯一的一個 Store 上,避免了狀態數據分散在多個 Store 中造成的數據冗余
2.保持狀態只讀:要修改 Store 的狀態,必須要通過派發一個 action 對象來完成
3.數據改變只能通過純函數完成:這里所說的純函數就是 Reducer,在 Redux 中,每個 reducer 的函數簽名如下:`reducer(state, action)`
第一個參數 state 是當前的狀態,第二個參數 action 是接收到的 action 對象,而 reducer 函數要做的事情,就是根據 state 和 action 的值產生一個新的對象返回。注意 reducer 必須是純函數,即函數的返回結果完全由參數 state 和 action 決定,而且不產生任何副作用,也不能修改參數 state 和 action 對象
## 容器組件與傻瓜組件
在 Redex 框架下,一個 React 組件基本上就是要完成以下兩個功能:
* 和 Redux Store 打交道,讀取 Store 的狀態,用于初始化組件的狀態,同時還要監聽 Store 的狀態改變;當 Store 狀態發生變化時,需要更新組件狀態,從而驅動組件重新渲染;當需要更新 Store 狀態時,就要派發 action 對象
* 根據當前 prop 和 state,渲染出用戶界面
>[warning] 疑問:現在有一種說法是所有組件的狀態全部丟到 Redux 的 Store 中進行管理,那么組件自身的“狀態”哪去了?Store 中狀態的更新是否必定觸發組件的重新渲染?個人的理解是:以 react-redux 的實現為例,我們在組件中是通過 this.props 來訪問 Store 中的數據和調用一些 dispatch 的,所以如果 Store 中的狀態改變了,以這些狀態為 props 的組件就會重新渲染(state 和 prop 的改變都可能觸發重新渲染)
如果 React 組件都是要包辦上面所說的兩個任務,似乎做的事情稍微多了點。所以我們可以考慮拆分為兩個組件,分別承擔一個任務,然后把兩個組件嵌套起來,完成原本一個組件完成的所有任務。
這樣的關系里,兩個組件是父子組件的關系。負責與 Redux Store 打交道的組件,處于外層,稱為容器組件(Container Component);只負責渲染界面的組件,處于內層,叫做展示組件(Presentational Component);外層的容器組件也叫作聰明組件(Smart Componnet),內層的展示組件又叫做傻瓜組件(Dumb Component)

狀態全部交由容器組件打理,展示組件只需要根據 props 來渲染結果,不需要 state;這只是設計 React組件的一種模式,和 Redux 沒有直接關系,另外,沒有 state 只有一個 render 方法,所有數據都來自于 props 的組件稱為 **無狀態組件**。
無狀態組件可以寫成一個函數,不再需要用對象表示
```js
// 解構賦值
function Counter ({caption, onIncrement, onDecrement, value}) {
return (
<div>
<button style={buttonStyle} onClick={onIncrement}>+</button>
<button style={buttonStyle} onClick={onDecrement}>-</button>
<span>{caption} count: {value}</span>
</div>
);
}
// 不使用解構賦值
function Counter (props) {
const {caption, onIcrement, onDecrement, value} = props
// ...
}
```
## 組件 Context
不使用 react-redux 之前,每個組件都需要直接導入 Redux Store
`import store from '../Store.js'`
雖然 Redux 應用全局就一個 Store,但是這樣的直接導入依然有問題(很麻煩)
為了解決這個問題,那就只能讓上層組件把 Store 傳遞下來,首先想到的是用 props,但是層層傳遞的方法顯然是不可行的。設想在一個嵌套多層的組件結構中,只有最里層的組件才需要使用 store,但是為了把 store 從最外層傳遞到最里層,就要求中間所有的組件都需要增加對這個 store prop 的支持,即使根本不使用它。
React 提供了一個叫 Context 的功能,能完美地解決這個問題。
所謂 Context,就是“上下文環境”,讓一個樹狀組件上所有組件都能訪問一個共同的對象。

> 可以查閱 React 文檔了解如何使用 React 提供的 Context API:[https://react.docschina.org/docs/context.html](https://react.docschina.org/docs/context.html)
其實 react-redux 這個庫就是基于 Context 實現的。
這里分析下它使用的這條語句:
```js
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
```
connect 是 react-redux 提供的一個方法,這個方法接收兩個參數,執行結果依然是一個函數,所以才可以在后面又加一個圓括號(柯里化?),把 connect 函數執行的結果立即執行。
這里有兩次函數的執行,第一次是 connect 函數的執行,第二次是把 connect 函數返回的函數再次執行,最后產生的就是容器組件。
connect 函數具體做了哪些事呢?
* 把 Store 上的狀態轉化為內層傻瓜組件的 prop
* 把內層傻瓜組件中的用戶動作轉化為派送給 Store 的動作
這兩個工作一個是內層傻瓜對象的輸入,一個是內層傻瓜對象的輸出
mapStateToProps(命名是業界習慣)就是把 Store 上的狀態轉化為內層組件的 props,建立映射關系
mapDispatchToProps 把內層傻瓜組件暴露出來的函數類型的 prop 關聯上 dispatch 函數的調用
```
// 第二個參數是直接傳遞給外層容器組件的 props
function mapDispatchToProps(dispatch, ownProps) {
return {
onIncrement: () => {
dispatch(Actions.increment(ownProps.caption));
},
onDecrement: () => {
dispatch(Actions.decrement(ownProps.caption));
}
}
}
```
# 第四章 模塊化 React 和 Redux 應用
## 代碼文件的組織方式
1.按角色組織
```js
reducers/
todoReducer.js
filterReducer.js
actions/
todoActions.js
filterAction.js
components/
todoList.js
todoItem.js
filter.js
containers/
todoListContainer.js
todoItemContainer.js
filterContainer.js
```
- reducer 目錄包含所有 Redux 的 reducer
- actions 目錄包含所有 action 構造函數
- components 目錄包含所有的傻瓜組件
- containers 目錄包含所有的容器組件
2.按功能組織
```js
todoList/
action.js
actionType.js
index.js
reducer.js
views/
component.js
container.js
filter/
action.js
actionType.js
index.js
reducer.js
views/
component.js
container.js
```
- actionType.js 定義 action 類型
- action.js 定義 action 構造函數,決定了這個功能模塊可以接受的動作
- reducer.js 定義這個功能模塊如何響應 action.js 中定義的動作
- views 目錄包含這個功能模塊中所有的 React 組件,包括傻瓜組件和容器組件
- index.js 把所有的角色導入,然后統一導出
>[warning]個人感覺不太能夠接受這兩種組織文件的方式......
# 第 5 章 React 組件的性能優化
## 引入性能檢測工具 React Pref
`npm install react-addons-perf -D`:開發環境下性能檢測(哪些組件造成了無意義的渲染)
`npm install redux-immutable-state-invariant -D`:開發環境下檢測 reducer 是否為純函數,不是則報錯
Store.js 文件
```js
import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
import {reducer as todoReducer} from './todos';
import {reducer as filterReducer} from './filter';
// 下面三行代碼!
import Perf from 'react-addons-perf'
const win = window;
win.Perf = Perf
const reducer = combineReducers({
todos: todoReducer,
filter: filterReducer
});
const middlewares = []; // 考慮到將來的擴展,使用數組變量 middlewares 來存儲所有的中間件,之后的中間件直接 push
if (process.env.NODE_ENV !== 'production') {
// 使用 require 是因為 import 語句不能存在于條件語句中
middlewares.push(require('redux-immutable-state-invariant')()); // react-immutable-state-invariant 中間件只在開發環境下有意義,用于檢查 reducer 是否為純函數
}
// Redux 提供 compose 函數把多個 Store Enhancer 組合在一起
const storeEnhancers = compose(
applyMiddleware(...middlewares),
(win && win.devToolsExtension) ? win.devToolsExtension() : (f) => f, // Redux Devtools 開發者工具
);
export default createStore(reducer, {}, storeEnhancers); // Store Enhancers 能夠讓 createStore 函數產生的 Store 對象具有更多的功能
```
## 單個組件的性能優化
更改 shouldComponentUpdate 函數的默認實現,根據每個 React 組件的內在邏輯定制其行為
```
shouldComponentUpdate(nextProps, nextState) {
// 假設影響渲染內容的 prop 只有 completed 和 text,只需要確保
// 這兩個 prop 沒有變化,函數就可以返回 false
return (nextProps.completed !== this.props.completed) ||
(nextProps.text !== this.props.text)
}
```
使用 immutable.js 解決復雜數據 diff、clone 等問題。
immutable.js 實現原理:持久化數據結構,也就是使用舊數據創建新數據時,要保證舊數據同時可用且不變。同時為了避免 deepCopy 把所有節點都復制一遍帶來的性能損耗,Immutable 使用了結構共享,即如果對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享。
## 多個組件的性能優化(這部分主要介紹了虛擬 DOM 的原理)
用戶操作引發界面的更新并不會讓 React 生成的虛擬 DOM 推倒重來,React 在更新階段巧妙地對比原有的 Virtual DOM 和新生成的 Virtual DOM,找出兩者的不同之處,根據不同來修改 DOM 樹,這樣就只需要做最小的必要改動。這個“找不同”的過程,就叫做 Reconciliation(調和)。
React 對比兩個 Virtual DOM 的樹形結構時,從根節點開始遞歸往下比對,在樹形結構上,每個節點都可以看作這個節點以下部分子樹的根節點。所以這個比對算法可以從 Virtual DOM上任何一個節點開始執行。
React 首先檢查兩個樹形結構的根節點的類型是否相同,根據相同或者不同有不同處理方式
**1.節點類型不同的情況**
直接扔掉原來的,構建新的 DOM 樹,原有的樹形結構上的 React 組件會經歷“卸載”的生命周期,而取而代之的組件會經歷“裝載”的生命周期
```html
<div>
<Todos />
</div>
// 我們想要更新成這樣
<span>
<Todos />
</span>
```
比如上面的代碼在比較時,根節點類型不一樣,一切推倒重來,重新構建一個 span 節點及其子節點
作為開發者,需要避免這種浪費的情景出現(div 和 span 的子節點是一樣的)
**2.節點類型相同的情況**
如果兩個樹形結構的根節點類型相同,React 就認為原來的根節點只需要更新過程,不會將其卸載,也不會引發根節點的重新裝載
這里有必要區分一下節點的類型:一類是 DOM 元素類型,一類是 React 組件;對于 DOM 元素類型, React 會保留節點對應的 DOM 元素,只對樹形結構根節點上的屬性和內容做一下比對,然后只更新修改的部分
```html
<div style={{color: 'red', fontSize: 15}} className="welcome">
Hello World
</div>
// 改變之后的 JSX,React 可以對比發現內容和屬性的變化,只修改這些變化的部分
<div style={{color: 'green', fontSize: 15}} className="farewell">
Hello World
</div>
```
如果屬性結構的根節點是 React 組件類型,React 能做的就是根據新節點的 props 取更新原來根節點的組件實例,即按順序觸發下列函數(舊生命周期)
- shouldComponentUpdate
- componentWillReceiveProps
- componentWillUpdate
- render
- componentDidUpdate
處理完根節點的對比之后,會對根節點的每個子節點重復一樣的動作。
**3.多個子組件的情況**
當一個組件包含多個子組件的情況
```html
<ul>
<TodoItem text="First" completed={false} />
<TodoItem text="Second" completed={false} />
</ul>
// 更新為
<ul>
<TodoItem text="Zero" completed={false} />
<TodoItem text="First" completed={false} />
<TodoItem text="Second" completed={false} />
</ul>
```
直觀上看,只需要創建一個新組件,更新之前的兩個組件;但是實際情況并不是這樣的,React 并沒有找出兩個序列的精確差別,而是直接挨個比較每個子組件。
在上面的新的 TodoItem 實例插入在第一位的例子中,React 會首先認為把 text 為 First 的 TodoItem 組件實例的 text 改成了 Zero,text 為 Second 的 TodoItem 組件實例的 text 改成了 First,在最后面多出了一個 TodoItem 組件實例。這樣的操作的后果就是,現存的兩個實例的 text 屬性被改變了,強迫它們完成了一個更新過程,創造出來的新的 TodoItem 實例用來顯示 Second。
我們可以看到,理想情況下只需要增加一個 TodoItem 組件,但實際上其還強制引發了其他組件實例的更新。
假設有 100 個組件實例,那么就會引發 100 次更新,這明顯是一個浪費;所以就需要開發人員在寫代碼的時候提供一點小小的幫助,這就是接下來要講的 key 的作用
> 如果 React 采用的是先找出兩個序列的差異的算法,時間是 O(N^2),這不適合一個對性能要求很高的場景
## key 的作用
```html
<ul>
<TodoItem key={1} text="First" completed={false} />
<TodoItem key={2} text="Second" completed={false} />
</ul>
// 新增一個 TodoItem 實例
<ul>
<TodoItem key={0} text="Zero" completed={false} />
<TodoItem key={1} text="First" completed={false} />
<TodoItem key={2} text="Second" completed={false} />
</ul>
```
React 根據 key 值,就可以知道現在的第二個和第三個 TodoItem 實例其實就是之前的第一個和第二個實例,所以 React 就會把新創建的 TodoItem 實例插在第一位,對于原有的兩個 TodoItem 實例只用原有的 props 來啟動更新過程,這樣 shouldComponentUpdate 就會發生作用,避免無謂的更新操作;
了解了這些之后,我們就知道 key 值應該是 **唯一** 且 **穩定不變的**
比如用數組下標值作為 key 就是一個典型的錯誤,看起來 key 值是唯一的,但是卻不是穩定不變的
比如:[a, b, c] 值與下標的對應關系:a: 0 b:1 c:2
刪除a -> [b, c] 值與下標的對應關系 b:0 c:1
無法用 key 值來確定比對關系(新的 b 應該與舊的 b 比,如果按 key 值則是與 a 比)

> 需要注意,雖然 key 是一個 prop,但是接受 key 的組件并不能讀取到 key 的值,因為 key 和 ref 是 React 保留的兩個特殊 prop,并沒有預期讓組件直接訪問
## 利用 reselect 提高數據選取的性能
跟 vuex 的 getter 原理類似,緩存數據?
# 第 6 章 React 高級組件
## 高階組件的概念及應用
高階組件(Higher Order Component,HOC)并不是 React 提供的某種 API,而是使用 React 的一種模式,用于增強現有組件的功能。
簡單來說,一個高階組件就是一個函數,這個函數接受一個組件作為輸入,然后返回一個新的組件作為結果,而且,返回的新組件擁有了輸入組件所不具有的功能。這里提到的組件指的并不是組件實例,而是一個組件類,也可以是一個無狀態組件的函數。
```js
import React from 'react'
function removeUserProp(WrappedComponent) {
return class WrappingComponent extends React.Component {
render() {
const {user, ...otherProps} = this.props
return <WrappedComponent {...otherProps} />
}
}
}
export default removeUserProp
```
這樣一個高階組件做的工作非常簡單,它接受一個名為 WrappedComponent 的參數,表示一個組件類,這個函數返回一個新的組件,所做的事情和 WrappedComponent 一模一樣,只是忽略名為 user 的 prop。假如我們不希望某個組件接收到 user 的 prop,那么我們就不要直接使用這個組件,而是把這個組件作為參數傳遞給 removeUserProp 函數,然后把這個函數的返回結果當作組件來使用
```js
const NewComponent = removeUserProp(SampleComponent)
```
定義高階組件的意義何在?
- 重用代碼
- 修改現有 React 組件的行為,假設我們不想修改原有組件的內部邏輯,那么可以考慮使用高階組件
## 以函數為子組件的模式
# Redxu 和服務器通信
## 利用代理服務快速解決跨域請求問題
代理服務器的作用大概如下圖

使用 create-react-app 創造的應用已經具備了代理功能,只需要在 package.json 中添加如下一行
`"proxy": "http://www.weather.com.cn/"`
這一行配置告訴我們的應用,當接收到不是要求本地資源(localhost)的 HTTP 請求時,這個 HTTP 請求的協議和域名部分"替換"為 `http://www.weather.com.cn` 轉發出去(代理服服務器會根據配置分析 入 和 出 的關系,重新構建新的請求),并將收到的結果返還給瀏覽器,但是注意在線上環境應該開發自己的代理服務器(如 Nginx 的代理配置)
## 如何關聯異步的網絡請求和同步的 React 組件渲染
可行的方法是這樣的
- 在裝載過程中,如果組件沒有獲得服務器結果,就不顯示結果或者顯示一個“正在加載”之類的提示信息(在 componentDidMount 函數中發送請求)
- 獲取了請求結果后,要引發組件的一次更新過程,讓該組件重新繪制自己的內容
就像下面這樣,即我們把請求返回的數據直接保存在 React 組件的 state 中
```js
import React from 'react';
//TODO: change to your city code according to http://www.weather.com.cn/
const cityCode = 101010100;
class Weather extends React.Component {
constructor() {
super(...arguments);
this.state = {weather: null};
}
componentDidMount() {
const apiUrl = `/data/cityinfo/${cityCode}.html`;
fetch(apiUrl).then((response) => {
if (response.status !== 200) { // 需要檢查狀態碼,因為 fetch 認為只要服務器返回一個合法的 HTTP 響應就算成功
throw new Error('Fail to get response with status ' + response.status);
}
// response.json 檢查返回的數據是否為 JSON 格式同時幫我們執行 JSON.parse(response.text) ?
response.json().then((responseJson) => {
this.setState({weather: responseJson.weatherinfo});
}).catch((error) => {
this.setState({weather: null});
});
}).catch((error) => {
this.setState({weather: null});
});
}
render() {
if (!this.state.weather) {
return <div>暫無數據</div>;
}
const {city, weather, temp1, temp2} = this.state.weather;
return (
<div>
{city} {weather} 最低氣溫 {temp1} 最高氣溫 {temp2}
</div>
)
}
}
export default Weather;
```
## 使用 redux-thunk 中間件
把狀態存放在組件中并不是一個很好的選擇, Redux 本身就是用來幫助管理應用狀態的,應該盡量把狀態存放在 Redux Store 中。
Redux 本身的設計理念是不允許異步操作的,所以就需要中間件如 redux-thunk、redux-saga
在 Redux 架構下,一個 action 對象在通過 store.dispatch 派發,在調用 reducer 函數之前,就會先經過一個中間件的環節,這就是產生異步操作的機會

redux-thunk 的工作是檢查 action 對象是不是函數,如果不是函數就放行,完成普通 action 對象的生命周期,而如果發現 action 對象是函數,那就執行這個函數,并把 Store 的 dispatch 函數和 getState 函數作為參數傳遞到函數中去,不會讓這個異步 action 對象繼續往前派發到 reducer 函數
舉一個簡單的例子來介紹異步 action:
```js
const increment = () => ({
type: ActionTypes.INCREMENT
})
const incrementAsync = () => {
return dispatch => {
setTimeout(() => {
dispatch(increment())
}, 1000)
}
}
```
這個函數被 dispatch 函數派發之后,會被 redux-thunk 中間件執行,于是 setTimeout 函數就會發生作用,在 1s 后利用參數 dispatch 函數派發出同步 action 構造函數 increment 的結果。應用到發送 AJAX 請求中就是:action 對象函數可以通過 fetch 發起一個對服務器的異步請求,當得到服務器結果之后,通過參數 dispatch 把成功或者失敗的結果當作 action 對象再派發到 reducer 上(這次發送的是普通的 action 對象),最終驅動 Store 上狀態的改變。
雖然大致了解了原理,但使用時還要注意設計異步操作的模式,如設計 action:
一個訪問服務器的 action,至少要設計到三個 action 類型:
- 表示異步操作已經開始的 action 類型
- 表示異步操作成功的 action 類型
- 表示異步操作失敗的 action 類型
當這三種類型的 action 對象被派發時,會讓 React 組件進入各自不同的三種狀態:(組件根據狀態來渲染不同的視圖)
- 異步操作正在進行中
- 異步操作已經成功完成
- 異步操作已經失敗
```js
import {FETCH_STARTED, FETCH_SUCCESS, FETCH_FAILURE} from './actionTypes.js';
// 返回 type 字段以驅動 reducer 函數去改變 Redux Store 上的某個字段的狀態,從而驅動對應的 React 組件重新渲染
export const fetchWeatherStarted = () => ({
type: FETCH_STARTED
});
export const fetchWeatherSuccess = (result) => ({
type: FETCH_SUCCESS,
result
})
export const fetchWeatherFailure = (error) => ({
type: FETCH_FAILURE,
error
})
export const fetchWeather = (cityCode) => {
return (dispatch) => {
const apiUrl = `/data/cityinfo/${cityCode}.html`;
dispatch(fetchWeatherStarted()) // 派發一個普通 action 對象,將視圖置于"有異步 action 還未結束"的狀態
return fetch(apiUrl).then((response) => {
if (response.status !== 200) {
throw new Error('Fail to get response with status ' + response.status);
}
response.json().then((responseJson) => {
dispatch(fetchWeatherSuccess(responseJson.weatherinfo)); // 派發一個表示請求成功的普通 action 對象
}).catch((error) => {
dispatch(fetchWeatherFailure(error)); // 派發一個表示請求失敗的普通 action 對象
});
}).catch((error) => {
dispatch(fetchWeatherFailure(error));
})
};
}
```
再來看下 reducer 函數如何處理
```
import {FETCH_STARTED, FETCH_SUCCESS, FETCH_FAILURE} from './actionTypes.js';
import * as Status from './status.js';
/*
./status.js
export const LOADING = 'loading';
export const SUCCESS = 'success';
export const FAILURE = 'failure';
*/
export default (state = {status: Status.LOADING}, action) => {
switch(action.type) {
case FETCH_STARTED: {
return {status: Status.LOADING};
}
case FETCH_SUCCESS: {
return {...state, status: Status.SUCCESS, ...action.result};
}
case FETCH_FAILURE: {
return {status: Status.FAILURE};
}
default: {
return state;
}
}
}
```
異步 action 構造函數的模板:
```
export const sampleAsyncAction = () => {
return (dispatch, getState) => {
// 在這個函數里可以調用異步函數,自行決定在合適的時機通過
// 參數派發出新的 action 對象
}
}
```
# Tips
1.在使用 JSX 的代碼文件中,即使代碼中并沒有直接使用 React,也一定要導入 React,因為 JSX 最終會被轉譯成依賴于 React 的表達式`React.createElement()`
*****
2.盡量避免使用 ref,ref 可以取得對元素的引用來訪問 DOM 元素,React 的產生就是為了避免直接操作 DOM 元素,因為直接訪問 DOM 元素很容易產生失控的情況。可以通過 **狀態綁定** 的方式來實現相應的功能,簡單來說,就是利用組件的狀態來存儲我們需要的內容。
```js
<input onChange={this.onInputChange} />
onInputChange(event) {
this.setState({
value: event.target.value // 把內容存在組件狀態的 value 字段上
})
}
```
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直線、矩形、多邊形
- Part2-曲線圖形
- Part3-線條操作
- Part4-文本操作
- Part5-圖像操作
- Part6-變形操作
- Part7-像素操作
- Part8-漸變與陰影
- Part9-路徑與狀態
- Part10-物理動畫
- Part11-邊界檢測
- Part12-碰撞檢測
- Part13-用戶交互
- Part14-高級動畫
- CSS
- SCSS
- codePen
- 速查表
- 面試題
- 《CSS Secrets》
- SVG
- 移動端適配
- 濾鏡(filter)的使用
- JS
- 基礎概念
- 作用域、作用域鏈、閉包
- this
- 原型與繼承
- 數組、字符串、Map、Set方法整理
- 垃圾回收機制
- DOM
- BOM
- 事件循環
- 嚴格模式
- 正則表達式
- ES6部分
- 設計模式
- AJAX
- 模塊化
- 讀冴羽博客筆記
- 第一部分總結-深入JS系列
- 第二部分總結-專題系列
- 第三部分總結-ES6系列
- 網絡請求中的數據類型
- 事件
- 表單
- 函數式編程
- Tips
- JS-Coding
- Framework
- Vue
- 書寫規范
- 基礎
- vue-router & vuex
- 深入淺出 Vue
- 響應式原理及其他
- new Vue 發生了什么
- 組件化
- 編譯流程
- Vue Router
- Vuex
- 前端路由的簡單實現
- React
- 基礎
- 書寫規范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 與 Hook
- 《深入淺出React和Redux》筆記
- 前半部分
- 后半部分
- react-transition-group
- Vue 與 React 的對比
- 工程化與架構
- Hybird
- React Native
- 新手上路
- 內置組件
- 常用插件
- 問題記錄
- Echarts
- 基礎
- Electron
- 序言
- 配置 Electron 開發環境 & 基礎概念
- React + TypeScript 仿 Antd
- TypeScript 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs