상위컴포넌트의 메소드를 하위컴포넌트에 전달하는 방법

마데슾 : My Dev Space·2020년 2월 17일
0

React

목록 보기
3/7
import React from 'react';
import Nav from './Nav';
import VideoPlayer from './VideoPlayer';
import VideoList from './VideoList';
import { YOUTUBE_API_KEY } from "../../config/youtube";

import { searchYouTube } from '../searchYouTube'; 

class App extends React.Component {
  constructor(props) {
    super(props);
	
    // class component의 state를 videos, currentVideo 2개를 지정하였다.
    // 2개로 충분한가요? state는 왜 App에 있고, 왜 2개를 만들었는지 고민하셨으면 합니다.
    // 정답은 없습니다. 다만, 어떻게, 왜 state를 표현하는지가 중요한것 같습니다. 
    this.state = {
      videos: [],
      currentVideo: null
    };
  }

  componentDidMount() {
    // 초기 state 결과가 확정되어 있지 않고, 비동기적으로 서버요청을 하는 자원이므로,
    // this.state에 할당하지 않습니다.
    // constructor에서 setState를 수행하는 것은 anti-pattern입니다.
    this.getYouTubeVideos('react tutorials');
  }

  // query를 인자로 받는 서버요청 함수를 만들어 줍니다.
  getYouTubeVideos(query) {
    var options = {
      key: YOUTUBE_API_KEY,
      query: query
    };
    
	// helper함수를 이용해서, 결과를 setState하여, state를 update해줍니다.
    searchYouTube(options, videos =>
      this.setState({
        videos: videos,
        currentVideo: videos[0]
      })
    );
  }
  
  // lifting state up 함수입니다. 자식 components가 setState를 실행할 수 있게합니다.
  // 그러기 위해서는 props로 내려줘야겠죠?
  // reactjs.org를 다시 한번 확인하세요!
  handleVideoListEntryTitleClick(video) {
  // 함수의 이름을 지을 때는 그 기능을 예상가능하게 지어야합니다.
  // 본인이 compiler 혹은 컴퓨터가 되려하지마세요. 저희는 개발자이고, 사람입니다.
  // ex) clickEventHandler() { ... } : 어떤 것을 하는지, 모릅니다. 클릭 이벤트 핸들러..
    this.setState({
      currentVideo: video
    });
  }

