React 기초

Hyunsol Park·2020년 9월 7일
2
post-thumbnail

React 기초

활용 자료

https://www.youtube.com/watch?v=Ke90Tje7VS0&list=PLTjRvDozrdlw5En5v2xrBr_EqieHf7hGs&index=2

리액트 앱 만드는 법 (세팅하는 법)

1) https://nodejs.org/en/ 여기서 node.js 를 다운받는다. →npm을 사용하기 위해 다운

2)이후 터미널에서 다음과 같이 적는다:

sudo npm i -g create-react-app

3)다음 원하는 폴더로 들어가서 다음과 같이 리액트 프로젝트를 생성한다. 이 경우 나는 프로젝트 이름을 react-app 이라 하겠다:

create-react-app reat-app

JSX

리액트에서는 아래와 같은 클래스를 많이 쓴다. 우선 여기서 주의깊게 살펴봐야 될 점은 return() 안에 있는 html태그들이다. 이를 JSX라고 부른다. JSX는 자바스크립트에서 html을 사용할 수 있는 리액트만의 기능이다.

class App extends Component {
	render(){
		return(
			<div>
				<h1>Hello World</h1>
			<div>
		)
	}
}

리액트 모듈에서 리액트 오브젝트 가져오는 법

import React from 'react'
import ReactDOM from 'react-dom';

간단한 es6 복습: 위는 'react'라는 모듈에서 React라는 오브젝트를 가져온다는 뜻.

Hello World

리액트에서 hello world를 출력해보겠다:

import React from "react";
import ReactDOM from "react-dom";

const element = <h1>hello world</h1>;
ReactDOM.render(element, document.getElementById("root"));

위를 보면 ReactDOM.render() 이라는 것이 있는데 이게 jsx를 실제 화면에 보이는 html로 보이게 해준다. render() method에는 두개의 parameter가 들어간다. 첫째는 element이고 둘째는 어디에 jsx를 띄울 것인가 하는 것이다. 위의 경우에는 index.html에 id가 root인 태그 안에 element를 띄어줘라 라고 하는 것이다.

Components

리액트는 컴포넌트를 기반하여 돌아가는 라이브러리이다. 컴포넌트의 기본 구조는 다음과 같다:

class App extends Component {
	state = {};
	render() {
		return(
			<div></div>
		)
	}
}

리액트에 모듈에 고유하게 있는 컴포넌트 오브젝트로부터 inherit 된 클래스를 만들고 그안에 보면 state라고 하는 리액트 만의 고유한 property가 있고 render() method가 있다. 그리고 이 render모듈안에 리턴 함수가 들어가고 리턴 함수 안에 jsx가 들어간다.

값을 다이나믹하게 DOM에 보여주는 법

다이나믹하게 변하는 값을 html에 띄우려면 jsx 안에 자바스크립트를 사용해야 된다.

class Counter extends Component {
  state = {
      count: 0
  };

  render() {
    return (
      <>
        <span>{this.state.count}</span>
        <button>Increment</button>
      </>
    );
  } 
}

위의 코드를 보면 state 오브젝트 안에 count property가 있고 이를 render method로 html에 표현하고 있다. 그 안에 보면 { }안에 자바스크립트 코드가 있는데 jsx안에 { }를 넣으면 자바스크립트 코드를 사용 할 수 있다. 이 안에는 자바스크립트 함수도 넣을 수 있다. 다음의 예시를 보자:

class Counter extends Component {
  state = {
    count: 0,
  };

  render() {
    return (
      <>
        <span>{this.formatCount()}</span>
        <button>Increment</button>
      </>
    );
  }

  formatCount() {
    const { count } = this.state;
    return count === 0 ? "Zero" : count;
  }
}

formatCount()라는 함수를 만들어서 안에 넣은 것을 볼 수 있다.

Setting Attributes

html 태그 안에 attribute를 만들고 싶다면 다음과 같이 state안에 property를 만들고 난 후 jsx를 attrbitue안에 넣으면 된다. 아래를 보자:

class Counter extends Component {
  state = {
    count: 0,
    imageUrl: "https://picsum.photos/200",
  };

  render() {
    return (
      <>
        <img src={this.state.imageUrl} alt="" />
        <span>{this.formatCount()}</span>
        <button>Increment</button>
      </>
    );
  }

