<리액트를 다루는 기술> 3장 컴포넌트

|

velopert님의 <리액트를 다루는="" 기술=""> 개정판을 보고 공부한 것을 정리합니다.

  • 리액트에서는 ‘컴포넌트’라는 구성단위로 프론트 개발을 하게 된다.
    • 컴포넌트: 구성요소
    • 화면을 구성하는 요소들을 각각 만들고 그것을 합쳐 하나의 페이지가 되는 형식
  • 이 컴포넌트 또한 작게 쪼개서 작은 부분을 합쳐 하나의 컴포넌트로 만들 수 있다. (하나의 큰 컴포넌트는 다른 작은 컴포넌트들의 조합이다, 라는 점이 함수형 프로그래밍을 생각나게 하는듯….)
  • 하나의 컴포넌트 안에서 쪼개고 쪼개기를 반복하면 하나의 작은 tree 구조처럼 될 수 있다. 큰 요소 안에 작은 요소가 있으니 부모 자식의 관계가 형성될 수 있다.

클래스형 컴포넌트

리액트를 사용하여 애플리케이션의 인터페이스를 설계할 때 사용자가 볼 수 있는 요소는 여러 가지 컴포넌트로 구성되어 있다.

뒤에서 만들어 볼 일정 관리 애플리케이션에 사용된 컴포넌트

  1. TodoTemplate: 현재 화면의 중앙에 있는 사각형 레이아웃 컴포넌트
  2. TodoInput: 새로운 항목을 추가할 수 있는 컴포넌트
  3. TodoList: 할 일 항목을 여러 개 보여주는 컴포넌트
  4. TodoItem: 각 항목을 보여 주기 위해 사용되는 컴포넌트

컴포넌트의 기능은 단순한 템플릿 이상이다. 데이터가 주어졌을 때 이에 맞추어 UI를 만들어 주는 것은 물론, 라이프사이클 API를 이용하여 컴포넌트가 화면에서 나타날 때, 사라질 때, 변화가 일어날 때 주어진 작업들을 처리할 수 있으며, 임의 메서드를 만들어 특별한 기능을 붙여 줄 수 있다.

앞에서 보았던 App 컴포넌트는 함수형 컴포넌트이며, 코드가 다음과 같은 구조로 이루어져 있다.

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

function App() {
  connst name = '리액트';
  return <div className="react">{name}</div>;
}

export default App;

컴포넌트를 선언하는 방식은 두 가지가 있다: 함수형 컴포넌트, 클래스형 컴포넌트

클래스형 컴포넌트는 다음과 같이 선언할 수 있다.

import React, {Component} from 'react';

class App extends Component {
  render() {
    const name = 'react';
    return <div className="react">{name}</div>;
  }
}

export default App;

클래스형 컴포넌트와 함수형 컴포넌트의 차이점은 클래스형 컴포넌트의 경우 이후 배울 state 기능 및 라이프사이클 기능을 사용할 수 있다는 것과 임의 메서드를 정의할 수 있다는 것이다.

ES6의 클래스 문법 ES6 이전에는 자바스크립트에 클래스(class)가 없었다. 개념 자체는 있었지만, 그것을 구현하려면 class 대신 prototype을 사용하여 작성해야 했다.

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

Dog.prototype.say = function() {
  console.log(this.name + ':멍멍');
}
var dog = new Dog('검둥이');
dog.say(); // 검둥이: 멍멍

ES6 문법부터는 이와 기능이 똑같은 코드를 class를 사용하여 다음과 같이 작성할 수 있다.

class Dog {
  constructor(nname) {
    this.name = name;
  }
  say() {
    console.log(this.name + ':멍멍');
  }
}

const dog = new Dog('흰둥이');
dog.say(); // 흰둥이: 멍멍

클래스형 컴포넌트에서는 render 함수가 꼭 있어야 하고, 그 안에서 보여 주어야 할 JSX를 반환해야 한다.

함수형 컴포넌트의 장점

  1. 클래스형 컴포넌트보다 선언하기가 훨씬 편하다.
  2. 메모리 자원도 클래스형 컴포넌트보다 덜 사용한다.
  3. 프로젝트를 완성하여 빌드한 후 배포할 때도 함수형 컴포넌트를 사용하는 것이 결과물의 파일 크기가 더 작다. (아주 적은 차이)

함수형 컴포넌트의 단점 state와 라이프사이클 API의 사용이 불가능하다 -> 리액트 v16.8 업데이트 이후 Hooks라는 기능이 도입되면서 해결됨 완전히 클래스형 컴포넌트와 똑같이 사용할 수 있는 것은 아니지만 조금 다른 방식으로 비슷한 작업을 할 수 있다.

리액트 공식 매뉴얼에서는 컴포넌트를 새로 작성할 때 함수형 컴포넌트와 Hooks를 사용하도록 권장.

첫 컴포넌트 생성

  1. 파일 만들기
  2. 코드 작성하기
  3. 코듈 내보내기 및 불러오기

src 디렉터리에 MyComponent.js 파일 생성

import React from 'react';

const MyComponent = () => {
    return <div>나의 새롭고 멋진 컴포넌트</div>;
};

export default MyComponent;

ES6의 화살표 함수

  • arrow function
  • ES6 문법에서 함수를 표현하는 새로운 방식
  • 주로 함수를 파라미터로 전달할 때 유용.
  • 기존 function을 대체할 수 없다. 서로 가리키고 있는 this 값이 다르다!
function BlackDog() {
  this.nname = '흰둥이';
  return {
    name: '검둥이',
    bark: function() {
      console.log(this.name+':멍멍!');
    }
  }
}

const blackDog = new BlackDog();
blackDog.bark(); // 검둥이:멍멍!

function WhiteDog() {
  this.name = '흰둥이';
  return {
    name: '검둥이',
    bark: () => {
      console.log(this.name+':멍멍!');
    }
  }
}

const whiteDog = new WhiteDog();
whiteDog.bark(); // 흰둥이: 멍멍!

일반 함수는 자신이 종속된 객체를 this로 가리키며, 화살표 함수는 자신이 종속된 인스턴스를 가리킨다.

const triple = (value) => value * 3;

따로 {}를 열어 주지 않으면 연산한 값을 그대로 반환한다는 의미

함수형 컴포넌트를 선언할 때에는 큰 차이가 없다.

모듈 내부내기 및 불러오기

export default MyComponent 코드는 다른 파일에서 이 파일을 import할 때, 위에서 선언한 MyComponent 클래스를 불러오도록 설정한다.

import React from 'react';
import MyComponent from './MyComponent';

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

export default App;

import 구문은 위와 같이 사용한다.

props

  • props는 properties를 줄인 표현으로 컴포넌트 속성을 설정할 때 사용하는 요소
  • props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트(이 상황에서는 App 컴포넌트를 부모 컴포넌트로 상정)에서 설정할 수 있다.

JSX 내부에서 props 렌더링

  • props 값은 컴포넌트 함수의 파라미터로 받아 와서 사용할 수 있다.
import React from 'react';

const MyComponent = props => {
  return <div>안녕하세요,  이름은 {props.name}입니다.</div>;
};

export default MyComponent;

컴포넌트를 사용할 때 props 값 지정하기

import React from 'react';
import MyComponent from './MyComponent';

const App = () => {
  return <MyComponent name="React" />;
}

위의 코드에서 name="React" 하는 부분을 자식 컴포넌트가 props.name으로 받아와서 보여줄 수 있는 것이다.

리액트에서 props는 Immutable data이다.

  • props는 상위 컴포넌트에서 하위 컴포넌트로 값을 전달할 때 사용한다.
  • 리액트의 Data Flow는 단방향 형식으로 부모에서부터 자식으로 이동하기 때문에 거꾸로 올라갈 수 없다.
  • 따라서 props에 있는 데이터들은 수정이 불가능하며, 오직 안에 있는 값을 꺼내서 사용할 수 있다.

props 기본값 설정: defaultProps

import React from 'react';

const MyComponent = props => {
  return <div>안녕하세요,  이름은 {props.name}입니다. </div>;
};

MyComponent.defaultProps = {
  name: '기본 이름',
};

export default MyComponent;

태그 사이의 내용을 보여주는 children

  • 리액트 컴포넌트를 사용할 때 컴포넌트 태그 사이의 내용을 보여 주는 props (일종의 예약어?)
import React from 'react';
import MyComponent from './MyComponent';

const App = () => {
  return <MyComponent>리액트</MyComponent>;
};

export default App;
  • MyComponent에서 props.children 값으로 가져올 수 있다.

