프론트엔드 개발/React

[React] Component

Jongung 2022. 7. 4. 16:11

Class Component 작성법

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <div id="root"></div>
    <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
        console.log(React)
        console.log(ReactDOM)

        //ClassComponent 정의하기
        class ClassComponent extends React.Component{ //React안에 Component라는 클래스를 상속 받음
            render(){ //render 메서드는 정의 되어야하고 return 값이 꼭 있어야 함
                return <div>Hello</div>;
            }
        }
        //ClassComponent 사용하기
        ReactDOM.render(
            <ClassComponent />,
            document.querySelector('#root')
        )
    </script>
</body>

</html>
  • 위와 같이 작성하면 Class Component를 사용할 수 있다. 

Class Component 정의하기

class ClassComponent extends React.Component{ //React안에 Component라는 클래스를 상속 받음
    render(){ //render 메서드는 정의 되어야하고 return 값이 꼭 있어야 함
        return <div>Hello</div>;
    }
}
  • ClassComponent라는 이름의 클래스 컴포넌트를 React 라이브러리 안에 있는 Component 클래스로부터 상속받았다.
  • Component 클래스 안에는 render() 메서드 값이 필요하며 render 값에는 꼭 return 값이 있어야 한다.

 

Class Component 사용하기

ReactDOM.render(
    <ClassComponent />,
    document.querySelector('#root')
)
  • ReactDom의 render 메서드를 이용하여 출력한다.
  • 첫 번째 인자는 만들어 두었던 Component를 가져오고, 두 번째 인자는 어떤 html에 넣을지 css 쿼리 실렉터로 넣어준 모습이다.

Function Component 작성법

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <div id="root"></div>
    <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
        console.log(React)
        console.log(ReactDOM)

        //Function Component 정의 1
        function FunctionComponent(){
            return <div>Hellos</div>;
        }

        //Function Component 정의 2
        const FunctionComponent = ()=><div>Hello</div>;

        //Function Component 사용
        ReactDOM.render(
        <FunctionComponent />,
            document.querySelector('#root')
        )

    </script>
</body>

</html>
  • 위와 같이 작성하면 Class Component를 사용할 수 있다. 

function Component 정의하기

//Function Component 정의 1
function FunctionComponent(){
    return <div>Hellos</div>;
}

//Function Component 정의 2
const FunctionComponent = ()=><div>Hello</div>;
  • 리액트 라이브러리에서 상속받는 것이 아닌 순수한 함수이다. 
  • function component 또한 return 값이 필수로 존재해야 하며 JSX로 들어가야 한다.
  • 인자는 있어도 되고 없어도 된다.
  • JS에서 function은 2가지 방법으로 만들 수 있기 때문에 두 가지 다 사용 가능하다.

 

Function Component 사용하기

ReactDOM.render(
<FunctionComponent />,
    document.querySelector('#root')
)
  • ReactDom의 render 메서드를 이용하여 출력한다.
  • 첫 번째 인자는 만들어 두었던 Component를 가져오고, 두 번째 인자는 어떤 html에 넣을지 css 쿼리 실렉터로 넣어준 모습이다.

 


React.createElement로 컴포넌트 만들기

React.createElement(
    type,  //태그 이름 문자열 | 기존에 만든 리액트 컴포넌트  | React.Fragment
    [props],  //리액트 컴포넌트에 넣어주는 데이터 객체
   [...children] //자식으로 넣어주는 요소들
)
  • createElement는 총 3가지 방법으로 제작 가능하다.

 

1. 태그 이름 문자열 type

// 1. 태그 이름 문자열  type
ReactDOM.render(
    React.createElement('h1', null, `type 이 "태그 이름 문자열" 입니다.`), //component 뿐만 아니라 create Element도 허용
    document.querySelector('#root')
)
  • <h1>`createElement 메서드 안에 3번째 인자의 내용이 들어감`</h1> 이 태그로 그려진다.

 

2. 리액트 컴포넌트 type

//2. 리액트 컴포넌트 type
const Component = () =>React.createElement('h1', null, `type 이 "React 컴포넌트" 입니다.`)

ReactDOM.render(
    React.createElement(Component, null, null), 
    document.querySelector('#root')
)
  • <Component></Component> 컴포넌트로 그려진다.

 