  formatCount() {
    const { count } = this.state;
    return count === 0 ? "Zero" : count;
  }
}

Class attribute을 생성하는 법

class는 자바스크립트에서 이미 지정이 되어있는 키워드 이므로 jsx에서는 class 속성을 쓸 때 class 대신 className을 쓴다.

<span className="bade badge-primary">{this.formatCount()}</span>

Styles 속성 사용하는 법

스타일 속성을 사용하기 위해서는 먼저 property를 만든 다음 render method에 그 property를 넘겨주는 방식으로 할 수 있다.

class Counter extends Component {
  state = {
    count: 0,
  };

  styles = {
      fontSize: 30,
      fontWeight: 'bold'
  }

  render() {
    return (
      <>
        <span style={this.styles} className="bade badge-primary m-2">{this.formatCount()}</span>
        <button className="btn btn-secondary btn-sm">Increment</button>
      </>
    );
  }

  formatCount() {
    const { count } = this.state;
    return count === 0 ? "Zero" : count;
  }
}

위에 보면 styles 오브젝트 안에 fontSize, fontWeight라는 property를 만든 후 태그 안에 활용하는 것을 볼 수 있다.

클래스를 다이나믹하게 바꾸는 법

위의 Counter 오브젝트에서 count가 0이면 버튼 className이 badge-warning 그 이외에는 badge-primary로 바꾸고 싶다고 해보자. 그러면 이렇게 할 수 있다:

render() {
    let classes = "badge m-2"
    classes += (this.state.count === 0) ? "badge-warning" : "badge-primary"
    return (
      <>
        <span className={classes}>{this.formatCount()}</span>
        <button className="btn btn-secondary btn-sm">Increment</button>
      </>
    );
  }

  formatCount() {
    const { count } = this.state;
    return count === 0 ? "Zero" : count;
  }
}

Rendering Lists

아래와 같이 state 안에 array 형식의 property를 만들어 보자:

class Counter extends Component {
  state = {
    count: 0,
    tags: ["tag1", "tag2", "tag3"],
  };

이를 jsx에서 리스트 태그를 활용해 렌더링을 하고 싶다면 다음과 같이 .map()을 사용하면 된다.

class Counter extends Component {
  state = {
    count: 0,
    tags: ["tag1", "tag2", "tag3"],
  };

  render() {
    return (
      <>
        <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        <button className="btn btn-secondary btn-sm">Increment</button>
        <ul>
          {this.state.tags.map((tag) => (
            <li>{tag}</li>
          ))}
        </ul>
      </>
    );
  }

Conditional Rendering

Conditional rendering이란 렌더링에 조건문을 사용하는 것을 이야기한다. 예를 들면 id가 true이면 'welcome user'라는 메세지를 띄우고 id가 false이면 'no user'라는 메세지를 띄우는 식을 이야기 한다. 이를 하기 위해서는 jsx에서 if조건문을 사용하면 된다:

class Counter extends Component {
  state = {
    count: 0,
    tags: ['tag1','tag2','tag3'],
  };

  renderTags() {
      if (this.state.tags.length === 0) return <p>there is no tag!</p>

      return <ul>{this.state.tags.map(tag =><li key={tag}>{tag}</li>)}</ul>
  }

