摘要:因為最近搞懂了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是一個很迷茫的東西,因為如果沒有項目導向,學完就很容易忘記,所以之前學過的基本都忘光了。
前端水很深啊,共勉之!!!
