使用react+redux實現計數器功能及遇到問題
Redux,本身就是一個單純的狀態管理者,我們不追溯它的歷史,從使用角度來說:它提供一個全局的對象store,store中包含state對象用以包含所有應用數據,並且store提供瞭一些reducer方法。這些方法可以自定義,使用調用者得以改變state的值。state的值僅為隻讀,如果需要更改則必須隻能通過reducer。
Redux
- 核心對象:store
- 數據存儲:state
- 狀態更新提交接口:==dispatch==
- 狀態更新提交參數:帶type和payload的==Action==
- 狀態更新計算:==reducer==
- 限制:reducer必須是純函數,不支持異步
- 特性:支持中間件
React + Redux
在recat中不使用redux 時遇到的問題
在react中組件通信的數據是單向的,頂層組件可以通過props屬性向下層組件傳遞數據,而下層組件不能向上層組件傳遞數據,要實現下層組件修改數據,需要上層組傳遞修改數據的方法到下層組件,當項目越來越的時候,組件之間傳遞數據變得越來越困難
在react中加入redux 的好處
使用redux管理數據,由於Store獨立於組件,使得數據管理獨立於組件,解決瞭組件之間傳遞數據困難的問題
使用redux
下載redux
npm install redux react-redux
redux 工作流程
- 組件通過 dispatch 觸發action
- store 接受 action 並將 action 分發給 reducer
- reducer 根據 action 類型對狀態進行更改並將更改後的數據返回給store
- 組件訂閱瞭store中的狀態,store中的狀態更新會同步到組件
使用react+redux實現計數器
1.創建項目,並安裝 redux
# 如果沒有安裝react腳手架則執行這條命令安裝reate腳手架 npm install -g create-react-app # 創建reate項目 create-react-app 項目名 # 進入項目 cd 項目名 # 安裝 redux npm install redux reate-redux
2.引入redux,並根據開始實現的代碼在react中實現計數器
//index.js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import { createStore } from 'redux'; const initialState = { count: 0 } function reducer(state = initialState, action) { switch (action.type) { case 'increment': return { count: state.count + 1 } case 'decrement': return { count: state.count - 1 } default: return state } } const store = createStore(reducer) const increment = { type: 'increment' } const decrement = { type: 'decrement' } function Count() { return <div> <button onClick={() => store.dispatch(increment)}>+</button> <span>{store.getState().count}</span> <button onClick={() => store.dispatch(decrement)}>-</button> </div> } store.subscribe( () => { console.log(store.getState()) ReactDOM.render( <React.StrictMode> <Count /> </React.StrictMode>, document.getElementById('root') ); }) ReactDOM.render( <React.StrictMode> <Count /> </React.StrictMode>, document.getElementById('root') );
明顯以上方式雖然可以實現計數器的功能,但在實際項目中肯定不能這樣使用,因為組件一般都在單獨的文件中的,這種方式明顯在其他組件中並不能獲取到Store。
計數器案例代碼優化-讓store全局可訪問
為瞭解決Store獲取問題需要使用react-redux來解決這個問題,react-redux給我們提供瞭Provider組件和connect方法
Provide 組件
是一個組件 可以吧創建出來的store 放在一個全局的地方,讓組件可以拿到store,通過provider組件,將 store 放在瞭全局的組件可以夠的到的地方 ,provider要求我們放在最外層組件
connect
connect 幫助我們訂閱store中的狀態,狀態發生改變後幫助我們重新渲染組件
通過 connect 方法我們可以拿到 store 中的狀態 把 store 中的狀態映射到props中
通過 connect 方法可以拿到 dispatch 方法
connect 的參數為一個函數 這個函數可以拿到store中的狀態,要求我們這個函數必須返回一個對象,在這個對象中寫的內容都會映射給組件的props屬性
connect 調用後返回一個函數 返回的這個函數繼續調用需要傳入組件告訴connect需要映射到那個組件的props
新建 Component 文件夾、創建 Count.js 文件
import React from 'react' function Count() { return <div> <button onClick={() => store.dispatch(increment)}>+</button> <span>{store.getState().count}</span> <button onClick={() => store.dispatch(decrement)}>-</button> </div> } export default Count
引入 Provider 組件放置在最外層,並制定store
ReactDOM.render( // 通過provider組件 將 store 放在瞭全局的組件可以夠的到的地方 provider要求我們放在最外層組件 <Provider store={store}><Count /></Provider>, document.getElementById('root') );
引入 connect 方法 根據 connect 的使用來包裹組件
const mapStateProps = state => ({ count: state.count, a: '1' }) // connect 的參數為一個函數 這個函數可以拿到store中的狀態,要求我們這個函數必須返回一個對象,在這個對象中寫的內容都會映射給組件的props屬性 // connect 調用後返回一個函數 返回的這個函數繼續調用需要傳入組件告訴connect需要映射到那個組件的props export default connect(mapStateProps)(Count)
改造 Count 組件把 action 復制到該文件中
const increment = { type: 'increment' } const decrement = { type: 'decrement' } function Count({count,dispatch}) { return <div> <button onClick={() => {dispatch(increment)}}>+</button> <span>{count}</span> <button onClick={() => {dispatch(decrement)}}>-</button> </div> }
現在項目已經可以運行瞭但是Count組件中的 提交Action的那一長串代碼影響視圖的可讀性,所以代碼還是需要優化
計數器案例代碼優化-讓視圖中的代碼可讀性更高
我們希望視圖中直接調用一個函數這樣視圖代碼可讀性強,這個需要利用connect的第二個參數,第二個參數是一個函數,這個函數的形參就是dispatch方法,要求這個函數返回一個對象,返回的這個對象中的內容都會映射到組件的props屬性上
申明一個變量為connect中的第二個參數,在這個變量中返回執行不同action操作的對象
// connect 的第二個參數 這個參數是個函數 這個函數的形參就是dispatch方法 要求返回一個對象 這個對象中的屬性會被映射到組件的props上 const mapDispatchToProps = dispatch => ({ increment (){ dispatch({ type: 'increment' }) }, decrement (){ dispatch({ type: 'decrement' }) } }) // connect 的參數為一個函數 這個函數可以拿到store中的狀態,要求我們這個函數必須返回一個對象,在這個對象中寫的內容都會映射給組件的props屬性 // connect 調用後返回一個函數 返回的這個函數繼續調用需要傳入組件告訴connect需要映射到那個組件的props export default connect(mapStateProps, mapDispatchToProps)(Count)
在組件中結構props在視圖中直接綁定事件
function Count({count,increment,decrement}) { return <div> <button onClick={increment}>+</button> <span>{count}</span> <button onClick={decrement}>-</button> </div> }
通過這次優化我們發現 調用 dispatch 觸發action 的方法的代碼都是重復的,所以還需要繼續優化
優化調用 dispatch 觸發action 的方法的重復代碼簡化
利用 bindActionCreators 來簡化 dispatch 觸發 action的操作,bindActionCreators來幫助我們生成執行action動作的函數
bindActionCreators 有兩個參數,第一個參數為 執行action的對象,第二個參數為 dispatch方法
分離action操作,新建store/actions/counter.actions.js文件把執行action操作單獨放在這個文件並導出
export const increment = () => ({type: 'increment'}) export const decrement = () => ({type: 'decrement'})
在Count.js中導入關於計數器的action,用bindActionCreators方法來生成dispatch執行action函數
import { bindActionCreators } from 'redux' import * as counterActions from './../store/actions/counter.actions' const mapDispatchToProps = dispatch => (bindActionCreators(counterActions, dispatch)) // connect 的參數為一個函數 這個函數可以拿到store中的狀態,要求我們這個函數必須返回一個對象,在這個對象中寫的內容都會映射給組件的props屬性 // connect 調用後返回一個函數 返回的這個函數繼續調用需要傳入組件告訴connect需要映射到那個組件的props export default connect(mapStateProps, mapDispatchToProps)(Count)
代碼優化到這裡我們發現,redux的代碼與組件融合在一起,所以我需要拆分成獨立的,為什麼要抽離redux呢?因為我們要讓我們的代碼結構更加合理
重構計數器,把redux相關代碼抽離
把reducer函數抽離為單獨的文件、把創建store抽離到單獨的文件中
因為在reducer 和 actions中我們都寫瞭字符串,但是字符串沒有提示所以我們把字符串定義成常量防止我們出現單詞錯誤這種低級錯誤,新建 src/store/const/counter.const.js 文件
export const INCREMENT = 'increment' export const DECREMENT = 'decrement'
新建 src/store/reducers/counter.reducers.js 文件把 reducer 函數抽離到此文件中
import { INCREMENT, DECREMENT} from './../const/counter.const' const initialState = { count: 0 } // eslint-disable-next-line import/no-anonymous-default-export export default (state = initialState, action) => { switch (action.type) { case INCREMENT: return { count: state.count + 1 } case DECREMENT: return { count: state.count - 1 } default: return state } }
更改actions中的字符串為引入變量
import { INCREMENT, DECREMENT} from './../const/counter.const' export const increment = () => ({type: INCREMENT}) export const decrement = () => ({type: DECREMENT})
創建src/store/index.js文件 ,在這個文件中創建store 並導出
import { createStore } from 'redux'; import reducer from './reducers/counter.reducers' export const store = createStore(reducer)
在引入store的文件中改變為沖項目中store文件中引入store
import React from 'react'; import ReactDOM from 'react-dom'; import Count from './components/Count'; import { store } from './store' import { Provider } from 'react-redux' /** * react-redux 讓react 和 redux 完美結合 * Provider 是一個組件 可以吧創建出來的store 放在一個全局的地方 讓組件可以拿到store * connect 是一個方法 */ ReactDOM.render( // 通過provider組件 將 store 放在瞭全局的組件可以夠的到的地方 provider要求我們放在最外層組件 <Provider store={store}><Count /></Provider>, document.getElementById('root') );
為action 傳遞參數,對計數器案例做擴展
這個計數器案例已經實現瞭點擊按鈕加一減一操作瞭,現在有個新需求我們需要加減一個數值例如加五減五
這就需要對action傳遞參數瞭
在視圖中按鈕綁定函數傳入參數
function Count({count,increment,decrement}) { return <div> <button onClick={() => increment(5)}>+</button> <span>{count}</span> <button onClick={() => decrement(5)}>-</button> </div> }
在dispacth執行action動作時接受參數並傳入到action中
export const increment = payload => ({type: INCREMENT, payload}) export const decrement = payload => ({type: DECREMENT, payload})
在reducers中接收參數並作相應處理
export default (state = initialState, action) => { switch (action.type) { case INCREMENT: return { count: state.count + action.payload } case DECREMENT: return { count: state.count - action.payload } default: return state } }
原文地址:https://kspf.xyz/archives/10/
到此這篇關於在react中使用redux並實現計數器案例的文章就介紹到這瞭,更多相關react redux實現計數器內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 一文搞懂redux在react中的初步用法
- React項目中使用Redux的 react-redux
- redux持久化之redux-persist結合immutable使用問題
- 詳解React 和 Redux的關系
- React 中使用 Redux 的 4 種寫法小結