React?

Jinmin Kim·2021년 2월 4일
0

React 기본

JSX

JSX는 JavaScript XML의 줄임말.
Javascript에 XML을 추가한 확장형 문법이다.

JSX의 render() 함수값은 HTML과 거의 비슷하지만
element의 끝에 />가 있다는것을 확인할수있다.

//ex)
<Counter count={this.state.count} />

리엑트 엔진은 JSX의 XML 구조를 분석하여 자바스크립트 함수 코드로 변환시켜준다.
개발자는 JSX작성, 리엑트 엔진은 JSX를 자바스크립트로 해석하는 역활만 하면된다.

vscode에서 rcc를 입력한다음 Tab을 누르게 되면 클래스형 컴포넌트의 뼈대를 손쉽게 만들수있다

JSX에서 import문의 파일이름 확장자가 생략된 이유

import Counter from './03/Counter';
import NewCounter from './03/NewCounter';

js 또는 jsx파일의 확장자를 생략해도 해당파일을 자동으로 찾을수있게 설정되어있다.
이렇게 되는 이유는 웹팩코드 검색 확장자기능 덕분이다.

  1. 확장자 옵션에서 정의된 확장자 목록을 보고 해당 확장자 이름을 포함한 파일이 있는지 확인하여서 임포트하게된다. ex) import MyFile은 MyFile.js > MyFile.jsx 순서로 파일을 확인하여 임포트한다.
  2. 경로에 파일이 없다면 같은 이름의 폴더가 있는지 확인하고, 폴더가 있다면 index파일을 검색하게된다. 또한 이때의 순서도 index.js > index.jsx 순서로 나타난다.

컴포넌트의 구성요소

  1. 프로퍼티 : 상위 컴포넌트에서 하위 컴포넌트로 전달하는 읽기 데이터
  2. state : 컴포넌트의 상태를 저장하고 변경할수있는 데이터
  3. 컨택스트 : 부모 컴포넌트에서 모든 자식 컴포넌트에 전달하는 데이터

프로퍼티(prop)

상위 컴포넌트에서 하위 컴포넌트로 데이터 전달

//App
render(){
	return(
    	<div>
		<MyComponent name="message"/>
        //MyComponent로 name의 값을 넘겨줌
	</div>
    )
}

//MyComponent
render(){
	const name = this.props.name;
    return <span>{name}</span>
}

상위에서 하위로 데이터가 전달된다고해서 단방향 데이터가 흐른다라고 표현한다.

프로퍼티 사용

프로퍼티에서는 javascript의 자료형을 모두 사용할수있는데, 프로퍼티의 자료형을
미리 선언해주는것이 좋다. 이 자료형을 선언하는 방법은 리엑트에서 사용하는
prop-types를 이용하면된다.

import propTypes from 'prop-types';
....

Input.propTypes = {
  type: propTypes.oneOf(['text', 'numver', 'price']),
  name: propTypes.string.isRequired,
  value: propTypes.oneOfType([propTypes.number, propTypes.string]),
  errorMessage: propTypes.string,
  label: propTypes.string,
  onChange: propTypes.func,
  onFocus: propTypes.func,
  autoFocus: propTypes.bool,
};

문자열을 전달할때는 ""를 사용하지만, 숫자형이나 불리언값을 전달할때는 ""을 사용할수없다.
""를 사용하면 문자열로 전달되기때문에 {}를 사용하여야한다.

<ChildComponent boolValue={true} numValue={1} arrayValue={[1, 2, 3]}
 objValue={{name: '제목', age: 30 }} nodeValue={<h1>노드</h1>}
/>
boolean property

불리언은 특별한 방법으로 전달할수가잇는데,
true는 프로퍼티의 이름만 선언해주어도 전달이가능하고
false는 이름을 생략한다면 프로퍼티에 false를 전달하게된다.

//App.jsx
render(){
	return(
    	<div>
        	<div><b>지루하다 : </b><BooleanComponent bored /><div>
            <div><b>재밌다 : </b><BooleanComponent/><div>
        </div>
    )
}
//BooleanComponent
 render() {
            const message = this.props.bored ? '놀러 갈까' : '아니 공부할까';
            return (
                <div className="message-container">
                    {message}
                </div>
            );
    }

