
하향 데이터 플로우
모든 부모 컴포넌트는 데이터를 가져올 권리를 가진다.
부모 컴포넌트에서 자식 컴포넌트로 데이터를 보낸다.
App 컴포넌트를 Component를 상속받고 constructor()와 render()를 가지는 클래스 기반 컴포넌트로 리팩토링class App extends Component {
constructor(props) {
super(props);
this.state = { videos: [] };
YTSearch({ key: API_KEY, term: 'surfboards' }, (videos) => {
this.setState({ videos });
});
}
render() {
return (
<div>
<SearchBar />
</div>
);
}
}
YTSearch({ key: API_KEY, term: 'surfboards' }, (videos) => { this.setState({ videos: videos }); });위의 코드에 Syntactic sugar를 가미하면 아래와 같이 작성할 수 있다.
(오브젝트의 프로퍼티와 변수명이 같을 때만 사용할 수 있음)YTSearch({ key: API_KEY, term: 'surfboards' }, (videos) => { this.setState({ videos }); });
// index.js
class App extends Component {
constructor(props) {
super(props);
this.state = { videos: [] };
YTSearch({ key: API_KEY, term: 'surfboards' }, (videos) => {
this.setState({ videos });
});
}
render() {
return (
<div>
<SearchBar />
// videos라는 props 전달
<VideoList videos={this.state.videos} />
</div>
);
}
}
props로 바로 사용이 가능하다.this.props로 사용해야 한다. (그냥 props로 작성 시 부모 컴포넌트의 props와 혼동될 가능성)// video_list.js
const VideoList = (props) => {
// App에서 전달한 videos props를 props.videos와 같이 사용
const videos = props.videos;
return <ul className='col-md-4 list-group'>{props.videos.length}</ul>;
};
VideoList는 상태 변화를 감지할 필요가 없으므로 함수형 컴포넌트로 선언하였다.
JSX에서 DOM에 클래스를 지정할 때는
className을 사용한다. (기존class는 클래스 기반 컴포넌트에 쓰이는class와 혼동되기 때문)
map()으로 리스트 만들기
array.map()배열 각 요소를 콜백 함수의 인자로 전달하여 리턴값을 받아 새로운 배열을 리턴한다. (참고)
const array1 = [1, 4, 9, 16]; // pass a function to map const map1 = array1.map(x => x * 2); console.log(map1); // expected output: Array [2, 8, 18, 32]
key props를 꼭 지정해야 한다.// video_list.js
import VideoListItem from './video_list_item';
const VideoList = (props) => {
const VideoItems = props.videos.map((video) => {
return <VideoListItem key={video.etag} video={video} />;
});
return <ul className="col-md-4 list-group">{VideoItems}</ul>;
};
// video_list_item.js
const VideoListItem = ({ video }) => {
const imageUrl = video.snippet.thumbnails.default.url;
return (
<li className="list-group-item">
...
</li>
);
};
const VideoListItem = (props) => { const video = props.video; return <li>Video</li>; };위의 코드와 아래 코드는 동일하다.
const VideoListItem = ({ video }) => { return <li>Video</li>; };
// index.js
class App extends Component {
constructor(props) {
super(props);
this.state = { videos: [] };
YTSearch({ key: API_KEY, term: 'cats' }, (videos) => {
this.setState({ videos });
});
}
render() {
return (
<div>
<SearchBar />
<VideoDetail video={this.state.videos[0]} />
<VideoList videos={this.state.videos} />
</div>
);
}
}
위의 코드에서 YTSearch()는 비동기적 요청이기 때문에 언제 응답이 완료될지 모른다. 이 상태에서 this.state.videos[0]를 전달하게 되면 Null 값을 참조하여 오류가 발생한다. 이를 아래와 같이 핸들링할 수 있다.
// video_details.js
const VideoDetail = ({ video }) => {
// 널 props 핸들링 (ajax 스피너)
if (!video) {
return <div>Loading...</div>;
}
const videoId = video.id.videoId;
const url = `https://www.youtube.com/embed/${videoId}`;
return (
<div className="video-detail col-md-8">
...
</div>
);
};
👉 응답이 완료될 때까지 Loading...을 띄우다가 응답이 완료되면 state가 변경되고, 자동으로 리렌더링된다.
// index.js
class App extends Component {
constructor(props) {
...
}
render() {
return (
<div>
<SearchBar />
<VideoDetail video={this.state.selectedVideo} />
// 콜백 함수를 props로 전달
<VideoList
onVideoSelect={(selectedVideo) => this.setState({ selectedVideo })}
videos={this.state.videos}
/>
</div>
);
}
}
// video_list.js
const VideoList = (props) => {
const VideoItems = props.videos.map((video) => {
return (
// 다시 한번 자식 컴포넌트로 콜백 함수 전달
<VideoListItem
onVideoSelect={props.onVideoSelect}
key={video.etag}
video={video}
/>
);
});
return <ul className="col-md-4 list-group">{VideoItems}</ul>;
};
// video_list_item.js
// li의 onClick 이벤트 리스너에 콜백 함수 붙이기
const VideoListItem = ({ video, onVideoSelect }) => {
const imageUrl = video.snippet.thumbnails.default.url;
return (
<li onClick={() => onVideoSelect(video)} className="list-group-item">
...
</li>
);
};
몇 단계를 거쳐서 콜백 함수를 전달하면 유지보수가 힘들어서 이렇게 잘 안 쓴다고 함
npm install --save lodash: lodash 라이브러리 설치import _ from 'lodash';: lodash importdebounce(): 키보드 입력이 발생하면 지정한 시간만큼 기다렸다가 함수를 호출한다. 여러 번 발생하는 이벤트 중 가장 마지막 이벤트만 실행한다. (디바운싱 참고)render() {
// 300 밀리초의 지연을 주고 videoSearch가 실행되도록 videoSearch 재정의
const videoSearch = _.debounce((term) => {
this.videoSearch(term);
}, 300);
return (
<div>
// 재정의된 videoSearch 전달
<SearchBar onSearchTermChange={videoSearch} />
...
</div>
);
}