React

Jiyoung·2021년 1월 12일
0

웹 페이지를 만들 때 HTML, CSS, JS로 DOM조작을 하는 방법과 프론트엔드 라이브러리(or Framework)인 리액트(React)를 활용하는 방법이 있다. 웹 페이지는 단순히 데이터만을 보여주는 공간이 아니라 사용자와 수많은 상호작용이 일어나는 공간인데 이를 위해선 그만큼 많은 상태 관리가 필요하다. 예를 들어 배경 이미지를 바꾸는 버튼을 눌렀을 때 1)바꿀 이미지 DOM을 찾고, 2)이미지 DOM의 소스를 바꿀 이미지로 변경하고, 3)바뀐 이미지를 다시 화면에 띄워주는 과정을 거쳐야 한다. 그러나 이 방법은 관리해야 할 DOM이 많아질수록 매우 복잡하고 어렵다. 따라서 DOM 관리와 상태 관리를 최소화하고 오직 기능 개발에만 집중할 수 있도록 만들어진 것이 바로 React이다.

주요 개념

컴포넌트(Component)

리액트는 컴포넌트(Component)개념에 집중하고 있는데 컴포넌트는 하나의 의미를 가진 독립적인 단위 모듈로, 나만의 HTML태그라고 할 수 있다. 따라서 컴포넌트를 활용하면 코드가 직관적이고 재사용성이 높아진다.

<Tweet userId="walli" time="43">
  hello, my name is walli :)
</Tweet>

Props

props는 속성을 나타내는 데이터파라미터, 입력값(input)이라고 보면 된다.

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

ES6문법을 적용하여 작성할 수도 있다.

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

ES6 & JSX

리액트는 기본적으로 ES6를 사용하기 때문에 기본적인 ES6 개념들을 숙지해야 한다(Destructuring, Spread operator, Rest parameters, Default parameters, Template literals, arrow function, for-of loop 등)

JSX는 자바스크립트의 확장 문법으로 리액트 컴포넌트를 화면에 보여주기 위해 사용한다. 리액트 컴포넌트에서는 아래와 같이 반드시 JSX를 리턴해줘야 한다.

class Hello extends Component{
  render(){
    return(
      <div>
        <h1>hello, world</h1>
      </div>
      )
  }
}
function Hello(){
  return(
    <div>
      <h1>hello, world</h1>
    </div>
    )
}

JSX를 활용하면 간단하게 태그를 작성할 수 있고 가독성을 높일 수 있다. JSX문법으로 코드를 작성하면 Babel이라는 자바스크립트 컴파일러가 JSX를 자바스크립트로 변환해준다. 단, JSX문법을 사용할 때 지켜야 할 몇가지 규칙이 있다.

1) 반드시 하나의 엘리먼트로 감싸야 한다. 즉 엘리먼트가 여러개일 경우 최상위 엘리먼트 하나로 모든 엘리먼트를 감싸줘야 한다.
2) 자바스크립트 코드를 적용할 땐 중괄호{} 안에 작성한다.

class App extends Component{
  render(){
    const name = 'walli';
    return(
      <div>
      hello {name}!
      </div>
    );
  }
}

3) JSX 내부에서는 if문을 사용할 수 없으며, 삼항연산자를 사용해야 한다.

<div>
  {
    (1 + 1 === 2)?(<h1>정답</h1>):(<h1>탈락</h1>)
  }
</div>

4) 엘리먼트의 클래스 이름을 적용할 때 class가 아닌 className을 사용해야 한다.


State(상태)

State는 살면서 변할 수 있는 값을 의미한다. 다시 말해, 컴포넌트 사용 중 컴포넌트 내부에서 변할 수 있는 값을 말한다.

props: 외부로부터 전달받은 값(위에서 넘겨받음)
state: 컴포넌트 내부에서 변화하는 값


State를 가진 컴포넌트는 함수 컴포넌트가 아닌 클래스 컴포넌트로 만들어야 한다.

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

클래스에 초기 this.state를 지정하려면 class constructor를 추가하면 된다.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

이벤트를 처리하는 방법은 다음과 같다.

<button onClick={activateLasers}>
  Activate Lasers
</button>

State를 변경할 때는 this.setState를 사용해야 하며, 이벤트 처리 시 바인딩을 해줘야 한다.

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

    // 콜백에서 `this`가 작동하려면 아래와 같이 바인딩 해주어야 합니다.
    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>
    );
  }
}

Lifecycle(생명주기)

컴포넌트에 있어서 중요한 순간은?

(생성)
생성될 때
화면에 등장한 후
(업데이트)
새로운 props를 받을 때
새로운 상태를 가질 때
새로운 상태를 갖고 난 후
(제거)
화면에서 사라지기(unmount) 전


