摘要:因為最近搞懂了redux的異步操作,所以覺得可以用react+redux來做一個小小的項目了,以此來加深一下印象。切記,是小小的項目,所以項目肯定是比較簡單的啦,哈哈。
項目效果圖如圖所示:(因為一張圖實現起來太長了 ,所以切成了兩半,略丑,罪過!!)
項目的github地址:https://github.com/xianyulaodi/react-redux
之前寫過一篇文章是redux的異步操作,本文只是將其應用到了實戰當中。
用react+redux的步驟是,首先需要知道大概有哪些狀態,上圖中,有以下幾個狀態:
同步狀態:排行版和規則頁切換的狀態
異步狀態:熱氣球星榜和魔術帽星榜等四個tab切換的狀態,查看上周的狀態
是的,不要搞那么復雜,這個活動頁就這幾個狀態的,不過這樣對於入門很有好處,因為比較好理解嘛
talk is cheap,show me the code。
來看我們的 actions
actions/index.js
// 由於目前大多數瀏覽器原生還不支持它,建議你使用 isomorphic-fetch 庫: // 每次使用 `fetch` 前都這樣調用一下 import fetch from 'isomorphic-fetch' export const RECEIVE_POSTS = 'RECEIVE_POSTS'; export const IS_LASTWEEK = 'IS_LASTWEEK'; export const TAB_CHANGE = 'TAB_CHANGE'; export const GIFT_ID_CHOICE = 'GIFT_ID_CHOICE'; // 本案例的狀態有以下幾個: // 1、上周和本周的切換 // 2、點擊四個tab的切換 // 3、排行版和規則頁的切換 /* 上周和本周的 action 其中weekOffset有兩個值,本周為0,上周為1 */ export function weekChoice(weekOffset) { return { type: IS_LASTWEEK, weekOffset } } /* 熱氣球星榜和魔術帽星榜的action,值為giftId giftId的值可為401,402 其中401是熱氣球,402是魔術帽 */ export function giftIdChoice(giftId) { return { type: GIFT_ID_CHOICE, giftId } } // 排行版和規則頁切換 export function tabChange(tabId) { return { type: TAB_CHANGE, tabId } } /* 獲取數據成功的action,將所有的數據傳回去 返回的狀態為 posts */ function receivePosts(reddit, json) { return { type: RECEIVE_POSTS, posts:json } } /** 請求的函數,傳值從這里傳 **/ function fetchPosts(weekOffset,giftId) { return function (dispatch) { // data.weekOffset=weekOffset; // 0 本周 1上周 // data.giftId=giftId; // 禮物id 401是熱氣球,402是魔術帽 return fetch(`http://api.ys.m.yy.com/api/internal/gift/rank.json?data={"platform":1,"weekOffset":${weekOffset},"giftId":${giftId},"uid":0}`) .then(response => response.json()) .then(json => dispatch(receivePosts(weekOffset, json)) ) } } //獲取數據 export function fetchPostsIfNeeded(weekOffset,giftId) { return (dispatch, getState) => { return dispatch(fetchPosts(weekOffset,giftId)) } }
這里定義了幾個action,是根據上面說的同步和異步的狀態來定義的,
定義了選擇是上周還是本周的action weekChoice,並返回一個weekOffset,這個值是要傳給請求的一個參數。0為本周,1為上周
定義了是熱氣球還是星球帽的action giftIdChoice,並返回giftId,這個也是傳給請求的一個參數。401為熱氣球的,402為星球帽
定義了排行版和規則頁切換的action tabChange,並返回一個tabId, 0為排行版,1為規則頁。這個頁面的切換是同步的。
其他的比如是fetch的請求的action可以參考我之前寫一篇文章,地址為:這里是上一篇文章地址。
action的代碼不多,接下來說一說reducer的代碼:
reducers/index.js
import { combineReducers } from 'redux' import { RECEIVE_POSTS, IS_LASTWEEK, GIFT_ID_CHOICE, TAB_CHANGE } from '../actions' function isNextWeek(state = 0, action) { switch (action.type) { case IS_LASTWEEK: return action.weekOffset //這里面的值是和action里面的值對應的 default: return state } } function tabIdState(state=0,action){ switch(action.type){ case TAB_CHANGE : return action.tabId default: return state } } function giftId(state = 401, action) { switch (action.type) { case GIFT_ID_CHOICE: return action.giftId //這里面的值是和action里面的值對應的 default: return state } } /** Object.assign是ES6的一個語法。合並對象,將對象合並為一個,前后相同的話,后者覆蓋強者。詳情可以看這里 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign 例如: var obj = { a: 1 }; var copy = Object.assign({}, obj,{'b':2}); console.log(copy); // { a: 1,b:2 } var obj = { a: 1 }; var copy = Object.assign({}, obj,{'a':2}); console.log(copy); // { a: 2} **/ function receiveData(state ={}, action) { switch (action.type) { case RECEIVE_POSTS: return Object.assign({}, state, { items: action.posts //請求得到的數據都存在了這里 }) default: return state } } // 將所有的reducer結合為一個,傳給store const rootReducer = combineReducers({ receiveData, isNextWeek, giftId, tabIdState }) export default rootReducer
reducre也比較簡單,Action對象僅僅是描述了行為的相關信息,至於如何通過特定的行為來更新state,就需要看看Reducer了。
從上面的代碼可以得知,reducer有一下幾點特性,
1、它是一個純函數。
2、它接收兩個參數,一個是舊的狀態previousState和一個Action對象。
3、它返回一個新的狀態NewState。 你可以返回任何的東西,對象、數組、字符串等等等等
比如我的代碼中,當接收到 IS_LASTWEEK狀態描述,我們就返回一個新的 weekOffset ,當接收到 RECEIVE_POSTS的狀態描述,我們就返回一個對象,將數據返回到items對象里面。等等等等。反正通過reducer,當你接收到某個狀態描述時,可以返回任何東西,而且state需要的話可以給一些默認值。
其實這些拆分開來的reducer的函數名,才是供View使用的狀態值。比如我reducer里的那些純函數,其實到達VIEW這里是這樣的,通過store,這樣所有的狀態都集中到了這里了。
通過 combineReducers,將多個reducer純函數結合為一個。
接下來,container/app.js
import React, { Component, PropTypes } from 'react' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import {fetchPostsIfNeeded,weekChoice,giftIdChoice,tabChange} from '../actions' import * as TodoActions from '../actions' class App extends Component { constructor(props) { super(props) this.weekChoiceFn = this.weekChoiceFn.bind(this) this.giftIdChoiceFn = this.giftIdChoiceFn.bind(this) this.tabFn = this.tabFn.bind(this) } //初始化渲染后觸發 componentDidMount() { const { dispatch,isLastWeek,giftId} = this.props dispatch(fetchPostsIfNeeded(isLastWeek,giftId)) } //每次接受新的props觸發 componentWillReceiveProps(nextProps) { if ((nextProps.isLastWeek !== this.props.isLastWeek) || (nextProps.giftId !== this.props.giftId) ) { const { dispatch, isLastWeek ,giftId} = nextProps dispatch(fetchPostsIfNeeded(isLastWeek,giftId)) } } // 上周還是本周 weekChoiceFn(isLastweek) { this.props.dispatch(weekChoice(isLastweek)); } // 送出熱氣球星榜還是魔術帽星榜 giftIdChoiceFn(giftId) { this.props.dispatch(giftIdChoice(giftId)); } tabFn (tabId) { this.props.dispatch(tabChange(3)); } render() { const { receiveData } = this.props //this.props里面包含着所有的狀態 return ( <div> <a href="#" onClick={this.weekChoiceFn.bind(this,1)}>上周</a> <br /> <a href="#" onClick={this.weekChoiceFn.bind(this,0)}>本周</a> <br /> <a href="#" onClick={this.giftIdChoiceFn.bind(this,401)}>熱氣球星榜</a><br /> <a href="#" onClick={this.giftIdChoiceFn.bind(this,402)}>魔術帽星榜</a><br /> <a href="#" onClick={this.tabFn}> tabId </a> <br /> 這里是請求到的數據 <br />{JSON.stringify(receiveData)}<br /> </div> ) } } function mapStateToProps(state) { // 這里很重要,這里需要用到的狀態都要返回,不然無法實現 const { receiveData ,isLastWeek,giftId,tabIdState} = state return { receiveData, isLastWeek, giftId, tabIdState } } // function mapDispatchToProps(dispatch) { // return { // actions: bindActionCreators(TodoActions, dispatch) // } // } export default connect( mapStateToProps // mapDispatchToProps )(App)
其實注意點在這里比較多,首先,所有的狀態是需要返回的。這個地方很重要。因為通過 mapStateToProps 這樣頂級組件才可以拿到所有的狀態。
function mapStateToProps(state) { // 這里很重要,這里需要用到的狀態都要返回,不然無法實現 const { receiveData ,isLastWeek,giftId,tabIdState} = state return { receiveData, isLastWeek, giftId, tabIdState } }
其次,要拿到狀態值,可以通過this.pros這里拿,比如 const { receiveData } = this.props ,我們就通過this.props拿到了 receiveData的狀態值。
那么如何知道狀態值需不需要更新你,可以通過以下方法:
//每次接受新的props觸發 componentWillReceiveProps(nextProps) { if ((nextProps.isLastWeek !== this.props.isLastWeek) || (nextProps.giftId !== this.props.giftId) ) { const { dispatch, isLastWeek ,giftId} = nextProps dispatch(fetchPostsIfNeeded(isLastWeek,giftId)) } }
這里,每次接受到新的props時就會觸發這個函數,里面有一個參數,nextProps,就是最近的狀態值,我們可以和我們舊的狀態值做對比,從而做相應的處理。比如,我們只要isLastWeek或者giftId的參數不等,我們就重新發一次請求。
這個componentWillReceiveProps函數使比較重要的。不然你接收到新的參數也重新發送請求。所以要注意這一點。
其他部分的東西跟我之前的redux異步操作筆記的內容有點像,這里就不再進行介紹了。界面的渲染也不做了,偷一下懶,只做了幾個按鈕,有需要的可以參考一下就行。本文跟之前寫的文章有點小類似,就不發布到首頁了。
后記:
關於react生態的學習就暫時告一段了,因為現在所處的部門是公司的活動組,基本都是一直在搞活動,從未停止過。所以都不是很大型的項目,不過這些活動用來試水還挺好的,不好的地方就是缺乏一個大項目使用react生態的那種經驗。
接下來的學習重心可能會從react的生態鏈中轉移開,關注一下其他的東西。八月份到九月份的學習計划是再提升一下自己的javascript水平,看兩本書《精通javascript》和《css權威指南》,回歸一下基礎。然后十月份之后重新學習一下node.js, node.js是一個很迷茫的東西,因為如果沒有項目導向,學完就很容易忘記,所以之前學過的基本都忘光了。
前端水很深啊,共勉之!!!