  render() {
    return (
      {/* lifting state up 함수를 props로 내려줍니다. this를 명시할 수 있도록 bind 해줍니다. */}
      <div>
        <Nav handleSearchInputChange={this.getYouTubeVideos.bind(this)} />
        <div className="col-md-7"> { {/* bootstrap를 참고하세요! 7의 의미는 무엇인가요? */}
          <VideoPlayer video={this.state.currentVideo} />
        </div>
        <div className="col-md-7">
          <VideoList
            // lifting state up 함수를 props로 내려줍니다. this를 명시할 수 있도록 bind 해줍니다.
            handleVideoListEntryTitleClick={this.handleVideoListEntryTitleClick.bind(
              this
            )}
            videos={this.state.videos}
          />
        </div>
      </div>
    );
  }
}

export default App;

위의 코드는 레퍼런스 전문이다
코드를 분석하며 내가 궁금했던 부분에 대해 알아본다

궁금했던 부분

1

VideoListEntry 컴포넌트 안에 있는 .video-list-entry-title를 클릭했을때 어떻게하면 app 컴포넌트state를 변경할 수 있을까?

// VideoListEntry.js
import React from 'react';

const VideoListEntry = ({video, handleVideoListEntryTitleClick}) => (
  <div className="video-list-entry">
    <div className="media-left media-middle">
      <img className="media-object" src={video.snippet.thumbnails.default.url} alt="" />
    </div>
    <div className="media-body">
      <div
        className="video-list-entry-title"
        onClick={() => handleVideoListEntryTitleClick(video)}
      >
        {video.snippet.title}
      </div>
      <div className="video-list-entry-detail">{video.snippet.description}</div>
    </div>
  </div>
);

export default VideoListEntry;

답은 아래의 코드에 있었다

// state
constructor(props) {
  super(props);
  
  this.state = {
    videos: [],
    currentVideo: null
  };
}

// handleVideoListEntryTitleClick 메서드
handleVideoListEntryTitleClick(video) {
  this.setState({
    currentVideo: video
  });
}

<VideoList
{handleVideoListEntryTitleClick={this.handleVideoListEntryTitleClick.bind(this)}
videos={this.state.videos}/>

handleVideoListEntryTitleClick 메서드에 현재 this를 바인딩하여 VideoList 컴포넌트에 props로 내려보내준다.

그리고 VideoList 컴포넌트에서는 아래와같이 사용할 수 있다

// VideoList.js
import React from 'react';
import VideoListEntry from './VideoListEntry';

const VideoList = ({videos, handleVideoListEntryTitleClick}) => (
  <div className="video-list media">
    {videos.map((video) =>
      <VideoListEntry
        key={video.etag}
        video={video}
        handleVideoListEntryTitleClick={handleVideoListEntryTitleClick}
      />
    )}
  </div>
);

export default VideoList;

위의 코드에서 props를 전달하는데 구조 분해 할당이 사용되었다.

VideoListEntry에 이벤트처리를 해줘야되는 .video-list-entry-title이 있으므로 app 컴포넌트에서 VideoList로 props를 내려보내주었던 것처럼 한번더 내려보내준다.

map 메소드를 통해 비디오배열에서 추출한 하나의 비디오도 VideoListEntry 컴포넌트의 props로 내려보내준다

{videos.map((video) =>
  <VideoListEntry
    key={video.etag}
    video={video}
    handleVideoListEntryTitleClick={handleVideoListEntryTitleClick}
  />
)}

그럼 이제 VideoListEntry 컴포넌트에서는,

// VideoListEntry.js
import React from 'react';

const VideoListEntry = ({video, handleVideoListEntryTitleClick}) => (
  <div className="video-list-entry">
    <div className="media-left media-middle">
      <img className="media-object" src={video.snippet.thumbnails.default.url} alt="" />
    </div>
    <div className="media-body">
      <div
        className="video-list-entry-title"
        onClick={() => handleVideoListEntryTitleClick(video)}
      >
        {video.snippet.title}
      </div>
      <div className="video-list-entry-detail">{video.snippet.description}</div>
    </div>
  </div>
);

export default VideoListEntry;
<div
  className="video-list-entry-title"
  onClick={() => handleVideoListEntryTitleClick(video)}
>

.video-list-entry-title의 onClick 이벤트에 VideoList 컴포넌트에서 내려받은 handleVideoListEntryTitleClick 함수를 넣어주고 그 함수의 인자로 VideoList 컴포넌트에서 내려받은 video를 넣어준다.

이제 .video-list-entry-title을 클릭하면 handleVideoListEntryTitleClick(video) 함수가 실행되어

handleVideoListEntryTitleClick(video) {
  this.setState({
    currentVideo: video
  });
}

app 컴포넌트의 this가 바인딩되어 전해졌으므로 app 컴포넌트의 state인 currentVideo가 handleVideoListEntryTitleClick(video) 함수가 실행될때 같이 전달된 인자인 video로 변경된다.

state가 변경되면,

  1. render 함수가 다시 호출되고
  2. VideoPlayer 컴포넌트가 {video : this.state.currentVideo}를 props로 VideoPlayer 컴포넌트를 호출한다.
    • <VideoPlayer video={this.state.currentVideo} />
import React from 'react';

const VideoPlayer = ({video}) => ( // (A)
  !video ? <div className="video-player">Please wait...</div> :
    <div className="video-player">
      <div className="embed-responsive embed-responsive-16by9">
        <iframe className="embed-responsive-item" src={`https://www.youtube.com/embed/${video.id.videoId}`} allowFullScreen></iframe>
      </div>
      <div className="video-player-details">
        <h3>{video.snippet.title}</h3>
        <div>{video.snippet.description}</div>
      </div>
    </div>
);

export default VideoPlayer;
  1. (A)에서 video는 app 컴포넌트의 this.state.currentVideo

  2. video.id.videoId를 동영상 소스 URL과 함께 'src' 어트리뷰트에 속성으로 할당한다

      <iframe className="embed-responsive-item" src={`https://www.youtube.com/embed/${video.id.videoId}`} allowFullScreen></iframe>

이렇게 해주면 현재 클릭한 타이틀의 영상이 .video-player 안에 생성된다

profile
👩🏻‍💻 🚀

0개의 댓글