위의 경우 bored를 입력해준 지루하다는 놀러갈까가 나타나고
bored를 생략한 재밌다는 아니 공부할까가 나타나면서 잘동작하게된다.

Object property
render(){
	const {objValue} = this.props;
    return(
    	<div>
        	<div>객체값 : {String(Object.entries(objValue))}</div>
        </div>
    )
}

ChildComponent2.propTypes = {
	objValue : PropTypes.shape({
    	name: PropTypes.string,
        age: PropTypes.number
    })
}
필수 property
import React from 'react'
import PropTypes from 'prop-types';

class ChildComponent2 extends React.Component {
    render(){
        const {
            objValue,
            requiredStringValue
        } = this.props;

        return (
            <div>
                <div>객체값 : {String(Object.entries(objValue))}</div>
                <div>필수값 : {requiredStringValue}</div>
                //필수값 나타내기
            </div>
        );
    }
}

ChildComponent2.propTypes = {
    objValue : PropTypes.shape({
        name: PropTypes.string,
        age: PropTypes.number
    }),
    //필수 프로퍼티
    requiredStringValue : PropTypes.string.isRequired,
}

export default ChildComponent2

만약 필수프로퍼티를 지정해두었지만 필수프로퍼티에 값을 전달받지못한다면
경고메세지가 나타나게된다.

//ex)
<ChildComponent2 objValue={{age : '20살'}}/>

위의 코드는 두개의 경고가 나타나게되는데 age에는 number type이 아니라는것과
필수프로퍼티를 지정했음에도 불구하고 프로퍼티가 전달되지않은것을 나타낸다.

프로퍼티 기본값
DefaultPropsComponent.propTypes = {
  boolValue: PropTypes.bool,
  boolValueWithoutDefault: PropTypes.bool,
};

//기본값 선언해주기
DefaultPropsComponent.defaultProps = {
  boolValue: false,
};
자식 프로퍼티사용
//App
<ChildProperty>
	<div><span>자식노드</span></div>
</ChildProperty>

//ChildProperty
render() {
        return (
            <div>
                {this.props.children}
            </div>
        );
    }

{this.props.children}이 방법으로 <div><span>자식노드</span></div>
나타낼수가 있게된다.

컴포넌트 상태관리

state

값을 저장하거나 변경할수있는 객체이며, 버튼을 클릭하거나 값을 입력하는등의
이벤트와 함께 사용된다.

import React from 'react';

class StateExample extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: true,
      formData: 'no data',
    };
    
    this.handleData = this.handleData.bind(this);
    //4초후에 handleData 함수 호출
    setTimeout(this.handleData, 4000);
  }

  handleData() {
    const data = 'new data';
    const { formData } = this.state; 
    //this.state로 state에 접근하기
    //setState -> 컴포넌트의 내장함수 state의 값 변경
    this.setState({
      loading: false,
      formData: data + formData,
    });
	
    console.log('loading값', this.state.loading);
  }
  render() {
    return (
      <div>
      //state 데이터는 this.state로 접근이 가능하다!!
        <span>로딩중: {String(this.state.loading)}</span>
        <span>결과: {this.state.formData}</span>
      </div>
    );
  }
}

export default StateExample;

props를 사용하는것보단 state를 사용하는것이 훨씬 효과적인데,
state를 사용할때는 주의할점이있다.

  1. 생성자에서 반드시 초기화해야한다
    (초기화 하지 않으면 접근이 불가능하다)
  2. state를 변경할때는 setState()함수를 사용하여서 변경하여야한다
  3. setState()는 비동기이며 연결된 함수들의 실행이 완료된 시점에 동기화 과정을 거친다.