비구조화 할당 문법을 통해 props 내부 값 추출하기

  • 비구조화 할당 문법? (destructuring assignment)
    • 객체 안에 있는 값을 추출해서 변수 혹은 상수로 바로 선언해줄 수 있도록 하는 ES6 문법 ```javascript const object = { a: 1, b: 2};

const { a, b } = object;

console.log(a); // 1 console.log(b); // 2

function print({ a, b }) { console.log(a); console.log(b); }

print(object);


```javascript
import React from 'react';

const MyComponent = props => {
  const { name, children } = props;
  return (
    <div>
      안녕하세요, 제 이름은 {name}입니다. <br/>
      children 값은 {children}입니다.
    </div>
  );
};

MyComponent.defaultProps = {
  name: '기본 이름',
};

export default MyComponent;
  • 함수의 파라미터 부분에서도 사용 가능
import React from 'react';

const MyComponent = ({ name, childeren }) => {
  return (
    <div>
    안녕하세요,  이름은 {name}입니다. <br/>
    children 값은 {children}입니다.
    </div>
  );
};

MyComponent.defaultProps = {
  name: '기본 이름',
};

export default MyComponent;

propTypes를 통한 props 검증

  • propTypes를 통해 컴포넌트의 필수 props를 지정하거나 props의 타입을 지정할 수 있다.
  • 우선 코드 상단에 import PropTypes from 'prop-types';
MyComponent.propTypes = {
  name: PropTypes.string,
};
  • 잘못된 type의 props를 넘겨줘도 값이 나타나기는 하지만, 콘솔에 경고 메시지가 출력된다.
Warning: Failed prop type: Invalid prop 'name' of type 'number' supplied to 'MyComponetn', expected 'string'.
  in MyComponent (at App.js:5)
  in App (at src/index.js:7)

isRequired를 사용하여 필수 propTypes 설정

MyComponent.propTypes = {
  favoriteNumber: PropTypes.number.isRequired,
};
  • 마찬가지로 값이 나타나기는 하지만, 콘솔에 경고 메시지가 출력된다.
Warning: Failed prop type: The prop 'favoriteNumber' is marked as required in 'MyComponent', but its value is 'undefined'.
  in MyComponent (at App.js:5)
  in App (at src/index.js:7)

더 많은 PropTypes 종류

  • array: 배열
  • arrayOf(다른 PropType): 특정 PropType으로 이루어진 배열
    • 예) arrayOf(PropTypes.number) -> 숫자로 이루어진 배열
  • bool: true 혹은 false 값
  • func: 함수
  • number: 숫자
  • object: 객체
  • string: 문자열
  • symbol: ES6의 Symbol
  • node: 렌더링할수 있는 모든 것(숫자, 문자열, 혹은 JSX 코드. children도 node PropType이다.)
  • instanceOf(클래스): 특정 클래스의 인스턴스
    • 예) instanceOf(MyClasss)
  • oneOf([‘dog’, ‘cat’]): 주어진 배열 요소 중 값 하나 (도메인 같은 느낌인듯)
  • oneOfType([React.PropTypes.string, PropTypes.number]): 주어진 배열 안의 종류 중 하나
  • objectOf(React.PropTypes.number): 객체의 모든 키 값이 인자로 주어진 PropType인 객체
  • shape({ name: PropTypes.string, num: PropTypes.number }): 주어진 스키마를 가진 객체
  • any: 아무 종류

클래스형 컴포넌트에서 props 사용하기

class MyComponent extends Component {
  render() {
    const { name, favoriteNumber, children } = this.props; // 비구조화 할당
    return (
      <div>
        안녕하세요,  이름은 {name}입니다. <br/>
        children 값은 {children}입니다. <br/>
        제가 좋아하는 숫자는 {favoriteNumber}입니다.
      </div>
    );
  }
}

MyComponent.defaultProps = {
  name: '기본 이름',
};

MyComponent.propTypes = {
  name: PropTypes.string,
  favoriteNumber: PropTypes.number.isRequired,
};

export default MyComponent;
  • defaultProps와 propTypes를 설정할 때 class 내부에서 지정하는 방법도 있다.
class MyComponent extends Component {
  static defaultProps = {
    name: '기본 이름'
  };
  static propTypes = {
    name: PropTypes.string,
    favoriteNumber: PropTypes.number.isRequired,
  };
  render() {
    const { name, favoriteNumber, children } = this.props;
    return (...);
  }
}

export default MyComponent;

state

  • state는 사용자(클라이언트)와의 더욱 dynamic한 통신을 위해 만들어졌다.
  • state는 컴포넌트의 특정 상태를 기억하여 화면에 반영하고, 상태가 사용자에 의해 변경되며 다시 화면이 변경되는 기능을 하기 위해 존재하는 객체.
  • props와 다르게 컴포넌트 내부에서 바뀔 수 있다.
  • 하위에서 상위로 event를 통해 값을 전달한다.
  1. 클래스형 컴포넌트의 state
  2. 함수형 컴포넌트에서 useState라는 함수를 통해 사용하는 state

클래스형 컴포넌트의 state

import React, { Component } from 'react';

class Counter extends Component {
    constructor(props) {
      super(props);
      // state의 초깃값 설정하기
      this.state = {
        number: 0,
      };
    }
    render(){
        const { number } = this.state; // state를 조회할 때는 this.state로 조회
        return (
            <div>
                <h1>{number}</h1>
                <button
                    onClick={() => {
                        // this.setState를 사용하여 state에 새로운 값을 넣을 수 있다.
                        this.setState(
                            { number: number + 1 });
                    }}
                >
                    +1
                </button>
            </div>
        );
    }
}

export default Counter;
  • 컴포넌트에 state를 설정할 때는 constructor 메서드를 작성하여 설정한다.
  • constructor란 컴포넌트의 생성자 메서드
  • 클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 super(props)를 호출해 주어야 한다.
    • super(props)를 호출하지 않으면 this.props 사용 시 생성자 내에서 정의되지 않아 버그 발생 가능성이 생긴다.
  • 이 함수가 호출되면 현재 클래스형 컴포넌트가 상속받고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출해 준다.
  • 그 후 this.state 값에 초깃값을 설정한다. 컴포넌트의 state는 객체 형식이어야 한다.
  • 이벤트로 설정할 함수를 넣어 줄 때는 화살표 함수 문법을 사용하여 넣어 주어야 한다.
    • 화살표 함수의 this 바인딩 특성
  • 함수 내부에서는 this.setState라는 함수를 사용하여 state 값을 바꿀 수 있게 해 준다.

state를 constructor에서 꺼내기

import React, { Component } from 'react';

class Counter extends Component {
        state = {
            number: 0,
            fixedNumber: 0
        };
    render(){
        const { number, fixedNumber } = this.state;
        return (...);
    }
}

export default Counter;
  • 위 방식대로 하면 constructor 메서드를 선언하지 않아도 state의 초깃값을 설정할 수 있다.
    • 자동적으로 constructor 메서드 호출?

this.setState에 객체 대신 함수 인자 전달하기

  • this.setState를 사용하여 state 값을 업데이트할 때는 상태가 비동기적으로 업데이트된다.
  • 그렇다면, 만약 onClick에 설정한 함수 내부에서 this.setState두 번 호출한다면 어떻게 될까?
onClick={() => {
  this.setState({number: number + 1});
  this.setState({number: this.state.number + 1});
}}
  • 상상해볼 수 있는 것: number가 한 번에 2씩 더해진다.
  • 하지만?
  • 실제로는 1씩 더해진다. (여전히)
  • 왜?
  • this.setState는 비동기적으로 상태를 업데이트한다. -> this.setState를 사용한다고 해서 state값이 바로 바뀌지 않는다.
  • 해결책? -> this.setState를 사용할 때 객체 대신 함수를 인자로 넣어준다.
this.setState((prevState, props) => {
  return {
    // 업데이트하고 싶은 내용
  }
})
  • prevState: 기존 상태
  • props: 현재 지니고 있는 props. 생략 가능.
<button
  // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정
  onClick={() => {
    this.setState(prevState => {
      return {
        number: prevState.number +1
      };
    });
    // 위 코드와 아래 코드는 완전히 똑같은 기능
    // 아래 코드는 함수에서 바로 객체를 반환한다는 의미
    this.setState(prevState => ({
      number: prevState.number +1
    }));
  }}
  >
    +1
</button>
  • 화살표 함수에서 값을 바로 반환하고 싶다면 코드 블록 {}을 생략하면 된다.
const sum = (a,b) => a + b;

this.setState가 끝난 후 특정 작업 실행하기

  • setState의 두 번째 파라미터로 콜백(callback) 함수 등록
