만들면서 학습하는 리액트(react):사용편 - 025

4 분 소요

사용편

검색 결과가 없을 경우

요구사항

검색 결과가 검색폼 아래 위치한다. 검색 결과가 없을 경우와 있을 경우를 구분한다.

구현

// main.js
class App extends React.Component {
  constructor() {
    super();

    this.state = {
      searchKeyword: "",
      searchResult: [], 
    }
  }
  ...
  render() {
    <>
      <div className="container">
        <form>
        ...
        </form>
        <div className="content">
          {this.state.searchResult.length > 0 ? (
            <div>검색 결과 목록 표시하기</div>
          ):(
            <div className="empty-box">검색 결과가 없습니다</div>
          )}
        </div>
      </div>
    </>
  }
}

검색 결과가 있을 경우

검색결과를 관리하기 위해 1-vanilla/js 폴더의 Store.js, storage.js, helpers.js 를 2-react/js 폴더로 복사해온다

// Store.js
import storage from './storage.js';
class Store { // export default 삭제
  constructor(storage) {
    ...
    this.searchKeyword = "";
    // 삭제 this.searchKeyword = "";
    // 삭제 this.searchResult = [];
    // 삭제 this.selectedTab = TabTypes.KEYWORD;
  }
  ...
}

const store = new Store(storage);
export default store;
// main.js
import store from './js/Store.js';

class App extends React.Component {
  ...
  handleSubmit(event) {
    event.preventDefault();
    this.search(this.state.searchKeyword);
  }

  search(searchKeyword) {
    const searchResult = store.search(searchKeyword);
    this.setState({ searchResult });
  }
  ...
  render() {
    <>
      <div className="container">
        ...
        <div className="content">
          {this.state.searchResult.length > 0 ? (
            <ul className="result">
              {this.state.searchResult.map(item => {
                return (
                  <li>
                    <img src={ item.imageUrl } alt={ item.name } />
                    <p>{ item.name }</p>
                  </li>
                )
              })}
            </ul>
          ):(
            <div className="empty-box">검색 결과가 없습니다</div>
          )}
        </div >
    </>
  }
}

setState는 변경된 필드만 기존 필드와 병합하는 방식으로 필드를 관리함

Store에서는 storage 외에 다른 변수(searchResult 등)을 더이상 보관하지 않음, 그래서 search() 함수를 수정

// Store.js
class Store {
  ...
  search(searchKeyword) {
    return this.storage.productData.filter(
      (product) => product.name.includes(searchKeyword)
    );
  }
  ...
}

데이터 흐름

  1. 처음에는 searchResult 가 빈배열 > 검색결과가 없다고 보여짐
  2. 검색어를 입력하고 엔터를 치면 handleSubmit() 함수 호출
  3. search() 함수 호출
  4. search() 함수는 입력한 검색어로 Store에서 search() 함수를 호출
  5. Store의 search() 함수는 searchResult를 반환
  6. 반환된 searchResult는 setState로 searchResult 상태를 갱신
  7. render() 함수 호출
  8. 검색 결과 노출

리스트와 키

key는 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 함

한마디로 searchResult의 li에 key 속성이 있어야 한다는 뜻

리액트의 가상돔과 이전 가상돔과

  1. 앨리먼트 타입이 다를 경우
  2. Key 값이 다를 경우

위의 두가지 가정하에 재조정 알고리즘을 사용하여 빅오계산법에 의해 O(n^3)만큼의 계산복잡도를 O(n)로 줄일 수 있다고 함

li 태그에 key를 등록

// main.js
import store from './js/Store.js';

class App extends React.Component {
  ...
  render() {
    <>
      <div className="container">
        ...
        <div className="content">
          {this.state.searchResult.length > 0 ? (
            <ul className="result">
              {this.state.searchResult.map(item => {
                return (
                  <li key={item.id}>
                    <img src={ item.imageUrl } alt={ item.name } />
                    <p>{ item.name }</p>
                  </li>
                )
              })}
            </ul>
          ):(
            <div className="empty-box">검색 결과가 없습니다</div>
          )}
        </div >
    </>
  }
}

map의 index를 key로 사용하는 경우가 있는데, 없는 것 보다는 낫지만 최후의 수단으로 사용

검색을 하기도 전에 검색 결과가 없다고 나오는 문제

class App extends React.Component {
  constructor() {
    this.state = {
      ...
      submitted: false,
    }
  }
  ...
  search(searchKeyword) {
    ...
    this.setState({
      searchResult : store.search(searchKeyword),
      submitted : true,
    });
  }
  ...
  render() {
    <>
      ...
      <div className="container">
        ...
        <div className="content">
          { this.state.submitted && (
            this.state.searchResult.length > 0 ? (
              ...
            ):(
              ...
            )
          )}
        </div>
      </div>
    </>
  }
}

render() 함수가 가독성이 떨어졌음, 리펙토링 필요

class App extends React.Component {
  ...
  render() {
    const searchForm = (
      <form
        onSubmit={ ( event ) => this.handleSubmit(event) }
        onReset={ () => this.handleReset() }
      >
        <input
          type="text"
          placeholder="검색어를 입력하세요"
          autoFocus
          value={ this.state.searchKeyword }
          onChange={ (event) => this.handleChangeInput(event) }
        />
        { this.state.searchKeyword.length > 0 && (
          <button type="reset" className="btn-reset"></button>
        )}
      </form>
    );

    const searchResult = (
      this.state.searchResult.length > 0 ? (
        <ul className="result">
          { this.state.searchResult.map((item, index) => {
            return (
              <li key={item.id}>
                <img src={item.imageUrl} alt={item.name} />
                <p>{item.name}</p>
              </li>
            );
          })}
        </ul>
      ) : (
        <div className="empty-box">검색 결과가 없습니다</div>
      )
    );
    <>

    </>
    result (
      <>
        <header>
          <h2 className="container">검색</h2>
        </header>
        <div className="container">
          { searchForm }
          <div className="content">{ this.state.submitted && ( searchResult ) }</div>
        </div>
      </>
    );
  }
}

검색결과 초기화

요구사항

x 버튼을 클릭하면 검색폼이 초기화 되고, 검색 결과가 사라진다

구현

...
class App extends React.Component {
  ...
  handleReset() {
    this.setState({
      searchKeyword: "",
      submitted: false,
    });
  }
  handleChangeInput(event) {
    const searchKeyword = event.target.value;
    if(serachKeyword.length <= 0 && this.state.submitted) {
      return this.handleReset();
    }
    this.setState({ searchKeyword });
  }
  ...
}

중간정리

검색결과를 리스트로 렌더링하였음

리액트는 가상돔의 전/후 버전을 비교해서 최소한의 돔 조작을 하면서 성능을 높임

계산 복잡도를 줄이기 위해 리스트일 경우 key 속성을 사용하도록 함

모든 UI가 state에 의존하기 때문에 잘 설계된 state만 관리하면 UI를 예측하기 쉽게 제어할 수 있음

참고