React学習6(リファクタリング)
カウンターコンポーネントを最終的にリファクタリングしたもの。
ファイル構成
. ├── App.jsx ├── components │ ├── Counter.jsx │ ├── Counters.jsx │ └── Navbar.jsx └── index.js
ファイルの中身
App.jsx
import React, { Component } from 'react' import _ from 'lodash' import Navbar from './components/Navbar' import Counters from './components/Counters' class App extends Component { state = { counters: [ { id: 1, value: 2 }, { id: 2, value: 0 }, { id: 3, value: 3 }, { id: 4, value: 4 }, ], } render() { const { counters } = this.state return ( <div className="App"> <Navbar totalCounters={counters.length > 0 && counters.length} /> <div className="container"> <Counters counters={counters} onReset={this.handleReset} onIncrement={this.handleIncrement} onDecrement={this.handleDecrement} onDelete={this.handleDelete} /> </div> </div> ) } handleIncrement = counter => { this.handleValueChange(counter, 'inc') } handleDecrement = counter => { this.handleValueChange(counter, 'dec') } handleValueChange = (counter, flag) => { const { counters } = this.state const clickedCounterIndex = counters.findIndex(c => c.id === counter.id) const countersChanged = _.cloneDeep(counters) if (flag === 'inc') { countersChanged[clickedCounterIndex].value++ } if (flag === 'dec') { if (countersChanged[clickedCounterIndex].value === 0) return countersChanged[clickedCounterIndex].value-- } if (flag === 'inc' || flag === 'dec') { this.setState({ counters: countersChanged, }) } } handleReset = () => { const counters = this.state.counters.map(c => { c.value = 0 return c }) this.setState({ counters, }) } handleDelete = counter => { const { counters } = this.state const filteredCounters = counters.filter(c => { return c !== counter }) this.setState({ counters: filteredCounters, }) } } export default App
Counters.jsx
import React, { Component } from 'react' import Counter from './Counter' class Counters extends Component { render() { const { counters, onReset, onIncrement, onDecrement, onDelete } = this.props if (counters.length === 0) return <p>Nothing!</p> return ( <> <button onClick={onReset} className="btn btn-primary btn-sm m-2"> Reset </button> {counters.map(counter => ( <Counter key={counter.id} value={counter.value} selected={true} onIncrement={() => onIncrement(counter)} onDecrement={() => onDecrement(counter)} onDelete={() => onDelete(counter)} disabled={counter.value === 0} /> ))} </> ) } } export default Counters
Counter.jsx
import React, { Component } from 'react' class Counter extends Component { render() { return <>{this.renderCounter()}</> } renderCounter() { const { onIncrement, onDecrement, onDelete, disabled } = this.props return ( <div className="row"> <div className="col-1" style={{ minWidth: '70px' }}> <span className={this.getBadgeClasses()}>{this.formatCount()}</span> </div> <div className="col"> <button onClick={onIncrement} className="btn btn-secondary btn-sm m-2" > + </button> <button onClick={onDecrement} className={`btn btn-secondary btn-sm m-2${ disabled ? ' disabled' : '' }`} > − </button> <button onClick={onDelete} className="btn btn-danger btn-sm m-2"> × </button> </div> </div> ) } getBadgeClasses() { let classes = 'badge m-2 badge-' classes += this.props.value === 0 ? 'warning' : 'primary' return classes } formatCount() { const { value } = this.props return value === 0 ? 'Zero' : value } } export default Counter
Navbar.jsx
import React from 'react' const Navbar = ({ totalCounters }) => { return ( <> <nav className="navbar navbar-light bg-light"> {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} <a className="navbar-brand" href="#"> Navbar <span className="badge badge-pill badge-info">{totalCounters}</span> </a> </nav> </> ) } export default Navbar