<button
    onClick={() => {
        this.setState(
            {
                number: number + 1
            },
            () => {
                console.log('방금 setState가 호출되었습니다.');
                console.log(this.state);
            }
        )
    }}
>
    +1
</button>

Component constructor 주의 사항

  • setState()를 생성자 안에서 호출하지 않는다.
    • 생성자 내에서는 setState를 사용하는 것이 아닌 this.state로 초기값을 할당해주어야 한다.
    • 생성자는 this.state를 직접 할당할 수 있는 곳으로, 그 외에는 꼭 this.setState()를 이용.
  • 생성자 내에서는 구독 작업이나 외부 API를 호출하면 안된다.
    • 외부 API 호출이 필요하다면 생성자 밖에서 componentDidMount()를 사용
  • stateprops를 복사하면 안 된다.
    • this.state = { color: props.color };
    • 불필요한 작업 (this.props.color를 직접 사용하면 된다.)
    • color props의 값이 변하더라도 state에 반영되지 않기 때문에 버그를 발생시킬 여지가 있다.
    • props의 갱신을 의도적으로 무시해야 할 때만 이와 같은 패턴을 사용하는 것이 좋지만, 이 경우에도 해당 props의 이름을 initialColor 등으로 변경하는 것이 자연스럽다.
    • 그러면 이후 필요에 따라 컴포넌트가 key를 변경하여 초기 state를 재설정하도록 강제할 수 있다.

함수형 컴포넌트에서 useState 사용하기

  • 리액트 16.8 버전 이후부터 가능
  • Hooks를 통해 가능

useState 사용하기

import React, {useState} from 'react';

const Say = () => {
    const [message, setMessage] = useState('');
    const onClickEnter = () => setMessage('안녕하세요!');
    const onClickLeave = () => setMessage('안녕히 가세요!');

    return (
        <div>
            <button onClick={onClickEnter}>입장</button>
            <button onClick={onClickLeave}>퇴장</button>
            <h1>{message}</h1>
        </div>
    )
}

export default Say;
  • useState 함수의 인자에는 상태의 초깃값을 넣어 준다.
    • 클래스형 컴포넌트에서의 state 초깃값과 달리, 반드시 객체가 아니어도 상관없다.
    • 숫자, 문자열, 객체, 배열,…
  • useState에서 반환하는 배열
    • 배열의 첫 번째 원소: 현재 상태 (‘message’)
    • 배열의 두 번째 원소: 상태를 바꾸어 주는 함수 (세터Setter함수) (‘setMessage’)
  • 배열 비구조화 할당을 통해 이름을 자유롭게 정해준 것

한 컴포넌트에서 useState 여러 번 사용하기

import React, {useState} from 'react';

const Say = () => {
    const [message, setMessage] = useState('');
    const onClickEnter = () => setMessage('안녕하세요!');
    const onClickLeave = () => setMessage('안녕히 가세요!');

    const [color, setColor] = useState('black');

    return (
        <div>
            <button onClick={onClickEnter}>입장</button>
            <button onClick={onClickLeave}>퇴장</button>
            <h1 style=>{message}</h1>
            <button style= onClick={() => setColor('red')}>
                빨간색
            </button>
            <button style= onClick={() => setColor('green')}>
                초록색
            </button>
            <button style= onClick={() => setColor('blue')}>
                파란색
            </button>
        </div>
    )
}

export default Say;
  • 여러 개의 상태를 하나의 컴포넌트에서 관리할 수 있다!

state를 사용할 때 주의 사항

  • 클래스형 컴포넌트, 함수형 컴포넌트 모두 해당
  • state는 초기값 설정이 필수이다.
  • state 값을 바꾸어야 할 때는 setState 혹은 useState를 통해 전달받은 세터 함수를 사용해야 한다.

잘못된 코드 예시

// class component
this.state.number = this.state.number +1;
this.state.array = this.array.push(2);
this.state.object.value = 5;

// functional component
const [object, setObject] = useState({ a: 1, b: 1 });
object.b = 2;
  • 왜냐하면 react는 state가 변경될 때마다 변경된 부분을 감지하여 리렌더링을 하는데, setState 메서드를 사용하지 않고 직접 state 값을 수정할 경우 변경을 감지하지 못해서 리렌더링을 하지 못한다.

  • 배열이나 객체를 업데이트해야 할 때는 배열이나 객체 사번을 만들고 그 사본에 값을 업데이트한 후, 그 사본의 상태를 setState 혹은 세터 함수를 통해 업데이트한다!

정리

  • 앞으로 새로운 컴포넌트를 만들 때는 useState를 사용할 것을 권장한다.
  • 이로써 코드가 더 간결해짐
  • 리액트 개발 팀이 함수형 컴포넌트와 Hooks를 사용하는 것이 주요 컴포넌트 개발 방식이 될 것이라고 공지

참고

더 알아보기..

<리액트를 다루는 기술> 2장 JSX

|

velopert님의 <리액트를 다루는="" 기술=""> 개정판을 보고 공부한 것을 정리합니다.

코드 이해하기

// scr/App.js

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
        </header>
      </div>
  );
}

export default App;
  • import React from 'react'
    • 리액트를 불러와서 사용할 수 있게 해 준다.
    • 리액트 프로젝트를 만들 때 node_modules라는 디렉터리도 함께 생성되는데, 프로젝트 생성 과정에서 node_modules 디렉터리에 react 모듈이 설치된다. 그리고 import 구문을 통해 리액트를 불러와서 사용할 수 있게 된다.

모듈을 불러와 사용하기

모듈을 불러와서 사용하는 것은 원래 브라우저에는 없던 기능이다. 브라우저가 아닌 환경에서 자바스크립트를 실행할 수 있게 해 주는 환경인 Node.js에서 지원하는 기능!

Node.js에서는 import가 아닌 require라는 구문으로 패키지를 불러올 수 있다.

이러한 기능을 브라우저에서도 사용하기 위해 번들러(bundler) 를 사용한다. 이름대로, 파일을 묶듯이 연결해준다.

대표적인 번들러로 웹팩, Parcel, browserify라는 도구들이 있으며, 각 도구마다 특성이 다르다. 리택트 프로젝트에서는 주로 웹팩을 사용하는 추세이다. 편의성과 확장성이 다른 도구보다 뛰어나기 때문. 번들러 도구를 사용하면 import로 모듈을 불러왔을 때 불러온 모듈을 모두 합쳐서 하나의 파일을 생성해준다. 또 최적화 과정에서 여러 개의 파일로 분리될 수도 있다.

이 책의 프로젝트에서는 src/index.js를 시작으로 필요한 파일을 다 불러와서 번들링하게 된다.

2017년부터 브라우저에서도 import 구문을 사용할 수 있게 되었지만, 이는 단순히 다른 경로에 있는 자바스크립트를 불러오는 용도로만 사용되기 때문에 프로젝트 번들링과는 다르다.

import logo from './logo.svg';
import './App.css';

리액트를 불러오는 코드 하단에 SVG 파일과 CSS 파일을 import하는 코드가 있다.

웹팩을 사용하면 SVG 파일과 CSS 파일도 불러와서 사용할 수 있다. 이렇게 파일들을 불러오는 것은 웹팩의 로더(loader) 라는 기능이 담당한다. 로더는 여러 가지 종류가 있다.

  • css-loader: CSS 파일을 불러올 수 있게 해 준다.
  • file-loader: 웹 폰트나 미디어 파일 등을 불러올 수 있게 해 준다.
  • babel-loader: 자바스크립트 파일들을 불러오면서 최신 자바스크립트 문법으로 작성된 코드를 바벨이라는 도구를 사용하여 ES5 문법으로 변환해 준다.

ES5는 이전 버전의 자바스크립트를 의미하는데, 구버전 웹 브라우저와의 호환을 위해 ES5 문법으로의 변환이 필요하다. 또, 리액트 컴포넌트에서 사용하는 JSX 문법도 정식 자바스크립트 문법이 아니기 때문에 ES5 형태의 코드로 변환해야 한다.

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

이 코드는 App이라는 컴포넌트를 만들어 준다. function 키워드를 사용하여 컴포넌트를 만들었는데, 이러한 컴포넌트를 함수형 컴포넌트라고 부른다. 프로젝트에서 컴포넌트를 렌더링(렌더링: ‘보여 준다’는 의미) 함수에서 반환하고 있는 내용을 나타낸다. 이 코드가 바로 JSX 이다.

JSX란?

JSX는 자바스크립트의 확장 문법이며 XML과 매우 비슷하게 생겼다. 이런 형식으로 작성한 코드는 브라우저에서 실행되기 전에 코드가 번들링되는 과정에서 바벨을 사용하여 일반 자바스크립트 형태의 코드로 변환된다.