3. React.Fragment type

//3. React.Fragment
ReactDOM.render(
    React.createElement(
        React.Fragment,
        null,
        `type 이 "React Fragment" 입니다.`
    ),
    document.querySelector('#root')
)

  • 다음 사진과 같이 React.Fragment를 사용하면 다른 태그 없이 <div id=root> 안에 바로 나타나게 된다.

JSX

  • 우리가 작성한 코드를 순수하게 실행 가능 한 자바 스크립트로 만들어주는 것은 babel이다.
  • 더 나아가 설명하면 es6를 지원하지 않는 브라우저에게 es 6 문법들을 사용할 수 있게 자동으로 변환시켜주는 것을 의미한다. (컴파일 또는 트랜스 파일)
  • babeljs.io

  • babel은 우리가 React.createElement를 직접 작성하지 않아도 다음과 같이 트랜스 파일 해준다. 
<script type="text/babel">
    console.log(React)
    console.log(ReactDOM)

    //복잡한 리액트 엘리먼트들의 모임

    ReactDOM.render(
        <div a ="a">
           <h1>주제</h1>
           <ul>
                <li>React</li>
                <li>Vue</li>
           </ul> 
        </div>,
        document.querySelector('#root')
    )
</script>
  • React.createElement VS. JSX일 때 가독성으로 JSX가 훨씬 좋다
  • babel과 같은 컴파일 과정에서 문법 오류를 인지하기 쉽다. 일반 html보다 babel을 통한 JSX는 문법 오류를 잘 잡는다.

JSX 문법 알아보기

  • 최상위 요소가 하나여야 한다.
  • 최상위 요소를 리턴하는 경우, ()로 감싸야한다.
  • 자식들을 바로 렌더링 하고 싶으면, <> 자식들 </>를 사용한다. 이런 걸 Fragment라고 부른다.
  • 자바스크립트 표현식을 중간에 사용하려면, { JS }를 이용하여야 한다.
  • if문은 사용할 수 없다. 삼항 연산자를 이용하여야 한다.
  • style을 이용해 인라인 스타일링이 가능하다.
  • class 대신 className을 사용하여야 제대로 된 class 적용이 가능하다.
  • 자식 요소가 있으면, 꼭 닫아야 하고, 자식 요소가 없으면 열면서 닫아야 한다.
    • <p> ~~~ </p>
    • <br />

Props와 State

  • Props는 컴포넌트 외부에서 컴포넌트에게 주는 데이터를 뜻한다. 
  • State는 컴포넌트 내부에서 변경할 수 있는 데이터를 뜻한다.
  • 각각 변경이 발생하면 재랜더 될 수 있다. (랜더 함수 실행)

 

  • 각각 class와 function으로 props를 사용한 component들이다.
// {message: '안녕하세요''} 메세지의 값이 바뀌면 재랜더가 일어나게 된다.
function Component(props){ //function 컴포넌트는 인자로 props가 들어오게 된다.
    return <div><h1>{props.message} 함수로 만든 컴포넌트 입니다.</h1></div>
}

ReactDOM.render(<Component message ="안녕하세요"/>, document.querySelector("#root"))
// {message: '안녕하세요''} 메세지의 값이 바뀌면 재랜더가 일어나게 된다.

class Component extends React.Component{
    render(){
        return(
            <div>
                <h1>{this.props.message} 클래스로 만든 컴포넌트 입니다</h1>
            </div>
        )
    }
}

ReactDOM.render(<Component message ="안녕하세요"/>, document.querySelector("#root"))

다음과 같이 class Component는 props를 사용할 때 this를 사용하여야 한다. 

  • 만약 message 값이 존재하지 않을 때 기본 값을 설정하려고 한다면 다음 방법을 이용하면 된다.
Component.defaultProps = {
    message: "기본값",
}
  • 또는 class 안에선 static defaultProps를 통해 기본 값을 지정할 수 있다. 
class Component extends React.Component{
    render(){
        return(
            <div>
                <h1>{this.props.message} 클래스로 만든 컴포넌트 입니다</h1>
            </div>
        )
    }
    static defaultProps = {
        message: "기본값",
    }
}

State 사용하기

