본문 바로가기
React

[React 스터디] 1 : 주요 개념

by 바나냥 2020. 8. 16.

https://blex.me/@yoyounn18/react-%EC%86%8C%EA%B0%9C-%EB%B0%8F-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC-react%EB%8A%94-%EC%99%9C-%EB%B9%A0%EB%A5%B4%EB%A9%B0-%EA%B0%81%EA%B4%91%EB%B0%9B%EB%8A%94-%EA%B8%B0%EC%88%A0%EC%9D%B8%EA%B0%80

 

React 소개 및 작동 원리 (React는 왜 빠르며 각광받는 기술인가)

React란? 사용자 인터페이스를 만들기 위한 JavaScript 라이브러리 페이스북의 소프트웨어 엔지니어 Jordan Walke가 개발 페이스북, 인스타그램 등 넓게 사용되는 중 class HelloMessage extends React.Component { re

blex.me

https://reactjs.org/docs/hello-world.html

 

Hello World – React

A JavaScript library for building user interfaces

reactjs.org

 


- React란?

React 컴포넌트는 render()라는 메서드를 이용하여 데이터를 입력받아 화면에 표시할 내용을 반환합니다.

 

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);

 

- JSX는 객체를 나타냅니다.

 

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

 

Babel은 JSX를 컴파일하여 React.createElement()호출합니다.

 

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

 

 

- 필요한 것만 업데이트하는 반응

React DOM은 요소와 하위 요소를 이전 요소와 비교하고 DOM을 원하는 상태로 가져 오는 데 필요한 DOM 업데이트 만 적용합니다.

 

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById('root'));
}

setInterval(tick, 1000);

 

 

 

- 구성 요소 작성

React를 기존 앱에 통합하면 상향식으로 시작하여 점차적으로 뷰 계층 구조의 맨 위로 올라갈 수 있습니다.

 

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

 

- 이벤트 처리

preventDefault () 메서드는 취소 가능한 경우 이벤트를 취소합니다. 즉, 이벤트에 속한 기본 작업이 발생하지 않습니다.

예를 들어 다음과 같은 경우에 유용 할 수 있습니다.

 

  • "제출"버튼을 클릭하면 양식이 제출되지 않습니다.
  • 링크를 클릭하면 링크가 URL을 따라 가지 못하도록 합니다.

 

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

 

JavaScript에서 클래스 메서드는 기본적으로 바인딩 되지 않습니다. ()와 같이 뒤에 없는 메서드를 참조하는 경우 onClick={this.handleClick} 해당 메서드를 바인딩 해야 합니다.

 

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

 

- 조건부 렌더링

사용자가 로그인했는지 여부에 따라 이러한 구성 요소 중 하나를 표시 하는 구성 요소를 만듭니다.

현재 상태에 따라 <LoginButton /> 또는 <LogoutButton />이 렌더링 됩니다. 또한 Greeting 컴포넌트에서<UserGreeting /> 또는 <GuestGreeting /> 을 렌더링합니다.

 

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);
class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }

  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

ReactDOM.render(
  <LoginControl />,
  document.getElementById('root')
);

 

https://codepen.io/gaearon/pen/QKzAgB?editors=0010

 

Element Variable Example

...

codepen.io

 

- Map 과 Key

key는 요소 목록을 만들 때 포함해야하는 특수 문자열 속성입니다.

key는 주변 배열의 컨텍스트에서만 의미가 있습니다. <li>요소가 아닌 배열 ListItem의 <ListItem />요소에 키를 유지해야 합니다.

 

function ListItem(props) {
  // Correct! There is no need to specify the key here:
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // Correct! Key should be specified inside the array.
    <ListItem key={number.toString()} value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

 

 

- 여러 입력 처리

input 요소에서 여러 제어를 처리해야 하는 경우 각 요소에 name 속성을 추가하고 handler functionevent.target.name 값에 따라 수행 할 작업을 선택하도록 할 수 있습니다.

 

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.name === 'isGoing' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

 

 

- 합성 vs 상속 특수화

React는 강력한 합성 모델을 가지고 있으며, 상속 대신 합성을 사용하여 컴포넌트 간에 코드를 재사용하는 것이 좋습니다.

어떤 컴포넌트들은 어떤 자식 엘리먼트가 들어올 지 미리 예상할 수 없는 경우가 있습니다. 범용적인 ‘박스’ 역할을 하는 Sidebar 혹은 Dialog와 같은 컴포넌트에서 특히 자주 볼 수 있습니다.

이러한 컴포넌트에서는 특수한 children prop을 사용하여 자식 엘리먼트를 출력에 그대로 전달하는 것이 좋습니다.

 

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
      {props.children}
    </FancyBorder>
  );
}

class SignUpDialog extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handleSignUp = this.handleSignUp.bind(this);
    this.state = {login: ''};
  }

  render() {
    return (
      <Dialog title="Mars Exploration Program"
              message="How should we refer to you?">
        <input value={this.state.login}
               onChange={this.handleChange} />
        <button onClick={this.handleSignUp}>
          Sign Me Up!
        </button>
      </Dialog>
    );
  }

  handleChange(e) {
    this.setState({login: e.target.value});
  }

  handleSignUp() {
    alert(`Welcome aboard, ${this.state.login}!`);
  }
}

 

 

- React로 생각하기

 