위와 같이 매 중요한 순간마다 컴포넌트는 새로 렌더링(render)된다(사라지기 전은 제외). 이를 다음 그림과 같이 나타낼 수 있다.
이미지 출처: https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

컴포넌트 클래스에서 Lifecycle메소드를 선언하여 컴포넌트가 마운트되거나 언마운트 될 때 일부 코드를 작동할 수 있다. Lifecycle메소드는 State와 밀접한 관련이 있기 때문에 클래스 컴포넌트에서만 사용할 수 있고 함수 컴포넌트에서는 사용할 수 없다.

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

    // 콜백에서 `this`가 작동하려면 아래와 같이 바인딩 해주어야 합니다.
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }
  
  //생성
  componentDidMount() {
    console.log('화면에 등장한 후')
  }
  
  //업데이트
  componentDidUpdate() {
    console.log('새로운 상태를 갖고 난 후')
  }

  //제거
  componentWillUnmount() {
    console.log('화면에서 사라지기 전')
  }

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

State가 변화되었을 때, 혹은 생성되었을 때, 컴포넌트 안에서 비동기 요청을 보낼 때 render나 constructor에서 보낼 수 없기 때문에(constructor, render는 async/await으로 사용할 수 없음), 그 때 Lifecycle메소드를 사용해서 비동기 요청을 보낼 수 있다.

React 컴포넌트 간 데이터 흐름

컴포넌트는 컴포넌트 외부에서 props를 이용해 데이터를 마치 인자(arguments) 혹은 속성(attributes)처럼 전달받을 수 있다. 즉 데이터를 전달하는 주체는 부모 컴포넌트가 된다. 이는 데이터 흐름이 하향식(top-down)임을 의미한다.이 원칙은 단방향 데이터 흐름(one-way data flow)이라는 키워드가 React를 대표하는 설명 중 하나일 정도로 매우 중요하다. 또한 컴포넌트는 props를 통해 전달받은 데이터가 어디서 왔는지 전혀 알지 못한다.

<데이터의 State(상태) 판단 기준>
부모로부터 props를 통해 전달되면 => state가 아님
시간이 지나도 변하지 않는다면 => state가 아님
컴포넌트 안의 다른 state나 props를 가지고 계산 가능하면 => state가 아님


어떤 데이터를 State(상태)로 두고 위치를 정하고 나면, 부모 컴포넌트의 상태가 하위 컴포넌트에 의해 변하는 경우(역방향 데이터 흐름)가 있다. 예를 들어, twittler 에서 새 글을 추가하는 이벤트가 발생할 때 전체 트윗 목록에 새로운 트윗 객체가 추가되는 경우가 이에 해당된다. 하위 컴포넌트에서의 클릭 이벤트가 부모의 상태를 변화시켜야 하는 것이다. 이를 해결할 수 있는 방법이 바로 "State 끌어올리기(Lifting state up)"이다.

State 끌어올리기(Lifting state up): 상위 컴포넌트의 "상태를 변경하는 함수" 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행하는 것(상위에 있는 state 값을 하위에서 수정하여 상위로 올려주는 것)


상태를 변경하는 함수는 handleChangeValue이다. 전달은 props(handleButtonClick)를 이용한다.

class ParentComponent extends React.Component {
  // ...생략...

  //상태 변경 함수
  handleChangeValue() {
    this.setState({
      value: '보여줄게 완전히 달라진 값'
    })
  }

  render() {
    return <div>
      <div>값은 {this.state.value} 입니다</div>
      <ChildComponent handleButtonClick={this.handleChangeValue} />
    </div>
  }
}

ChildComponent는 props로 전달받은 함수를 컴포넌트 내에서 실행할 수 있게 된다. "상태 변경 함수"는 버튼이 클릭할 때 실행되기를 원하므로, 해당 부분에 콜백 함수를 실행한다.

function ChildComponent({ handleButtonClick }) {
  const handleClick = () => {
    // Q. 이 버튼을 눌러서 부모의 상태를 바꿀 순 없을까?
    // A. 인자로 받은 상태 변경 함수를 실행하자!

    handleButtonClick()
  }

  return (
    <button onClick={handleClick}>값 변경</button>
  )
}

필요에 따라 설정할 값을 콜백 함수의 인자로 넘길 수도 있다.

class ParentComponent extends React.Component {
  // ...생략...

  handleChangeValue(newValue) {
    this.setState({
      value: newValue
    })
  }

  // ...생략...
}

function ChildComponent({ handleButtonClick }) {
  const handleClick = () => {
    handleButtonClick('넘겨줄게 자식이 원하는 값')
  }

  return (
    <button onClick={handleClick}>값 변경</button>
  )
}
profile
경계를 넘는 삶

0개의 댓글