본문 바로가기

프로그래밍/React

React 알아둘 개념

JSX는 기존 자바스크립트에 더해 XML까지 합쳐 한 번에 코드 작성이 가능하도록 해주는 문법이다.

자주 바뀌는 화면 내의 공간은 state로 등록해야 한다.

constructor(props) {
    super(props);
    
    this.state = {
    	~
    }
}

 

코드 내부의 변수값 저장을 위한 state를 설정하고자 할 때 위와 같은 방식을 사용할 수도 있지만 super를 사용하지 않은 아래의 방식도 같은 효과를 얻을 수 있다.

state = {
    ~
}

 

실무에서는 constructor를 쓰지 않고 state를 관리하는 경우가 더 많다.

//return내 태그를 입력할 때 소괄호는 넣지 않아도 무방하나 깔끔한 가시성을 위해서 넣었다.
render() {
  return (
    <div>
      <!--JSX와 JavaScript를 혼용하지 않는것이 좋다. 그렇지 않다면 가독성 등의 여러 손해가 발생한다.-->
      <div>{this.state.firstNumber}곱하기 {this.state.secondNumber}은 무엇입니까?</div>
        <form onSubmit={this.onSubmit}>
          <!--JSX는 XML이기 때문에 single tag일 시 /를 맨 뒤에 붙여줘야 오류가 발생하지 않는다.-->
          <!--onChange 또는 onInput을 사용해야 정상적으로 가변 값을 state에 넣어줄 수 있다.-->
          <input type="number" value={this.state.inputValue} onChange={this.onChange}/>
          <button>입력</button>
        </form>
      <div>{this.state.resultString}</div>
    </div>
  );
};

 

리액트에서는 HTML의 class를 그대로 불러올 수 없다. 이를 불러오기 위해서는 className을 사용해야 정상적으로 리액트가 이를 인식하게 된다.

또한 라벨을 붙일 때 사용하는 for도 자바스크립트의 반복문과 혼동될 수 있기 때문에 htmlFor로 바꿔 사용해야 한다.

<!--class대신 className을, for대신 htmlFor를 써줘야 한다.-->
<Button id='idButton' className='user' htmlFor='tag'> ~ </button>

 

dependencies와 devDependencies


Node.js를 통해 패키지를 설치하게 되면 기본적으로 package.json내의 dependencies에 모두 기록되게 된다. 이 때 패키지를 설치하기 전 -D라는 옵션을 추가적으로 기입해준다면 해당 패키지를 개발작업 시에만 사용하겠다고 선언해줄 수 있으며 또한 devDependencies에 기록된다.

웹팩을 통해 모듈을 내려받고 webpack.config.js, cliend.jsx를 이용해 리액트 모듈을 불러오는 등의 작업을 create-react-app을 통해 한 번에 진행할 수 있다.

새 리액트 프로젝트를 시작하고자 한다면 create-react-app을 사용하는 것이 편리하지만 내부의 구동 원리를 정확하게 파악하는 것이 좋다.

webpack.config.js내의 옵션들에 대한 대략적인 설명


webpack은 그 자체로는 npm이나 node처럼 명령어로서 사용이 불가능하다. 이를 해결하기 위해서는 webpack에 대한 명령을 등록해주거나 스크립트로서 적는 등의 세 가지 방법이 존재한다.

package.json에 scripts로 적어준다
스크립트로 입력 후 webpack명령이 생겨났다
cmd에 npx webpack라 작성해도 동일한 효과를 얻을 수 있다


또한 babel환경도 갖추어야 완전히 리액트 개발 환경이 다 갖춰졌다라고 할 수 있다.

또한 babel은 개발시에만 쓰이기 때문에 -D옵션을 통해 devDependencies에 넣어주는 것을 권장한다.

babel환경을 구축한다


@babel/core : 기본적인 babel이며 문법들을 최신 문법으로 바꿔주는 역할을 수행한다.

@babel/preset-env : 각 사용자들의 브라우저 환경에 맞춰 문법 버전을 조정해주는 역할을 수행한다.

@babel/preset-react : 코드를 jsx로 바꿔주는 역할을 수행한다.