  render() {
    return <div>{this.renderTags()}</div>;
  }
}

위의 코드는 tags list에 property가 하나도 없으면 'there is no tag'라는 메세지를 띄우고 그렇지 않으면 tags 안에 있는 각 아이템을 li태그에 붙여서 렌더링을 하게 하는 코드이다.

혹은 다음과 같이 할 수도 있다:

render() {
    return <div>
        {this.state.tags.length === 0 && "Please create a new tag!"}
        {this.renderTags()}
        </div>;

위와 같이 조건에 &&를 붙여서 메세지를 출력할 수도 있다.

Handling Events

버튼을 클릭하면 메세지를 호출하는 것을 한다고 해보자. 그럼 다음과 같이 하면 된다:

handleIncrement() {
      console.log("Increment Clicked!")
  }

  render() {
    return (
      <>
        <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        <button onClick={this.handleIncrement} className="btn btn-secondary btn-sm">Increment</button>
      </>
    );
  }

위의 코드를 보면 button 태그 안에 onClick 속성이 있고 이 속성은 handleIncrement를 호출한다. 여기서 눈여겨 봐야 될 것은 onClick이 리액트에서는 C가 대문자라는 것과 handleIncrement가 함수를 바로 실행하는 (handleIncrement();)가 아닌 단순히 reference 한다는 점이다.

Binding Event Handlers

this는 오브젝트 안의 메소드에서 사용될 때에는 그 오브젝트를 가리키지만 독립적인 function에서 사용될 때에는 글로벌 오브젝트인 window를 가르킨다. 이를 코드에서 보면 다음과 같다:

  • 오브젝트 안에서 this를 사용할 때
const person = {
	name: "John",
	walk(){
		console.log(this)
	}
}

walk();
//returns
{name: "John", walk: f}
  • 독립적인 function에서 this를 사용할 때
const person = {
	name: "John",
	walk(){
		console.log(this)
	}
}

const getPerson = person.walk

getPerson();

//returns
Windows Object

이를 해결하기 위해서는 .bind를 사용해야 된다:

const person = {
	name: "John",
	walk(){
		console.log(this)
	}
}

const getPerson = person.walk.bind(person)

getPerson();

//returns
{name: "John", walk: f}

이 개념을 리액트에서 버튼을 누르면 이벤트가 생성되는 것에 적용을 해보면 이렇게 된다:

class Counter extends Component {
  state = {
    count: 0,
  };

  constructor() {
      super();
      this.handleIncrement = this.handleIncrement.bind(this)
  }

  handleIncrement() {
    console.log("Increment Clicked!", this);
  }

  render() {
    return (
      <>
        <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        <button
          onClick={this.handleIncrement}
          className="btn btn-secondary btn-sm"
        >
          Increment
        </button>
      </>
    );
  }

  getBadgeClasses() {
    let classes = "badge m-2 badge-";
    classes += this.state.count === 0 ? "warning" : "primary";
    return classes;
  }

  formatCount() {
    const { count } = this.state;
    return count === 0 ? "Zero" : count;
  }
}

위에서 보면 Component 오브젝트로부터 constructor 메소드를 가져온 후 그 안에 this.handleIncrement, 즉 console.log("increment Clicked!", this)를 Counter 클래스에 bind한 것을 볼 수 있다. 이를 더 축약해서는constructor method를 없애고 arrow function을 사용해 다음과 같이 쓸 수 있다:

class Counter extends Component {
  state = {
    count: 0,
  };

  handleIncrement = () => {
    console.log("Increment Clicked!", this);
  }

  render() {
    return (
      <>
        <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        <button
          onClick={this.handleIncrement}
          className="btn btn-secondary btn-sm"
        >
          Increment
        </button>
      </>
    );
  }

  getBadgeClasses() {
    let classes = "badge m-2 badge-";
    classes += this.state.count === 0 ? "warning" : "primary";
    return classes;
  }

  formatCount() {
    const { count } = this.state;
    return count === 0 ? "Zero" : count;
  }
}

setState

setState는 리액트에게 component가 바뀔 것이라는 것을 알려준다. 리액트에서는 virtual DOM을 사용하기 때문에 state가 다이나믹하게 변경이 되어도 이를 DOM에 즉각적으로 반영하지 못한다. 그래서 사용하는게 setState이다. setState를 사용하면 리액트에서 변경된 state를 감지해 그 부분만 DOM에 반영을 한다. 이를 하는 방법은 다음과 같다:

class Counter extends Component {
  state = {
    count: 0,
  };

  handleIncrement = () => {
    this.setState({ count:this.state.count + 1 })
  }

  render() {
    return (
      <>
        <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        <button
          onClick={this.handleIncrement}
          className="btn btn-secondary btn-sm"
        >
          Increment
        </button>
      </>
    );
  }

  getBadgeClasses() {
    let classes = "badge m-2 badge-";
    classes += this.state.count === 0 ? "warning" : "primary";
    return classes;
  }

  formatCount() {
    const { count } = this.state;
    return count === 0 ? "Zero" : count;
  }
}

위의 handleIncrement를 보면 Counter 클래스의 state에 setState를 사용해 count라는 키 안의 value는 count+1이다 라고 지정을 해주는 것을 볼 수 있다.

Passing Event Arguments

이벤트 핸들러에 argument를 넣고 싶다면 다음과 같이 하면 된다:

class Counter extends Component {
	state = {
		count:0,
	}

	handleIncrement = (product) => {
		this.setState({ count:this.state.count + 1 })
	}

	redner() {
		return(
			<div>
				<button onClick={()=>this.handleIncrement(product)}>Increment</button>	
			</div>
		)
	}
}

.map을 사용해 반복되는 component 렌더 하는 법

Component들은 서로 조합이 될 수 있다. 웹사이트를 반복적으로 사용되는 것들이 있는데 이들을 component 단위로 나눠서 html태그 처럼 사용을 해가면서 렌더링을 할 수 있다. 이때 반복적인 리스트를 만들 때 좋은 것은 .map 이다. 다음과 같이 두개의 component가 각각의 jsx파일에 저장되어 있다고 해보자:

  • 1) 버튼을 누르면 숫자를 카운팅 하는 컴포넌트
import React, { Component } from "react";

class Counter extends Component {
  state = {
    count: 0,
  };

