Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
Frontend/ReactNative

React 와 JavaScript정리

by Toughie 2024. 7. 28.

 

React는 JavaScript 기반 UI 라이브러리이다.

 

리액트는 기본적으로 index.html 하나로 동작하는 Single Page Application이다. 

또한 클라이언트 사이드에서 JavaScript를 통해 동적으로 컨텐츠를 렌더링 하는 CSR 방식이다. 

 

- CSR(Client Side Rendering) 클라이언트 단에서 화면을 그리기 (React,Angular,Vue.js 등이 있다.)

- SSR - 서버에서 완성된 html,css,js 파일을 전달하는 방식 (요즘은 Next.js가 핫하더라)

 

 

React 기본 프로젝트의 index.js파일을 보면

React와 ReactDOM을 불러와서 App을 렌더링 한다. 

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

 

이런 특별한 문법을 비롯해 React 이해를 위해 필요한 간단한 JavaScript 개념에 대해 정리해 보자.


JSX

JSX는 JS의 확장 문법으로, React에서 사용하는 특별한 형태의 코드이다.

//App.js
import React from 'react';

const App = () => {
  return <h1>React App</h1>;
};

export default App;

 

html을 리턴한다? 자바스크립트 파일에는 html 못 넣는데? 

이것은 JSX 구문으로 편의성을 위해 JS파일에 HTML처럼 보이는 코드를 사용하는 것이다.

 

h1 태그 부분은 React.createElement('h1', {title: 'React App'}, ...);로 대치된다고 볼 수 있겠다.

이런 편의성을 위한 부분을 syntatic sugar라고 부르기도 한다.

 

JSX는 XML/HTML과 유사한 구문을 통히 편리하게 React 컴포넌트를 정의할 수 있게 해 준다.

JSX는 브라우저가 이해할 수 없기 때문에, Babel 같은 트랜스파일러를 거쳐서 JavaScript 코드로 변환된다.

 

[React의 역할]

- JSX코드를 보고 createElement를 호출해 React 요소를 만든다

React 요소들을 통해 가상 DOM을 구성하며 실제 DOM과 비교해 효율적으로 업데이트한다.  


Component

1. (Modern⭐️) Functional Component with Hooks

단순한 함수로 정의되며, Hook을 통해 상태와 생명주기 메서드를 사용한다.

import React, { useState } from 'react';

const MyComponent = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
};

export default MyComponent;

 

2. (전통 방식) Class Component => render 메서드가 있는 JS 클래스

class App extends React.Component {
	render() {
    	    return <h1 title="REACT"> Hello, <span>there</span> ReactJS!</h1>;
    }
}

 

컴포넌트의 이름은 반드시 대문자로 시작한다 !(html 태그와 헷갈리지 않기 위한 규칙)


화살표 함수

JS ES6에서 도입된 새로운 함수 표현 방식.

function add(a, b) {
    return a + b;
}

const add = (a, b) => a + b;

 

간결한 문법뿐만 아니라, 화살표 함수에는 this 바인딩 차이가 존재한다.

 

일반 함수 - 자신만의 this 바인딩 생성, 함수 호출에 따라 this값이 변함.

 

화살표 함수 - 자신의 this 바인딩을 생성하지 않음, 외부 스코프의 this를 그대로 사용함. (렉시컬 this)


this란?

this는 자바스크립트에서 현재 실행 컨텍스트를 가리키는 키워드이다.

즉 현재 코드가 실행되는 객체를 참조하는 것이다. 

함수가 어디서 어떻게 호출되냐에 따라 this는 바뀔 수 있다. 

 

- 전역 컨텍스트

전역 스코프에서 this는 전역객체를 가리킨다. (Node.js에서는 global, 브라우저에서는 window)

 

- 객체 메서드 내부

아래 예시에서는 this가 person 객체를 가리킨다.

const person = {
    name: "toughie",
    greets: function() {
        console.log("안녕하세요," + this.name + "입니다.");
    }
};

person.greets(); // 안녕하세요 toughie 입니다.

 

- 이벤트 핸들러

아래 예시와 같이 html 요소에 이벤트 핸들러를 추가한 경우, this는 이벤트가 발생하는 컴포넌트를 가리킨다.

  const button = document.getElementById('myButton');

  button.addEventListener('click', function() {
    console.log(this); // 이 'this'는 버튼 요소
    this.style.backgroundColor = 'red';
    this.textContent = 'clicked!';
  });

 

