第十六步:角色(資料)組件

2021-10-23 14:32 更新

第十六步:角色(資料)組件

在這一節(jié)里我們將為角色創(chuàng)建資料頁面。它和其它組件有些不同,其不同之處在于:

  1. 它有一個覆蓋全頁面的背景圖片。
  2. 從一個角色頁面導航至另一個角色頁面并不會卸載組件,因此,在componentDidMount內(nèi)部的getCharacter action僅會被調(diào)用一次,比如它更新了URL但并不獲取新數(shù)據(jù)。

Component

在app/components目錄新建文件Character.js

import React from 'react';
import CharacterStore from '../stores/CharacterStore';
import CharacterActions from '../actions/CharacterActions'

class Character extends React.Component {
  constructor(props) {
    super(props);
    this.state = CharacterStore.getState();
    this.onChange = this.onChange.bind(this);
  }

  componentDidMount() {
    CharacterStore.listen(this.onChange);
    CharacterActions.getCharacter(this.props.params.id);

    $('.magnific-popup').magnificPopup({
      type: 'image',
      mainClass: 'mfp-zoom-in',
      closeOnContentClick: true,
      midClick: true,
      zoom: {
        enabled: true,
        duration: 300
      }
    });
  }

  componentWillUnmount() {
    CharacterStore.unlisten(this.onChange);
    $(document.body).removeClass();
  }

  componentDidUpdate(prevProps) {
    // Fetch new charachter data when URL path changes
    if (prevProps.params.id !== this.props.params.id) {
      CharacterActions.getCharacter(this.props.params.id);
    }
  }

  onChange(state) {
    this.setState(state);
  }

  render() {
    return (
      <div className='container'>
        <div className='profile-img'>
          <a className='magnific-popup' href={'https://image.eveonline.com/Character/' + this.state.characterId + '_1024.jpg'}>
            <img src={'https://image.eveonline.com/Character/' + this.state.characterId + '_256.jpg'} />
          </a>
        </div>
        <div className='profile-info clearfix'>
          <h2><strong>{this.state.name}</strong></h2>
          <h4 className='lead'>Race: <strong>{this.state.race}</strong></h4>
          <h4 className='lead'>Bloodline: <strong>{this.state.bloodline}</strong></h4>
          <h4 className='lead'>Gender: <strong>{this.state.gender}</strong></h4>
          <button className='btn btn-transparent'
                  onClick={CharacterActions.report.bind(this, this.state.characterId)}
                  disabled={this.state.isReported}>
            {this.state.isReported ? 'Reported' : 'Report Character'}
          </button>
        </div>
        <div className='profile-stats clearfix'>
          <ul>
            <li><span className='stats-number'>{this.state.winLossRatio}</span>Winning Percentage</li>
            <li><span className='stats-number'>{this.state.wins}</span> Wins</li>
            <li><span className='stats-number'>{this.state.losses}</span> Losses</li>
          </ul>
        </div>
      </div>
    );
  }
}

Character.contextTypes = {
  router: React.PropTypes.func.isRequired
};

export default Character;

componentDidMount里我們將當前Character ID(從URL獲?。﹤鬟f給getCharacter action并且初始化Magnific Popup lightbox插件。

注意:我從未成功使用ref="magnificPopup"進行插件初始化,這也是我采用代碼中方法的原因。這也許不是最好的辦法,但它能正常工作。

另外你需要注意,角色組件包含一個全頁面背景圖片,并且在componentWillUnmount時移除,因為其它組件不包含這樣的背景圖。它又是什么時候添加上去的呢?在store中當成功獲取到角色數(shù)據(jù)時。

最后值得一提的是在componentDidUpdate中發(fā)生了什么。如果我們從一個角色頁面跳轉至另一個角色頁面,我們?nèi)匀惶幱诮巧M件內(nèi),它不會被卸載掉。而因為它沒有被卸載,componentDidMount不會去獲取新角色數(shù)據(jù),所以我們需要在componentDidUpdate中獲取新數(shù)據(jù),只要我們?nèi)匀惶幱谕粋€角色組件且URL是不同的,比如從/characters/1807823526跳轉至/characters/467078888。componentDidUpdate在組件的生命周期中,每一次組件狀態(tài)變化后都會觸發(fā)。