  handleIncrement = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        <button
          onClick={this.handleIncrement}
          className="btn btn-secondary btn-sm"
        >
          Increment
        </button>
      </div>
    );
  }

  getBadgeClasses() {
    let classes = "badge m-2 badge-";
    classes += this.state.count === 0 ? "warning" : "primary";
    return classes;
  }

  formatCount() {
    const { count } = this.state;
    return count === 0 ? "Zero" : count;
  }
}

export default Counter;
  • 2) 위 1)의 카운터 컴포넌트를 렌더링하는 컴포넌트
import React, { Component } from "react";
import Counter from "./counter";

class Counters extends Component {
  state = {
    counters: [
      { id: 0, value: 0 },
      { id: 1, value: 0 },
      { id: 2, value: 0 },
      { id: 3, value: 0 },
    ],
  };
  render() {
    return (
      <div>
      
      </div>
    );
  }
}

export default Counters;

위를 보면 state 안에 counters라는 array 형태의 property가 있다. 그리고 그 안에 각각의 오브젝트 마다 고유한 아이디가 있다. 이를 렌더링 하기 위해서는 다음과 같이 하면 된다.

class Counters extends Component {
  state = {
    counters: [
      { id: 0, value: 0 },
      { id: 1, value: 0 },
      { id: 2, value: 0 },
      { id: 3, value: 0 },
    ],
  };
  render() {
    return (
      <div>
          <Counter/>
          {this.state.counters.map(counter => <Counter key={counter.id}/>)}
      </div>
    );
  }
}

export default Counters;

counters안의 각각의 오브젝트에 map을 활용해 안에 고유 id를 키값으로 준다.

Props & Passing Data to Components

모든 리액트 component에는 props라는object가 내장 되어있다. props는 하나의 component에서 다른 component로 데이터를 주고 받을 때 사용이 된다. Props는 jsx 태그 안에 있는 attribute의 값을 다른 클래스에 전달해 준다. 예를 들면 다음과 같다.

아래 Counters 클래스의 jsx 부분을 보면 컴포넌트가 사용되고 있고 그 안에 key와 value attribute가 있다.

class Counters extends Component {
    
  state = {
    counters: [
      { id: 0, value: 0 },
      { id: 1, value: 0 },
      { id: 2, value: 0 },
      { id: 3, value: 0 },
    ],
  };
  render() {
    return (
      <div>
        {this.state.counters.map((counter) => (
          <Counter key={counter.id} value={counter.value} />
        ))}
      </div> 
    );
  }
}

export default Counters;

이들의 값을 우리가 위에서 만든 Counter 클래스로 넘겨주고 싶다고 한다면 다음과 같이 하면 된다:

class Counter extends Component {
  state = {
    value: this.props.value,
  };

  handleIncrement = () => {
    this.setState({ value: this.state.value + 1 });
  };

  render() {
    return (
      <div>
        <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        <button
          onClick={this.handleIncrement}
          className="btn btn-secondary btn-sm"
        >
          Increment
        </button>
      </div>
    );
  }

  getBadgeClasses() {
    let classes = "badge m-2 badge-";
    classes += this.state.count === 0 ? "warning" : "primary";
    return classes;
  }

  formatCount() {
    const { count } = this.state;
    return count === 0 ? "Zero" : count;
  }
}

export default Counter;

위를 보면 this.props.value 를 활용해 Counters클래스에 있는 value attribute의 값을 즉,

{counter.value}를 가져오는 것을 알 수 있다.

0개의 댓글