1. 程式人生 > >《深入淺出React和Redux》一書的部分章節例子程式碼演練及相關知識點

《深入淺出React和Redux》一書的部分章節例子程式碼演練及相關知識點

個人學習心得專案地址

前言

針對《深入淺出React和Redux》一書中,某一個被挑選的例子,完成之後,會建立一個相應的分支。

  1. 《深入淺出React和Redux》原書例子程式碼,傳送門
  2. 在原書程式碼的基礎上,把相關依賴升級到當前最新版(截止 2018-11-06)。
    • react v16.6.0
    • redux v4.0.1
    • react-redux v5.1.0
  3. 第一章程式碼位於 chapter-01,第二章的程式碼位於 chapter-02,依次類推。
  4. 子目錄名即為分支名,如第四章程式碼目錄下的子目錄: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

知識點

  1. 元件。
  2. 元件的 state、props。
  3. 父元件通過 props 向子元件傳遞資料。

第二章,分支 controlpanel_with_summary

# 切換至該分支
git checkout controlpanel_with_summary
git pull

知識點

  1. 元件的 props,父元件向子元件傳遞資料,包括傳遞函式。
  2. 子元件通過呼叫父元件的函式,來達到向父元件傳遞資料的目的。

第三章,分支 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

知識點

  1. UI 元件(presentational component)(傻瓜元件)
  2. 容器元件(container component)
  3. 應用 redux 的三大原則
  4. redux 庫:const store = createStore(reducer, initValues)
  5. React-Redux 庫
    • connect(mapStateToProps, mapDispatchToProps)(componentName)
    • mapStateToProps
    • mapDispatchToProps

相關知識點,已經總結到文件:redux 知識點、參考連結

第四章,分支 todo_controlled_component

# 切換至該分支
git checkout todo_controlled_component
git pull

知識點

程式碼檔案組織結構,以及確定模組的邊界。

參考《深入淺出React和Redux》P75-81。

  1. 推薦目錄組織方式:按照功能組織。
  2. 把一個目錄看做一個模組,那麼我們要做的是明確這個模組對外的介面,而這個介面應該實現把內部封裝起來。
  3. 目錄下人 index.js 檔案,就是我們的模組介面。
  4. 各個模組之間只能假設其他模組包含 index.js 檔案,要引用模組只能匯入 index.js,不能夠直接去導人其他檔案。
  5. 導人一個目錄的時候,預設導人的就是這個目錄下的 index.js 檔案, index.js 檔案中匯出的內容,就是這個模組想要公開出來的介面。
  6. 推薦使用 export(命名式)的方式匯出模組,而不是用 export default(預設)的方式,因為 export default 在匯入時,會增加程式碼量。

狀態樹的設計

參考《深入淺出React和Redux》P81-83。

  1. 一個模組控制一個狀態節點。
  2. 避免冗餘資料。
  3. 樹形結構扁平。
  4. 只能通過 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 變化會引起頁面的重新渲染,作如下修改:

  1. 將 checkbox 的 onClick 事件刪除,這樣,點選 checkbox 控制元件不會有任何反應(checkbox 設定了只讀屬性)。
  2. 將設定待辦事項狀態的點選事件放到 label 上,添加了 a 標籤。

    不過,a 標籤的 href 屬性只是 # 會引發另外的警告,這個下面再解決。

  3. 同時將 checkbox 的 checked 屬性新增上去,其值就是待辦事項的資料:currentState.completed,這是一個 bool 變數。
  4. 將變數 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.