class Component extends React.Component{
state = { //state는 항상 객체여야 한다.
    count: 0,
};
render(){
    return(
        <div>
            <h1>{this.props.message} 클래스로 만든 컴포넌트 입니다</h1>
            <p>{this.state.count}</p>
        </div>
    )
}

componentDidMount(){
    setTimeout(() =>{
        //this.state.count = this.state.count + 1과 같은 의미의 코드이다.
        this.setState({
            count: this.state.count + 1
        });
    }, 1000)
}
ReactDOM.render(<Component message ="안녕하세요"/>, document.querySelector("#root"))

 

  • 원래의 state 생성 방식은 다음과 같다.
//state = { //state는 항상 객체여야 한다.
//    count: 0,
//};
constructor(props){
    super(props);

    this.state = {count: 0 };
}

 

  • 또한 this.setState도 다른 방식으로 작성이 가능하다.
this.setState({
    count: this.state.count + 1
});

this.setState((previousState) => {
	const newState = { count: previousState.count + 1 };
    return newState;
)};

Event Handling이란?

  • HTML DOM에 클릭하면 이벤트가 발생하고, 발생하면 그에 맞는 변경이 일어나도록 해야 한다.
  • camelCase로만 사용할 수 있다.
    • onClick, onMouseEnter
  • 이벤트에 연결된 자바스크립트는 함수이다.
    • 이벤트 = {함수}와 같이 사용한다.
  • 실제 DOM 요소들에만 사용 가능하다.
    • 리액트 컴포넌트에 사용하면 그냥 props로 전달한다. 
function Component() {
    return (<div>
        <button
            onClick={() => { // 다음과 같이 onClick 사용
                console.log("clicked");
            }}>
            클릭
        </button>
    </div>);
}

ReactDOM.render(<Component/>, document.querySelector("#root"))

 

  • 다음은 state를 사용하여 이벤트가 발생했을 때 숫자가 올라가는 코드입니다.
class Component extends React.Component {
    state = {
        count: 0,
    };
    render() {
        return (<div>
            <p>{this.state.count}</p>
            <button
                onClick={() => {
                    this.setState((state) => ({ ...state, count: state.count + 1, }))
                }}>
                클릭
            </button>
        </div>);
    }
}

ReactDOM.render(<Component />, document.querySelector("#root"))

 

  • 이렇게 따로 메서드를 분리하여서 코드 작성도 가능합니다.
class Component extends React.Component {
    state = {
        count: 0,
    };
    render() {
        return (<div>
            <p>{this.state.count}</p>
            <button
                onClick={click}>
                클릭
            </button>
        </div>);
    }


    click = () => {
        this.setState((state) => ({ ...state, count: state.count + 1, }))
    }
}

ReactDOM.render(<Component />, document.querySelector("#root"))

Component LifeCycle이란?

  • 리액트 컴포넌트는 브라우저에 그려져서 사라질 때까지 여러 지점에서 개발자가 작업이 가능하도록 메서드를 오버 라이딩할 수 있게 해 준다.

  • Initialization은 constructor 부분을 의미한다. 즉 props가 설정되고 state 또한 설정된다.
  • mounting 단계에서 render 작업을 한다. 이때 브라우저에 그려지게 된다. 
  • Unmounting은 사라지기 전 단계를 의미한다.
  • 그 외 나머지들은 props와 state가 변경되어 렌더 메서드가 실행될 때 실행되는 메서드들이다.

현재 리액트 버전을 기준으로 하는 것이 아닌 16.3 버전 이전의 life Cycle을 설명한다.


Component 생성 (mount)

class App extends React.Component {
    state = {
        age:39,
    }
    constructor(props){
        super(props);

        console.log('constructor', props);
    }
    render(){
        console.log('render'); // 렌더 될 때 마다 로그가 찍힘
        return (
            <div>
                <h2>Hello {this.props.name} - {this.state.age}</h2>
            </div>
        );
    }
    componentWillMount() {
        console.log('componentWillMount')
    }
    componentDidMount() {
        console.log('componentDidMount')
        //보통 타이머나 API 요청하는 곳
        setInterval(()=>{this.setState((state)=>({ age:state.age+1}))},1000)
    }
}
ReactDOM.render(<App name = "mark" />, document.querySelector("#root"))

 