- 생성자 함수

new 키워드를 사용할 때 this는 새로 생성된 객체를 가리킨다.

function Dog(name) {
    this.name = name;
}

const puppy = new Dog("golden");
console.log(puppy.name); // golden

Lexical Scope (렉시컬 스코프)

함수가 호출된 위치에 따라 상위 스코프가 결정되는 동적 스코프와 달리

렉시컬 스코프는 함수가 '어디서 선언되었는가'에 따라 스코프가 결정되는 것을 말한다.

(그냥 대부분의 프로그래밍 언어처럼 상식적으로 생각하면 된다.)

let globalVar = "전역변수";

function outerFunc() {
    let outerVar = "외부 함수 변수";
    
    function innerFunc() {
        let innerVar = "내부 함수 변수";
        console.log(globalVar, outerVar, innerVar);
    }
    
    innerFunc();
}

outerFunc(); // 전역변수 외부 함수 변수 내부 함수 변수

Binding

바인딩은 특정 값이나 객체를 어떤 변수나 속성에 연결하는 과정을 말한다.

JS에서 this 바인딩은 this키워드가 실제로 가리키는 것을 제어하고 결정하는 것을 의미한다.  

즉 함수의 스코프는 기본적으로 렉시컬 스코프이나, 이를 바인딩을 통해 명시적으로 제어할 수 있다는 것이다.

 

call()

- 함수를 즉시 실행하며 첫 번째 인자로 this를 바인딩한다.

function greet() {
    console.log(`안녕, ${this.name}!`);
}

let user = { name: "김터피" };

greet(); //어떻게 될까?

이 상황에서 그냥 greet을 호출하면 어떻게 될까?

 

브라우저 환경(비엄격 모드)

- this는 전역 객체(window)를 가리키게 되고, window.name은 빈문자열이기 때문에 안녕,!이 출력된다.

 

Node.js 또는 엄격 모드

- this가 undefined이기 때문에 에러가 발생한다.

 

따라서 call메서드에 user를 전달해 주면 정상적으로 실행할 수 있다.

greet.call(user); //안녕, 김터피!

apply()

call과 거의 똑같지만 인자로 배열을 전달한다.

function introduce(age, job) {
    console.log(`${this.name}님 ${age}살에 ${job} 입니다.`);
}

let user = {name: "김터피"};

introduce.apply(user, [20, "개발자"];
// 김터피님 20살에 개발자 입니다.

bind()

새로운 함수를 생성하고, 생성된 함수의 this는 bind의 첫 번째 인자로 고정된다.

function greet() {
  console.log(`안녕하세요, ${this.name}님`);
}
let user = {name: "김터피"};
let boundGreet = greet.bind(user);

boundGreet(); //안녕하세요, 김터피님

boundGreet은 항상 안녕하세요, 김터피님을 출력하게 된다.

 

이렇게 call, apply, bind를 활용하면 렉시컬 스코프 자체를 변경하지는 않지만,

함수가 실행될 때의 this 컨텍스트를 개발자가 원하는 대로 제어할 수 있게 해 준다.


Props

React의 주요 철학 중 하나는 '앱을 재사용 가능한 작은 컴포넌트로 나누기' 이다.

이를 통해 유지보수를 용이하게 하는 것이다.

 

Props는 컴포넌트 사이에서 데이터를 주고받는 방법이다.

 

1. 부모 컴포넌트에서 자식 컴포넌트로 props를 통해 데이터 전달하기

const ParentComp = () => {
  const data = ["apple", "banana", "melon"];

  return (
    <div>
    //부모의 데이터를 props로 전달
      <ChildComp fruits={data} />
    </div>
  );
};

//부모로부터 props 전달 받기
//props 객체 안에 data를 담아서 전달
const ChildComp = (props) => {
  return (
    <ul>
      {props.data.map((fruit, index) => {
        <li key={index}>{fruit}</li>;
      })}
    </ul>
  );
};

// 구조분해할당
// props 객체를 통해 데이터가 전달되면 props 객체를 즉시 분해해서 속성을 추출함(props.data).
const ChildCompDest = ({ fruits }) => {
  return (
    <ul>
      {fruits.map(({ id, name }) => (
        <li key={id}>{name}</li>
      ))}
    </ul>
  );
};

 

 

2. 자식 컴포넌트에서 부모 컴포넌트로 데이터를 전달하기 (콜백 함수 사용)

const ParentComp = () => {
  const handleChildData = (data) => {
    console.log("received from child:", data);
  };

  return (
    <div>
      <ChildComp onDataReceived={handleChildData} />
    </div>
  );
};

const ChildComp = (props) => {
  const sendDataToParent = () => {
    const data = "Child's data";
    props.onDataReceived(data);
  };

  return (
  <button onClick={sendDataToParent}>
    SendDataToParent
    </button>
  );
};

 

부모에서 props를 통해 자식에게 핸들러 함수를 전달하고,

자식에서 데이터를 콜백함수에 넘겨 실행하는 방식이다.

 

리액트는 구조는 기본적으로 부모 -> 자식으로 이어지는 컴포넌트의 트리 형태이고

데이터를 주고받을 때 props를 사용한다.

위와 같이 간단한 예시에서는 괜찮지만, 트리의 깊이가 깊어질수록

props를 끊임없이 전달해야 하는데이겠을 props drilling이라 부르기도 한다.

 

props drilling과 같은 문제를 해결하기 위해 

상위 레벨에서 하위 레벨까지 데이터를 쭉 주입시켜 주는 Context provider, Redux 등을 많이 사용한다. 


Hook & State

Hook은 use로 시작하며 함수형 컴포넌트 내부에서 실행가능한 특별한 함수이다. 

상태관리나 생명주기 기능을 이용할 수 있으며 아래와 같은 특징들을 지닌다.

 

- 조건문, 루프, 중첩 함수 내부에서 사용 불가능

- 커스텀 Hook 생성 가능(use~~)

 

State와 리렌더링

useState Hook을 활용해서 함수형 컴포넌트에서 state를 관리할 수 있다.

state가 변경되면 컴포넌트가 리렌더링 된다.

 

import React, { useState } from "React";

const Counter = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>카운트: {count}</p>
      <button onClick={() => {setCount(count + 1);}}>1증가</button>
    </div>
  );
};

 