function App() {
  return (
    <div>
      Hello <b> react </b>
    </div>
  );
}

이렇게 작성된 코드는 다음과 같이 변환된다.

function App() {
  return React.createElement("div", null, "Hello", React.createElement("b", null, "react"));
}

컴포넌트를 렌더링할 때마다 JSX 코드를 작성하는 것이 아니라 위 코드처럼 매번 React.createElement 함수를 사용해야 한다면 매우 불편할 것이다.

JSX는 리액트로 프로젝트를 개발할 때 사용되므로 공식적인 자바스크립트 문법이 아니다. 바벨에서는 여러 문법을 지원할 수 있도록 preset 및 plugin을 설정한다. 바벨을 통해 개발자들이 임의로 만든 문법, 혹은 차기 자바스크립트의 문법들을 사용할 수 있다.

JSX의 장점

보기 쉽고 익숙하다

JSX를 사용하는 편이 더 가독성이 높고 작성하기도 쉽다. HTML 코드를 작성하는 것과 비슷하기 때문이다.

더욱 높은 활용도

JSX에서는 div나 span같은 HTML 태그를 사용할 수 있을 뿐만 아니라, 앞으로 만들 컴포넌트도 JSX 안에서 작성할 수 있다. App.js에서 만든 App 컴포넌트를 다른 js 파일 안에서 마치 하나의 HTML 태그처럼 사용할 수 있다.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

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

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

ReactDOM.render?

이 코드는 컴포넌트를 페이지에 렌더링하는 역할을 하며, react-dom 모듈을 불러와 사용할 수 있다. 이 함수의 첫번째 파라미터에는 페이지에 렌더링할 내용을 JSX 형태로 작성하고, 두 번째 파라미터에는 해당 JSX를 렌더링할 document 내부 요소를 설정한다. 여기서는 id가 root인 요소 안에 렌더링을 하도록 설정했다.

JSX 문법

감싸인 요소

컴포넌트에 여러 요소가 있다면 반드시 부모 요소 하나로 감싸야 한다. Virtual DOM 내에서 컴포넌트 변화를 감지해 낼 때 효율적으로 비교할 수 있도록 컴포넌트 내부는 하나의 DOM 트리 구조로 이루어져야 한다는 규칙이 있기 때문이다.

이때 <div> 요소를 사용하고 싶지 않다면, 리액트 v16 이상부터 도입된 Fragment 라는 기능을 사용하면 된다.

import React, { Fragment } from 'react';

function App() {
  return (
    <Fragment>
      <h1>리액트 안녕!</h1>
      <h2> 작동하니?</h2>
    </Fragment>
  );
}

export default App;

다음과 같은 형태로도 표현될 수 있다.

import React from 'react';

function App() {
  return (
    <>
      <h1>리액트 안녕!</h1>
      <h2> 작동하니?</h2>
    </>
  );
}

export default App;

자바스크립트 표현

JSX 안에서 자바스크립트 표현식을 쓸 수 있다. 자바스크립트 표현식을 작성하려면 JSX 내부에서 코드를 {}로 감싸면 된다.

import React from 'react';

function App() {
  const name = '리액트';
  return (
    <>
      <h1>{name}안녕!</h1>
      <h2> 작동하니?</h2>
    </>
  );
}

export default App;
const

const는 ES6 문법에서 새로 도입되었으며 한번 지정하고 나면 변경이 불가능한 상수를 선언할 때 사용하는 키워드이다. let은 동적인 값을 담을 수 있는 변수를 선언할 때 사용하는 키워드이다.

var는 scope가 함수 단위이다.

letconst는 scope가 함수 단위가 아닌 블록 단위이므로, if문 내부에서 선언한 a값은 if문 밖의 a값을 변경하지 않는다.

또한 letconst를 사용할 때 같은 블록 내부에서 중복 선언이 불가능하며, const는 한번 선언하면 재설정할 수 없다.

기본적으로 const를 사용하고, 해당 값을 바꾸어야 할 때는 let를 사용하면 된다.

If문 대신 조건부 연산자

JSX 내부의 자바스크립트 표현식에서 if문을 사용할 수는 없다. 하지만 조건에 따라 다른 내용을 렌더링해야 할 때는 JSX 밖에서 if문을 사용하여 사전에 값을 설정하거나, {} 안에 조건부 연산자(삼항 연산자)를 사용 하면 된다.

import React from 'react';

function App() {
  const name = '리액트';
  return (
    <div>
        {name === '리액트'?(
            <h1>리액트입니다.</h1>
        ):(
            <h2>리액트가 아닙니다.</h2>
            )}
    </div>
  );
}

export default App;
  • condition ? true_value : false_value

AND 연산자(&&)를 사용한 조건부 렌더링

조건부 연산자를 통해 null을 렌더링하면 아무것도 보여 주지 않도록 할 수 있다.

import React from 'react';

function App() {
  const name = '뤼왝트';
  return (
    <div>
        {name === '리액트'?(
            <h1>리액트입니다.</h1>
        ):null}
    </div>
  );
}

export default App;

&& 연산자를 사용해서 더 짧은 코드로 똑같은 작업을 할 수 있다.

import React from 'react';

function App() {
  const name = '뤼왝트';
  return (
    <div>
        {name === '리액트'&&
            <h1>리액트입니다.</h1>
        }
    </div>
  );
}

export default App;

&& 연산자로 조건부 렌더링을 할 수 있는 이유는 리액트에서 false를 렌더링할 때는 null과 마찬가지로 아무것도 나타나지 않기 때문이다. 다만 falsy한 값인 0은 예외적으로 화면에 나타난다.

const number = 0;
return number && <div>내용</div>

위의 코드는 화면에 숫자 0을 보여 준다.

주로 JSX를 여러 줄로 작성할 때 괄호로 감싸고, 한 줄로 표현할 수 있는 코드는 감싸지 않는다. JSX를 괄호로 감싸는 것은 필수 사항이 아니다. 감싸도 되고 감싸지 않아도 된다.

undefined를 렌더링하지 않기

리액트 컴포넌트에서는 함수에서 undefined만 반환하여 렌더링하는 상황을 만들면 안 된다.

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

function App(){
  const name = undefined;
  return name;
}

export default App;

위 코드는 다음과 같은 오류를 출력한다.

