一. 純原生 redux 的使用
Redux 是常用的狀態(tài)管理容器,能夠幫助我們對一些全局變量進行有效管理凰浮。首先我抠,舉一個場景:我正在開開心心的逛淘寶商城,點進了一個淘寶店家的商品 A 的詳情頁袜茧,覺得不錯菜拓,于是點擊 ”加入購物車“,接著我又點進了一個天貓店家的商品 B 的詳情頁笛厦,也覺得不錯纳鼎,又點擊了 ”加入購物車“,最終我打開購物車發(fā)現(xiàn)有 A B 兩件商品裳凸。假設(shè)淘寶商品詳情頁和天貓商品是兩個相互獨立的組件贱鄙,那么要實現(xiàn)上述場景,購物車就應(yīng)當(dāng)是一個共享組件姨谷,此時就可以使用 redux 對購物車實現(xiàn)統(tǒng)一管理逗宁。那么以當(dāng)前這個場景,使用原生 redux 應(yīng)該怎么實現(xiàn)呢梦湘?廢話不多說疙剑,看如下實現(xiàn):
-
安裝 redux
redux 支持 npm 等包管理工具,故而進入項目目錄中践叠,執(zhí)行
npm install redux --save
-
建立一個倉庫來存儲數(shù)據(jù)
// store.js import {createStore} from 'redux' // 聲明一個實際的操作函數(shù)言缤,響應(yīng)操作 const cartReducer = (state = { goods: [] }, action) => { switch (action.type) { case 'add': return {...state, goods: [...state.goods, action.good]} case 'delete': let deletedGoodIdx = action.good.index let newGoods = [...state.goods] newGoods.splice(deletedGoodIdx, 1) return {...state, goods: newGoods} default: return state } } // 創(chuàng)建 store 實例并導(dǎo)出 const store = createStore(cartReducer) export default store
-
在組件中使用 store
讀取:
store.getState()
即可獲取到 reducer 中的 state修改:
store.dispatch(action)
通過 dispatch 一個 action 來觸發(fā) reducer 中的修改行為/********** App.js **************/ import React from 'react' import {Link, Route} from 'react-router-dom' import store from './store' // 淘寶商品詳情組件 function TbItem() { return ( <div> <h2>這是淘寶詳情頁</h2> <p>當(dāng)前購物車內(nèi)商品數(shù)量:{store.getState().goods.length}</p> <button onClick={() => store.dispatch({type: 'add', good: {title: `淘寶商品${Date.now()}`, price: 100}})}>添加購物車</button> </div> ) } // 天貓商品詳情組件 function TmItem() { return ( <div> <h2>這是天貓詳情頁</h2> <p>當(dāng)前購物車內(nèi)商品數(shù)量:{store.getState().goods.length}</p> <button onClick={() => store.dispatch({type: 'add', good: {title: `天貓商品${Date.now()}`, price: 200}})}>添加購物車</button> </div> ) } // 購物車組件 function Cart() { let goods = store.getState().goods return ( <ul> { goods.map((good, index) => { return ( <li key={index}> <span>{good.title}</span> <button onClick={() => store.dispatch({type: 'delete', good: {index}})}>刪除</button> </li> ) }) } </ul> ) } // 使用 react-router-dom 配置兩個頁面 function App() { return ( <div className="App"> <div> <Link to="/tb">淘寶</Link> | <Link to="/tm">天貓</Link> </div> <Route path="/tb" component={TbItem}></Route> <Route path="/tm" component={TmItem}></Route> <Cart></Cart> </div> ); } export default App
-
在 index.js 中訂閱 store
一旦 store 發(fā)生更改禁灼,則重新 render 整個 App
/*************index.js***************/ import React from 'react' import ReactDOM from 'react-dom' import App from './App' import store from './store' import {BrowserRouter} from 'react-router-dom' const render = () => { ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter>, document.getElementById('root') ) } // 第一次啟動時管挟,直接渲染 render() // 監(jiān)聽 store 的變化 store.subscribe(render)
二. react-redux 及中間件使用
從上面的例子中可以看到,使用原生的 redux 對 store 的讀取和操作都比較麻煩弄捕,同時還需要在 index.js
上手動調(diào)用渲染函數(shù)僻孝。因此,在實際項目中守谓,通常使用 react-redux 庫結(jié)合 redux-thunk 中間件(用于異步操作)來管理狀態(tài)穿铆,同樣以上述中的例子,看看 react-redux 的實現(xiàn)有什么不同斋荞。
-
安裝
npm install react-redux redux-thunk --save
-
同樣需要創(chuàng)建一個倉庫來存儲
一般將 reducer 獨立為一個文件書寫荞雏,使代碼結(jié)構(gòu)更加清晰
/************cart.redux.js************/ // cartReducer 與上一章節(jié)保持一致 const cartReducer = (state = { goods: [] }, action) => { // ... } // 定義 action 操作 const addGood = (good) => ({type: 'add', good}) const deleteGood = (good) => ({type: 'delete', good}) // 異步 action,使用 thunk 中間件之后可以如下書寫 const asyncAddd = (good) => dispatch => { setTimeout(() => { dispatch({type: 'add', good}) }, 2000) } // 將 reducer 和 action 方法導(dǎo)出 export {cartReducer, addGood, deleteGood, asyncAddd}
-
使用 Provider 包裹 App,并傳遞 store 參數(shù)
/****************index.js******************/ import React from 'react' import ReactDOM from 'react-dom' import App from './App' import {BrowserRouter} from 'react-router-dom' import {Provider} from 'react-redux' import {createStore, applyMiddleware} from 'redux' import thunk from 'redux-thunk' import {counterReducer} from './cart.redux' const store = createStore(counterReducer, applyMiddleware(thunk)) ReactDom.render( <BrowserRouter> <Provider store={store}> <App /> </Provider> </BrowserRouter>, document.querySelector('#root') )
-
使用 connect 高階組件加工 App 組件
/****************app.js******************/ // ... import {connect} from 'react-redux' import {addGood, deleteGood, asyncAdd} from './reactRedux/cart.redux' function App() { // ... } App = connect( state => ({goods: state.goods}), {addGood, deleteGood, asyncAdd} )(App) export default App
也可以配置裝飾器模式凤优,采用裝飾器模式書寫:
@connect( state => ({goods: state.goods}), {addGood, deleteGood, asyncAdd} ) // 使用裝飾器時需要用 class 聲明組件 class App extends React.Component { // ... }
在 App 組件內(nèi)需要讀取商品列表可采用
this.props.goods
獲取悦陋,需要添加商品則可以調(diào)用this.props.addGood(good)
,需要刪除商品時則可以調(diào)用this.props.deleteGood(good)
筑辨。至于如何將參數(shù)傳遞給子組件TbItem
和TmItem
則有三種實現(xiàn)方案:使用父子組件通信的方法俺驶,通過
<Provider></Provider>
<Consumer></Consumer>
實現(xiàn);同樣用 connect 高階組件裝飾一下組件棍辕,組件內(nèi)通過
this.props
獲取 store 中的屬性和方法暮现;-
利用 context 實現(xiàn),在 Parent 組件中定義 靜態(tài)屬性
childContextTypes
和getChildContext
方法楚昭,Child 組件中定義 靜態(tài)屬性contextTypes
并在構(gòu)造函數(shù)中將 context 傳遞給super()
函數(shù)/****************父組件******************/ // ... import PropTypes from 'prop-types' class App extends React.Component { static childContextTypes = { goods: PropTypes.object } getChildContext() { return { goods: this.props.goods }; } // ... } /****************子組件******************/ class TbItem extends React.Component { static contextTypes = { goods: PropTypes.object, } constructor(props, context){ super(props, context) // ... } // ... }