這兩天在忙公司移動(dòng)端的項(xiàng)目没咙,我們采用的技術(shù)是React锰扶,配合Redux進(jìn)行數(shù)據(jù)的存取瞄桨,期間遇到了不少坑遣疯。
1.根據(jù)當(dāng)前的列表數(shù)list.size和列表總數(shù)count來(lái)判斷是否需要按鈕來(lái)執(zhí)行加載更多。
1.切換category扶镀,首先獲取categoryID蕴侣。
2.然后根據(jù)categoryID獲取List Count,將Count存入對(duì)應(yīng)的category中臭觉,同時(shí)將讀取狀態(tài)初始化并存入category中昆雀,后續(xù)會(huì)根據(jù)數(shù)據(jù)讀取狀態(tài)來(lái)判斷Loading是否顯示。
3.更改category中的讀取狀態(tài)蝠筑,顯示為獲取中狞膘,然后根據(jù)categoryID獲取List,將List存入對(duì)應(yīng)的Blog List中菱肖,再更改讀取狀態(tài)客冈,表示讀取成功。
handleClickCategory(e) {
const categoryID = parseInt(e.target.id.replace('blog_category_', ''), 10);
if (this.props.BlogStateActiveCategoryID !== categoryID) {
this.props.updateBlogStateActiveCategoryID(categoryID);
const blogListAfterFilter = this.props.BlogStateBlogList.filter((blog) => (blog.category === categoryID));
if (blogListAfterFilter.isEmpty()) {
this.updateActiveCategoryBlogCount(categoryID);//更新count
this.updateBlogList(categoryID);//獲取List并更新
}
}
}
//獲取count 并初始化fetch狀態(tài)
updateActiveCategoryBlogCount(categoryID) {
const { BlogStateCategory } = this.props;
getBlogCount(categoryID)
.then((blogCount) => {
this.props.updateBlogStateCategory(
BlogStateCategory.update(
BlogStateCategory.findIndex((categoryItem) => (categoryItem.get('id') === categoryID)),
(categoryItem) => (categoryItem.merge({
count: parseInt(blogCount, 10),
fetchPending: true,//表示讀取數(shù)據(jù)中稳强,需要顯示loading和更改button文案
fetchFail: false,
fetchSuccess: false,
}))
)
);
});
}
updateBlogList(categoryID) {
const { BlogStateBlogList, BlogStateCategory } = this.props;
this.props.updateBlogStateCategory(
BlogStateCategory.update(
BlogStateCategory.findIndex((categoryItem) => (categoryItem.get('id') === categoryID)),
(categoryItem) => (categoryItem.merge({
fetchPending: true,//表示讀取數(shù)據(jù)中场仲,需要顯示loading和更改button文案
fetchFail: false,
fetchSuccess: false,
}))
)
);
getBlogList(
categoryID,
Math.ceil(BlogStateBlogList.filter((blog) => (blog.category === categoryID)).size / DEFAULT_BLOG_LIST_SIZE)
)
.then((blogList) => {
setTimeout(() => {
// 以下使用BlogStateCategory等,最好加上 this.props.來(lái)使用store中的最新數(shù)據(jù)
this.props.updateBlogStateCategory(
this.props.BlogStateCategory.update(
this.props.BlogStateCategory.findIndex((categoryItem) => (categoryItem.get('id') === categoryID)),
(categoryItem) => (categoryItem.merge({
fetchPending: false,//數(shù)據(jù)讀取結(jié)束后退疫,將該狀態(tài)更新為false
fetchFail: false,
fetchSuccess: true,
}))
)
);
this.props.updateBlogStateBlogList(
BlogStateBlogList.push(...List(blogList))
);
}, 500);
})
.catch(() => {
this.props.updateBlogStateCategory(
this.props.BlogStateCategory.update(
this.props.BlogStateCategory.findIndex((categoryItem) => (categoryItem.id === categoryID)),
(categoryItem) => (Object.assign(categoryItem, {
fetchPending: false,
fetchFail: true,
fetchSuccess: false,
}))
)
);
});
}
問(wèn)題:
第一次將count和fetch狀態(tài)存入category成功渠缕,但獲取List之后再更改category中的數(shù)據(jù),會(huì)發(fā)現(xiàn)count不見(jiàn)了褒繁,只有fetch了亦鳞。
原因:原category被copy了兩份,一份被用于步驟2棒坏,一份被用于步驟3燕差。步驟2執(zhí)行成功之后,count和fetch均被保存坝冕。但是后續(xù)的步驟3完成之后徒探,步驟3中只更改了fetch,然后被存入category中喂窟,此時(shí)由于步驟3執(zhí)行在后测暗,在時(shí)間軸上,就將步驟2的跳過(guò)了磨澡,因此這個(gè)時(shí)候看數(shù)據(jù)碗啄,會(huì)發(fā)現(xiàn)是沒(méi)有count的。
解決辦法:
每次更改數(shù)據(jù)稳摄,都通過(guò)this.props.BlogStateCategory
來(lái)獲取最新的內(nèi)容進(jìn)行更改稚字,而不是在一開(kāi)始將它賦值于一個(gè)新變量,然后通過(guò)該變量來(lái)更新數(shù)據(jù)厦酬。因?yàn)樗@個(gè)是內(nèi)容的復(fù)制尉共,然后再替換褒傅,而不是通過(guò)指針來(lái)修改的。
換句話說(shuō)袄友,要改數(shù)據(jù),可以霹菊,但請(qǐng)?jiān)谧钚碌臄?shù)據(jù)上進(jìn)行更改剧蚣,否則在這中間被更改過(guò)的數(shù)據(jù)將被最后一次更改的數(shù)據(jù)替換掉。
2.導(dǎo)航切換時(shí)旋廷,狀態(tài)更新有問(wèn)題
1.切換category鸠按,首先獲取categoryID。
2.然后根據(jù)categoryID判斷當(dāng)前是否有數(shù)據(jù)饶碘,有目尖,就將內(nèi)容篩選出來(lái)顯示在頁(yè)面上,如果沒(méi)有扎运,更改fetch狀態(tài)為讀取中并執(zhí)行l(wèi)oading動(dòng)畫(huà)瑟曲,然后獲取List。
3.List獲取成功后豪治,將列表存入store中洞拨,更改fetch狀態(tài)為讀取結(jié)束,頁(yè)面上會(huì)自動(dòng)根據(jù)fetch狀態(tài)來(lái)判斷是否顯示Loading以及按鈕文案负拟。
4.由于獲取數(shù)據(jù)比較快烦衣,出現(xiàn)問(wèn)題之后認(rèn)為是兩次更改太接近,導(dǎo)致沒(méi)有看清掩浙,在獲取成功后加了5s的延遲花吟,結(jié)果是沒(méi)有變化,store中的變化沒(méi)有反映在頁(yè)面上厨姚。
5.或者在列表底部點(diǎn)擊加載更多按鈕衅澈,再內(nèi)容顯示出來(lái)之前,改變store中的pending狀態(tài)遣蚀,并更改按鈕文案矾麻。
出現(xiàn)的問(wèn)題:
切換不同的導(dǎo)航,可以看到fetch狀態(tài)確實(shí)發(fā)生的更改芭梯,但是頁(yè)面上的loading沒(méi)有顯示险耀,按鈕的文案也沒(méi)有變化,一直是加載更多玖喘。
原因:
在一開(kāi)始就已經(jīng)通過(guò)fromJS將數(shù)據(jù)改變成不可變對(duì)象了甩牺,此時(shí)要更改數(shù)據(jù)內(nèi)容,需要使用immutable的api來(lái)更改
解決辦法:
更改用merge
3.route設(shè)定indexRoute后累奈,子頁(yè)面跳轉(zhuǎn)后贬派,indexRoute會(huì)始終激活activeClassName急但。
在“關(guān)于我們”這一模塊中,嵌套了公共的menu搞乏,以及不同子頁(yè)面的內(nèi)容波桩,導(dǎo)航上設(shè)置了activeClassName,但是頁(yè)面跳轉(zhuǎn)后请敦,導(dǎo)航條上會(huì)有兩個(gè)被激活的樣式镐躲。
解決辦法:
在route中不設(shè)置indexRoute,而是改為onEnter事件侍筛,對(duì)路由進(jìn)行重定向萤皂。
但此時(shí)又出現(xiàn)了一個(gè)新的問(wèn)題,在子頁(yè)匣椰,比如About/Career
頁(yè)面中時(shí)裆熙,切換到/about
目錄,此時(shí)不會(huì)進(jìn)行重定向禽笑,原因是不會(huì)再一次進(jìn)入onEnter事件入录。此時(shí)需要一個(gè)onChange事件,在路由更改后蒲每,根據(jù)判斷來(lái)進(jìn)行頁(yè)面跳轉(zhuǎn)纷跛。
{
path: '/about',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('AppContainers/About/reducer'),
System.import('AppContainers/About'),
]);
const renderRoute = loadModule(cb);
importModules.then(([reducer, component]) => {
injectReducer('about', reducer.default);
renderRoute(component);
});
importModules.catch(errorLoading);
},
onEnter({ location }, replace) {
if (location.pathname === '/about') {
replace('/about/company');
}
},
onChange(prevState, nextState, replace) {
if (
prevState.location.pathname !== nextState.location.pathname &&
nextState.location.pathname === '/about'
) {
replace('/about/company');
}
},
childRoutes: [
{
path: 'company',
getComponent(nextState, cb) {
System.import('AppContainers/About/Company')
.then(loadModule(cb))
.catch(errorLoading);
},
}, {
path: 'career',
getComponent(nextState, cb) {
System.import('AppContainers/About/Career')
.then(loadModule(cb))
.catch(errorLoading);
},
},
{
path: 'events',
getComponent(nextState, cb) {
System.import('AppContainers/About/Events')
.then(loadModule(cb))
.catch(errorLoading);
},
},
{
path: 'contact',
getComponent(nextState, cb) {
System.import('AppContainers/About/Contact')
.then(loadModule(cb))
.catch(errorLoading);
},
},
{
path: 'pr',
getComponent(nextState, cb) {
System.import('AppContainers/About/Pr')
.then(loadModule(cb))
.catch(errorLoading);
},
},
{
path: 'pr/:id',
getComponent(nextState, cb) {
System.import('AppContainers/About/PrDetail')
.then(loadModule(cb))
.catch(errorLoading);
},
},
{
path: 'faqs',
getComponent(nextState, cb) {
System.import('AppContainers/About/FAQs')
.then(loadModule(cb))
.catch(errorLoading);
},
},
{
path: 'privacy',
getComponent(nextState, cb) {
System.import('AppContainers/About/Privacy')
.then(loadModule(cb))
.catch(errorLoading);
},
},
{
path: 'terms',
getComponent(nextState, cb) {
System.import('AppContainers/About/Terms')
.then(loadModule(cb))
.catch(errorLoading);
},
},
{
path: 'feedback',
getComponent(nextState, cb) {
System.import('AppContainers/About/Feedback')
.then(loadModule(cb))
.catch(errorLoading);
},
},
],
},
4.react監(jiān)聽(tīng)頁(yè)面滾動(dòng)事件
首先是在頁(yè)面中,當(dāng)元素加載之前和加載之后邀杏,綁定滾動(dòng)事件贫奠。
constructor(props) {
super(props);
this.state = {
sidebarFixed: false,
};
}
componentWillMount() {
window.addEventListener('scroll', this.handleScroll.bind(this));
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll.bind(this));
}
handleScroll() {
const scrollTop = document.body.scrollTop;
const headerHeight = document.getElementById('header').offsetHeight;
if (scrollTop >= headerHeight) {
this.setState({ sidebarFixed: true });
} else {
this.setState({ sidebarFixed: false });
}
}
這里會(huì)出現(xiàn)一個(gè)問(wèn)題,就是當(dāng)頁(yè)面跳轉(zhuǎn)時(shí)望蜡,該事件沒(méi)有被解除唤崭,在其他頁(yè)面進(jìn)行頁(yè)面滾動(dòng)時(shí),會(huì)報(bào)錯(cuò)脖律。因?yàn)楂@取不到頁(yè)面元素header的高度谢肾,所以當(dāng)離開(kāi)該頁(yè)面的時(shí)候,需要解除綁定事件小泉。
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll.bind(this));
}
這時(shí)會(huì)發(fā)現(xiàn)芦疏,這個(gè)并沒(méi)有其效果,原因在于我們是在事件中綁定的this微姊,這里每次都是一個(gè)新的對(duì)象酸茴,因此沒(méi)辦法解除我們一開(kāi)始綁定的事件對(duì)象。
于是將代碼更改為
constructor(props) {
super(props);
this.state = {
sidebarFixed: false,
};
this.handleScroll = this.handleScroll.bind(this);
}
componentWillMount() {
window.addEventListener('scroll', this.handleScroll);
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll() {
const scrollTop = document.body.scrollTop;
const headerHeight = document.getElementById('header').offsetHeight;
if (scrollTop >= headerHeight) {
this.setState({ sidebarFixed: true });
} else {
this.setState({ sidebarFixed: false });
}
}
好了兢交,將事件在構(gòu)造函數(shù)中綁定好薪捍,然后調(diào)用和解除的都是同一對(duì)象。