prerequisite
Break the UI into a component hierarchy
화면을 보고 화면을 어떻게 컴포넌트 단위로 나눌지를 먼저 설계해야 한다. 설계 원칙은 클래스 설계원칙 SOLID의 첫번째 single responsibility principle 이다. 하나의 컴포넌트는 하나의 책임을 가지도록 한다.
score 가 0이상은 good player로 분류하고 0 미만은 bad player로 분류하도록 설계한다. 먼저 화면을 보고 전체적인 컴포넌트 설계를 해야 한다.
모든 플레이어를 보여주는 All Player와 good player 와 bad player 에서 공통화 할수 있는 부분을 찾으면 상단 라벨과 하단 플레이어 리스트가 동일하다.
그러므로 상단 라벨과 플레이어 리스트를 보여주는 제목을 포함하는 PlayerList 컴포넌트를 먼저 설계하한다.
상단 라벨은 All Player, Good Player, Bad Player라는 각각의 제목을 속성으로 받는 스트링 타입의 playerState 와 배열타입의 플레이 리스트인 players 속성 두가지를 받는다.
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 |
class PlayerList extends Component { getHighScore() { const highScore = this.props.players.reduce((maxScore, player) => maxScore > player.score ? maxScore : player.score, 0); return highScore > 0 ? highScore : null; } render() { let titleClass = ''; if (this.props.playerState.indexOf('All') >= 0) { titleClass = 'all-title'; } else if (this.props.playerState.indexOf('Good') >= 0) { titleClass = 'good-title'; } else if (this.props.playerState.indexOf('Bad') >= 0) { titleClass = 'bad-title'; } return ( <> <p className={titleClass}>{this.props.playerState}</p> {/*Players List*/} { this.props.players.map((item, index) => <CustomPlayer name={item.name} score={item.score} key={item.id.toString()} index={index} isHighScore={item.score === this.getHighScore()} id={item.id} />) } </> ) } } export default PlayerList; |
App 컴포넌트에는 isHighscore() 함수를 제거하고, PlayerList에 일단 All Players 와 Good Players, Bad Players 를 모두 보여주도록 한다.
players에서 스코어가 0이상은 goodPlayers 배열에 0 미만은 badPalyers에 계산해서 저장한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
render() { const {players} = this.props; const goodPlayers = players.filter(item => item.score >= 0); const badPlayers = players.filter(item => item.score < 0); return ( <div className="scoreboard"> <Header players={players} /> {/*Players List*/} { [ <PlayerList playerState='Good Players' players={goodPlayers} />, <PlayerList playerState='Bad Players' players={badPlayers} />, <PlayerList playerState='All Players' players={players} /> ] } <AddPlayerForm /> </div> ); } |
all-title, good-title, bad-title에 해당하는 클래스명이 없으면 아래 라인을 추가한다.
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 |
... .search-box { display: flex; flex-direction: column; padding: 0.5rem 1rem; background: #2db7f5; } .all-title { background: #999999; padding: 1rem; color: white; margin: 0; } .good-title { background: green; padding: 1rem; color: white; margin: 0; } .bad-title { background: red; padding: 1rem; color: white; margin: 0; } |
여기까지 실행해서 화면에 all, good, bad 플레이어가 모두 나타난다면 정상적으로 실행된것이다.
UI 컴포넌트 만들기
이제 체크박스 버튼을 추가해서 체크가 되어있지 않으면 all player 를 체크를 하면 good, bad 플레이어를 보여주도록 하는 컴포넌트를 만들자.
체크박스를 포함하는 UI 컴포넌트를 추가하면 다른 컴포넌트와 통신을 해야 하는데, 통신을 간편하게 하기위해서 redux를 사용한다.
소팅을 할건지 말건지를 결정하는 is_sorted라는 boolean 타입 변수와 이 변수를 세팅하는 리듀서를 만들어야 한다.
액션 타입을 추가한다.
1 |
export const SET_ISSORTED = 'player/SET_ISSORTED'; |
액션 creator를 추가한다.
1 2 3 4 5 6 |
export const setIsSorted = (isSorted) => { return { type: SET_ISSORTED, isSorted } } |
isSorted 라는 시간에 따라 변하는 상태 변수를 리덕스에 위치한다. 그리고 reducer에 isSorted 액션을 받아서 state 를 변경하는 코드도 추가한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const playerInitialState = { title: 'My Scoreboard', players: [ {name: 'LDK', score: 0, id: 1}, {name: 'HONG', score: 0, id: 2}, {name: 'KIM', score: 0, id: 3}, {name: 'PARK', score: 0, id: 4}, ], isSorted: false } ... case SET_ISSORTED: state.isSorted = action.isSorted; return { ...state, } |
SearchPlayer 컴포넌트를 작성한다.
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 |
class SearchPlayer extends Component { handleChange = (e) => { this.props.setIsSorted(e.target.checked); }; render() { return ( <div className="search-box"}> <label> <input type="checkbox" checked={this.props.isSorted} onChange={this.handleChange}></input> show good player and bad player </label> </div> ) } } let mapStateToProps = (state) => ({ isSorted: state.playerReducer.isSorted }) let mapDispatchToProps = (dispatch) => ({ setIsSorted: (isSorted) => dispatch(setIsSorted(isSorted)) }) export default connect(mapStateToProps, mapDispatchToProps)(SearchPlayer); |
App컴포넌트에 SearchPlayer 컴포넌트를 추가한다.
그리고, store로 부터 소팅 여부를 가져오는 isSorted props를 redux를 이용해서 추가한다. 이 props를 이용해서 conditional 렌더링을 사용하여 all or good/bad 플레이어를 보여주도록 한다.
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 |
class Scoreboard extends Component { render() { const {players} = this.props; const goodPlayers = players.filter(item => item.score >= 0); const badPlayers = players.filter(item => item.score < 0); return ( <div className={styles.scoreboard}> <Header players={players} /> <SearchPlayer></SearchPlayer> {/*Players List*/} { this.props.isSorted ? [ <PlayerList playerState='Good Players' players={goodPlayers} />, <PlayerList playerState='Bad Players' players={badPlayers} /> ] : <PlayerList playerState='All Players' players={players} /> } <AddPlayerForm /> </div> ); } } let mapStateToProps = (state) => { return { players: state.playerReducer.players, isSorted: state.playerReducer.isSorted } } export default connect(mapStateToProps)(Scoreboard); |