こんにちは、mabuiです。
前回作成した仮想通貨銘柄の一覧表示サンプルに
画面表示を変更するボタンを取り付けて、
状態を更新して画面をレンダリングし直す、よりreactらしいコードにしていきます。
動作イメージ
名前、金額、時価総額、24時間出来高でソートするボタンと
金額の表示をJPY, USDで変換するボタンを作成しました。
金額変換のボタンはボタン名の表示も切り替わります。
コード紹介
change_state_only_react.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
import fetch from 'isomorphic-fetch'; import React from 'react'; import './index.css'; const DISPLAY_JPY = "JPY"; const DISPLAY_USD = "USD"; // コンポーネント作成 class ChangeStateOnlyReact extends React.Component { checkDisplay(display) { return typeof display === 'undefined' ? this.state.display : display; } convertJsx(list, display) { display = this.checkDisplay(display); // JSXに変換 return list.map((coin) => <li>{coin.rank}:{coin.name}, [price: {display === DISPLAY_JPY ? coin.price_jpy : coin.price_usd}], [market_cap: {display === DISPLAY_JPY ? coin.market_cap_jpy : coin.market_cap_usd}], [percent_change_24h: {coin.percent_change_24h}]</li> ) } sortName() { this.sortCondition(this.state.list, 'name'); }; sortPrice() { this.sortCondition(this.state.list, 'price'); }; sortMarketCap() { this.sortCondition(this.state.list, 'market_cap'); }; sortPercentChange24h() { this.sortCondition(this.state.list, 'percent_change_24h'); }; sortCondition(list, condition) { // ソート list.sort((a, b) => { return a[condition] < b[condition] ? -1 : 1; }); let jsxList = this.convertJsx(list); jsxList.unshift(this.createHeader()) this.setState({ jsxList: jsxList }); }; changeDisplay() { // stateのdisplayをUSD ⇔ JPYに変換して、ヘッダー、金額の表示も更新 const display = this.state.display === DISPLAY_JPY ? DISPLAY_USD : DISPLAY_JPY const jsxList = this.convertJsx(this.state.list, display); jsxList.unshift(this.createHeader(display)) this.setState({ display: display, jsxList: jsxList }); } createHeader(display) { const name = <button onClick={() => this.sortName()}>name</button> const price = <button onClick={() => this.sortPrice()}>price</button> const marketCap = <button onClick={() => this.sortMarketCap()}>market_cap</button> const percentChange24h = <button onClick={() => this.sortPercentChange24h()}>percent_change_24h</button> const display_btn = <button onClick={() => this.changeDisplay()}>{this.checkDisplay(display)}</button> return <li>#:{name}, {price}, {marketCap}, {percentChange24h}, {display_btn}</li> } renderCoins() { // coinmarketcapから上位10位の銘柄取得api let endpoint = "https://api.coinmarketcap.com/v2/ticker/?convert=JPY&limit=10&sort=rank" // fetchでリクエストを投げる。非同期処理でPromiseのオブジェクトが返却される。 let dict = {}; fetch(endpoint).then((response) => { let json = response.json(); json.then((value) => { let data = value.data; for (let key in data) { let coin = data[key]; let quotes_jpy = coin['quotes']['JPY']; let quotes_usd = coin['quotes']['USD']; // レスポンスを連想配列に詰める。 dict[coin.rank] = { rank: coin['rank'], name: coin['name'], price_jpy: quotes_jpy['price'], market_cap_jpy: quotes_jpy['market_cap'], price_usd: quotes_usd['price'], market_cap_usd: quotes_usd['market_cap'], percent_change_24h: quotes_usd['percent_change_24h'], } } }) }, (error) => { console.error(error); }).then(() => { // setTimeoutで非同期処理の結果(dict)が帰ってくるのを待つ。 setTimeout(() => { let list = []; // rank順に並び替え for (let rank in dict) { list.push(dict[rank]); } let jsxList = this.convertJsx(list); // ヘッダーを追加 jsxList.unshift(this.createHeader()); // stateが更新されたら、render()が自動的に実行される。 this.setState({ list: list, jsxList: jsxList }); }, 100) }) }; constructor() { super(); // Promiseは結果をそのまま返せないので、stateを用意して結果を格納しておく this.renderCoins(); this.state = { display: DISPLAY_JPY, list: [] } } render() { return <ul>{this.state.jsxList}</ul> } } export default ChangeStateOnlyReact |
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; // import JsonTest from './display_json_test.js'; import ChangeStateOnlyReact from './change_state_only_react.js'; ReactDOM.render( // コンポーネントをレンダリング <div> {/* <JsonTest /> */} <ChangeStateOnlyReact /> </div>, document.getElementById('root') ) |
コンポーネントを切り出した
今回のコードでは、ReactDOM.render()を実行するルートのindex.jsファイルと、
React.render()を実行するコンポーネントのファイルを切り出してみました。
今後開発する上で、レンダリングするコンポーネントの切り替えをやりやすくするためです。
開発中は下記のようにindex.jsで表示するコンポーネントをコメントアウトで切り替えています。
1 2 3 4 5 6 7 8 9 |
ReactDOM.render( // Appコンポーネントをレンダリング <div> {/* <JsonTest /> */} <ChangeStateOnlyReact /> </div>, document.getElementById('root') ) |
表示している情報の持ち方
画面にはstate.jsxListを表示していて、ボタンを押すごとに
この情報が全て更新される作りになっています。
外部apiで取得したコインの情報をstate.listに入れて、
convertJsxメソッドでJSXに変換してstate.jsxListを更新します。
更新前にcreateHeaderメソッドでヘッダーを作成し、jsxListの先頭に詰めています。
1 2 3 4 |
let jsxList = this.convertJsx(list); // ヘッダーを追加 jsxList.unshift(this.createHeader()); |
項目ごとにソートするボタンの実装
1 2 3 4 5 6 7 8 9 |
createHeader(display) { const name = <button onClick={() => this.sortName()}>name</button> const price = <button onClick={() => this.sortPrice()}>price</button> const marketCap = <button onClick={() => this.sortMarketCap()}>market_cap</button> const percentChange24h = <button onClick={() => this.sortPercentChange24h()}>percent_change_24h</button> const display_btn = <button onClick={() => this.changeDisplay()}>{this.checkDisplay(display)}</button> return <li>#:{name}, {price}, {marketCap}, {percentChange24h}, {display_btn}</li> } |
createHeader内でconstで定義している定数がそれぞれのボタンになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
sortName() { this.sortCondition(this.state.list, 'name'); }; ... sortCondition(list, condition) { // ソート list.sort((a, b) => { return a[condition] < b[condition] ? -1 : 1; }); let jsxList = this.convertJsx(list); jsxList.unshift(this.createHeader()) this.setState({ jsxList: jsxList }); }; |
ソート用のメソッドはそれぞれsortConditionメソッドで条件ごとにソートされ、
ヘッダーも含め表示用のjsxListに詰めて、stateを更新して画面が再度レンダリングされます。
金額の表示をJPY, USDに変換するボタンの実装
1 2 3 4 5 6 7 8 9 10 11 |
changeDisplay() { // stateのdisplayをUSD ⇔ JPYに変換して、ヘッダー、金額の表示も更新 const display = this.state.display === DISPLAY_JPY ? DISPLAY_USD : DISPLAY_JPY const jsxList = this.convertJsx(this.state.list, display); jsxList.unshift(this.createHeader(display)) this.setState({ display: display, jsxList: jsxList }); } |
changeDisplayメソッドでdisplayの情報を切り替えて、stateを更新しています。
それに合わせてconvertJsxメソッドにdisplayの情報を渡してコインの表示金額情報を切り替えています。