728x90
러닝 리엑트을 요약한 내용입니다.
- 컴포넌트 트리는 프로퍼티를 통해 데이터가 흘러갈 수 있는 컴포넌트 계층 구조를 뜻한다.
- 상태와 프로퍼티는 서로 관계가 있다.
- 컴포넌트 트리의 상태가 바뀌면 프로퍼티도 바뀐다.
- 새로운 데이터는 컴포넌트 트리를 타고 흐르고, 콘텐츠에 새로 반영되도록 한다.
- 반영된 데이터를 가지고 다시 랜더링 된다.
6.1 별점 컴포넌트 만들기
- StartRating 컴포넌트를 통해 사용자는 콘텐츠에 별의 개수를 바탕으로 점수를 매길수 있다.
- 별에 대한 icon은 react-icons에서 설치해서 사용한다.
- // 프로젝트 생성 create-react-app star-rating // 별 아이콘을 가져오기 위한 라이브러리 설치 npm install react-icons
- 별 다섯 개 렌더링하는 StarRating 컴포넌트
- 3개는 빨간색, 2개는 회색으로 채워진다.
import React from 'react' import { FaStar } from 'react-icons/fa' export default function StarRating() { return [ <FaStar color="red" />, <FaStar color="red" />, <FaStar color="red" />, <FaStar color="grey" />, <FaStar color="grey" /> ] } // App.js import './App.css'; import StarRating from './components/StartRating'; function App() { return ( <div className="App"> <StarRating /> </div> ); } export default App;
- 선택된 프로퍼티에 따라 자동으로 별을 만들어내는 컴포넌트
- 프로퍼티 값에 따라 별이 빨강색이 되거나 회색이 되도록 한다.
import React from 'react' import { FaStar } from 'react-icons/fa' export default function StarRating({ selectd = false }) { return <FaStar color={ selectd ? "red" : "grey"} /> }
- 5점, 10점 별점 시스템을 만들 수 있도록 별 개수를 지정할 수 있도록 수정
- import React from 'react' import Star from './Star' const createArray = length => [...Array(length)] export default function StarRating({ totalStars = 5}) { return createArray(totalStars).map((n, i) => <Star key={i} />) }
6.2 useState 훅
- rating 값은 변경이 될수 있는 값이기 때문에 리액트 상태에서 값을 저장하고 변경을 해야한다.
- 상태를 리액트 함수 컴포넌트에 넣을 때는 훅스라고 부르는 기능을 사용한다.
- 훅스에는 컴포넌트 트리와 별도로 재사용 가능한 코드 로직이 들어 있다.
- 리액트에는 몇 가지 훅을 기본 제공한다.
- useState 훅은 react 패키지에 들어 있다.
// useState 를 사용하는 방법 import React, { useState } from 'react'
- 선택한 별의 개수를 저장하는 selectedStart라는 상태 변수를 만들고, useState 훅을 사용
- 컴포넌트와 상태를 서로 엮었다(hooked)
- selectedStars는 초기값으로 3으로 설정
import React, { useState } from 'react' import Star from './Star' const createArray = length => [...Array(length)] export default function StarRating({ totalStars = 5 }) { const [selecedStars] = useState(3) return ( <> {createArray(totalStars).map((n, i) => <Star key={i} selectd={selecedStars > i} />)} <p> {selecedStars} / {totalStars} </p> </> ) }
- 이벤트를 만들어 별을 선택할 수 있도록 수정
- onSelect라는 프로퍼티를 추가
- 별을 클릭하면 부모 컴포넌트에게 이벤트 알림
- 디폴트 값은 f ⇒ f, 아무것도 하지 않는 함수
- 오류를 발생하지 않도록 하기 위한 코드
import React from 'react' import { FaStar } from 'react-icons/fa' export default function StarRating({ selectd = false, onSelect = f => f }) { return <FaStar color={ selectd ? "red" : "grey"} onClick={onSelect} /> }
- StarRating의 상태를 변경해보기
- useState 훅이 반환하는 배열의 두 번째 원소는 상태를 변경할 때 사용하는 함수
- 함수 이름에 set을 붙여 setSelectedStars로 정의
- 값이 변경이 되면 다시 컴포넌트를 랜더링 해준다.
import React, { useState } from 'react' import Star from './Star' const createArray = length => [...Array(length)] export default function StarRating({ totalStars = 5 }) { const [selecedStars, setSelectedStars] = useState(3) return ( <> {createArray(totalStars).map((n, i) => <Star key={i} selectd={selecedStars > i} onSelect={ () => setSelectedStars(i + 1) }/> )} <p> {selecedStars} / {totalStars} </p> </> ) }
<aside> 💡 훅이 걸린 컴포넌트를 렌더와 연동 시킨다. selectedStars의 값이 변경될 때마다 랜더링이 다시 이루어진다.
</aside>
구시대적 리액트 상태 관리 - 시간 나면 추가 해볼 것!!!
- v16.8.0 이전에 사용 방법
6.3 재사용성을 높이기 위한 리팩터링
- 추후 컨테이너의 스타일을 변경 할 수 있기 때문에 스타일을 수정할 수 있도록 추가
- 모든 리액트 엘리먼트는 스타일 프로퍼티를 제공한다.
- 대부분의 컴포넌트도 스타일 프로퍼티를 제공한다.
export default function App() { return <StarRating style={{ backgroundColor: "lightblue" }} /> }
- 모든 스타일을 모아서 SartRating 컨테이너에 전달하자
- 프레그먼트를 div 엘리먼트로 업그레이드하고 div에게 스타일을 전달
import React, { useState } from 'react' import Star from './Star' const createArray = length => [...Array(length)] export default function StarRating({ style = {}, totalStars = 5 }) { const [selecedStars, setSelectedStars] = useState(3) return ( <div style={{ padding: "5px", ...style}}> {createArray(totalStars).map((n, i) => <Star key={i} selectd={selecedStars > i} onSelect={ () => setSelectedStars(i + 1) }/> )} <p> {selecedStars} / {totalStars} </p> </div> ) }
6.4 컴포넌트 트리 안의 상태
- 모든 컴포넌트에 상태를 넣는 것은 좋은 생각이 아니다.
- 컴포넌트 트리에서 어느 부분에 상태가 존재하는지를 제대로 알기 어려움
- 애플리케이션의 상태나 어떤 특성의 상태를 한곳에서 관리하는 것이 이해하기 더 쉬움
- 상태를 컴포넌트 트리에 저장, 자식 컴포넌트에 전달 하는 방법에 대해서 알아보기
- 색의 목록을 관리하는 애플리케이션의 데이터
- { "colros": [ { "id": "1", "title": "해질녘 바다", "color": "#00c4e2", "rating": 5 }, { "id": "2", "title": "잔디", "color": "#26ac56", "rating": 3 }, { "id": "3", "title": "밝은 빨강", "color": "#ff0000", "rating": 0 } ] }
6.4.1 상태를 컴포넌트 트리의 아래로 내려보내기
- App 컴포넌트가 트리의 최상위이기 때문에 useState를 사용해서 색관련 데이터를 저장 및 관리
- colorData를 colors의 초기 상태로 사용
- import './App.css'; import React, { useState } from 'react' import colorData from './color-data.json' import ColorList from './components/ColorList'; export default function App() { const [colors] = useState(colorData) return <ColorList colors={colors} /> }
- ColorList는 App 컴포넌트에세 색을 전달 받는다.
- colors가 비어 있으면 컴포넌트는 사용자에게 메세지를 표시한다.
- 세부 정보를 Color로 정보를 내려보낸다.
import React from 'react' import Color from './Color' export default function ColorList({ colors = [] }) { if (!colors.length) return <div>표시할 색이 없습니다.</div> return ( <div> { colors.map(color => <Color key={color.id} {...color} />) } </div> ) }
- 세부 정보를 표시하는 Color 컴포넌트
- Color 컴포넌트는 title, color, rating을 프로퍼티로 받는다.
- {...color} 스프레드 연산자로 전달 받는다.
import React from 'react' import StarRating from './StartRating' export default function Color({ title, color, rating }) { return ( <section> <h1>{title}</h1> <div style={{ height: 50, backgroundColor: color }} /> <StarRating selectedStarts={rating} /> </section> ) }
- Color 컴포넌트는 title, color, rating을 프로퍼티로 받는다.
- StarRating 컴포넌트는 rating 값을 선택된 별의 개수로 표시
- StarRating 컴포넌트를 순수 컴포넌트로 변경
- 순수 컴포넌트는 상태가 없기 때문에 항상 같은 프로퍼티에 대해서 같은 결과를 리턴 또는 랜더링
import React from 'react' import Star from './Star' const createArray = length => [...Array(length)] export default function StarRating({ style = {}, totalStars = 5, selectedStarts = 0 }) { return ( <div style={{ padding: "5px", ...style}}> {createArray(totalStars).map((n, i) => <Star key={i} selectd={selectedStarts > i} /> )} <p> {selectedStarts} / {totalStars} </p> </div> ) }
6.4.2 상호작용을 컴포넌트 트리 위쪽으로 전달하기
- 리스트에서 색을 제거하거나 리스트에 있는 색의 평점을 변경하는 방법 알아보자
- title 옆에 색을 제거하는 삭제 버튼을 추가해보기
- 사용자가 쓰레기통 아이콘을 클릭하면 onRemove가 호출되고 삭제될 id를 전달
- Color 컴포넌트는 상태가 없기 때문에 이벤트에 신경쓰지 않아도 됨
- Color 컴포넌트를 순수 컴포넌트로 유지할 수 있다.
import React from 'react' import { FaTrash } from 'react-icons/fa' import StarRating from './StartRating' export default function Color({ id, title, color, rating, onRemove = f => f }) { return ( <section> <h1>{title}</h1> <button onClick={() => onRemove(id)}> <FaTrash /> </button> <div style={{ height: 50, backgroundColor: color }} /> <StarRating selectedStarts={rating} /> </section> ) }
- 사용자가 쓰레기통 아이콘을 클릭하면 onRemove가 호출되고 삭제될 id를 전달
- ColorList도 이벤트를 상위 컴포넌트에게 전달
- import React from 'react' import Color from './Color' export default function ColorList({ colors = [], onRemoveColor = f => f }) { if (!colors.length) return <div>표시할 색이 없습니다.</div> return ( <div> { colors.map(color => <Color key={color.id} {...color} onRemove={onRemoveColor} />) } </div> ) }
- App 컴포넌트에서 id를 사용해 상태에서 색을 제거
- setColors 변수를 추가 한다. (useState가 반환하는 배열의 두 번째 인자는 상태를 변경할때 사용하는 함수)
- ColorList 가 onRemoveColor 이벤트를 발생시키면 색의 id를 제외한 나머지 값을 다시 저장
- colors 상태를 바꾸면 App 컴포넌트가 새로운 색 목록에 맞춰 다시 랜더링 된다.
import './App.css'; import React, { useState } from 'react' import colorData from './color-data.json' import ColorList from './components/ColorList'; export default function App() { const [colors, setColors] = useState(colorData) return ( <ColorList colors={colors} onRemoveColor={id => { const newColors = colors.filter(color => color.id !== id) setColors(newColors) }} /> ) }
색의 평점을 수정하는 코드
- onRemoveColor와 비슷한 방식으로 onRate 이벤트를 적용
- StarRating에 onRate 핸들러에서 평점 점수를 입력
- import React from 'react' import Star from './Star' const createArray = length => [...Array(length)] export default function StarRating({ totalStars = 5, selectedStarts = 0, onRate = f => f }) { return ( <> {createArray(totalStars).map((n, i) => <Star key={i} selectd={selectedStarts > i} onSelect={() => onRate(i + 1)}/> )} <p> {selectedStarts} / {totalStars} </p> </> ) }
- Color 컴포넌트는 상위 컴포넌트에서 프로퍼티로 받은 onRate를 StarRating으로 전달
- onRate를 상위 컴포넌트로 전달 할때 id값도 같이 넘겨줌
import React from 'react' import { FaTrash } from 'react-icons/fa' import StarRating from './StartRating' export default function Color({ id, title, color, rating, onRemove = f => f, onRate = f => f }) { return ( <section> <h1>{title}</h1> <button onClick={() => onRemove(id)}> <FaTrash /> </button> <div style={{ height: 50, backgroundColor: color }} /> <StarRating selectedStarts={rating} onRate={onRate} /> </section> ) }
- ColorList 컴포넌트는 Color 컴포넌트에서 발생한 onRate 이벤트를 상위 컴포넌트로 전달
- import React from 'react' import Color from './Color' export default function ColorList({ colors = [], onRemoveColor = f => f, onRateColor = f => f }) { if (!colors.length) return <div>표시할 색이 없습니다.</div> return ( <div> { colors.map(color => <Color key={color.id} {...color} onRemove={onRemoveColor} onRate={onRateColor} />) } </div> ) }
- App 컴포넌트에서 평점을 수정하는 코드를 작성
- import './App.css'; import React, { useState } from 'react' import colorData from './color-data.json' import ColorList from './components/ColorList'; export default function App() { const [colors, setColors] = useState(colorData) return ( <ColorList colors={colors} onRemoveColor={id => { const newColors = colors.filter(color => color.id !== id) setColors(newColors) }} onRateColor={(id, rating) => { const newColors = colors.map(color => color.id === id ? {...color, rating} : color) setColors(newColors) }} /> ) }
- 프로퍼티를 통해 데이터를 컴포넌트 트리로 내려보는 것과 비슷하게, 사용자 상호작용은 함수 프로퍼티를 통해 데이터와 함께 트리 위로 전달된다.
6.5 폼 만들기
- HTML 폼 엘리먼트 모두는 리액트 엘리먼트로도 제공된다.
- 색을 추가하는 폼
- <form> <input type="text" placeholder="color title..." required /> <input type="color" required /> <button>ADD</button> </form>
6.5.1 참조 사용하기
- 리액트에서 폼 컴포넌트를 만들때 몇가지 패턴중 참조라는 리액트 기능을 사용해 직접 DOM에 접근하는 방법을 알아보기
- AddColorForm 컴포넌트 생성
- useRef 훅을 사용하여 참조를 만든다
- ref 프로퍼티를 사용하면 참조의 값을 직접 JSX에서 설정 가능
- ref 속성을 추가하면 DOM 엘리먼트를 직접 참조 객체에 대한 current 필드를 생성됨
- ADD 버튼을 누르면 submit 함수가 호출 됨
- e.preventDefault를 사용해 이벤트 막기
- onNewColor 함수 프로퍼티를 통해 상위 컴포넌트로 값 전달
import React, {useRef} from 'react' export default function AddColorForm({ onNewColor = f => f}) { const txtTitle = useRef() const hexColor = useRef() const submit = e => { e.preventDefault() const title = txtTitle.current.value const color = hexColor.current.value onNewColor(title, color) txtTitle.current.value = '' hexColor.current.value = '' } return ( <form onSubmit={submit}> <input ref={txtTitle} type="text" placeholder="color title..." required /> <input ref={hexColor} type="color" required /> <button>ADD</button> </form> ) }
DOM 노드의 value 값을 직접 ""로 설정함으로써 DOM 노드의 속성을 변경했다. 이런 코드는 명령형 코드이다. AddColorForm은 DOM을 통해 폼 값을 저장하기 때문에 제어되지 않는 컴포넌트가 된다. 하지만 제어가 되는 컴포넌트가 더 좋은 접근 방법이다.
6.5.2 제어가 되는 컴포넌트
- 제어가 되는 컴포넌트에서는 폼 값을 DOM이 아니라 리액트로 관리 한다.
- 제어가 되는 컴포넌트를 사용하면 튼튼한 폼 검증 기능 등의 추가가 훨씬 더 쉬워진다.
- 제어가 되는 AddColorForm 만들기
- 참조를 사용하는 대신 title, color 값을 리액트 상태를 통해 저장
- setTItle, setColor 함수를 정의
- form의 input 엘리먼트 안에 있는 value 속성을 설정해서 값을 표시 할 수 있음
- input 엘리먼트에 문자를 입력할 때마다 상태 변수를 변경해 값을 변경
- 폼을 제출할 때 onNewColor 함수 프로퍼티를 호출하면서 title, color의 상태값 전달
- 폼 전달 이후 setTitle, setColor를 통해 값을 초기화 할수 있음
import React, {useRef} from 'react' export default function AddColorForm({ onNewColor = f => f}) { const [title, setTitle] = useRef('') const [color, setColor] = useRef('#000000') const submit = e => { e.preventDefault() onNewColor(title, color) setTitle('') setColor('#000000') } return ( <form onSubmit={submit}> <input value={title} onChange={event => setTitle(event.target.value)} type="text" placeholder="color title..." required /> <input value={color} onChange={event => setColor(event.target.value)} type="color" required /> <button>ADD</button> </form> ) }
리액트가 폼의 상태를 모두 제어하기 때문에 이런 컴포넌트를 제어가 되는 컴포넌트라고 부른다. 값을 바꿀 때마다 다시 랜터링 된다. 하지만 리액트는 이러한 부하를 처리할 수 있도록 설계됐으니 괜찮다. 다시 랜더린 된다는 사실만 기억하고 부하가 가는 처리는 가급적 하지 않는 것이 좋다. 리액트 컴포넌트를 최적화를 한다면 이런 부분에 대한 지식이 필요함
6.5.3 커스텀 훅 만들기
- input 엘리먼트가 많이 들어 있는 큰 폼을 만든다면 다음 코드를 복사해 붙여 넣을 수 있다.
- 이런 경우 함수에서 추상화 할 수 있는 중복이 발생했다고 볼수 있다.
<input value={title} onChange={event => setTitle(event.target.value)} />
- 중복이 되는 코드를 추상화한 useInput 훅
- 커스텀 훅
- useSstate 훅을 이용해 상태 value를 만듬
- value와 상태의 value를 변경하는 onChange 함수 프로퍼티가 배열의 첫 번째 원소
- value의 값을 초기값으로 재설정하는 두번째 원소
import { useState } from 'react' export default function({initValue}) { const [value, setValue] = useState(initValue) return [ { value, onChange: e => setValue(e.target.value) }, () => setValue(initValue) ] }
- useInput 훅을 사용해 AddColorForm 안에서 사용
- useState훅은 useInput 훅 안에 캡슐화된다.
- 커스텀 훅이 반환하는 배열의 첫 번째 원소를 구조 분해해서 title, color 값을 얻는다.
- 커스텀 훅으로부터 프로퍼티를 스프레드 연산으로 넣는다.
- useInput 함수가 반환하는 커스텀 재설정 함수를 사용해 값을 재설정
import React, {useRef} from 'react' import useInput from './useInput' export default function AddColorForm({ onNewColor = f => f}) { const [titleProps, resetTitle] = useInput('') const [colorProps, resetColor] = useInput('#000000') const submit = e => { e.preventDefault() onNewColor(titleProps.value, colorProps.value) resetTitle() resetColor() } return ( <form onSubmit={submit}> <input {...titleProps} type="text" placeholder="color title..." required /> <input {...colorProps} type="color" required /> <button>ADD</button> </form> ) }
6.5.4 색을 상태에 추가하기
- 새로운 색이 추가되면 onNewColor 프로퍼티가 호출
- colors의 스프레드 연산을 통해 새로운 배열을 만들고 값을 추가 한다.
- uuid 패키지에 있는 v4함수를 사용해 색의 새로운 id 를 생성
- setColors를 호출해서 색 배열을 상태에 저장
import './App.css';
import React, { useState } from 'react'
import colorData from './color-data.json'
import ColorList from './components/ColorList';
import AddColorForm from './components/AddColorForm'
import {v4} from 'uuid'
export default function App() {
const [colors, setColors] = useState(colorData)
return (
<>
<AddColorForm
onNewColor={(title, color) => {
const newColors = [
...colors,
{
id: v4(),
rating: 0,
title,
color
}
]
setColors(newColors)
}}
/>
<ColorList
colors={colors}
onRemoveColor={id => {
const newColors = colors.filter(color => color.id !== id)
setColors(newColors)
}}
onRateColor={(id, rating) => {
const newColors = colors.map(color => color.id === id ? {...color, rating} : color)
setColors(newColors)
}}
/>
</>
)
}
6.6 리액트 콘텍스트
- 트리 루트의 한 위치에서 상태를 저장하는 패턴은 리액트의 초기 버전이 성공할 수 있는 이유
- 리액트가 진화하고 컴포넌트 트리가 커짐에 따라, 상태를 한 군데 유지하는 원직을 따르는 것이 점점 어려워짐
- 트리 구조가 복잠해짐에 따라 하위 컴포넌트로 전달해야하는 코드들이 많아짐
- 코드의 크기 증가 밎 UI 규모 확장이 어려움
- 이런 구조로 인해 유지보수가 어려워짐
- 리액트 콘텍스트는 데이터를 위한 제트기와 같다
- 콘텍스트 프로바이더는 컴포넌트 트리 전체나 일부를 감싸는 이랙트 컴포넌트
- 콘텍스트 프로바이더는 데이터 허브이기도 하다
- 목적지를 콘텍스트 소비자라고 한다
콘텍스트를 사용하면 데이터를 한 위치에 저장할 수 있지만, 데이터를 사용하지 않는 여러 컴포넌트를 거쳐서 최종 컴포넌트에 전달할 필요가 없어진다.
6.6.1 콘텍스트에 색 넣기
- 리액트에서 콘텍스트를 사용하려면 먼저 콘텍스트 프로바이더에게 데이터를 넣고, 프로바이더를 컴포넌트 트리에 추가 해야함
- 리액트에는 새로운 콘텍스트 객체를 만들 때 쓰는 createContext라는 함수가 있다.
- 만들어진 콘텍스트 객체에는 콘텍스트 Provider와 콘텍스트 Consumer라는 2가지 컴포넌트가 있다.
- color-data.json 파일에서 찾은 디폴트 색 정보를 콘텍스트에 넣자 (index.js 파일)
- createContext를 사용해 새로운 리액트 콘텍스트를 만들고 ColorContex로 정의
- ColorContext.Provider와 ColorContext.Consumer라는 2가지 컴포넌트가 있다.
- 색의 상태를 넣기 위해서는 Provider의 value를 설정하면 콘텍스트에 데이터 추가 가능
import React, { createContext } from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; import colorData from './color-data.json'; export const ColorContext = createContext() ReactDOM.render( , document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: <https://bit.ly/CRA-vitals> reportWebVitals();
- App 컴포넌트는 더이상 상태를 보관하고 하위 컴포넌트에 전달할 필요가 없다.
- 더이상 데이터에 신경쓸 필요 없다.
import './App.css'; import React from 'react' import ColorList from './components/ColorList'; import AddColorForm from './components/AddColorForm' export default function App() { return ( <> <AddColorForm /> <ColorList /> </> ) }
6.6.2 useContext를 통해 색 얻기
- useContext 훅을 사용해 콘텍스트에서 값을 얻을 수 있다.
- 콘텍스트에서 colors를 가져오기 때문에 colors=[] 프로퍼티를 제거
- 프로바이더를 추가했던 index.js 파일로부터 ColorContext 인스턴스를 import 한다.
import React, {useContext} from 'react' import Color from './Color' import { ColorContext } from '../' export default function ColorList() { const { colors } = useContext(ColorContext) if (!colors.length) return <div>표시할 색이 없습니다.</div> return ( <div> { colors.map(color => <Color key={color.id} {...color} />) } </div> ) }
6.6.3 상태가 있는 콘텍스트 프로바이더
- 콘텍스트 프로바이더는 객체를 콘텍스트에 넣을 수 있다. 하지만 프로바이더 자체로는 콘텍스상에 들어 있는 값을 변경할 수 없다.
- 부모 컴포넌트의 도움을 받아야 값을 변경할 수 있다.
- 상태가 있는 컴포넌트의 상태가 변경되면 컴포넌트가 새로운 콘텍스트 데이터를 가지고 콘텍스트 프로바이더를 다시 랜더링한다.
- 콘텍스트 프로바이더를 랜더링하느 상태가 있는 컴포넌트는 우리가 만드는 커스텀 프로바이더 이다.
- 프로바이더와 함께 App을 감싸야 할 때 이 컴포넌트를 사용해야 한다는 뜻이다.
- ColorProvider를 만들자
- ColorProvider는 ColorContext.Provider를 랜더링하는 컴포넌트 이다.
- useState를 사용해 colors라는 상태 변수를 만든다.
- 초기 데이터는 color-data.json에서 가져온다.
- ColorProvider는 상태에서 얻은 colors를 ColorContext.Provider의 value 프로퍼티를 통해 콘텍스트에 설정
- setColors를 콘텍스트에 추가하는 것이 가장 좋은 생각은 아닐 수 있다.
- 콘텍스트에 setColors를 추가하면 다른 개발자가 나중에 이 함수를 사용하면서 실수를 할 여지가 있다.
- 정해진 함수만을 노출하는 것이 좋다
- setColors 함수를 소비자에게 노출하지 않도록 수정
import React, { createContext, useState } from 'react' import colorData from '../color-data.json' import { v4 } from 'uuid' const ColorContext = createContext() export default function ColorProvider({ children }) { const [colors, setColors] = useState(colorData) const addColor = (title, color) => { setColors([ ...colors, { id: v4(), rating: 0, title, color } ]) } const rateColor = (id, rating) => { setColors(colors.map(color => (color.id === id ? {...color, rating} : color ))) } const removeColor = id => setColors(colors.filter(color => color.id !== id)); return ( <ColorContext.Provider value={{ colors, addColor, removeColor, rateColor }}> {children} </ColorContext.Provider> ) }
6.6.4 콘텍스트와 커스텀 훅
- ColorContext 인트턴스를 노출하는 대신, 콘텍스트에서 색을 반환해주는 useColors 훅을 만들 수 있다.
- 필요한 기능을 한개의 자바스크립트 모듈로 감쌌다.
import React, { createContext, useContext, useState } from 'react' import colorData from '../color-data.json' import { v4 } from 'uuid' const ColorContext = createContext() export const useColors = () => useContext(ColorContext) export function ColorProvider({ children }) { const [colors, setColors] = useState(colorData) const addColor = (title, color) => { setColors([ ...colors, { id: v4(), rating: 0, title, color } ]) } const rateColor = (id, rating) => { setColors(colors.map(color => (color.id === id ? {...color, rating} : color ))) } const removeColor = id => setColors(colors.filter(color => color.id !== id)); return ( <ColorContext.Provider value={{ colors, addColor, removeColor, rateColor }}> {children} </ColorContext.Provider> ) }
- index.js 파일에서도 ColorProvider로 감싸야 한다.
- import React, { createContext } from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { ColorProvider } from './components/ColorProvider' ReactDOM.render( , document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: <https://bit.ly/CRA-vitals> reportWebVitals();
- ColorList 컴포넌트 화면에서 색을 표시하기 위해 colors 배열에 접근 해야한다.
- import React, {useContext} from 'react' import Color from './Color' import { useColors } from './ColorProvider' export default function ColorList() { const { colors } = useColors() if (!colors.length) return <div>표시할 색이 없습니다.</div> return ( <div> { colors.map(color => <Color key={color.id} {...color} />) } </div> ) }
- Color 컴포넌트는 훅을 사용해 색을 제거하고 색의 평점을 매기는 함수를 직접 얻을 수 있다.
- Color 컴포넌트는 더이상 함수를 상위 컴포넌트로 이벤트를 전달하지 않아도 된다.
import React from 'react' import { FaTrash } from 'react-icons/fa' import StarRating from './StartRating' import { useColors } from './ColorProvider' export default function Color({ id, title, color, rating }) { const { rateColor, removeColor } = useColors() return ( <section> <h1>{title}</h1> <button onClick={() => removeColor(id)}> <FaTrash /> </button> <div style={{ height: 50, backgroundColor: color }} /> <StarRating selectedStarts={rating} onRate={rating => rateColor(id, rating)} /> </section> ) }
- AddColorForm 컴포넌트는 addColor 함수를 통해 색을 직접 추가할 수 있다.
- import React from 'react' import useInput from './useInput' import { useColors } from './ColorProvider' export default function AddColorForm() { const [titleProps, resetTitle] = useInput('') const [colorProps, resetColor] = useInput('#000000') const { addColor } = useColors() const submit = e => { e.preventDefault() addColor(titleProps.value, colorProps.value) resetTitle() resetColor() } return ( <form onSubmit={submit}> <input {...titleProps} type="text" placeholder="color title..." required /> <input {...colorProps} type="color" required /> <button>ADD</button> </form> ) }
훅은 관심사를 분리 해주는 놀라운 도구이다. UI와 훅스는 따로 개발될 수 있고, 분리해 테스트될 수 있으며, 따로 배치 될 수도 있다.
728x90
'자바스크립트 > 러닝 리엑트' 카테고리의 다른 글
데이터 포함시키기 (0) | 2023.11.13 |
---|---|
훅스 컴포넌트 개선하기 (1) | 2023.11.13 |
JSX를 사용하는 리액트 (1) | 2023.11.13 |
리액트의 작동 원리 (0) | 2023.11.13 |
자바스크립트를 활용한 함수형 프로그래밍 (0) | 2023.11.13 |