在使用React和React-router實現單頁面應用時,會有這樣一個場景:從列表頁面點選某項條目進入詳情頁,然後回退至列表頁面時,列表頁面會重新重新整理,不僅資料重新獲取了,滾動條也回到了頂部。使用者要繼續檢視剩餘資料的話,需要重新滑動到之前點選的那個條目,如果列表做了分頁的話就更麻煩了,這對於使用者體驗來說是非常不好的。
所以我們希望能做到,從二級頁面回退至列表頁面時,列表頁面能保留之前的狀態(資料和滾動條位置)。
那麼怎麼實現呢?下面分享一下React通過redux來快取列表資料以及滑動位置,以達到保留列表頁面狀態的方法。
關於redux以及react-redux的使用,這裡就不做講解了,可以參考我之前寫的 React-redux的原理以及使用 。當然網路上有很多講解得更清晰的文章,讀者可以自行搜尋。
下面直接進入正題,介紹實現需求的步驟吧
1、安裝redux以及react-redux
cnpm install redux react-redux -dev --save
2、編寫操作列表頁面相關資料的action
/**
* Created by RaoMeng on 2018/12/10
* Desc: 列表資料快取
*/
import {CLEAR_LIST_STATE, LIST_STATE} from "../constants/actionTypes";
import store from '../store/store'
/**
* 儲存列表狀態
* @param data
* @returns {Function}
*/
export const saveListState = (data) => {
return () => {
store.dispatch({
type: LIST_STATE,
...data
})
}
}
/**
* 清除列表狀態
* @returns {Function}
*/
export const clearListState = () => {
return () => {
store.dispatch({
type: CLEAR_LIST_STATE
})
}
}
複製程式碼
這裡實現了兩個actionType,一個是儲存列表狀態,一個是清除列表狀態。 儲存列表狀態就是為了達到回退時不重新整理頁面的需求; 清除列表狀態則是因為:從選單頁面進入列表頁面時,是要求重新載入頁面資料的,假如不清除redux中的快取資料,頁面就會讀取快取資料而不會重新請求網路資料,所以這個action也是很有必要的。
3、實現配合action操作state的reducer
import {CLEAR_LIST_STATE, LIST_STATE} from "../constants/actionTypes";
const initListState = {
scrollTop: 0,//列表滑動位置
listData: [],//列表資料
pageIndex: 1,//當前分頁頁碼
itemIndex: -1,//點選的條目index
}
const redListState = (state = initListState, action) => {
if (action === undefined) {
return state
}
switch (action.type) {
case LIST_STATE:
//更新列表狀態
return {
...state,
...action
}
case CLEAR_LIST_STATE:
//清空列表狀態
return initListState
default:
return state
}
}
export default redListState
複製程式碼
/**
* Created by RaoMeng on 2018/12/10
* Desc: 資料處理中心
*/
import {combineReducers} from 'redux'
import redUserInfo from './redUserInfo'
import redListState from './redListState'
import redClassData from './redClassData'
const reducers = combineReducers({redUserInfo, redListState, redClassData})
export default reducers
複製程式碼
這裡解釋下為什麼要記錄分頁頁碼以及點選的條目index。 記錄分頁頁碼只是在列表資料做了分頁的情況下需要。是為了回退到列表頁面後,使用者繼續上拉載入資料時頁碼是正確的。 記錄點選的條目index則是為了能在詳情頁更新所點選的條目資料。比如說一個會議簽到列表,使用者點選某條資料進入詳情頁後,點選簽到按鈕,這時我們要根據itemIndex來呼叫action的*saveListState()()*方法更新快取中相應的資料,將該條資料的狀態改為已簽到。這樣回退至列表頁面時,該條資料的展示才會正確。
4、建立store
import {createStore} from 'redux'
import reducers from '../reducers/index'
import {persistStore, persistReducer} from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
const persistConfig = {
key: 'root',
storage: storage,
stateReconciler: autoMergeLevel2 // 檢視 'Merge Process' 部分的具體情況
};
const myPersistReducer = persistReducer(persistConfig, reducers)
const store = createStore(myPersistReducer)
export const persistor = persistStore(store)
export default store
複製程式碼
這裡用到了redux-persist來實現redux資料的持久化儲存,我在 React通過redux-persist持久化資料儲存 有做簡單講解。
5、在點選條目的回撥事件中呼叫saveListState方法儲存列表狀態
<父佈局
ref={el => {
this.container = el
}}
> </父佈局>
onItemClick = index => {
console.log('scrollTop', ReactDOM.findDOMNode(this.container).scrollTop)
saveListState({
scrollTop: ReactDOM.findDOMNode(this.container).scrollTop,
listData: this.state.meetingSignList,
pageIndex: mPageIndex,
itemIndex: index,
})()
const {meetingSignList} = this.state
this.props.history.push('/meet-detail/' + meetingSignList[index].meetId)
}
複製程式碼
通過ReactDOM.findDOMNode(this.container).scrollTop來獲取父佈局的滑動距離
6、在頁面的componentDidMount方法中獲取redux資料
首先通過react-redux的connect方法將state中的資料繫結到頁面的props中,方便訪問
let mapStateToProps = (state) => ({
listState: {...state.redListState}
})
let mapDispatchToProps = (dispatch) => ({})
export default connect(mapStateToProps, mapDispatchToProps)(MeetingSignIn)
複製程式碼
這樣,在頁面中就可以通過this.props.listState來訪問redux中快取的列表資料了
然後,在componentDidMount中獲取快取的列表資料,如果有快取資料,則載入,如果沒有則重新請求
componentDidMount() {
document.title = '會議管理'
console.log('listState', this.props.listState)
if (this.props.listState && !isObjEmpty(this.props.listState.listData)) {
this.setState({
meetingSignList: this.props.listState.listData,
isLoading: false,
}, () => {
ReactDOM.findDOMNode(this.container).scrollTop = this.props.listState.scrollTop
})
mPageIndex = this.props.listState.pageIndex
} else {
Toast.loading('資料載入中...', 0)
mPageIndex = 0
this.loadMeetList()
}
}
複製程式碼
這樣就實現了React通過redux快取列表資料以及滑動位置,回退時恢復頁面狀態的需求。