Flux --gt; Redux --gt; Redux React 入门 基础实例教程
本文的目的很簡單,介紹Redux相關概念用法 及其在React項目中的基本使用
?
假設你會一些ES6、會一些React、有看過Redux相關的文章,這篇入門小文應該能幫助你理一下相關的知識
一般來說,推薦使用?ES6+React+Webpack?的開發模式,但Webpack需要配置一些東西,你可以先略過,本文不需要Webpack基礎
入門,只是一些基礎概念和用法的整理,更完整的內容推薦去看看文檔,英文,中文
(不過我個人認為,官方文檔的例子相對來說太復雜了,很難讓新手馬上抓住重點)
(官方的例子正統且聯系業務,不同類型的操作或數據放在不同文件中,很規范,但也很繞,所以本文使用的例子非常簡單,且直接放在一個文件中 以便于理解)
一、Flux
Flux是一種概念思想,或者說是一種應用架構
根據它的概念,一個應用中的數據流動應是單向的,且應用中的所有數據保存在一個位置,數據變化時保證視圖也同步變化,保證了數據和視圖的狀態是一一對應起來的
此應用應該分為四層:
view層:應用的視圖,頁面的(數據)展示
action層:(視圖)發出的某些動作,比如點擊事件
dispatcher層:派發器,接收action并處理這些動作,更新數據
store層:存放應用的數據,數據更新后,提醒view層更新視圖
它的概念思想可能一時半會理解不了,沒關系,過段時間就好了
?
二、Redux
上面說到,Flux只是一個思想,我們可以根據這個思想來自己實現出一個技術方案,來解決問題
是要解決什么問題呢?
在使用React的過程中,在組件間通信的處理上我們用了回調的方式,如果組件層級很深,不同組件間的數據交流就會導致回調及其觸發的函數非常多,代碼冗雜
需要一個狀態管理方案,方便管理不同組件間的數據,及時地更新數據
而Flux思想中的Store層,切合了這個問題
?
1. 什么是Redux
Redux是受Flux啟發實現的一個技術方案,可以認為它是Flux的產物,但它并沒有沿用Flux所有的思想
主要區別是Flux的派發器dispatcher,Redux認為使用派發器就得增加事件訂閱/發布的規則,倒不如直接用函數調用的方式來得實在,簡單而統一,所以就將處理action的任務交給了store層(直接調用這個對象的dispatch方法)
2.?什么時候用Redux
Redux說簡單簡單,因為也就幾個API,理解好概念就好用了;說復雜也復雜,因為它將一個應用分成了不同部分(action、處理action、store數據等),在正規的項目中是推薦將各部分區分到不同文件中的(如官方的例子),文件數量很多可能會比較難管理,當然,細粒化了也就減少了耦合度。最后還要加個操作把Redux的數據更新給React組件(如果用了React)
在大多數情況下,Redux是不需要用的,如UI層非常簡單,沒有太多互動的
用戶的使用方式非常簡單
用戶之間沒有協作
不需要與服務器大量交互,也沒有使用 WebSocket
視圖層(View)只從單一來源獲取數據
?
而在多交互,多數據源的時候可以考慮使用
用戶的使用方式復雜
不同身份的用戶有不同的使用方式(比如普通用戶和管理員)
多個用戶之間可以協作與服務器大量交互,或者使用了WebSocketView
要從多個來源獲取數據
在需要管理復雜組件狀態的時候,可以考慮使用
某個組件的狀態,需要共享某個狀態
需要在任何地方都可以拿到一個組件
需要改變全局狀態一個組件
需要改變另一個組件的狀態
?
3. 開始用Redux
上面講了那么多字,還是看代碼來得實在
這里先純粹講Redux,畢竟它和React是沒啥關系的
首先是環境配置,基本上都會使用ES6,所以Babel的支持是必須的
然后是Redux的支持,如果使用Webpack打包編譯,就用npm安裝個redux包
這里采用直接在瀏覽器引入的方式,使用?這個庫
<body><div id="box"></div><script type="text/javascript" src="../lib/react.js"></script><script type="text/javascript" src="../lib/react-dom.js"></script><script type="text/javascript" src="../lib/redux.min.js"></script><script type="text/javascript" src="../build/reduxStart.js"></script></body>最后build里的為demo代碼用babel編譯之后的es5文件
在全局之中有Redux這個對象,取其中的幾個屬性來用
let {Component} = React; let {render} = ReactDOM; let {createStore, combineReducers} = Redux;3.1 Redux需要一個store來存放數據
這個store就由createStore創建
3.2 需要定義各個操作是什么,即action
通常來說它是一個對象,包含type屬性表示是什么操作,以及其他屬性攜帶一些數據
它可能長這樣子,建議是遵循官方的?一些規范
let upAction = {type: 'UP'};我們不止會傳type,還會傳一些值,如果傳不同的值就let一次就太冗雜了,一般來說就會用一個方法代替
let upAction = function(value) { ? ?return {type: 'up',value}; };3.3 需要定義怎么處理操作,在redux中它被稱作reducer
為什么把這種操作稱作reducer呢
redux引入了JS數組reduce方法的思想,JS的reduce長這樣
var arr = [1, 2, 3, 4];var num = arr.reduce((a, b) => { ? ?return a + b; });num // 10var num = arr.reduce((a, b) => { ? ?return a + b; }, 5);num // 15當然了,只是看起來像,實際上差別挺大的,redux的reducer看起來像這樣
let upReducer = function(state = 0, action) { ? ?switch (action.type) { ? ? ? ?case 'up': ? ? ? ? ? ?return state + action.value; ? ? ? ?default: ? ? ? ? ? ?return state;} };它是一個函數,接收兩個參數,第一個參數為數據(即某個狀態state),第二個參數為action操作對象
為了切合store中數據與view中視圖是一一對應的,reducer規定需始終返回新的state數據,不能直接在原有state中修改;
并且,建議在匹配不到action的時候始終返回默認的state狀態,且建議在第一個參數中初始化默認的state值
?
3.4 在創建store的時候綁定reducer
redux基本上把所有操作都給了store,所以大部分方法都是用store來調用的
其實,你也可以認為Flux中的派發器(dispatcher)就是在里面自動綁定的
let store = createStore(reducer);// let store = createStore(reducer, 10);如上,創建store的時候傳入reducer,可以接收第二個參數表示reducer使用的默認值
3.5 視圖發出action動作
在某個時刻,發出了這些動作
store.dispatch(upAction(10)); store.dispatch(upAction(100));3.6 使用store.getState()獲取store中的數據
3.7 動作發出后,reducer匹配動作更新store中的數據,視圖view層使用subscribe監聽數據的改變
store.subscribe(() => console.log(store.getState()));來看一下完整的代碼
let {Component} = React; let {render} = ReactDOM; let {createStore, combineReducers} = Redux;let upAction = function(value) { ? ?return {type: 'up',value}; }let upReducer = function(state = 0, action) { ? ?switch (action.type) { ? ? ? ?case 'up': ? ? ? ? ? ?return state + action.value; ? ? ? ?default: ? ? ? ? ? ?return state;} };let store = createStore(upReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());console.log(store.getState());store.subscribe(() => console.log(store.getState()));store.dispatch(upAction(10)); store.dispatch(upAction(100));注意上面createStore中第二個參數是用于Redux DevTool的配置,即這個東西
使用這個工具可以便于開發
看看上面代碼的輸出
初始獲取到的值為0,兩次action后分別更新相關的數據狀態。如果加上初始默認值10
let store = createStore(upReducer, 10, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());?
3.8 使用多個reducer時,使用Redux的combineReducers方法
action當然不會只是up,可能是down,這時可以直接用switch語句切換;但如果action不是這里增減的操作,放在一起就有點亂套了
所以需要定義多個reducer,但createStore方法只接收一個reducer,所以就需要整合多個reducer為一個,再統一傳入
它看起來像這樣
let reducer = combineReducers({upReducer, downReducer});// let reducer = combineReducers({// ? ? upReducer: upReducer, // ? ? downReducer: downReducer// });接收一個reducer組成的對象,屬性表示該reducer對應的state名(如state.upReducer),值表示這個reducer
當然,這個方法我們可以自己定義,看起來是這樣
let myCombineReducers = function(reducerObj) {let newState = {}; ? ?return function(state = {}, action) { ? ? ? ?for (let item in reducerObj) {newState[item] = reducerObj[item](state[item], action);} ? ? ? ?return newState;} };其實就是遍歷reducer組,返回一個統一的新的reducer,且新的reducer中返回一個新的state
?
加上個down操作,來看看完整代碼
let {Component} = React; let {render} = ReactDOM; let {createStore, combineReducers} = Redux;let upAction = function(value) { ? ?return {type: 'up',value}; } let downAction = function(value) { ? ?return {type: 'down',value}; }let upReducer = function(state = 0, action) { ? ?switch (action.type) { ? ? ? ?case 'up': ? ? ? ? ? ?return state + action.value; ? ? ? ?default: ? ? ? ? ? ?return state;} };let downReducer = function(state = 0, action) { ? ?switch (action.type) { ? ? ? ?case 'down': ? ? ? ? ? ?return state - action.value; ? ? ? ?default: ? ? ? ? ? ?return state;} };let reducer = combineReducers({upReducer, downReducer});let store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());console.log(store.getState());store.subscribe(() => console.log(store.getState()));store.dispatch(upAction(10)); store.dispatch(upAction(100)); store.dispatch(downAction(10)); store.dispatch(downAction(100));給reducer設個初始值,要注意的是,這個初始值是針對整個state的
如果只有一個reducer,那reducer函數中的state就是這個state
如果用combineReducer整理了多個reducer,那各個reducer函數中的state是整個state中的reducer同名屬性的值
let reducer = combineReducers({upReducer, downReducer});let store = createStore(reducer, {upReducer: 10, downReducer: 10}, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() );如上代碼定義了初始值,看看執行結果
?
四. 在React中使用Redux
Redux是一個獨立的技術方案,我們將它運用到React項目中
接下來的問題主要有三個:
如何將store中的數據同步給React組件
如何讓React組件調用Redux的dispatch方法
上面兩個
直接點,就是在React組件中調用Redux的subscribe方法來監聽同步數據,再在某個時機調用dispatch即可
但官方并不建議使用subscribe這個方法,而是建議使用封裝好的另一個庫?React-Redux
?
4.1 引入庫
與引入Redux類似,你可以使用Webpack引入包或瀏覽器直接引入這個庫
然后在全局window下可以獲取到這個對象,取一些用到的屬性如
let {Provider, connect} = ReactRedux;4.2 先定義一個有增長操作的React組件
class Increase extends Component {constructor(props) {super(props);}componentWillReceiveProps(nextProps) {console.log(nextProps);}increase() {let {dispatch} = this.props;dispatch({type: 'up'});}render() { ? ? ? ?return <p onClick={this.increase.bind(this)}>increase: {this.props.number}</p> ? ?} }組件定義了一個action,即點一次執行一次增長(increase)函數,里面調用dispatch方法發出action,先看看其他東西
4.3 定義一個reducer
function couterUp(state = {number: 100}, action) { ? ?switch (action.type) { ? ? ? ?case 'up': ? ? ? ? ? ?return {number: state.number + 1}; ? ? ? ?default: ? ? ? ? ? ?return state;} }4.4 創建一個store
let store = createStore(couterUp, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());4.4 使用ReactRedux的connect方法
要將Redux中的數據同步給React,需要用到這個方法
它看起來像是這樣子
let APP = connect(mapStateToProps,mapDispatchToProps )(Increase);可以把它看成是一個中間件,首先接收幾個參數完成配置階段,然后傳入React組件,包裝成一個新的東東(它并沒有直接修改Increase組件)
而一般來說,一般來說會傳入兩個參數(支持四個參數),顧名思義:
第一個參數(類型為函數)
如果不傳或置入undefined或null,則表示不需要進行數據更新;否則表示將store中的數據通過props的形式傳給React組件
第二個參數(類型為函數)
如果不傳或置入undefined或null,則表示將React-Redux中默認的dispatch方法傳給React組件;否則表示將redux中的dispatch發出動作通過props的形式傳給React組件
注意到上面的React組件代碼中,通過props獲取到了dispatch方法,然后自行發出動作
increase() {let {dispatch} = this.props;dispatch({type: 'up'});}如果要這樣做,mapDispatchToProps?這里就不傳入了,即
let APP = connect(mapStateToProps )(Increase);用回常見的方式,在React組件中改一改,直接從props中獲取某個dispatch的發出動作
render() { ? ? ? ?return <p onClick={this.props.increase}>increase: {this.props.number}</p>}同時修改兩個都傳入
let APP = connect(mapStateToProps,mapDispatchToProps )(Increase);4.5?mapStateToProps?和?mapDispatchToProps
我們定義一下這兩個參數(函數),它看起來長這樣
function mapStateToProps(state) { ? ?return {number: state.number}; }function mapDispatchToProps(dispatch) { ? ?return {increase: () => dispatch({type: 'up'})}; }mapStateToProps?中第一個參數為一個對象,表示store中整體的state數據
當然,第一個參數也可以為函數,也可以接收第二個參數,表示自身擁有的屬性(ownProps),具體可以看API
最后它返回了一個新的對象,表示要傳給React組件的數據
與mapStateToProps類似,mapDispatchToProps?也可以接收兩個參數,
第一個表示當前的dispatch方法,第二個表示自身擁有的?propsDispatch方法(即類似省略參數傳給字組件的默認dispatch方法)
最后它返回了一個action發出動作(一個函數),傳給React組件調用
?
4.6 使用Provider
基本好了,只差一步:將connect包裝組件后生成的新東東與實際頁面聯系起來
使用ReactRedux提供的<Provider />,它看起來是這樣
render( ? ?<Provider store={store}><APP /></Provider>,document.getElementById('box') );使用store屬性傳入上面的store對象
在children中置入有connect生成的APP組件,注意這里只能包含一個父層
如果向其中傳入屬性,如
<APP name="app" />那么,mapStateToProps中的第二參數ownProps就可以擁有這個name屬性
看一下運行結果
4.7 多個React組件中的使用
上面說的是單個React組件中的使用,實際使用中會有多個組件
多個組件的使用類似單個,只不過需要注意兩點
<Provider />中只能包含一個父級
mapStateToProps中第一個參數是指整體store中的數據
下面以兩個組件的栗子,看看如何實現
?
4.7.1 首先定義兩個組件,一增一減
class Increase extends Component {constructor(props) {super(props);}componentWillReceiveProps(nextProps) {console.log('increase: ', nextProps);}render() {return <p onClick={this.props.increase}>increase: {this.props.number}</p>} }class Decrease extends Component {constructor(props) {super(props);}componentWillReceiveProps(nextProps) {console.log('decrease: ', nextProps);}render() {return <p onClick={this.props.decrease}>decrease: {this.props.number}</p>} }4.7.2 定義對應的兩個reducer
function couterUp(state = {number: 100}, action) { ? ?switch (action.type) { ? ? ? ?case 'up': ? ? ? ? ? ?return {number: state.number + 1}; ? ? ? ?default: ? ? ? ? ? ?return state;} }function counterDown(state = {number: -100}, action) { ? ?switch (action.type) { ? ? ? ?case 'down': ? ? ? ? ? ?return {number: state.number - 1}; ? ? ? ?default: ? ? ? ? ? ?return state;} }4.7.3 創建store
let couter = combineReducers({couterUp,counterDown });let store = createStore(couter,{couterUp: {number: 10}},window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() );4.7.4 創建連接兩個組件對應的兩個mapStateToProps 和 mapDispatchToProps
注意state為整個store中的state,取值要取各reducer同名屬性如?state.couterUp
function mapStateToProps_1(state) { ? ?return {number: state.couterUp.number}; }function mapDispatchToProps_1(dispatch) { ? ?return {increase: () => dispatch({type: 'up'})}; }function mapStateToProps_2(state, props) { ? ?return {number: state.counterDown.number}; }function mapDispatchToProps_2(dispatch) { ? ?return {decrease: () => dispatch({type: 'down'})}; }4.7.5 ?各組件用connect包裝
let APP_1 = connect(mapStateToProps_1,mapDispatchToProps_1 )(Increase);let APP_2 = connect(mapStateToProps_2,mapDispatchToProps_2 )(Decrease);4.7.6 ?置入<Provider />中
注意只能有一個父級,所以得先簡單包裝一層
let APP = () => ( ? ?<div><APP_1 /><APP_2 name="APP_2"/></div>);render( ? ?<Provider store={store}><APP /></Provider>,document.getElementById('box') );Good ! 完成了,看看結果
?
4.7.7 ?再看connect方法剩余的兩個參數
connect方法接收可接收四個參數,上面已經談到了前兩個,后兩個不那么常用
第三個參數,這里不多說:[mergeProps(stateProps, dispatchProps, ownProps): props] (Function)
第四個參數:[options]?(Object)
這個options中有如下幾個屬性:
pure: true(默認)|false 表示是否在調用connect前三個參數的函數方法之前先檢測前后store中的值是否改變,改變才調用,否則不調用
areStatesEqual: 函數,當pure為true時調用這個函數檢測是否相等,返回true|false表示是否相等,默認以嚴格相等===來判斷前后值是否相等
areOwnPropsEqual: 類似areStatesEqual,只不過它默認是用不嚴格相等==來判斷
areStatePropsEqual:?類似areStatesEqual,只不過它默認是用不嚴格相等==來判斷
areMergedPropsEqual:?類似areStatesEqual,只不過它默認是用不嚴格相等==來判斷
來看個例子,現在要手動的定義這個參數
針對Decrease,在減1時直接返回了false
let APP_2 = connect(mapStateToProps_2,mapDispatchToProps_2, ? ?null,{pure: true,areStatesEqual: (next, prev) => {console.log(next.counterDown, prev.counterDown); ? ? ? ? ? ?return next.counterDown.number < prev.counterDown.number;}} )(Decrease);可以看到,減1的操作并沒有傳給Decrease組件,頁面沒有更新
?
順便看看有connect包裝后的組件
?
4.7.8 在React-Redux中使用異步
因Redux中操作的執行是同步的,如果要實現異步,比如某個操作用來發個異步請求獲取數據,就得引入中間件來處理這種特殊的操作
即這個操作不再是普通的值,而是一個函數(如Promise異步),通過中間件的處理,讓Redux能夠解析?
先修改上面的栗子,在Increase組件中不再是每次增加1,而是根據action中的value來指定,比如
function mapDispatchToProps_1(dispatch) { ? ?return {increase: () => dispatch({type: 'up',value: 10})}; } function couterUp(state = {number: 100}, action) { ? ?switch (action.type) { ? ? ? ?case 'up': ? ? ? ? ? ?return { ? ? ? ? ? ? ? ?// number: state.number + 1number: state.number + action.value}; ? ? ? ?default: ? ? ? ? ? ?return state;} }這里定義了value是10,但假如value的值得由一個異步的請求才得出呢,要如何放進去
?
使用Redux提供的中間件applyMiddleware
let {createStore, combineReducers, applyMiddleware} = Redux;這只是基礎的中間件apply函數,它幫助Redux將中間件包裝
現在來模擬一個異步請求
function mapDispatchToProps_1(dispatch) { ? ?return { ? ? ? ?// increase: () => dispatch({// ? ? type: 'up',// ? ? value: 10// })increase: () => dispatch(fetchIncreaseValue('redux-ajaxTest.php'))}; }可一看到,dispatch中的action是一個函數(這個調用返回的還是一個函數),而Redux默認只支持對象格式的action,所以這樣會報錯
這里的fetchIncreaseValue看起來像這樣
function fetchIncreaseValue(url) { ? ?return function(dispatch) { ? ? ? ?return $.get(url).then(re => {re = JSON.parse(re);console.log(re);dispatch({type: 'up',value: re.value});})} }而請求后臺后返回值
<?php ? ?echo json_encode(array('value' => 100));?>可以看到,異步獲取數據之后才執行dispatch發出操作,這里需要一個dispatch關鍵字
為了拿到這個關鍵字,得和thunkMiddleware搭配使用(讓這個方法能夠在內層函數中使用),當然,你也可以再搭配其他中間件
如果使用Webpack打包,就安裝好?redux-thunk?包再 import 進來
這里直接引入到瀏覽器中,引入這個庫,然后直接使用(注意這里沒有?{}?)
let thunkMiddleware = window.ReduxThunk.default;然后在創建store的時候,傳給redux的applyMiddleware即可
let store = createStore(couter,{couterUp: {number: 10}},applyMiddleware(thunkMiddleware),window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() );官方給的例子太復雜了,不過還是去看看吧,我這里抽出了主要的部分,
先來看看結果
?
使用這個Redux Dev Tool就得在createStore中配上最后一個參數,而createStore自身的某個參數又能給reducer設置初始值,且applyMiddleware也是在參數中定義
所以要注意的是:
如果用了這個Redux Dev?Tool,就要保證applyMiddleware在第三個參數
// {},applyMiddleware(thunkMiddleware),window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() );
類似這樣省略第二個初始值參數,是會報錯的
把注釋去掉,放上一個空的初始即可,或者不用這個Dev?Tool
let store = createStore(couter,applyMiddleware(thunkMiddleware) );可以去看看其他的Dev Tool
原文地址:http://www.cnblogs.com/imwtr/p/6327384.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的Flux --gt; Redux --gt; Redux React 入门 基础实例教程的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 在ASP.NET Core Web AP
 - 下一篇: 微软开源Visual Studio测试平