React + Redux 遇到的坑

這兩天在忙公司移動(dòng)端的項(xiàng)目没咙,我們采用的技術(shù)是React锰扶,配合Redux進(jìn)行數(shù)據(jù)的存取瞄桨,期間遇到了不少坑遣疯。

導(dǎo)航切換
加載更多

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ì)象。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市酪穿,隨后出現(xiàn)的幾起案子凳干,更是在濱河造成了極大的恐慌,老刑警劉巖被济,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件救赐,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡溉潭,警方通過(guò)查閱死者的電腦和手機(jī)净响,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)喳瓣,“玉大人,你說(shuō)我怎么就攤上這事赞别∥飞拢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵仿滔,是天一觀的道長(zhǎng)惠毁。 經(jīng)常有香客問(wèn)我,道長(zhǎng)崎页,這世上最難降的妖魔是什么鞠绰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮飒焦,結(jié)果婚禮上蜈膨,老公的妹妹穿的比我還像新娘。我一直安慰自己牺荠,他們只是感情好翁巍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著休雌,像睡著了一般灶壶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杈曲,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天驰凛,我揣著相機(jī)與錄音,去河邊找鬼担扑。 笑死恰响,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的魁亦。 我是一名探鬼主播渔隶,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了间唉?” 一聲冷哼從身側(cè)響起绞灼,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呈野,沒(méi)想到半個(gè)月后低矮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡被冒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年军掂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昨悼。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蝗锥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出率触,到底是詐尸還是另有隱情终议,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布葱蝗,位于F島的核電站穴张,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏两曼。R本人自食惡果不足惜皂甘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望悼凑。 院中可真熱鬧偿枕,春花似錦、人聲如沸佛析。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)寸莫。三九已至捺萌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間膘茎,已是汗流浹背桃纯。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留披坏,地道東北人态坦。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像棒拂,于是被迫代替她去往敵國(guó)和親伞梯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子玫氢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容