既然React的賣點是組件化(話說昨天面試小姐姐問React除了組件化還有什麼,我竟然忘了聲明式,悔しい)。上圖是本身的劃分思路:html
這裏必須得多說幾句,簡直炸毛。前端
首先是在fixer申請了key,還不錯,看下圖。然鵝坑爹的是,他們家不提供貨幣全稱API(好比USD對應United States Dollor;CNY對應Chinese Yuan醬紫)。雖然作這個項目只須要實時匯率就足夠了,可是隻顯示乾巴巴的簡稱確定是不友好滴。node
而後又去了currencylayer申請了key,貨幣全稱卻是有了,看下圖,但他家的匯率api超級坑爹,免費用戶只能以美金爲base貨幣,不像fixer無限制。遂兩家api一塊兒用...react
名字懶得起了,就叫App好了,這個類主要用來異步加載兩個json文件,第一次用到了fetch,據說能代替傳統的Ajax,與之相似的還有什麼axois、superagent,忙過這幾天依次作一下研究。git
class App extends React.Component { constructor(props) { super(props); this.state = {ratesObj: '', countriesObj: ''}; } componentDidMount() { // 注意fetch的語法,其實跟Promise差很少少 this.getCurrencyRates = fetch(this.props.api.getCurrencyRatesAPI).then(res => { res.json().then(resJSON => this.setState({ratesObj: resJSON.rates})); }); this.getCountries = fetch(this.props.api.getCountriesAPI).then(res => { res.json().then(resJSON => this.setState({countriesObj: resJSON.currencies})); }); } componentWillUnmount() { this.getCurrencyRates.abort(); this.getCountries.abort(); } render() { return ( <div> <CountryChoice ratesObj={this.state.ratesObj} countriesObj={this.state.countriesObj}/> </div> ) } } ReactDOM.render( // 這裏的api是個對象,存放上面所說的兩個url,這裏就不貼出來了 <App api={api}/>, document.getElementById('root'), );
這個類就是個受,本身啥都幹不了。當select元素觸發onchange事件抑或button被點擊(觸發onclick事件)時,這裏就會發生變更。這個類只須要接受CountryChoice類(就是上面說到的核心類)的三個參數,分別是示意圖中④和⑤區正在被選中的那兩個幣種,還有就是兩個幣種之間的匯率。github
class CurrentExchangeRate extends React.Component { constructor(props) { super(props); } render() { return ( <h1>1 <span className="exchange-country-name">{this.props.firstSelectedCountry}</span> = {this.props.latestRates + ' '} <span className="exchange-country-name">{this.props.secondSelectedCountry}</span> </h1> ); } }
這個類接受一個Boolean類型的flag,flag的定義一樣是在CountryChoice類裏面,每點擊一次按鈕,flag值就會從true和false之間切換,而後經過this.props.buttonToggle這個方法將實時的flag值傳遞迴CountryChoice類,固然this.props.buttonToggle方法定義在CountryChoice類裏,下面會說到。面試
// exchange按鈕交換兩個select,同時會改變 實時匯率展現 模塊 class ChangeCountry extends React.Component { constructor(props) { super(props); this.state = {currentFlag: this.props.currentFlag}; this.buttonClick = this.buttonClick.bind(this); } buttonClick() { // 必定要把 !this.state.currentFlag 先存到一個變量,再把這個變量賦值到setState裏 // 不然第一次點擊按鈕仍是true,第二次才變成false const currentFlag = !this.state.currentFlag; this.setState({ currentFlag: currentFlag }); this.props.buttonToggle(currentFlag); } render() { return ( <button className="button" onClick={this.buttonClick}>Exchange</button> ) } }
首先先寫一個isNumber方法,是爲了阻止用戶輸入非數字,且只能輸入一個小數點。由於有兩個input元素,因此先給兩個元素命名。由於在任意一個input中輸入值都會實時影響到另外一個,因此這裏就涉及到了<mark>狀態提高</mark>問題,能夠去研究官方文檔-狀態提高的這個例子。json
// 命名兩個input輸入框 const inputNames = {f: 'firstInput', s: 'secondInput'}; function isNumber(input) { if (/^[0-9]+([.][0-9]*)?$/.test(input) === false) { return input.slice(0, -1) } else { return input } } class MoneyInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(ev) { this.props.onInputChange(isNumber(ev.target.value)); } render() { const inputName = this.props.inputName; const inputValue = this.props.inputValue; return ( // 這裏用到了ES6的計算屬性名 <input name={inputNames[inputName]} className="input-value" type="text" value={inputValue} placeholder="0" onChange={this.handleChange}/> ); } }
高潮來了,額,重點來了。這個類控制着幣種的選擇,解釋都放在了註釋裏,代碼寫得有點兒繞,都是exchange按鈕惹的禍。寫下來發現React的一個重點就是React 組件間通信:segmentfault
這裏推薦兩篇文章,一個是淘寶前端團隊的React 組件間通信;另外一個是在SegmentFault的一篇文章React 組件之間如何交流,把人家演示的例子敲一敲,基本上就能理解了。api
// 貨幣選擇:初始化第一個選中的是美刀,第二個選中的歐元 // 貨幣選擇的變化影響着 匯率展現 和 匯率計算 class CountryChoice extends React.Component { constructor(props) { super(props); // 初始化狀態 this.state = { firstSelectedCountry: 'USD',// 第一個默認被選中的幣種是美金 secondSelectedCountry: 'EUR',// 第二個默認被選中的幣種是歐元 flag: true,// 立一個flag,初始是true,button被點擊時在true和false之間切換 inputName: 'f',// 默認選中的是第一個input標籤 inputValue: ''// 默認input標籤的值是空 }; this.handleChange = this.handleChange.bind(this); this.buttonToggle = this.buttonToggle.bind(this); this.firstInputChange = this.firstInputChange.bind(this); this.secondInputChange = this.secondInputChange.bind(this); } // 經過ChangeCountry類傳遞過來的flag值來設置成當前狀態 buttonToggle(flag) { this.setState({flag: flag}) } // 設置select標籤的狀態 // 當flag是true時,把firstSelectedCountry的狀態設置爲name屬性爲「first-select」的value, // 把secondSelectedCountry的狀態設置爲name屬性爲「second-select」的value // 當flag是true時,把firstSelectedCountry的狀態設置爲name屬性爲「first-select」的value, // 把secondSelectedCountry的狀態設置爲name屬性爲「second-select」的value // 也就是說當flag是false時,此時name屬性爲「first-select」的select標籤控制的是name屬性爲「second-select」的select標籤 // 固然我這裏設計的不合理,應該經過動態修改name值纔好,放在下一次的項目迭代吧,留個坑先 handleChange(ev) { const target_name = ev.target.name; if (this.state.flag) { if (target_name === 'first-select') { this.setState({firstSelectedCountry: ev.target.value}); } else if (target_name === 'second-select') { this.setState({secondSelectedCountry: ev.target.value}); } } else { if (target_name === 'first-select') { this.setState({secondSelectedCountry: ev.target.value}); } else if (target_name === 'second-select') { this.setState({firstSelectedCountry: ev.target.value}); } } } // 獲取第一個input輸入的值 firstInputChange(inputValue) { this.setState({inputName: 'f', inputValue}); } // 獲取第二個input輸入的值 secondInputChange(inputValue) { this.setState({inputName: 's', inputValue}); } render() { const inputName = this.state.inputName; const inputValue = this.state.inputValue; // 由於要用到用戶輸入的值乘以匯率來進行計算 // 當用戶清空某個input標籤的值時,這裏就成了NaN // 這個函數就是當檢測到輸入的值爲空時,自動設爲數字0 // 啊啊啊,確定有更好的方法 function formatInputValue(inputValue) { if (inputValue === '') { inputValue = 0; return inputValue } else { return parseFloat(inputValue) } } // 這邊就寫的很笨重了,匯率是根據flag的狀態定的 // 若是是true,匯率是第二個select標籤選中的值除以第一個select // 假設當前在第一個input輸入數值,那麼下面的 inputName === 'f' 就是true, 因此第二個input的值(sI)就會被實時計算 // 反正就是很繞,若是不加exchange按鈕要省不少事兒,一切都是爲了學習... const fI = inputName === 's' ? formatInputValue(inputValue) * (!this.state.flag ? this.props.ratesObj[this.state.secondSelectedCountry] / this.props.ratesObj[this.state.firstSelectedCountry] : this.props.ratesObj[this.state.firstSelectedCountry] / this.props.ratesObj[this.state.secondSelectedCountry]) : inputValue; const sI = inputName === 'f' ? formatInputValue(inputValue) * (this.state.flag ? this.props.ratesObj[this.state.secondSelectedCountry] / this.props.ratesObj[this.state.firstSelectedCountry] : this.props.ratesObj[this.state.firstSelectedCountry] / this.props.ratesObj[this.state.secondSelectedCountry]) : inputValue; return ( <div className="container"> {/*這邊就是把當前狀態(兩個被選中的貨幣全稱和之間的匯率)傳遞給①區來顯示*/} <CurrentExchangeRate firstSelectedCountry={this.state.flag ? this.props.countriesObj[this.state.firstSelectedCountry] : this.props.countriesObj[this.state.secondSelectedCountry]} secondSelectedCountry={!this.state.flag ? this.props.countriesObj[this.state.firstSelectedCountry] : this.props.countriesObj[this.state.secondSelectedCountry]} latestRates={this.state.flag ? this.props.ratesObj[this.state.secondSelectedCountry] / this.props.ratesObj[this.state.firstSelectedCountry] : this.props.ratesObj[this.state.firstSelectedCountry] / this.props.ratesObj[this.state.secondSelectedCountry]} /> {/*當在第二個input輸入數字時,換算出來的值會實時顯示在第一個input裏*/} <div className="item"> <MoneyInput inputName='f' inputValue={fI} onInputChange={this.firstInputChange} /> {/*傳統設置默認選項是在option標籤設置selected=selected, 如今放在select標籤裏,固然還有個Select的三方庫*/} {/*經過map將option標籤循環添加到第一個select標籤裏面*/} <select className="select" name="first-select" value={this.state.flag ? this.state.firstSelectedCountry : this.state.secondSelectedCountry} onChange={this.handleChange}> { Object.keys(this.props.ratesObj).map((key) => ( <option key={key.toString()} value={key}>{key} - {this.props.countriesObj[key]}</option>)) } </select> </div> <div className="item"> // 當在第一個input輸入數字時,換算出來的值會實時顯示在第二個input裏 <MoneyInput inputName='s' inputValue={sI} onInputChange={this.secondInputChange} /> {/*經過map將option標籤循環添加到第一個select標籤裏面*/} <select className="select" name="second-select" value={!this.state.flag ? this.state.firstSelectedCountry : this.state.secondSelectedCountry} onChange={this.handleChange}> { Object.keys(this.props.ratesObj).map((key) => ( <option key={key.toString()} value={key}>{key} - {this.props.countriesObj[key]}</option>)) } </select> {/*exchange按鈕 將當前flag值傳遞給ChangeCountry類,同時將ChangeCountry類改變的flag值做爲參數,經過buttonToggle方法傳回當前這個類*/} <ChangeCountry currentFlag={this.state.flag} buttonToggle={flag => this.buttonToggle(flag)}/> </div> </div> ) } }
項目寫的很醜,隨着學習的深刻還要進行迭代,加油吧。項目傳到了github上了,沒有勞駕node服務器,fork下來直接打開html文件就能使用了。
至於爲何當時想到了作一個匯率計算器,可能就是下圖吧:
以上、よろしく。