2. 리액트와 비동기 리퀘스트(Ajax Requests)

hhkim·2021년 7월 14일
0
post-thumbnail

모던 리액트(React)와 리덕스(Redux)

강사 Github


하향 데이터 플로우

모든 부모 컴포넌트는 데이터를 가져올 권리를 가진다.
부모 컴포넌트에서 자식 컴포넌트로 데이터를 보낸다.


함수형 컴포넌트를 클래스 기반 컴포넌트로 리팩토링

  • 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 });
});

Props

  • 부모 컴포넌트에서 자식 컴포넌트에 데이터를 전달할 때 props를 사용한다.
// 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가 인자로 전달되어 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>;
};

널(Null) props 핸들링

// 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가 변경되고, 자동으로 리렌더링된다.


콜백 함수 전달

  • 콜백 함수를 전달해서 부모와 자식 컴포넌트 간에 소통을 할 수 있다.
  • 자식 컴포넌트에서 이벤트가 발생하면 전달받은 콜백 함수를 실행하여 부모 컴포넌트의 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 import
  • debounce(): 키보드 입력이 발생하면 지정한 시간만큼 기다렸다가 함수를 호출한다. 여러 번 발생하는 이벤트 중 가장 마지막 이벤트만 실행한다. (디바운싱 참고)
render() {
  // 300 밀리초의 지연을 주고 videoSearch가 실행되도록 videoSearch 재정의
    const videoSearch = _.debounce((term) => {
      this.videoSearch(term);
    }, 300);
    return (
      <div>
        // 재정의된 videoSearch 전달
        <SearchBar onSearchTermChange={videoSearch} />
        ...
      </div>
    );
  }

0개의 댓글