다섯개의 컴포넌트로 이루어진 앱을 한번 봅시다. 각각의 컴포넌트에 들어간 데이터는 이탤릭체로 표기했습니다.

 

  1. FilterableProductTable(노란색): 예시 전체를 포괄합니다.
  2. SearchBar(파란색): 모든 유저의 입력(user input) 을 받습니다.
  3. ProductTable(연두색): 유저의 입력(user input)을 기반으로 데이터 콜렉션(data collection)을 필터링 해서 보여줍니다.
  4. ProductCategoryRow(하늘색): 각 카테고리(category)의 헤더를 보여줍니다.
  5. ProductRow(빨강색): 각각의 제품(product)에 해당하는 행을 보여줍니다.

 

이제 목업에서 컴포넌트를 확인하였으므로 이를 계층 구조로 나열해봅시다. 모형의 다른 컴포넌트 내부에 나타나는 컴포넌트는 계층 구조의 자식으로 나타냅니다.

 

  • FilterableProductTable

    • SearchBar
    • ProductTable

      • ProductCategoryRow
      • ProductRow

 

 

데이터 모델을 렌더링하는 앱의 정적 버전을 만들기 위해 다른 컴포넌트를 재사용하는 컴포넌트를 만들고 props를 이용해 데이터를 전달해줍시다. props는 부모가 자식에게 데이터를 넘겨줄 때 사용할 수 있는 방법입니다. 정적 버전을 만들기 위해 state를 사용하지 마세요. state는 오직 상호작용을 위해, 즉 시간이 지남에 따라 데이터가 바뀌는 것에 사용합니다. 우리는 앱의 정적 버전을 만들고 있기 때문에 지금은 필요하지 않습니다.

 

 

https://ko.reactjs.org/docs/faq-state.html#what-is-the-difference-between-state-and-props

 

컴포넌트 State – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

앱을 만들 때 하향식(top-down)이나 상향식(bottom-up)으로 만들 수 있습니다. 다시 말해 계층 구조의 상층부에 있는 컴포넌트 (즉 FilterableProductTable부터 시작하는 것)부터 만들거나 하층부에 있는 컴포넌트 (ProductRow) 부터 만들 수도 있습니다. 간단한 예시에서는 보통 하향식으로 만드는 게 쉽지만 프로젝트가 커지면 상향식으로 만들고 테스트를 작성하면서 개발하기가 더 쉽습니다.

계층구조의 최상단 컴포넌트 (FilterableProductTable)는 prop으로 데이터 모델을 받습니다. 데이터 모델이 변경되면 ReactDOM.render()를 다시 호출해서 UI가 업데이트 됩니다.

 

애플리케이션은 다음과 같은 데이터를 가지고 있습니다.

 

  • 제품의 원본 목록
  • 유저가 입력한 검색어
  • 체크박스의 값
  • 필터링 된 제품들의 목록

 

각각 살펴보고 어떤 게 state가 되어야 하는 지 살펴봅시다. 이는 각 데이터에 대해 아래의 세 가지 질문을 통해 결정할 수 있습니다.

 

  1. 부모로부터 props를 통해 전달됩니까? 그러면 확실히 state가 아닙니다.
  2. 시간이 지나도 변하지 않나요? 그러면 확실히 state가 아닙니다.
  3. 컴포넌트 안의 다른 state나 props를 가지고 계산 가능한가요? 그렇다면 state가 아닙니다.

 

결과적으로 애플리케이션은 다음과 같은 state를 가집니다.

 

  • 유저가 입력한 검색어
  • 체크박스의 값

 

애플리케이션이 가지는 각각의 state에 대해서

 

  • state를 기반으로 렌더링하는 모든 컴포넌트를 찾으세요.
  • 공통 소유 컴포넌트 (common owner component)를 찾으세요. (계층 구조 내에서 특정 state가 있어야 하는 모든 컴포넌트들의 상위에 있는 하나의 컴포넌트).
  • 공통 혹은 더 상위에 있는 컴포넌트가 state를 가져야 합니다.
  • state를 소유할 적절한 컴포넌트를 찾지 못하였다면, state를 소유하는 컴포넌트를 하나 만들어서 공통 오너 컴포넌트의 상위 계층에 추가하세요.

 

이 전략을 애플리케이션에 적용해봅시다.

 

  • ProductTable은 state에 의존한 상품 리스트의 필터링해야 하고 SearchBar는 검색어와 체크박스의 상태를 표시해주어야 합니다.
  • 공통 소유 컴포넌트는 FilterableProductTable입니다.
  • 의미상으로도 FilterableProductTable이 검색어와 체크박스의 체크 여부를 가지는 것이 타당합니다.

 

좋습니다. state를 FilterableProductTable에 두기로 했습니다. 먼저 인스턴스 속성인 this.state = {filterText: '', inStockOnly: false}  FilterableProductTable의 constructor에 추가하여 애플리케이션의 초기 상태를 반영합니다. 그리고 나서 filterText와 inStockOnly를 ProductTable와 SearchBar에 prop으로 전달합니다. 마지막으로 이 props를 사용하여 ProductTable의 행을 정렬하고 SearchBar의 폼 필드 값을 설정하세요.

'React' 카테고리의 다른 글

[React 스터디] 2 : 접근성  (0) 2020.08.21
React Context Provider Login  (0) 2020.08.18
조건부 렌더링  (0) 2020.08.10
Route Component에 props 전달하기  (0) 2020.08.10
className 에 if문 적용하기  (0) 2020.07.31

댓글