從本節開始,咱們開始用 Redux、React-redux 來重構第二階段的評論功能。產品需求跟以前同樣,可是會用 Redux、React-redux 來幫助管理應用狀態,而不是「狀態提高」。讓整個應用更加接近真實的工程。html
你們能夠在第二階段的代碼上進行修改 comment-app2(非高階組件版本)。若是已經忘了第二階段評論功能的同窗能夠先簡單回顧一下它的功能需求,實戰分析:評論功能(四)。第1、2、三階段的實戰代碼均可以在這裏找到:react-naive-book-examples。react
咱們首先安裝好依賴,如今 comment-app2 須要依賴 Redux、React-redux 了,進入工程目錄執行命令安裝依賴:npm
npm install redux react-redux --save
而後咱們二話不說先在 src
下創建三個空目錄:components
、containers
、reducers
。redux
咱們以前的 reducer 都是直接寫在 src/index.js
文件裏面,這是一個很差的作法。由於隨着應用愈來愈複雜,可能須要更多的 reducer 來幫助咱們管理應用(這裏後面的章節會有所說起)。因此最好仍是把全部 reducer 抽出來放在一個目錄下 src/reducers
。數組
對於評論功能其實仍是比較簡單的,回顧一下咱們在狀態提高章節裏面不斷提高的狀態是什麼?其實評論功能的組件之間共享的狀態只有 comments
。咱們能夠直接只在 src/reducers
新建一個 reducer comments.js
來對它進行管理。app
思考一下評論功能對於評論有什麼操做?想清楚咱們才能寫好 reducer,由於 reducer 就是用來描述數據的形態和相應的變動。新增和刪除評論這兩個操做是最明顯的,你們應該都可以輕易想到。還有一個,咱們的評論功能其實會從 LocalStorage 讀取數據,讀取數據之後其實須要保存到應用狀態中。因此咱們還有一個初始化評論的操做。因此目前能想到的就是三個操做:函數
// action types const INIT_COMMENTS = 'INIT_COMMENTS' const ADD_COMMENT = 'ADD_COMMENT' const DELETE_COMMENT = 'DELETE_COMMENT'
咱們用三個常量來存儲 action.type
的類型,這樣之後咱們修改起來就會更方便一些。根據這三個操做編寫 reducer:性能
// reducer export default function (state, action) { if (!state) { state = { comments: [] } } switch (action.type) { case INIT_COMMENTS: // 初始化評論 return { comments: action.comments } case ADD_COMMENT: // 新增評論 return { comments: [...state.comments, action.comment] } case DELETE_COMMENT: // 刪除評論 return { comments: [ ...state.comments.slice(0, action.commentIndex), ...state.comments.slice(action.commentIndex + 1) ] } default: return state } }
咱們只存儲了一個 comments
的狀態,初始化爲空數組。當遇到 INIT_COMMENTS
的 action 的時候,會新建一個對象,而後用 action.comments
覆蓋裏面的 comments
屬性。這就是初始化評論操做。測試
一樣新建評論操做 ADD_COMMENT
也會新建一個對象,而後新建一個數組,接着把原來 state.comments
裏面的內容所有拷貝到新的數組當中,最後在新的數組後面追加 action.comment
。這樣就至關新的數組會比原來的多一條評論。(這裏不要擔憂數組拷貝的性能問題,[...state.comments]
是淺拷貝,它們拷貝的都是對象引用而已。)優化
對於刪除評論,其實咱們須要作的是新建一個刪除了特定下標的內容的數組。咱們知道數組 slice(from, to)
會根據你傳進去的下標拷貝特定範圍的內容放到新數組裏面。因此咱們能夠利用 slice
把原來評論數組中 action.commentIndex
下標以前的內容拷貝到一個數組當中,把 action.commentIndex
座標以後到內容拷貝到另一個數組當中。而後把兩個數組合並起,就至關於「刪除」了 action.commentIndex
的評論了。
這樣就寫好了評論相關的 reducer。
以前咱們使用 dispatch
的時候,都是直接手動構建對象:
dispatch({ type: 'INIT_COMMENTS', comments })
每次都要寫 type
其實挺麻煩的,並且還要去記憶 action type 的名字也是一種負擔。咱們能夠把 action 封裝到一種函數裏面,讓它們去幫助咱們去構建這種 action,咱們把它叫作 action creators。
// action creators export const initComments = (comments) => { return { type: INIT_COMMENTS, comments } } export const addComment = (comment) => { return { type: ADD_COMMENT, comment } } export const deleteComment = (commentIndex) => { return { type: DELETE_COMMENT, commentIndex } }
所謂 action creators 其實就是返回 action 的函數,這樣咱們 dispatch
的時候只須要傳入數據就能夠了:
dispatch(initComments(comments))
action creators 還有額外好處就是能夠幫助咱們對傳入的數據作統一的處理;並且有了 action creators,代碼測試起來會更方便一些。這些內容你們能夠後續在實際項目當中進行體會。
整個 src/reducers/comments.js
的代碼就是:
// action types const INIT_COMMENTS = 'INIT_COMMENTS' const ADD_COMMENT = 'ADD_COMMENT' const DELETE_COMMENT = 'DELETE_COMMENT' // reducer export default function (state, action) { if (!state) { state = { comments: [] } } switch (action.type) { case INIT_COMMENTS: // 初始化評論 return { comments: action.comments } case ADD_COMMENT: // 新增評論 return { comments: [...state.comments, action.comment] } case DELETE_COMMENT: // 刪除評論 return { comments: [ ...state.comments.slice(0, action.commentIndex), ...state.comments.slice(action.commentIndex + 1) ] } default: return state } } // action creators export const initComments = (comments) => { return { type: INIT_COMMENTS, comments } } export const addComment = (comment) => { return { type: ADD_COMMENT, comment } } export const deleteComment = (commentIndex) => { return { type: DELETE_COMMENT, commentIndex } }
有些朋友可能會發現咱們的 reducer 跟網上其餘的 reducer 的例子不大同樣。有些人喜歡把 action 單獨切出去一個目錄 actions
,讓 action 和 reducer 分開。我的觀點以爲這種作法可能有點過分優化了,其實多數狀況下特定的 action 只會影響特定的 reducer,直接放到一塊兒能夠更加清晰地知道這個 action 其實只是會影響到什麼樣的 reducer。而分開會給咱們維護和理解代碼帶來額外沒必要要的負擔,這有種矯枉過正的意味。可是這裏沒有放之四海皆準的規則,你們能夠多參考、多嘗試,找到適合項目需求的方案。
我的寫 reducer 文件的習慣,僅供參考: