《深入淺出React和Redux》一書的部分章節例子程式碼演練及相關知識點
個人學習心得專案地址
- 託管在 gitee 上的專案連結 :https://gitee.com/uncleAndyChen/react-full-stack-learning
- 託管在 github 上的專案連結:https://github.com/uncleAndyChen/react-full-stack-learning
前言
針對《深入淺出React和Redux》一書中,某一個被挑選的例子,完成之後,會建立一個相應的分支。
- 《深入淺出React和Redux》原書例子程式碼,傳送門
- 在原書程式碼的基礎上,把相關依賴升級到當前最新版(截止 2018-11-06)。
- react v16.6.0
- redux v4.0.1
- react-redux v5.1.0
- 第一章程式碼位於 chapter-01,第二章的程式碼位於 chapter-02,依次類推。
- 子目錄名即為分支名,如第四章程式碼目錄下的子目錄:
todo_controlled_component
,會有一個對應分支也叫todo_controlled_component
。
PropTypes 依賴變化
react 的型別檢查 PropTypes 自 React v15.5 起已棄用,請使用 prop-types。
《深入淺出React和Redux》一書示例程式碼使用的 react 是 15.4.1 版本,需要調整 PropTypes 的引用:
// 書中的程式碼是
import { PropTypes } from 'react';
// 要改為:
import PropTypes from 'prop-types';
擴充套件閱讀:使用 PropTypes 進行型別檢查
第二章,分支 controlpanel
# 切換至該分支
git checkout controlpanel
git pull
知識點
- 元件。
- 元件的 state、props。
- 父元件通過 props 向子元件傳遞資料。
第二章,分支 controlpanel_with_summary
# 切換至該分支 git checkout controlpanel_with_summary git pull
知識點
- 元件的 props,父元件向子元件傳遞資料,包括傳遞函式。
- 子元件通過呼叫父元件的函式,來達到向父元件傳遞資料的目的。
第三章,分支 react-redux
# 切換至該分支
git checkout react-redux
git pull
到專案根目錄,新增 redux 和 react-redux 依賴。
以下操作會新增最新版的 redux(截止 2018-11-06,版本為:4.0.1) 和 react-redux(截止 2018-11-06,版本為:5.1.0)
cnpm i redux --save
cnpm i react-redux --save
如果不事先新增 redux 依賴而直接新增 react-redux 依賴,會有警告:
peerDependencies WARNING [email protected]* requires a peer of [email protected]^2.0.0 || ^3.0.0 || ^4.0.0-0 but none was installed
知識點
- UI 元件(presentational component)(傻瓜元件)
- 容器元件(container component)
- 應用 redux 的三大原則
- redux 庫:const store = createStore(reducer, initValues)
- React-Redux 庫
- connect(mapStateToProps, mapDispatchToProps)(componentName)
- mapStateToProps
- mapDispatchToProps
相關知識點,已經總結到文件:redux 知識點、參考連結
第四章,分支 todo_controlled_component
# 切換至該分支
git checkout todo_controlled_component
git pull
知識點
程式碼檔案組織結構,以及確定模組的邊界。
參考《深入淺出React和Redux》P75-81。
- 推薦目錄組織方式:按照功能組織。
- 把一個目錄看做一個模組,那麼我們要做的是明確這個模組對外的介面,而這個介面應該實現把內部封裝起來。
- 目錄下人 index.js 檔案,就是我們的模組介面。
- 各個模組之間只能假設其他模組包含 index.js 檔案,要引用模組只能匯入 index.js,不能夠直接去導人其他檔案。
- 導人一個目錄的時候,預設導人的就是這個目錄下的 index.js 檔案, index.js 檔案中匯出的內容,就是這個模組想要公開出來的介面。
- 推薦使用 export(命名式)的方式匯出模組,而不是用 export default(預設)的方式,因為 export default 在匯入時,會增加程式碼量。
狀態樹的設計
參考《深入淺出React和Redux》P81-83。
- 一個模組控制一個狀態節點。
- 避免冗餘資料。
- 樹形結構扁平。
- 只能通過 reducer 純函式修改 state,不能直接修改 state。
- 所以,push 和 unshift 會改變原來那個陣列,是不能直接作用於 state 的。
- 利用擴充套件操作符。
combineReducers
-
利用 combineReducers 可以把多個只針對區域性狀態的“小的”reducer 合併為一個操縱整個狀態樹的“大的“ reducer。
-
更妙的是,沒有兩個”小的“ reducer 會發生衝突,因為無論怎麼組合,狀態樹上一個子狀態都只會被一個 reducer 處理,Redux 就是用這種方法隔絕了各個模組。
-
很明顯,無論我們有多少“小的” reducer,也無論如何組合,都不用在乎它們被因為呼叫的順序,因為呼叫順序和結果沒有關係。
-
隨著應用變得複雜,需要對 reducer 函式進行拆分,拆分後的每一塊獨立負責管理 state 的一部分。
-
combineReducers 輔助函式的作用是,把一個由多個不同 reducer 函式作為 value 的 object,合併成一個最終的 reducer 函式,然後就可以對這個 reducer 呼叫 createStore。
-
合併後的 reducer 可以呼叫各個子 reducer,並把它們的結果合併成一個 state 物件。state 物件的結構由傳入的多個 reducer 的 key 決定。
-
最終,state 物件的結構會是這樣的:
{ todos: ... filter: ... }
-
通過為傳入物件的 reducer 命名不同來控制 state key 的命名。例如,你可以呼叫 combineReducers({ todos: todoReducer, filter: filterReducer }) 將 state 結構變為 { todos, counter }。
-
個人認為,更好的做法是直接用 reducer 名作為 state 的 key,使用 ES6 的簡寫方法:combineReducers({ todos, filter })。這與 combineReducers({ todos: todoReducer, filter: filterReducer }) 產生的 state 結果是一樣的。
關於 state key 的使用,實際開發過程中還需要注意些什麼呢?看筆者總結的踩坑經驗:state 的 key
bindActionCreators
把原來笨重的函式呼叫過程封裝起來,使最終的業務程式碼更加優雅。
程式碼修改及完善
新增新的依賴
cnpm i --save react-addons-perf
...
peerDependencies WARNING [email protected]* requires a peer of [email protected]^15.4.2 but [email protected] was installed
意思是,需要 react v15.4.2 支援,所以,將 Store.js 修改如下:
// 以下程式碼刪除
import Perf from 'react-addons-perf'
win.Perf = Perf;
const middlewares = [];
if (process.env.NODE_ENV !== 'production') {
middlewares.push(require('redux-immutable-state-invariant')());
}
const storeEnhancers = compose(
applyMiddleware(...middlewares),
(win && win.devToolsExtension) ? win.devToolsExtension() : (f) => f,
);
export default createStore(reducer, {}, storeEnhancers);
// 上一行程式碼改為
export default createStore(reducer, {}, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
createStore 第三個引數:window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
,是為了支援 Redux DevTools 外掛。
警告 Warning: A component is changing an uncontrolled input of type checkbox to be controlled…
完整的警告如下:
Warning: A component is changing an uncontrolled input of type checkbox to be controlled.
Input elements should not switch from uncontrolled to controlled (or vice versa).
Decide between using a controlled or uncontrolled input element for the lifetime of the component.
More info: https://fb.me/react-controlled-components
in input (at todoItem.js:13)
in li (at todoItem.js:7)
in TodoItem (at todoList.js:14)
in ul (at todoList.js:11)
in TodoList (created by Connect(TodoList))
in Connect(TodoList) (at todos.js:11)
in div (at todos.js:9)
in Unknown (at TodoApp.js:8)
in div (at TodoApp.js:7)
in TodoApp (at src/index.js:10)
in Provider (at src/index.js:9)
這是因為 todoItem.js:13 程式碼中的 checkbox 的 checked 屬性沒有用 state 來記錄,所以會警告,但這並不影響該示例的正常執行。
關於頁面控制元件是否受控,以及相關問題,請看官方文件:Controlled Components
解決
為了消除以上警告,同時,為了更方便理解 state 變化會引起頁面的重新渲染,作如下修改:
- 將 checkbox 的 onClick 事件刪除,這樣,點選 checkbox 控制元件不會有任何反應(checkbox 設定了只讀屬性)。
- 將設定待辦事項狀態的點選事件放到 label 上,添加了 a 標籤。
不過,a 標籤的 href 屬性只是
#
會引發另外的警告,這個下面再解決。 - 同時將 checkbox 的 checked 屬性新增上去,其值就是待辦事項的資料:currentState.completed,這是一個 bool 變數。
- 將變數
checkedProp
定義行const checkedProp = completed ? {checked: true} : {};
刪除。
最後,關鍵程式碼如下:
<input className="toggle" type="checkbox" checked={completed} readOnly/>
<label className="text"><a href="#" onClick={onToggle}>{text}</a></label>
a 標籤的 href 屬性只是 #
會引發的警告
./src/todos/views/todoItem.js
Line 13: The href attribute requires a valid value to be accessible.
Provide a valid, navigable address as the href value.
If you cannot provide a valid href, but still need the element to resemble a link,
use a button and change it with appropriate styles.
Learn more: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md
jsx-a11y/anchor-is-valid
專案中用到的 Link 元件 ./src/filter/views/link.js
也有同樣的警告,一起修改。
./src/filter/views/link.js
Line 11: The href attribute requires a valid value to be accessible.
...
解決
參照文章anchor-is-valid,作如下調整:
將 a 標籤 <a href="#" onClick={onToggle}>{text}</a>
換成 button 控制元件,同時增加 button 相關的 style.css 檔案放到 src 根目錄下。
<button
type="button"
className="link-button"
onClick={onToggle}>
{text}
</button>
將 link.js 元件中的 a 標籤也換成 button,這裡就不貼程式碼了,直接看程式碼檔案吧。
最後還有一個警告,在點選【新增】按鈕的時候觸發的,不過 chrome 瀏覽器才有該警告,QQ 瀏覽器沒有,沒再深入研究。
[Deprecation] Using unescaped '#' characters in a data URI body is deprecated and will be removed in M71, around December 2018.
Please use '%23' instead. See https://www.chromestatus.com/features/5656049583390720 for more details.