react 공식문서의 -react로 사고하기-를 기반으로 정리해 본다.
UI의 컴포넌트 계층화
정적인 버전
최소한의 state 설정
state의 위치 설정
역방향 데이터 흐름 추가
우선적으로 해야 할 일은 만들고자 하는 앱이나 웹의 구조를 컴포넌트 계층 구조로 만드는 것이다.
위의 맛집 검색 앱은 4개의 컴포넌트로 이루어져있다.
위와 같이 구분된 검포넌트는 계층 구조로 나열될 수 있으며, 컴포넌트 내부의 컴포넌트는 서로 부모자식 관계로 나타낸다.
- AppFrame
- SearchBar
- Restaurants
- Restaurant
컴포넌트 계층구조가 구성 되었다면, 그 구조를 기반으로 UI 렌더링만 가능한 정적인 버전의 앱이나 웹을 만든다.
한번에 기능까지 구현하지 않는 이유는 정적 버전을 만드는 데에는 생각이 적게 필요하지만 타이핑이 많이 필요하고, 기능을 구현하는 것은 생각이 많이 필요하지만 타이핑은 적게 필요하기 때문이다. 이처럼 과정을 나눠서 만들면 각각 많이 요구되는 능력을 구분해 집중적으로 활용할 수 있다.
정적인 버전을 만들기 위해 다른 컴포넌트를 재사용하는 컴포넌트를 만들고 props를 이용해 데이터를 전달한다.
정적인 버전에 state를 사용하지 않는다.
state는 오직 상호 작용(기능)을 위해 즉 시간이 지남에 따라 데이터가 바뀌는 것에 사용한다.
상향식과 하향식
간단한 수준에서는 하향식으로 만드는게 더 쉽지만 프로젝트가 커지면 상향식으로 만들고 테스트를 작성해가면서 개발하는게 더 쉽다.
간단하게 만든 예시 코드(안 될 가능성 다분)
// AppFrame import restaurantList from './../dummy/data.js' function AppFrame() { return ( <> <SearchBar /> <Restaurants data={restaurantList}/> </> ) } // SearchBar function SearchBar() { return ( <form> <h1>근처 맛집 검색 앱</h1> <div> <input type='text' placeholde='메뉴를 검색해보세요..'/> <button>🔎</button> </div> </form> ) } // Restaurants function Restaurants(props) { return ( <div> <p> <input type='checkbox'/> 배달 가능 </p> <ul> {props.data.map((item) => { return <Restaurant key={item.id} item={item}/> )} </ul> </div> ) } // Restaurant function Restaurant(props) { const data = props.item return ( <li> <img src={data.logo}/> <h2> <a href={data.link}>{data.title}</a> </h2> </li> ) }
UI를 상호 작용하게 만들려면 기반 데이터 모델을 변경할 수 있는 가변적 변수인 state가 필요하다.
앱을 올바르게 만들기 위해서는 해당 앱에서 필요로 하는 변경 가능한 state의 최소 집합을 고려해야 한다.
앱이 필요로 하는 가장 최소한의 state를 찾고 이를 통해 나머지 모든 것들이 상황에 따라 계산되도록 만든다.
- 사용자가 입력한 검색어
- 체크박스 체크 유무
- 식당 데이터 목록
- 필터링 된 식당 데이터 목록
react에서는 세가지 질문을 기반으로 state인지 props인지 구별한다.
부모(외부)로부터 전달받은 값인가?
시간이 지나도 변하지 않는가?
컴포넌트 내부의 다른 state나 props를 가지고 계산이 가능한 값인가?
위의 세가지 질문에 해당하지 않으면 그 값은 state가 될 수 있다.
식당 데이터 목록
: props를 통해 전달받은 값이므로 X필터링 된 식당 데이터 목록
: 원본 데이터와 체크박스, 검색어로 계산 가능하므로 X사용자가 입력한 검색어
: 시간이 지남에 따라 변경되면서 다른 것들로 계산되지 않음으로 O체크박스 체크 유무
: 시간이 지남에 따라 변경되면서 다른 것들로 계산되지 않음으로 O최소한의 state를 정했으면, state들이 어디에 위치 되어야 할지 고려해야 한다.
react는 단방향 데이터 흐름을 따르기 때문에 다음과 같은 전략을 참고해 결정해 볼 수도 있다.
state 기반 렌더링 : Restaurants, Restaurant
공통 소유 컴포넌트 : Restaurants, Appfram
state는 Restaurants나 AppFrame 컴포넌트가 적절하다.
SearchBar도 state에 영향을 주는 컴포넌트이기에 state를 AppFrame과 Restaurants에 위치시킨다.
// AppFrame import restaurantList from './../dummy/data.js' function AppFrame() { const [text, setText] = useState(''); return ( <> <SearchBar /> <Restaurants data={restaurantList}/> </> ) } // Restaurants function Restaurants(props) { const [isCheck, setIsCheck] = useState(false); return ( <div> <p> <input type='checkbox'/> 배달 가능 </p> <ul> {props.data.map((item) => { return <Restaurant key={item.id} item={item}/> )} </ul> </div> ) }
입력값을 통해서 식당 목록이 변해야 하기때문에 SearchBar 컴포넌트에서 state를 변경할 수 있어야 하며, 체크 박스의 값을 통해 식당 목록이 변해야 하기 때문에 Restaurants에서도 state를 변경할 수 있어야 한다.
상태 변경 함수를 props로 전달해 하위 컴포넌트에서 상위 컴포넌트의 state를 변경하는 상태 끌어올리기를 통해 최소한의 state를 관리 한다.
공식문서의 예시가 class 컴포넌트로 되어있어서 혼자서 함수형으로 변환하려고 했지만 예시에서 식당의 원본 목록을 관리하는 과정에서 state 외에 관리하는 방법을 알아내지 못했다.
ajax로 데이터를 가져온다면 state로 안만들어도 가능할 것 같지만 우선 아래 예시에서는 식당 목록을 state로 관리 하는 방식으로 코드를 작성하고 추후에 방법을 찾게 되면 수정하겠다
// AppFrame import restaurantList from './../dummy/data.js' function AppFrame() { const [list, setList] = useState(restaurantList) const [text, setText] = useState(''); const handleSearchClick = (search) => { setText(search) } useEffect(()=>{ if(text!=='') { const filtered = restaurantList.filter(item=>item.title===text) setList(filtered) } else { setList(restaurantList) } }, [text]) return ( <> <SearchBar handleSearchClick={handleSearchClick}/> <Restaurants data={list}/> </> ) } // SearchBar function SearchBar({handleSearchClick}) { const inputRef = useRef(null) return ( <form> <h1>근처 맛집 검색 앱</h1> <div> <input type='text' placeholde='메뉴를 검색해보세요..' ref={inputRef}/> <button onClick={()=>handleSearchClick(inputRef.current.value)} 🔎</button> </div> </form> ) } // Restaurants function Restaurants({data}) { const [isCheck, setIsCheck] = useState(false); const handleCheck = () => { setIsCheck(!isCheck) } return ( <div> <p> <input type='checkbox' onClick={handleCheck} /> 배달 가능 </p> <ul> {!isCheck?data.map((item) => { return <Restaurant key={item.id} item={item}/> ):data.filter(item=>item.deliver===true).map((item) => { return <Restaurant key={item.id} item={item}/> )} </ul> </div> ) }
참고 사이트
React-문서-React로 사고하기