App(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.

어떤 값이 undefined일 수도 있다면, OR(||) 연산자를 사용하면 해당 값이 undefined일 때 사용할 값을 지정할 수 있으므로 간단하게 오류를 방지할 수 있다.

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

function App(){
  const name = undefined;
  return name||'값이 undefined입니다.';
}

export default App;

반면 JSX 내부에서 undefined를 렌더링하는 것은 괜찮다.

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

function App() {
  const name = undefined;
  return <div>{name}</div>;
}

export default App;

name 값이 undefined일 때 보여주고 싶은 문구가 있다면 다음과 같이 작성

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

function App() {
  const name = undefined;
  return <div>{name||'리액트'}</div>;
}

export default App;

인라인 스타일링

리액트에서 DOM 요소에 스타일을 적용할 때는 문자열 형태로 넣는 것이 아니라 객체 형태로 넣어 주어야 한다.

스타일 이름 중 background-color처럼 - 문자가 포함되는 이름이 있다. 이러한 이름은 - 문자를 없애고 카멜 표기법 (camelCase)으로 작성해야 한다. 따라서 background-colorbackgroundColor로 작성한다.

import React from 'react';

function App() {
  const name = '리액트';
  const style = {
    backgroundColor: 'black',
    color: 'aqua',
    fontSize: '48px',
    fontWeight: 'bold',
    padding: 16 // 단위를 생략하면 px로 지정된다.
  };
  return <div style={style}>{name}</div>;
}

export default App;

style 객체를 미리 선언하지 않고 바로 style 값을 지정하고 싶다면 다음과 같이 작성.

import React from 'react';

function App() {
  const name = '리액트';
  return (
    <div
      style = 
    >
      {name}
    </div>
  );
}

export default App;

class 대신 className

일반 HTML에서 CSS 클래스를 사용할 때는 <div class="myclass"></div>와 같이 class라는 속성을 설정한다. 하지만 JSX에서는 class가 아닌 className으로 설정해 주어야 한다.

우선 새 App.css를 작성한다.

.react {
  background: aqua;
  color: black;
  font-size: 48px;
  font-weight: bold;
  padding: 16px;
}
import React from 'react';
import './App.css';

function App() {
    const name = '리액트';
    return <div className="react">{name}</div>;
}

export default App;

JSX를 작성할 때 CSS 클래스를 설정하는 과정에서 className이 아닌 class 값을 설정해도 스타일이 적용되기는 한다. 하지만 그렇게 사용하면 브라우저 개발자 도구의 Console 탭에 다음과 같은 경고가 나타난다.

Warning: Invalid DOM property 'class'. Did you mean 'className'?
  in div (at App.js:6)
  in App (at src/index.js:7)

이전에는 class로 CSS 클래스를 설정할 때 오류가 발생하고 CSS 클래스가 적용되지 않았는데, 리액트 v16 이상부터는 class를 className으로 변환시켜 주고 경고를 띄웁니다.

꼭 닫아야 하는 태그

HTML 코드를 작성할 때는 태그를 닫지 않은 상태로 코드를 작성해도 문제없이 작동하지만, JSX에서는 태그를 닫지 않으면 오류가 발생한다.

태그 사이에 별도의 내용이 들어가지 않는 경우에는 <input /> self-closing 태그로 작성할 수 있다. 태그를 선언하면서 동시에 닫을 수 있는 태그이다.

주석

JSX 안에서 주석을 작성하는 방법은 일반 자바스크립트에서 주석을 작성할 때와 조금 다르다.

{/* 주석 */}
<div
  className="react" // 시작 태그를 여러 줄로 작성하게 된다면 여기에 주석을 작성
>

ESLint와 Prettier 적용하기

ESLint

ESLint는 문법 검사 도구이고, Prettier는 코드 스타일 자동 정리 도구이다. ESLint는 코드를 작성할 때 실수를 하면 에러 혹은 경고 메시지를 VS Code 에디터에서 바로 확인할 수 있게 해 준다.

Prettier

VS Code에서 F1을 누르고 format이라고 입력한 다음 Enter를 누르면 Prettier를 통해 자동 코드 정리가 진행된다. 이 과정에서 코드가 제대로 정렬되고, 세미콜론(;)이 빠진 곳에는 세미콜론이 자동으로 추가되고, 기존에 사용하던 작은따옴표는 모두 큰따옴표로 바뀐다.

Prettier의 장점은 코딩 스타일을 쉽게 커스터마이징할 수 있다는 것이다. 현재 열려 있는 프로젝트의 루트 디렉터리(src, public 디렉터리들이 위치한 곳)에서 .prettierrc라는 파일을 생성한 후 다음과 같이 입력하면,

{
  "singleQuote": true,
  "semi": true,
  "useTabs": false,
  "tabWidth": 2
}

들여쓰기를 할 때 탭 대신 공백을 두 칸 사용하도록 되어 있고, 큰따옴표 대신 작은따옴표를, 세미콜론은 언제나 붙이도록 설정되어 있다. (참고: prettier options 페이지)

저장할 때 자동으로 코드 정리하기

  1. VS Code 환경 설정 열기
    • Code > 기본 설정 > 설정
  2. 상단 텍스트 박스에서 format onn save를 검색
  3. 나타나는 체크 박스에 체크
  4. 저장할 때마다 코드가 자동으로 정리됨!

정리

JSX는 코드로 보면 XML 형식이지만 실제로는 자바스크립트 객체이며, 용도도 다르고 문법도 조금씩 차이가 난다.

Spring과 MyBatis 3.x 연동 - Mapper

|

XML에서 SqlSessionFactory 빌드하기

모든 마이바티스 애플리케이션은 SqlSessionFactory 인스턴스를 사용한다.

<bean id = "sqlSessionFactory" class="org.mybatis.spring.SqlSessionBean">
  <property name = "datasource" ref="dataSource" />
  <property name = "mapperLocations" value = "{mapper_xml_path}" />
  <property name = "configLocation" value = "{mybatis_config_xml_path}" />
</bean>

쿼리 매핑을 위한 XML 파일을 검색하여 자동 추가하도록 설정한다.

MyBatis와 Spring DAO 연동하기

mybatis는 Spring의 DAO (데이터베이스 작업을 담당하는 객체; Data Access Object) 객체와 mapping될 수 있다.

database 작업을 하는 구문은 XML이나 어노테이션을 사용하여 정의할 수 있다. 마이바티스가 제공하는 대부분의 기능은 XML을 통한 매핑 기법을 사용한다.

mapper xml을 작성하기 전, DAO 인터페이스를 생성한다.

public interface ExampleDao {
  public String selectBlog(@Param("userId") String userid);
}

DAO 클래스를 일단 인터페이스로 선언하고 그 내에는 데이터베이스 작업을 함수 단위로 분리하여 parameter와 함수명을 명시해준다. 그리고 mapper xml을 다음과 같이 생성해준다.

<?xml version "1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.mybatis.example.dao.ExampleDao" /> <!--dao_class_path-->
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{userId}
  </select>
</mapper>

napespace에는 패키지명을 포함한 DAO 인터페이스의 경로를 정확히 써 주고, 각각의 쿼리문들의 id는 DAO 인터페이스에서 정의한 메소드명과 일치해야 한다.

그 후, 컨트롤러에서 sqlSession빈을 가져다 쓰면 된다.

@Controller
public class ExampleController {
  @Autowired
  private SqlSession sqlSession;

  @RequestMapping("/test/mybatis/{userId}")
  public String testMybatis(@PathVariable("userId") String userId) {
    ExampleDao exampleDao = sqlSession.getMapper(ExampleDao.class);
    return exampleDao.getUserName(userId);
  }
}

위와 같이 DAO를 꺼내 쓰기만 하면 실제 쿼리가 XML에 정의된 쿼리들과 자동으로 매핑되어 결과값이 반환된다.

네임스페이스(Namespace)

네임스페이스는 3.x 이전 버전에서는 선택사항이었다. 하지만 이제는 패키지 경로를 포함한 전체 이름을 가진 구문을 구분하기 위해 필수로 사용해야 한다.

네임스페이스는 인터페이스 바인딩을 가능하게 한다. 네임스페이스를 사용하고 자바 패키지의 네임스페이스를 두면 코드가 깔끔해지고 마이바티스의 사용성이 크게 향상된다.

노드 리액트 기초 강의 15 - 리액트란?

|

velopert님의 <리액트를 다루는="" 기술=""> 개정판과 inflearn의 [따라하며 배우는 노드, 리액트 기본](https://www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%A9%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%85%B8%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B8%B0%EB%B3%B8)를 보고 공부한 것을 정리합니다.

react JS

Library, Made by Facebook, Released in 2013

현재 자바스크립트는 웹 애플리케이션에서 가장 핵심적인 역할 더 나아가 영역을 확장하여 서버 사이드는 물론 모바일, 데스크톱 애플리케이션에서도 활약 이제 자바스크립트만으로도 규모가 큰 애플리케이션을 만들 수 있는 시대! -> 그렇다면, 대규모 애플리케이션 중 프론트엔드 사이드에서 돌아가는 애플리케이션 구조를 관리하려면 어떻게 해야 할까? 라는 문제

기존에 많이 사용되는 MVC 아키텍처 (Model, View, Controller)

  • 모델은 애플리케이션에서 사용하는 데이터를 관리
  • 뷰는 사용자에게 보이는 부분
  • 프로그램이 사용자에게서 어떤 작업(예: 버튼 클릭, 텍스트 입력 등)을 받으면 컨트롤러는 모델 데이터를 조회하거나 수정하고, 변경된 사항을 뷰에 반영
  • 반영하는 과정에서 보통 뷰를 변형(mutate)

업데이트하는 항목에 따라 어떤 부분을 찾아서 변경할지에 대한 규칙을 정하는 작업은 간단하지만, 애플리케이션 규모가 크면 상당히 복잡해지고 제대로 관리하지 않으면 성능도 떨어질 수 있다.

그렇다면, 어떤 데이터가 변할 때마다 어떤 변화를 줄지 고민하는 것이 아니라 그냥 기본 뷰를 날려 버리고 처음부터 새로 렌더링하면 어떨까? (by Facebook)

리액트는 사용자 인터페이스를 만드는 데 사용하는 라이브러리이다. MVC 중에서도 오직 V(view)만 신경쓰는 라이브러리이다.

다른 웹 프레임워크가 Ajax, 데이터 모델링, 라우팅 등과 같은 기능을 내장하고 있는 반면, 리액트는 정말 뷰만 신경쓰는 라이브러리이므로 기타 기능은 직접 구현하여 사용해야 한다.

라우팅에는 리액트 라우터, Ajax 처리에는 axios나 fetch, 상태 관리에는 리덕스(redux)나 MobX를 사용할 수 있다. 해당 분야에서 마음에 드는 라이브러리를 사용하면 되기 때문에 자신의 취향대로 스택을 설정할 수 있다는 장점이 있지만, 여러 라이브러리를 접해야 한다는 단점도 있다.

또 리액트는 Backbone.js, Angular JS 등의 프레임워크나 라이브러리와 혼용할 수도 있다.

component

리액트 프로젝트에서 특정 부분이 어떻게 생길지 정하는 선언체가 있는데, 이를 컴포넌트(component)라고 한다. 템플릿과는 다른 개념! 컴포넌트는 재사용이 가능한 API로 수많은 기능들을 내장하고 있으며, 컴포넌트 하나에서 해당 컴포넌트의 생김새와 작동 방식을 정의한다.

렌더링 : 사용자 화면에 뷰를 보여주는 것

리액트 컴포넌트는 최초로 ‘초기 렌더링’을 실행하고, 데이터 변경이 일어나면 다시 ‘리렌더링’이 실행된다.

초기 렌더링

어떤 UI 관련 프레임워크, 라이브러리를 사용하든지 맨 처음 어떻게 보일지를 정하는 초기 렌더링이 필요하다. 리액트에서는 render()가 이를 담당한다. render() {...}

이 함수는 뷰가 어떻게 생겼고 어떻게 작동하는지에 대한 정보를 지닌 객체를 반환한다. 컴포넌트 내부에는 또 다른 컴포넌트들이 들어갈 수 있는데, render 함수를 실행하면 그 내부에 있는 컴포넌트들도 재귀적으로 렌더링한다.

이렇게 최상위 컴포넌트의 렌더링 작업이 끝나면 지니고 있는 정보들을 사용하여 HTML 마크업을 만들고, 이를 우리가 정하는 실제 페이지의 DOM 요소 안에 주입한다.

조화 과정

리액트에서 뷰를 업데이트할 때는 “업데이트 과정을 거친다”라고 하기보다는 “조화 과정(reconcilation)을 거친다”라고 하는 것이 더 정확한 표현이다. 업데이트 과정에서 뷰가 변형되는 것이 아니라, 내부적으로는 새로운 요소로 갈아끼우는 것이기 때문이다.

이 작업 역시 render 함수가 하는 일이다. 컴포넌트는 데이터를 업데이트했을 때 단순히 업데이트한 값을 수정하는 것이 아니라, 새로운 데이터를 가지고 render 함수를 또 다시 호출한다.

이때 이전에 render 함수가 만들었던 컴포넌트 정보(스냅샷)와 현재 render 함수가 만든 컴포넌트 정보를 비교한다. 자바스크립트를 사용하여 두 가지 뷰를 최소한의 연산으로 비교한 후, 둘의 차이를 알아내 최소한의 연산으로 DOM 트리를 업데이트한다. (diffing 과정)

real DOM에 바로 변화를 업데이트하게 되면 전체를 reload하는 과정이 수반되기 때문에 시간이 오래 걸리지만, 이렇게 두 가지 뷰를 비교하여 변경이 발생한 부분만을 갈아 끼우게 되면 자원이 절약되면서 훨씬 더 빠르게 변화가 일어난다.

virtual DOM

DOM

Document Object Model 객체로 문서 구조를 표현하는 방법으로, XML이나 HTML로 작성한다. 웹 브라우저는 DOM을 활용하여 객체에 자바스크립트와 CSS를 적용한다. DOM은 트리 형태이기 때문에 특정 노드를 찾거나(탐색), 수정하거나(업데이트), 제거하거나(삭제), 원하는 곳에 삽입할 수 있다.

DOM은 과연 느릴까? DOM은 동적 UI에 최적화되어 있지 않다는 치명적인 약점이 있다.

DOM 자체는 빠르다. DOM 자체를 읽고 쓸 때의 성능은 자바스크립트 객체를 처리할 때의 성능과 비교하여 큰 차이가 나지 않는다. 단, 웹 브라우저 단에서 DOM에 변화가 일어나면 웹 브라우저가 CSS를 다시 연산하고, 레이아웃을 구성하고, 페이지를 리페인트한다. 이 과정에서 시간 자원이 소모된다.

해결법 DOM을 최소한으로 조작한다! 리액트는 Virtual DOM 방식을 사용하여 DOM 업데이트를 추상화함으로써 DOM 처리 횟수를 최소화하고 효율적으로 진행한다.

Virtual DOM

Virtual DOM을 사용하면 실제 DOM에 접근하여 조작하는 대신, 이를 추상화한 자바스크립트 객체를 구성하여 사용한다. 마치 실제 DOM의 가벼운 사본 과 비슷하다.

리액트에서 데이터가 변하여 웹 브라우저에 실제 DOM을 업데이트할 때 밟게 되는 절차는 다음과 같다.

  1. 데이터를 업데이트하면 전체 UI를 Virtual DOM에 리렌더링한다.
  2. 이전 Virtual DOM에 있던 내용과 현재 내용을 비교한다.
  3. 바뀐 부분만 실제 DOM에 적용한다.

그렇다면 Virtual DOM을 사용하면 어떤 상황에서나 무조건 빠를까? 다음은 리액트 매뉴얼에 있는 문장이다.

우리는 다음 문제를 해결하려고 리액트를 만들었습니다. 지속적으로 데이터가 변화하는 대규모 애플리케이션 구축하기

단순 라우팅 정도만 있는 정적인 페이지에서는 오히려 리액트를 사용하지 않는 편이 더 나은 성능을 보이기도 한다. 리액트와 Virtual DOM이 언제나 제공할 수 있는 것은 바로 업데이트 처리 간결성 이다. UI를 업데이트하는 과정에서 생기는 복잡함을 모두 해소하고, 더욱 쉽게 업데이트에 접근할 수 있다.

웹앱 과정 - 제이쿼리 (2)

|

제이쿼리 메소드

제이쿼리 스타일 관련 메소드

CSS3 메소드

  • 제이쿼리는 CSS3 스타일 속성을 제어할 수 있다. 즉, HTML5 문서의 스타일을 동적으로 변경할 수 있다.
  • $().css(CSS속성명) : 선택된 엘리먼트에 적용된 CSS 스타일 속성값을 반환
  • $().css(CSS속성명, CSS속성값) : 선택된 엘리먼트에 CSS 스타일을 적용
  • $().css(CSS속성집합) : 속성값을 한꺼번에 설정(맵 형식)
  • $().addClass(CSS클래스명) : 선택된 엘리먼트에 class 속성값을 설정(CSS 클래스명으로 선언된 스타일을 적용)
  • $().removeClass(CSS클래스명) : 선택된 엘리먼트의 class 속성값을 제거 (적용된 CSS 클래스 스타일을 제거)
  • $().toggleClass(CSS클래스명) : 선택된 엘리먼트에 class 속성값이 존재하면 제거, 없으면 추가(CSS 클래스 스타일을 적용/해제 전환)
  • $().hasClass(CSS클래스명) : 선택된 엘리먼트에 class 속성값 존재 유무를 반환(CSS 클래스 스타일 적용 유무를 반환)
  • $().width() : 선택된 엘리먼트의 너비 값을 반환
  • $().width(너비값) : 선택된 엘리먼트의 너비 값을 설정
  • $().height() : 선택된 엘리먼트의 높이 값을 반환
  • $().height(높이값) : 선택된 엘리먼트의 높이 값을 설정
// DOM CSS 메소드 적용
$('span').css('padding', 20px);
$('p:odd').css({'background-color':'purple', 'color':'white'});
$('#span2').css('background-color', $('p:eq(1)').css('background-color'));
$('p').css('border-color', 'green')
    .not(':eq(1)')
    .css('border-width', 'thick');

맵 방식, 메소드 체인 방식

  • 맵 방식 : 속성과 값의 쌍을 :로 구분하여 여러 개를 나열
  • 메소드 체인 방식 : 메소드 호출 뒤에 마침표를 찍고 또 다른 메소드 호출 -> 한 line 안에서 체인처럼 연속적으로 메소드 호출 이때, 메소드들은 실행 후 다음 메소드를 수행할 수 있도록 입력받은 엘리먼트 그룹을 다시 반환
    // 맵 방식과 메소드 체인 방식
    $('div:eq(2)').css('border-width', 'thick');
    $('div:eq(2)').find('.class2').css('border-width', 'thick');
    $('div:eq(2)').find('.class2').text('노드 변경').css('border-width', 'thick');
    $('div:eq(2)').find('.class2').text('노드 변경').append('<h5>노드추가</h5>').css('border-width', 'thick');
    

스타일 클래스 메소드

.class2 { border-width: thick; }
.u_bgpurple { background-color: purple; color: white; }
.u_dotted { border-style: dotted; }
// 스타일 클래스 메소드
$('span:first').addClass('u_bgpurple');
$('p:eq(1)').removeClass('class2').addClass('u_bgpurple');
$('span').toggleClass('u_bgpurple');
  • addClass() 메소드의 입력인자에 addClass('class1 class2') 처럼 클래스명을 띄어쓰기하여 여러 개 명세할 수 있다.
  • removeClass() 도 동일.

DOM 트리 관련 메소드

DOM 탐색 메소드

  • 여러 DOM 탐색 메소드를 통해 엘리먼트에 접근 가능
  • 탐색(traversing) 메소드 : DOM 트리의 선택된 위치를 기준으로 원하는 노드(주로 엘리먼트)를 찾도록 함
  • 이전 또는 다음 엘리먼트를 접근하는 경우처럼 선택자 대신 현재의 참조 엘리먼트를 기준으로 탐색 메소드를 사용하는 것이 효과적인 경우도 있다.
  • $().find() : 선택된 엘리먼트 중 [조건을 충족하는] 모든 자손 엘리먼트를 반환
  • $().children() : 선택된 엘리먼트 중 [조건을 충족하는] 모든 자식 엘리먼트들을 반환
  • $().parent() : 선택된 엘리먼트의 부모 엘리먼트 반환
  • $().parents() : 선택된 엘리먼트의 [조건을 충족하는] 모든 조상 엘리먼트들을 반환
  • $().siblings() : 선택된 엘리먼트 중 자신을 제외한 [조건을 충족하는] 모든 형제 엘리먼트들을 반환
  • $().prev() : 선택된 엘리먼트 중 바로 앞에 위치한 [조건을 충족하는] 형제 엘리먼트를 반환
  • $().prevAll() : 선택된 엘리먼트 중 앞에 위치한 모든 [조건을 충족하는] 모든 형제 엘리먼트들을 반환
  • $().next() : 선택된 엘리먼트 중 바로 다음에 위치한 [조건을 충족하는] 형제 엘리먼트를 반환
  • $().nextAll() : 선택된 엘리먼트 중 다음에 위치한 [조건을 충족하는] 모든 형제 엘리먼트들을 반환
// DOM 탐색 메소드
$('span:eq(2)').prev().css('border-style', 'hidden');
$('span').parents().css('border', 'dashed thick red');
$('#p2').nextAll().css('border', 'solid thick green');
$('#div2').find('p').css({'background-color':'purple', 'color':'white'});

DOM 필터링 메소드

  • $().filter() : 선택된 엘리먼트들 중 필터링 조건을 충족하는 엘리먼트를 반환
  • $().slice(start[, end]) : 선택된 엘리먼트들 중 start부터 end 이전까지의 엘리먼트를 반환
  • $().first() : 선택된 엘리먼트들 중 첫 번째 엘리먼트를 반환
  • $().last() : 선택된 엘리먼트들 중 마지막 엘리먼트를 반환
  • $().eq(n) : 선택된 엘리먼트들 중 n번째 엘리먼트를 반환
  • $().has() : 선택된 엘리먼트들 중 특정 자손 엘리먼트를 갖는 엘리먼트를 반환
  • $().is() : 선택된 엘리먼트들 중 특정 조건을 만족하는 엘리먼트가 있으면 true 반환
  • $().not() : 선택된 엘리먼트들 중 특정 조건을 만족하지 않는 엘리먼트를 반환
// DOM 트리 필터링 메소드
$('div').first().css('border-style', 'hidden');
$('span').not('#span2').css('border', 'dashed thick red');
$('p').css('border', 'dotted thick silver').filter('#div0 p:last').css('border', 'solid thick green');
$('p').slice(1,3).css({'backgrouund-color':'purple', 'color':'white'});

DOM 트리 엘리먼트 조작 메소드

  • 정적인 HTML5 문서에 동적인 특성 제공
  • DOM 트리에 새로운 노드를 추가하거나 변경 또는 제거를 자유롭게 가능
  • $().append() : 선택된 엘리먼트의 마지막 자식 엘리먼트로 추가
  • $().prepend() : 선택된 엘리먼트의 첫 번째 자식 엘리먼트로 추가
  • $(code).appendTo() : code를 선택된 엘리먼트의 마지막 자식 엘리먼트로 추가
  • $(code).prependTo() : code를 선택된 엘리먼트의 첫 번째 자식 엘리먼트로 추가
  • $().alter() : 선택된 엘리먼트의 뒤에 형제 엘리먼트로 추가
  • $().before() : 선택된 엘리먼트의 앞에 형제 엘리먼트로 추가
  • $().empty() : 선택된 엘리먼트의 내용을 비움(자식 엘리먼트만 제거
  • $().remove() : 선택된 엘리먼트를 제거(엘리먼트 자신도 포함하여 제거)
  • $().replaceWith() : 선택된 엘리먼트를 특정 엘리먼트로 바꿈
  • $().clone() : 선택된 엘리먼트를 복사
  • $().wrap() : 선택된 엘리먼트를 특정 엘리먼트로 둘러쌈(부모 엘리먼트로 삽임
  • $().unwrap() : 선택된 엘리먼트의 부모 엘리먼트를 제거
// DOM 엘리먼트 메소드 적용
$('#div2').append($('<p id="p5">p5항복</p>'));
$('#span3').before($('<span>블록5</span>')).empty();
$('#p1').remove();
$('#p2').replaceWith($('#p4'));

기타 메소드

프로그래밍 관련 메소드

  • 사용자와의 상호 작용을 위한 DOM 트리와의 정보 교환에 관련된 제이쿼리 메소드들
  • $().html() : 선택된 엘리먼트의 내용을 HTML5 형식의 문자열로 반환( 마크업 포함 )
  • $().html(HTML5문자열) : 선택된 엘리먼트 밑에 HTML5 문자열을 엘리먼트로 변환하여 추가
  • $().text() : 선택된 엘리먼트의 텍스트 내용을 텍스트 형식의 문자열로 변환
  • $().text(문자열) : 선택된 엘리먼트 밑에 텍스트 내용으로 문자열을 추가
  • $().size() : 선택된 엘리먼트의 개수를 반환 $().length와 기능 동일
  • $().get(n) : 선택된 엘리먼트 중 n번째 엘리먼트 개체를 반환
  • $().index() : 선택된 엘리먼트 중 첫 번째 형제 엘리먼트와의 상대적인 첨자를 반환
  • $().each(콜백함수) : 선택된 엘리먼트들을 차례로 순환하면서 콜백 함수를 반복 호출
// 프로그래밍 메소드 적용
$('p:eq(5)').text('33333');
$('span:eq(3)').text($('p:last').text());
$('p:first').html($('div:eq(1)').html());

DOM 트리 속성 조작 메소드

  • 엘리먼트의 시작 태그 안에 포함된 속성 이름과 속성값은 DOM 트리에서 엘리먼트 노드의 하위 노드가 된다.
  • 속성에 대해서도 메소드를 통해 DOM 트리에 추가하거나 제거 가능
  • $().attr(속성명) : 선택된 엘리먼트들 중 첫 번째 엘리먼트의 특정 속성값을 반환
  • $().attr(속성명, 속성값) : 선택된 모든 엘리먼트에 속성값을 설정
  • $().attr({속성집합}) : 여러 속성값을 한꺼번에 설정(맵형식)
  • $().attr(속셩명, 함수()) : 함수 반환 값을 속성값으로 설정
  • $().removeAttr(속성명) : 선택된 모든 엘리먼트의 특정 속성을 제거
  • $().val() : 선택된 첫 번째 엘리먼트(폼 관련이어야 함)의 value 속성값을 반환, 웹 폼 엘리먼트에서 가장 많이 사용되는 메소드
  • $().val(속성값) : 선택된 모든 엘리먼트(폼 관련이어야 함)의 value 속성값을 설정
// DOM 속성 메소드 적용
alert('학번 disabled 속성값 : ' + $('#s_id').attr('disabled'));
$('#s_name').attr('disabled', 'flase');
$('input[name="s_tel"]').removeAttr('placeholder');
$('input:radio').attr('checked', false);
var age = $('input[type="number"]').val();
age = eval(age) + 15;
$('input[type="number"]').val(age);

제이쿼리 이벤트

  • 웹 페이지와 사용자 사이의 동적인 상호작용 지원

제이쿼리 이벤드

  • 이벤트 : 이벤트 핸들러에 해당하는 제이쿼리 함수를 호출
  • 이벤트 핸들러(event handler)
    • 리스너(listener)
    • 기다리던 이벤트가 발생하면 이를 감지해 적절한 처리를 하도록 함
    • 주로 이벤트와 제이쿼리 이벤트를 연결시킴

이벤트 핸들러 연결 및 해제

  • 이벤트 메소드 직접 연결
    • 표준 이벤트 유형을 단축형 메소드로 지겆ㅂ 사용
    • 재이쿼리 선택자에 의해 지정된 객체에 이벤트 유형으로 명세한 사건이 발생하면 함수가 호출되어 실행됨
      $('선택자').이벤트유형('함수명');
    
  • on() 메소드 간접 연결
    • 이전의 bind() 메소드를 대체
    • 이벤트 유형별로 이벤트 핸들러 함수를 연결
    • 하나 또는 여러 개의 이벤트가 발생할 때 함수가 호출되어 실행되도록 함
      $('선택자').on('이벤트유형'|'이벤트유형리스트', 함수명);
    
  • off() 메소드 연결 해제
    • 이전의 unbind() 메소드를 대체
    • 이벤트 연결 해제
      $('선택자').off('이벤트유형'); // 선택자 객체에 연결된 특정 이벤트를 해제
      $('선택자').off(); // 선택자 객체에 연결된 모든 이벤트를 해제
    

이벤트 메소드

이벤트 연결/해제 메소드

  • $().on() : 특정 엘리먼트(제이쿼리 객체)에 이벤트 핸들러를 연결
  • $().off() : on()으로 연결된 이벤트 핸들러를 해제
  • $().one() : 특정 엘리먼트(제이쿼리 객체)에 이벤트 핸들러를 단 한번 연결 후 해제 (일회용)
  • $().trigger() : 특정 엘리먼트(제이쿼리 객체)에 직접 이벤트를 발생시켜 이벤트 핸들러 함수를 실행

이벤트 단축형 메소드

  • 표준 이벤트 유형은 on() 메소드 없이도 단축형 메소드로 사용 가능
  • 이벤트 유형 이름을 메소드 이름으로 사용
  • 마우스 이벤트
    • $().click() : 엘리먼트 표시 영역을 마우스로 클릭할 때 동작
    • $().hover() : 엘리먼트 표시 영역 안으로 마우스 포인터가 들어올 때 (또는 나갈 때) 동작
    • $().focus() : 폼 엘리먼트 표시 영역이 포커스를 얻을 때 동작
    • $().blur() : 폼 엘리먼트 표시 영역이 포커스를 얻을 때 동작
    • $().change() : 폼 엘리먼트 표시 영역의 값이 변경될 때 동작
    • $().select() : 폼 엘리먼트 표시 영역의 텍스트 일부를 선택할 때 동작
  • 키보드
    • $().keydown() : 키보드를 눌렀을 때 동작
  • 문서
    • $().ready() : 브라우저에 문서가 읽혀질 때 동작
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>jQuery</title>
    <style>
        body * { margin: 5px; padding: 5px; border: solid thin gray; }
        div { width: 20px; height: 20px; border: solid thick green; }
        input { background-color: silver; }
    </style>
</head>
<body>
  focus/blur 이벤트 <br/>
  <input type="text" name="text1" value="입력하세요"/><br/>
  click 이벤트<div></div>
  toggle 이벤트 <br/><img src="static/bgimg1.jpg">
  <!--제이쿼리 라이브러리 선언 -->
  <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
  <!-- 제이쿼리 선택자(메소드) 실습 변경 부분 -->
  <script>
      $(document).ready( function() {
          // focus/blur 이벤트
          $('input').focus(function () {
              $(this).css('background-color', 'orange');
          });
          $('input').blur(function() {
              $(this).css('background-color', 'white');
          });
          // click 이벤트
          $('div').bind('click', function() {
              $(this).width($(this).width() * 1.5);
              $(this).height($(this).height() * 1.5);
          });
          // toggle 이벤트
          $('div').click(function() {
              $('img').toggle(4000);
          });
      })
  </script>
</body>
</html>
  • 이벤트 핸들러 함수(콜백 함수) 에서의 this
    해당 이벤트가 발생한 DOM 엘리먼트

제이쿼리 효과

제이쿼리 효과

  • 간단한 애니메이션 효과를 메소드로 제공
  • show([ms][,function()]) : 선택된 엘리먼트를 화면에서 보이게 함
  • hide([ms][,function()]) : 선택된 엘리먼트를 화면에서 사라지게 함
  • toggle([ms][,function()]) : 선택된 엘리먼트가 화면에 보였다가 사라지는 상태를 반복. show()hide()를 번갈아 수행.
  • slideUp([ms][,function()]) : 선택된 엘리먼트의 높이를 점차 위로 감소시켜 화면에서 사라지게 함. 위로 접는 효과
  • slideDown([ms][,function()]) : 선택된 엘리먼트의 높이를 점차 아래로 증가시켜 화면에서 보이게 함. 아래로 보이는 효과
  • slideToggle([ms][,function()]) : 선택된 엘리먼트의 높이를 변경하여 화면에서 사라지거나 보이게 함
  • fadeIn([ms][,function()]) : 선택된 엘리먼트의 불투명도를 점차 높여서 보이게 함
  • fadeOut([ms][,function()]) : 선택된 엘리먼트의 불투명도를 점차 낮춰서 사라지게 함
  • fadeToggle([ms][,function()]) : 선택된 엘리먼트의 불투명도를 변경하여 사라지거나 보이게 함

ms : 효과를 수행하는 지속 시간 (밀리초, 1/1000초 단위) slow(600ms), normal(400ms), fast(200ms) 문자열 중 하나를 지정해도 된다. 두 번째 function() 인자는 콜백 함수로 애니메이션 효과가 종료된 후 자동으로 실행될 사용자 정의 함수

// 빈 입력인자
show();
// 수치 입력
show(600);
// 문자열 입력
show('slow');
// 콜백함수 입력인자
show(200, function() { ... });

사용자 정의 효과 생성

  • animate([properties][,ms][,function()])
    • 기본적인 효과 외 맞춤형 효과를 사용자가 직접 정의하여 사용할 수 있음
    • 수치값을 사용하는 CSS3 스타일 속성값은 모두 가능
    • properties : 다양한 움직임 효과를 줄 수 있는 CSS 속성과 속성값을 맵 방식으로 명세
      $('p').animate({font-size: "3em"}, 2000); // 문단 글자 크기를 3배로 확대
      $('div').animate({height: "25%"}, "slow"); // div 영역의 높이를 1/4로 축소
      $('div:has(img)').animate({width: "0px"}, 1000); // 이미지가 포함된 div 영역을 왼쪽으로 접는 효과
    
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>jQuery</title>
    <style>
        div { margin: 5px; padding: 5px; border: solid thin gray; }
    </style>
</head>
<body>
  show/hide/toggle 효과
  <button id="hide">hide</button>
  <button id="show">show</button>
  <button id="toggle">toggle</button>
  <div><input type="text" name="text1" value="안녕하세요!!!"/></div>
  <!--제이쿼리 라이브러리 선언 -->
  <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
  <!-- 제이쿼리 선택자(메소드) 실습 변경 부분 -->
  <script>
      $(document).ready(function() {
          // show/hide/toggle 효과
          $('#hide').click(function () {
              $('input[name="text1"]').hide(600);
          })
          $('#show').click(function () {
              $('input[name="text1"]').show(1000);
          })
          $('#toggle').click(function () {
              $('input[name="text1"]').toggle(2000);
          })
      })
  </script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>jQuery</title>
    <style>
        div { margin: 5px; padding: 5px; border: solid thin gray; }
        #img2 { display: none; }
    </style>
</head>
<body>
  fadeOut/fadeIn/fadeToggle 효과
  <button id="fadeOut">fadeOut</button>
  <button id="fadeIn">fadeIn</button>
  <button id="fadeToggle">fadeToggle</button>
  <div>
      <img id="img1" src="static/bgimg1.jpg">
      <img id="img2" src="static/bgimg2.jpg">
  </div>
  <!--제이쿼리 라이브러리 선언 -->
  <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
  <!-- 제이쿼리 선택자(메소드) 실습 변경 부분 -->
  <script>
      $(document).ready(function () {
          // fadeOut/fadeIn/fadeToggle 효과
          $('#fadeOut').click(function () {
              $('#img1').fadeOut(2000, function () {
                  $('#img2').fadeIn(100);
              });
          });
          $('#fadeIn').click(function () {
              $('#img2').fadeOut(2000, function () {
                  $('#img1').fadeIn(100);
              });
          });
          $('#fadeToggle').click(function () {
              $('#img1').fadeToggle(1000);
          });
      })
  </script>
</body>
</html>

참고