위 예시에서는 버튼을 누르면 setCount함수를 호출해서 count State값을 변경시키고, state값이 변경되었기 때문에

화면이 리렌더링 돼서 값이 1씩 계속 증가한다.


React의 가상 DOM을 이용한 리렌더링

리렌더링, 즉 화면이 바뀌는 작업은 굉장히 비용이 많이 드는 작업이다.

따라서 React는 가상 DOM 개념을 통해 효율적으로 리렌더링 작업을 진행한다.

 

*DOM: Document Object Model의 약자로 HTML 문서의 구조를 트리 형태로 표현한 것 

 

1. state 값이 변경된다. -> 컴포넌트의 render 메서드가 호출된다.

2. React는 직접 HTML DOM을 수정하기 전에 새로운 가상 DOM을 생성한다. 

3. 이전에 생성된 가상 DOM과 차이점(diffing 알고리즘 사용)을 비교한다.(Reconciliation)

4. 변경된 부분만 실제 DOM에 업데이트한다.

 

위와 같은 최적화가 기본적으로 진행되지만, 리렌더링 작업이 많아질 경우 리렌더링이 지연될 수 있는데

이 경우에 useCallback, useMemo 등의 Hook을 사용해서 성능 최적화를 추가적으로 진행해 줄 수 있다.


다양한 Hooks

 

useState 이외에도 컴포넌트 기능 구현을 위해 사용할 수 있는 Hook들이 존재한다.

 

useRef -> React에서 DOM 요소나 JS 객체에 대한 참조를 생성하는 데 사용한다.

(input의 포커스 조절 등)

 

useEffect -> 컴포넌트가 렌더링 될 때마다 사이드 이펙트를 수행하기 위해 사용된다.

*사이드 이펙트 : 컴포넌트 렌더링 이외에 발생하는 작업들

데이터 fetching, 이벤트 리스너 구독, 타이머 설정, 로컬 스토리지 접근, DOM 조작 등

 

useContext -> props drilling을 방지하기 위해 컴포넌트 트리 전반에 걸쳐 데이터를 전달한다.(주입)


 

JavaScript와 React는 이것만으로 절대 충분하지 않지만, 우선 이 정도로 정리하고

ReactNative 학습을 통해 디테일을 채워갈 예정이다 🙄

'Frontend > ReactNative' 카테고리의 다른 글

Expo로 RN 프로젝트 시작하기 (settings)  (1) 2024.07.21