Actions

在app/actions目錄新建文件CharacterActions.js

import alt from '../alt';

class CharacterActions {
  constructor() {
    this.generateActions(
      'reportSuccess',
      'reportFail',
      'getCharacterSuccess',
      'getCharacterFail'
    );
  }

  getCharacter(characterId) {
    $.ajax({ url: '/api/characters/' + characterId })
      .done((data) => {
        this.actions.getCharacterSuccess(data);
      })
      .fail((jqXhr) => {
        this.actions.getCharacterFail(jqXhr);
      });
  }

  report(characterId) {
    $.ajax({
      type: 'POST',
      url: '/api/report',
      data: { characterId: characterId }
    })
      .done(() => {
        this.actions.reportSuccess();
      })
      .fail((jqXhr) => {
        this.actions.reportFail(jqXhr);
      });
  }
}

export default alt.createActions(CharacterActions);

Store

在app/store目錄新建文件CharacterStore.js

import {assign, contains} from 'underscore';
import alt from '../alt';
import CharacterActions from '../actions/CharacterActions';

class CharacterStore {
  constructor() {
    this.bindActions(CharacterActions);
    this.characterId = 0;
    this.name = 'TBD';
    this.race = 'TBD';
    this.bloodline = 'TBD';
    this.gender = 'TBD';
    this.wins = 0;
    this.losses = 0;
    this.winLossRatio = 0;
    this.isReported = false;
  }

  onGetCharacterSuccess(data) {
    assign(this, data);
    $(document.body).attr('class', 'profile ' + this.race.toLowerCase());
    let localData = localStorage.getItem('NEF') ? JSON.parse(localStorage.getItem('NEF')) : {};
    let reports = localData.reports || [];
    this.isReported = contains(reports, this.characterId);
    // If is NaN (from division by zero) then set it to "0"
    this.winLossRatio = ((this.wins / (this.wins + this.losses) * 100) || 0).toFixed(1);
  }

  onGetCharacterFail(jqXhr) {
    toastr.error(jqXhr.responseJSON.message);
  }

  onReportSuccess() {
    this.isReported = true;
    let localData = localStorage.getItem('NEF') ? JSON.parse(localStorage.getItem('NEF')) : {};
    localData.reports = localData.reports || [];
    localData.reports.push(this.characterId);
    localStorage.setItem('NEF', JSON.stringify(localData));
    toastr.warning('Character has been reported.');
  }

  onReportFail(jqXhr) {
    toastr.error(jqXhr.responseJSON.message);
  }
}

export default alt.createStore(CharacterStore);

這里我們使用了Underscore的兩個輔助函數(shù)assigncontains,來合并兩個對象并檢查數(shù)組是否包含指定值。

注意:在我寫本教程時Babel.js還不支持Object.assign方法,并且我覺得contains比相同功能的Array.indexOf() > -1可讀性要好得多。

就像我在前面解釋過的,這個組件在外觀上和其它組件有顯著的不同。添加profile類到<body>改變了頁面整個外觀和感覺,至于第二個CSS類,可能是caldari、gallente、minmatar、amarr其中的一個,將決定使用哪一個背景圖片。我一般會避免與組件render()之外的DOM直接交互,但這里為簡單起見還是允許例外一次。最后,在onGetCharacterSuccess方法里我們需要檢查角色在之前是否已經(jīng)被該用戶舉報過。如果舉報過,舉報按鈕將設置為disabled。因為這個限制很容易被繞過,所以如果你想嚴格對待舉報的話,你可以在服務端執(zhí)行一個IP檢查。

如果角色是第一次被舉報,相關信息會被存儲到Local Storage里,因為我們不能在Local Storage存儲對象,所以我們需要先用JSON.stringify()轉換一下。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號