  • 위 코드는 다음과 같은 순서로 로그가 찍힐 것이다.
  • constructor (생성) -> componentWillMount -> render -> componentDidMount -> 재랜더 (반복)
  • Warning이 뜨는 이유는 상위 버전에서는 사라진 component life cycle들이 있기에 duplicated 될 위험이 있음을 알리는 것이다.

Component props, state의 변경

componentWillReceiceProps(nextProps){
    console.log("componentWillReceiveProps", nextProps);
}
shouldComponentUpdate(nextProps, nextState){
    console.log('shouldComponentUpdate', nextProps, nextState);
    return true;
}
componentWillUpdate(nextProps, nextState){
    console.log('componentWillUpdate',nextProps, nextState);
}
componentDidUpdate(prevProps, prevState){
    console.log('componentWillUpdate',prevProps, prevState);
    //렌더가 발생한 후이니 previous props와 prevState를 가지게 된다.
}

  • 로그 찍히는 순서를 잘 보고, prevProps와 nextProps를 잘 확인하도록 하자.

 

  • ComponentWillReceiveProps
    • props를 새로 지정했을 때 바로 호출된다.
    • 여기는 state의 변경에 반응하지 않는다.
      • 여기서 props의 값에 따라 state를 변경해야 한다면, setState를 통해 state를 변경합니다. 그러면 다음 이벤트로 각각 가는 것이 아니라 한 번에 변경된다..
  • shouldComponentUpdate
    • props만 변경되어도 되고 state만 변경되어도 되고 둘 다 변경되어도 발생한다. 
    • newProps와 newState를 인자로 해서 호출한다.
    • return 타입이 bool형식이다. 
      • true면 render 함수를 호출하고,
      • false면 render 함수를 호출하지 않기 때문에 재 랜더링 되지 않는다.
      • 이 함수를 구현하지 않는다면 default 값은 true이다.
  • componentWillUpdate
    • 컴포넌트가 재 랜더링 되기 직전에 호출된다.
    • 여기선 setState 같은 것들을 사용하면 안 된다.
  • componentDidUpdate
    • 컴포넌트가 재 랜더링 되고 난 후 불린다.

Component unmount

componentWillUnmount(){
    clearInterval(this.interval)
}
  • 사용하고 있는 API의 호출을 끊어주거나 불필요한 메모리 제거를 위해 사용한다. (ex. clearInterval)

React 16.3 버전 이후의 라이프 사이클을 알아보자.

  • constructor
  • componentWillMount -> getDerivedStateFromProps
  • render
  • componentDidMount
  • ComponentWillReceiveProps -> getDerivedStateFromProps
  • shouldComponentUpdate
  • componentWillUpdate -> getSnapshotBeforeUpdate
  • componentWillUpdate
  • componentDidUpdate

getDerivedStateFromProps

// componentWillMount() {
//     console.log('componentWillMount')
//}


static getDerivedStateFromProps(nextProps, previousState){
    console.log('getDerivedStateFromProps', nextProps, previousState)
    return null;
}
  • 리턴 값에는 state가 들어온다. 
  • 드문 경우에 사용 되게 변경되었다.
let i = 0
class App extends React.Component{
state = {
    list:[]
}
render(){
    return(
        <div id="list" style={{height: 100, overflow:"scroll"}}>
            {this.state.list.map( i =>{
                return <div>{i}</div>;
            })}
        </div>
    )
}
componentDidMount(){
    setInterval(()=>{
        this.setState(state => ({
            list: [...state.list, i++]
        }))
    }, 1000);
}

getSnapshotBeforeUpdate(prevProps, prevState){
    if(prevState.list.length == this.state.list.length) return null;
    const list = document.querySelector('#list');
    return list.scrollHeight - list.scrollTop;
}

componentDidUpdate(prevProps, prevState, snapshot){
    console.log(snapshot)
}

컴포넌트 에러 캐치하는 방법 componentDidCatch

class App extends React.Component{
    state = {
        hasError: false
    };
    render(){
        if(this.state.hasError){
            return <div>예상치 못한 에러 발생</div>
        }
        return <WebService/>;
    }
    componentDidCatch(error, information){
        this.setState({hasError : true});
    }
}

 

레퍼런스