setState로 변경해야하는 이유는 render() 함수로 화면을 그려줄때 리액트 엔진이 정하기때문,
직접 State를 변경해도 render 함수는 새로 호출하지 않는다.
주의 할점

 constructor(props) {
    super(props);
    this.state = {
      //초기 카운트 값을 프로퍼티에서 전달된 값으로 설정한다
      count: props.count,
      foo: {
        bar: 0,
        foobar: 1
      }
    };
    this.increaseCount = this.increaseCount.bind(this);
  }
  //틀린 방법
  **아래에서 foobar의 값만 바꿔주려고 한다면 foo에 bar:0값은 
  없어지고 foo라는 객체에 foobar라는 값만 들어가게된다.
  increaseCount() {
    this.setState( {
    count: 0,
  	foo: {
    foobar: 2
  	};
  })
  //옳은 방법
 increaseCount() {
    this.setState( {
    count: 0,
  	foo: {
    ...this.state.foo,
    foobar: 2
  	};
  })
  

또한 setState를 사용할때 파라미터로 함수를 전달하면 이전 state값을 쉽게 읽을수있는데,

    handleData(data) {
        this.setState((prevState) => {
            return {
                loading: false,
                formData: data + prevState.formData
            }
        })
    }

이러한 형식을 사용하면 이전값을 쉽게 읽어올수있다.

forceUpdate

setState를 사용하지않고 state를 사용하고싶다면 forceUpdate() 함수를 사용하면
클래스 인스턴스 변수와 화면을 강제로 출력해주게된다.

import React, { Component } from 'react';

class ForceUpdateExample extends Component {
  constructor(props) {
    super(props);
    //state
    this.loading = true;
    this.formData = 'no data';

    this.handleData = this.handleData.bind(this);
    setTimeout(this.handleData, 4000);
  }

  handleData() {
    const data = 'new data';

    this.loading = false;
    this.formData = data + this.formData;
    this.forceUpdate();
  }
  render() {
    return (
      <div>
        <span>로딩중: {String(this.loading)}</span>
        <span>결과: {this.formData}</span>
      </div>
    );
  }
}

export default ForceUpdateExample;

setState를 사용할때와 비교하여보면 state라는것이 명시는 되어있지않지만
this.loading = true; 처럼 가상의 state를 정의하여서 사용하고있다.
하지만 이방법은 리액트 성능의 제약이 있으므로 매번 새롭게 화면을 출력해야하는 경우가 아니라면 가급적 사용하지 않기를 바란다.라고 저자는 이야기하더라

컴포넌트 생명주기

생명주기 함수는 리액트 엔진에서 자동으로 호출되게되므로 개발자가 마음대로 호출할수없다.
4개의 생성과정, 5개의 갱신과정, 1개의 소멸과정으로 진행되게된다.

생성과정

constructor(props)
constructor(props){
	super(props);
}

처음에 단 한번만 호출되며, constructor를 정의할때에는 항상 super()를 초기화를 위해서가장위에 호출하여야한다.

render

데이터가 변경되어서 새화면을 그려야할때 나타나는 함수
render함수가 반환하는 JSX를 화면에 그려주게된다.

staic getDerivedStateFromProps(props,state)

여기에선s this.props, this.state를 사용할수없기때문에 전달된 인자로
props, state를 사용하여야한다. 상위 컴포넌트에서 전달받은 프로퍼티로 state값을 연동할때 주로 사용하게되며, 변환값으로 state를 변환한다.

componentDidMount

render 함수가 JSX를 화면에 그리고 난후에 호출되는 함수이며,
컴포넌트가 화면에 표시된 이후에 하는작업들은 이곳에서 하면된다.


갱신과정

shouldComponentUpdate(nextProps, nextState)

프로퍼티를 변경 또한 setState를 호출하여 state를 변경하면 화면을 새로 출력해야할지
판단하는 함수이다. 이함수는 화면을 새로 출력할지 말지를 판단하고, 데이터 변화를 비교하는 작업을 포함하므로 리액트 성능에 영향을 많이 주게된다. 화면변경을 위해 검증 작업을 하는경우에
이 함수가 사용되게된다.

getSnapshotBeforeUpdate(prevProps, prevState)

컴포넌트의 변경된 내용이 가상화면에 완성된 이후 호출, 컴포넌트가 실제로 출력되기 전에 호출되므로 엘리먼트의 크기 또는 스크롤 위치등의 DOM 정보에 접근할때 사용하게된다.

componentDidUpdate(prevProps, prevState, snapshot)

컴포넌트가 실제 화면에 표시되고 난후에 호출되는 함수, getSnapshotBeforeUpdate에서 받은 snapshot과 prevProps, prevState를 이용하여서 스크롤 위치 변경과 같은 DOM 정보를 변경할때 사용한다.


소멸과정

componentWillUnmount

컴포넌트의 소멸되기 직전의 호출함수이며, setInterval 같은 함수가 적용되고 있다면
clearInterval을 하여서 해제작업을 하지않으면 메모리 누수 현상이 발생할수있다.

componentDidMount(){
    console.log('componentDidMount 호출');
    this.setState({update: true});
}

위와 같이 setState 함수를 사용하여서 update라는 state값을 true로 만들었을때에
생명주기가 나타나는 결과는
constructor -> getDerivedStateFromProps -> render -> componentDidMount -> getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate의 순서대로 호출되게된다.

만약 shouldComponentUpdate 함수의 값이 false이면 어떻게 될까?
constructor -> getDerivedStateFromProps -> render -> componentDidMount -> getDerivedStateFromProps -> shouldComponentUpdate 까지만 되고
shouldComponentUpdate 함수에서 stop되게된다.

shouldComponentUpdate 함수의 결과값과 상관없이 동기화 하고싶다면
forceUpdate함수를 사용하면된다.
constructor -> getDerivedStateFromProps -> render -> componentDidMount -> getDerivedStateFromProps -> render -> getSnapshotBeforeUpdate -> componentDidUpdate

getDerivedStateFromProps를 이용하면 갱신된 프로퍼티 값을 동기화 시킬수있다.
기본적으로 state값은 갱신과정에서 변경이 되진 않는다.

클래스형 Component

Component의 종류는
1. Component 2. PureComponent 두가지가있다.
PureComponent는 Component를 상속받는 클래스이며,
shouldComponentUpdate 함수를 얕은비교를 하게만들어서 재정의한 클래스이다.
PureComponent는 얕은비교를 통해서 데이터가 변경된경우 render함수 호출
Component는 그런 비교없이 데이터가 변경된경우 render함수 호출

함수형 Component

state가 없는 함수형 컴포넌트 즉 SFC라고 이야기한다.
함수와 동일한 구조를 가지고있으며, 입력받은 프로퍼티와 컨택스트를 이용하여
화면을 출력하는것. 클래스 선언이 없으며 state와 생명주기 함수를 사용할수가없다.
단순한 구조의 UI 컴포넌트를 계획할때 많이 사용되게된다.

import React from 'react';
import PropTypes from 'prop-types';

function SFC(props, context) {
  const { somePropValue } = props;
  const { someContextValue } = context;
  return <h1>Hello, {somePropValue}</h1>;
}

SFC.propTypes = { somePropValue: PropTypes.any };
SFC.defaultProps = { somePropValue: 'default value' };

export default SFC;

클래스 컴포넌트와 함수형 컴포넌트의 차이점을 간단히 보면

//클래스 컴포넌트
class TodaysPlanApp extends React.Component{
render(){
	const { onButtonClick, hasPlan } = this.props
    return()
	}
}
//함수형 컴포넌트
Function TodaysPlanApp(props){
render(){
	const { onButtonClick, hasPlan } = props
    return()
	}
}

아래와 같이 이러한 근소한 차이밖에없다.

배열 컴포넌트

배열안에는 여러가지의 자료형을 저장할수있는데,
여기에는 JSX, XML등을 포함해서 저장할수가있다.

const todoList = {
    {taskName: '빨래하기', finished: false},
    {taskName: '공부하기', finished: true},
};
//배열 데이터를 JSX로 만들어주기
const todos = todoList.map(todo => <div>{todo.taskName}</div>)

//배열 데이터를 프로퍼티로 넘겨주기
const todos = todoList.map(todo => <TodoTask taskName="공부하기">)
import React from 'react';

class ListExample extends React.PureComponent {
  render() {
    const priceList = [1000, 2000, 3000, 4000];
    const prices = priceList.map((price) => <div>가격 : {price}원</div> );
    return (
      <div>
        <label>가격 목록</label>
        {prices}
      </div>
    );
  }
}

export default ListExample;



  render() {
    const priceList = [1000, 2000, 3000, 4000];
    return (
      <div>
        <label>가격 목록</label>
        {priceList.map((price) => <div>가격 : {price}원</div>);}
      </div>
    );
  }

배열 컴포넌트의 경우 배열 요소의 개수만큼 반복하므로 성능에 영향을 많이주게된다.
따라서 배열 컴포넌트에는 키값을 key로 꼭 정의해주어야한다.
key값은 고유한 배열 항목을 사용하면 이전키값이나 다른 키값과 충돌하는 문제를
방지할수있다.

import React from 'react';

class TodoList extends React.PureComponent {
  render() {
    const todoList = [
      { taskName: '빨래하기', finished: false },
      { taskName: '공부하기', finished: true },
    ];
    return (
      <div>
        {todoList.map((todo) => (
          <div key={todo.taskName}>{todo.taskName}</div>
        ))}
      </div>
    );
  }
}

export default TodoList;
render 함수에서 여러개 JSX 반환

render함수는 트리구조의 노드를 반환해야하는데 트리노드 하나만 반환이 가능하다
만약 여러개의 노드를 반환하고 싶을경우에는 최상위 노드를 하나 추가해주어야한다.

render(){
	return(
    <div>
    	<input type="radio" name="option1" value="1" label="1개" />
    	<input type="radio" name="option1" value="2" label="2개" />
    	<input type="radio" name="option1" value="3" label="3개" />
    </div>
    )
}
-> React.Fragment라는 표현을 사용하면 의미없는 노드를 추가하는것이 가능해진다.
render(){
	return(
    <React.Fragment>
    	<input type="radio" name="option1" value="1" label="1개" />
    	<input type="radio" name="option1" value="2" label="2개" />
    	<input type="radio" name="option1" value="3" label="3개" />
    </React.Fragment>
    )
}

하위에서 프로퍼티 변경하기

단방향이므로 하위에서 프로퍼티는 변경이 불가능하다고햇지만,
프로퍼티 원본을 수정할수있는 함수를 하위 컴포넌트에 제공하면 가능해진다.

//App.jsx
increaseCount(){
	this.setState(({ count }) => ({ count : count + 1 }));
}

render() {
    return (
          <Counter2 count={this.state.count} onAdd={this.increaseCount.bind(this)}/>
          //이러한 방법으론 render를 할때마다 bind(this)를 해주어야한다.
         )
  }
  
//Counter2.jsx
render() {
    return (
          <div>
            현재 카운트 : {this.props.count}
            <button onClick = {() => this.props.onAdd()}>
            카운트 증가
            </button>
          </div>
         )
  }

bind()함수를 constructor() 함수에 모아두면 매번 render 함수를 호출할때마다
새로 작성하지 않아도된다.

constructor(props){
    super(props);
    this.increateCount = this.increateCount.bind(this)
}

DOM 객체 함수 사용 ref

ref 프로퍼티는 document.getElementById()가 반환하는 객체를 반환한다.

import React from 'react';

export default class ScrollSpy extends React.PureComponent {
  constructor(props) {
    super(props);
    this.setRef = this.setRef.bind(this);
    this.checkPosition = this.checkPosition.bind(this);
  }
  setRef(ref) {
    this.ref = ref;
  }

  checkPosition() {
    if (!this.ref) {
      return;
    }

    if (this.ref.getBoundingClientRect().top < window.innerHeight) {
      console.log('enter');
    } else {
      console.log('exit');
    }
  }

  componentDidMount() {
    window.addEventListener('scroll', this.checkPosition);
    this.checkPosition();
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.checkPosition);
  }

  render() {
    return <div ref={this.setRef} />;
  }
}

위의 코드는 스크롤을 내릴때 자동으로 다음 페이지 목록을 추가할때 사용이 가능하다

컴포넌트에서 DOM 이벤트

HTML 엘리먼트의 이벤트의 'on + 이벤트' 형태의 프로퍼티로 제공

onClick -  마우스 클릭
onSubmit - 폼의 데이터 전송
onMouseMove - 마우스 커서 움직일때
onMouseOver - 마우스 커서 돌아다닐때
onMouseOut - 마우스 커서가 영역을 나갈때
onKeyDown - 키보드 버튼 눌려졌을때
onKeyPress - 키보드 버튼 입력 끝났을때
return(
	<div>
    	<button onClick={this.increateCount}
        onMouseOut={this.resetCount}
    	>
         카운트 증가
        </button>
    </div>
)

do it react 책을 정리한 내용입니다!!

profile
Let's do it developer

0개의 댓글