babel-loader ; babel과 webpack을 연결해주는 역할을 한다.

Webpack을 통해 정상적으로 프로젝트를 생성했을 때의 결과물

 


정규표현식

  • /를 이용해 시작과 종료를 알려준다.
  • 정규표현식 내의 플래그

    플래그는 옵션이기 때문에 필요할 때만 선택적으로 사용할 수 있으며 플래그를 사용하지 않는다면 해당 문자열 내 검색 옵션에 맞는 결과가 1개 이상 나와도 첫 번째로 찾아낸 대상만 검색 후 종료하게 된다.

    i : ignore Case -  대소문자를 구별하지 않고 검색한다.
    g : Global - 문자열 내의 모든 패턴을 검색한다.
    m : Multi Line - 문자열의 행이 바뀌어도 검색을 계속한다.
  • 패턴

    패턴에는 찾고자 하는 대상을 문자열로 지정하며 패턴은 특별한 의미를 가지는 메타문자나 기호로 표현 가능하다.

    ex) /.../
    .은 임의의 문자 한 개를 의미하며 문자 내용은 어떤 것이든 상관하지 않는다. 또한 플래그가 없기 때문에 검색을 반복해 수행하지 않는다. 문자열 내의 모든 패턴을 검색하기 위해서는 g 등의 플래그를 사용해야 한다.

    ex) /A+/
    위 표현에서 +는 앞선 패턴을 반복한다는 뜻으로 AA와 같은 의미를 가진다.
    /A+|B+/라고 적어주게 되었을 때 |는 or의 의미로 앞 또는 뒤의 패턴을 가진 문자열을 찾도록 해준다.
    이는 또한 /[AB]+/로 간소화시킬 수도 있다. []내의 문자는 or로서 인식된다.
  • 자주 활용되는 정규표현식

    특정 단어로 시작하는지 검사
    => /^hello/

    특정 단어로 끝나는지 검사
    => /hello$/

    숫자인지 검사
    => /&\d+$/

    하나 이상의 공백으로 시작하는지 검사
    => /^[\s]+/

    아이디로 사용 가능한지 검사
    => /^[A-Za-z0-9]{4,10}$/
    {4,10}은 해당 문자열이 4~10자리의 길이를 가지도록 제한한다는 것이다.

    메일 주소 형식에 맞는지 검사
    /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/

    휴대폰 번호 형식에 맞는지 검사
    /^\d{3}-\d{3,4}-\d{4}$/

    특수 문자 포함 여부 검사
    /[\{\}\[\]\/?.,;:|\)*~`!^\-_+<>@\#$%&\\\=\(\'\"]/gi

 

webpack.config.js내부 옵션들

  • module.exports에 대한 옵션을 지정해줄 수 있다.
  • 옵션을 지정해주기 전에는 먼저 node.js에서 기본으로 제공해주는 모듈인 path모듈을 requrie메소드를 통해 불러오는 것이 좋다. 이 모듈을 활용한다면 파일시스템 내의 원하는 파일들을 쉽게 찾아올 수 있게 된다.
  • mode옵션을 통해서는 development상태인지 production인지의 여부를 지정해줄 수 있으며 배포시에는 반드시 production상태로 지정해주어야 한다.
  • devtool옵션에서의 eval은 개발 시 사용되며 배포시에는 hidden-source-map으로 둬야 한다.
  • resolve옵션을 통해 원하는 파일을 찾아올 수 있는데 이 때 extensions 추가 옵션을 통해 입력한 확장자에 해당하는 파일들을 웹팩이 찾아올 수 있도록 만들어줄 수 있다.

  • https://github.com/browserslist/browserslist#queries 왼쪽의 링크를 활용한다면 정확하게 babel이 어느 수준의 브라우저 버전까지 지원할지에 대한 정확한 옵션을 넣어줄 수 있게 된다.

 

react-hot-loader

  •  

webpack-dev-server

  • 코드의 변경점 등을 감지해 자동으로 빌드 등을 수행해주는 역할을 수행한다.
  • webpack.config.js를 읽어서 빌드를 해주며 또한 항상 서버에 유지시켜준다.

위 두 모듈을 적용시키고자 한다면 package.json의 script옵션 내에 "dev"옵션을 넣어주며 "webpack-dev-server --hot"이라 입력해주어야 한다.

코드를 서버에 유지시켜주도록 만듬


jsx에서 ref를 불러오고자 한다면 class구조에서는 특정한 변수를 만든 후 별도의 함수를 선언해 해당 ref를 가리키는 방식을 쓸 수 있다.

this.inputRef.focus();

...

//ref를 바인딩할 변수
input;
inputRef = (target) => {
	this.input = target;
}

...

	<form
    	ref={this.inputRef}
    >
    
    ...

 

이 방식의 장점은 함수를 통해 ref를 바인딩하는것으로 끝나는 것이 아니라 추가적으로 console.log를 호출하는 등 프로그래머의 의도대로 커스텀이 가능하다는 것이다.

이 방식을 사용하지 않고 좀 더 간단하게 ref를 불러오는 방식은 아래와 같다.

//createRef메소드를 사용하고자 한다면 반드시 먼저 import(require)를 통해 해당 모듈을 불러온다.
import React, {Component, createRef} from 'react';

...

//createRef메소드를 통해 ref를 불러온다면 추가적으로 current를 넣어주어야 한다.
//입력할 내용이 많아진다는 점에서 손해가 발생하나, Hooks구조와 같은 방식으로 ref를 불러오게
//된다는 점에서 코드의 통일성을 얻을 수 있고 프로그래머가 덜 헷갈릴 수 있다는 장점이 있다.
this.inputRef.current.focus();

...

inputRef = createRef();

...

 

이 방식은 불러올때는 current를 추가적으로 입력해야한다는 단점이 있지만 한 줄로 간편하게 ref를 불러올 수 있다는 것과 Hooks구조와 불러오는 방식이 일치해 프로그래머가 덜 헷갈리게 된다는 장점이 있다.

Hooks구조에서는 useRef메소드를 사용하는 방식으로 ref를 불러올 수 있다.

import React, {useRef} from 'react';

...

const inputRef = useRef(null);

...

inputRef.current.focus();

 

require과 import

  • 엄밀히 따지자면 둘은 서로 다르지만 얕은 활용을 하려고 한다면 혼용해도 상관없다.

require (ex> const React = require('react');)

  • commonJS라고 불린다.
  • node.js에서는 이 방식만 지원하고 있다.
  • 리액트에서 import를 사용할 수 있는 이유는 babel이 자동으로 commonJS형식으로 바꿔주어 가능한 것이다.

import (ex> import React from 'react';)

  • ES2015 module 문법이라고 불린다.
  • 위의 require과 호환이 가능하다.
  • import를 하게 된다면 구조 분해 문법으로 된 모듈도 한 번에 불러오는 방식이 가능하다.
import React, {Component} from 'react';

 

  • 작성이 완료된 코드 자체를 모듈로 저장하고자 할 경우 export default를 사용하면 되며 default는 파일당 단 한개만 export가 가능하다.
  • 객체나 배열등의 경우 export const를 사용해 모듈로 저장이 가능하며 한 파일에 여러개 export가 가능하다.
//const는 여러 개 export가 가능하다.
//import시 import {hi} 등으로 분할해 불러올 수도 있으며
//import {hi, bye} 와 같이 한 번에 불러올 수도 있다.
export const hi = 'hello';
export const bye = 'hello';

//default의 경우 파일당 하나만 export가 가능하다.
export default Meet;

 

과거 React 초창기 화살표함수를 쓰지 않았을 경우 별도의 바인딩 절차가 필요했다


과거 React가 처음 나왔을 때는 화살표함수를 적용하지 않아 위 사진과 같이 추가적인 바인딩 작업을 수행해야 했다. 바인딩 절차가 이루어져야 각 기능을 담은 메소드들 안에서 this를 활용해 상태를 관리할 수 있게 되었다.

//과거
function(event) {
    ...
}

=>

//현재
function = (event) => {
	...
}

 

React를 크롬환경에서 디버그하고자 할 경우 반드시 React Developer Tools 확장 프로그램을 설치하는 것이 좋다.

React를 디버그할 때 큰 도움이 되는 확장 프로그램


위 프로그램을 깔고 난 후에는 Components와 Profiler탭이 페이지 소스창 옵션에 추가된다.

가장 오른쪽 두 옵션이 React Developer Tools를 설치했을 경우 나타나는 창이다.


이 창은 기본적인 Elements탭에서는 보여주지 않는 각 state들의 현재 상태나 props상태 등을 추가적으로 표시해주기 때문에 React를 활용해 프로젝트를 수행할 때 큰 도움을 받을 수 있게 된다.

이 툴의 색깔이 빨간색일 경우 개발 모드라는 뜻이며 파란색일 경우 배포 모드라는 뜻이다. 각 상태로 전환하기 위해서는 webpack.config.js 내의 module.exports속의 속성인 mode를 development나 production으로 바꾸며 또한 최상단에 process.env.NODE_ENV = 'production'을 배포시에는 추가해주어야 정상적으로 전환이 완료된다.

이 도구를 사용하게 되면 어떤 부분이 바뀌는지에 대해 더 명확하게 확인이 가능하도록 위 사진의 톱니부분을 누른 후 Hightlight updates...옵션을 체크하는 것이 이후에 코딩을 원활히 하는것에 큰 도움을 줄 수 있다.

이 옵션을 활성화한 후에는 랜더링이 되는 태그에 하이라이트가 되는데 이 때 파란색 -> 빨간색 순으로 점점 랜더링이 겹치게 될 경우 색이 변화한다.

파란색으로 하이라이팅될 경우 문제가 없는 상태라고 볼 수 있겠지만 빨간색으로 변화할수록 같은 시간에 랜더링이 겹쳐 일어나 성능문제가 발생할 수 있다는 문제를 내포해 개선이 필요함을 보이게 된다.

웹 프로그래밍을 할 때 성능 향상은 아주 중요한 문제다. react는 위와 같은 랜더링 문제를 자동으로 해결해주지 못해 프로그래머가 자체적으로 해결책을 마련해야 한다.

shouldComponentUpdate함수를 사용하게 된다면 이미 랜더링된 컴포넌트의 상태를 저장한 후 새로 들어온 정보와 비교해 변경점이 있을 때만 다시 랜더링하도록 만들어준다.

shouldComponentUpdate함수


이 함수를 자체적으로 내포하고 있는 컴포넌트가 PureComponent다.

const React, {PureComponent} from 'react';

...

//PureComponent는 shouldComponentUpdate와 같은 기능을 제공해준다.
class ExampleClass extends PureComponent {

    ...

}

 

이 컴포넌트를 클래스에 extends시켜준다면 자체적으로 업데이트 여부를 파악해주게 된다. 하지만 이 컴포넌트도 참조관계 등이 상태로 들어서기 시작하면 관계가 어려워져 업데이트 여부를 판단하는데 어려움을 겪기 시작한다. 그렇기 때문에 변화된 사실을 컴포넌트가 인식하도록 하기 위해서는 반드시 새로운 배열 등에 이전 배열을 넣어줌과 동시에 새로운 요소를 추가하는 방식을 써주어야 업데이트여부를 제대로 인식하게 된다.

결과적으로는 setState에 객체 구조를 쓰는 것은 update여부를 판단하기에 어렵도록 만들기 때문에 복잡한 객체 구조는 안쓰는 것이 좋다는 것이다.

PureComponent와 shouldComponentUpdate의 차이는 후자가 프로그래머의 의도에 맞게 커스터마이징이 가능하다는 것이다.

 

Hooks구조에는 PureComponent와 shouldComponentUpdate 둘다 없다. 이를 대신해 존재하는 것이 memo다.

memo를 사용하게 되면 랜더링이 다시 일어날 때 자식 컴포넌트로 들어오는 props가 같다면 이미 이전에 랜더링되었던 내용을 재사용하도록 해주는 기능을 한다.

 

JSX 내에서 if와 for문 쓰는 방법

JSX내부에 if문을 사용하는 방법 (즉시실행 함수를 만든 후 넣어준다.)

기본적으로는 JSX내에 스크립트를 통해 외부에서 함수를 불러오는 방식을 사용하나 위 사진처럼 즉시실행 함수를 통해 JSX내부에 바로 넣어줄 수도 있다. 하지만 이렇게 할 경우 코드의 가독성이 떨어지는 결과를 낳기 때문에 권장하지 않는다.

 

useEffect 이해하기

  • Hooks구조에서는 componentDidMount 등의 메소드를 사용할 수 없다. 이를 대신해 useEffect를 사용하게 된다.
  • useEffect도 함수이기 때문에 return이 존재하며 return부분이 class구조에서의 componentWillUnmount와 같은 역할을 한다.
  • return을 제외한 부분은 class구조의 componentDidMount, componentDidUpdate와 비슷한 역할을 한다.
  • 화살표 함수가 끝난 후에는 배열을 넣어주어야 하며 해당 배열에 들어간 state의 값이 변하면 이를 감지해 useEffect가 실행된다.
  • 여러 useEffect를 중복해서 선언해줄 수 있으며 이를 통해 state별로 변화했을 때 서로 다른 결과를 얻을 수 있도록 만들어줄 수 있다. 또한 배열에는 반드시 변화를 감지할 값만 넣어주어야 한다. 그렇지 않을 경우 프로그래머가 의도하지 않은 부작용이 발생할 가능성이 매우 높아지게 된다.
  • useEffect를 대신해 쓰일 수 있는 대안으로는 useLayoutEffect가 있다. 둘의 차이는 useEffect가 화면이 전부 완성된 후 호출되는 반면 useLayoutEffect는 화면이 바뀌기 전에 먼저 호출된다는 것이다.
useEffect(() => {
    interval.current = setInterval(exFunc, 500);
    
    return () => {
        clearInterval(interval.current);
    }
}, [exState]);

 

Hooks구조에서 useEffect를 활용해 componentDidMount없이 오직 componentDidUpdate의 기능만 가지도록 만드는 패턴식

const isMounted = useRef(false);

useEffect(() => {
    if (!isMounted.current) {
    	isMounted.current = true;
    }
    
    //업데이트 발생시의 동작 코드
}, [바뀌는 값]);

 

Class와 Hooks구조의 생명주기 차이

  resultString imgCoord totalScore
componentDidMount      
componentDidUpdate      
componentWillUnmount      

위 표를 통해 구별할 경우 간단하게 class구조는 가로로, Hooks구조는 세로로 조작이 가능하다고 생각하면 된다.

한 useEffect별로 한 state씩을 담당한다는 생각을 하면 된다. 물론 여러 state를 함께 관리도 가능하다.

 

useReducer

  • 기존에 React를 개발할 때 많이 사용해왔던 Redux를 굳이 사용하지 않더라도 소규모 프로젝트에서는 useReducer와 contextAPI를 조합해서 어느정도 Redux 라이브러리를 사용하지 않고 대체할 수 있게 되었다.
  • 하지만 여전히 대규모 프로젝트에서는 Redux를 활용해야지 useReducer와 contextAPI만으로는 비동기 작업을 수행할 때 체약사항이 많이 발생하게 된다.

 

렌더링시 쓸데없이 렌더링되는 부분이 있는지 찾을 때 도움되는 코드 패턴

import React, {useRef, useEffect} from 'react';

const exFunc = ({rowIndex, cellIndex, dispatch, cellData}) => {
    const ref = useRef([]);
    
    useEffect(() => {
        ref.current = [rowIndex, cellIndex, dispatch, cellData];
        //부모 컴포넌트로 받아온 값들 중 변화가 없어야 할 때 변화할 경우 경우 
        //false가 반환되며 해당 값을 추가적으로 log로 확인하며 어떤 부분이 문제인지 확인해갈 수 있다.
        console.log(rowIndex === ref.current[0], cellIndex === ref.current[1], 
                    dispatch === ref.current[2], cellData === ref.current[3]);
        
        
    }, [rowIndex, cellIndex, dispatch, cellData]);
}

 

 

참고 링크

https://poiemaweb.com/js-regexp

https://feel5ny.github.io/2018/01/20/JS_11/