React-3

Gavri·2021년 7월 23일
0

컴포넌트를 표현하는 JSX

JSX는 JavaScript XML의 줄임말로 자바스크립트에 XML을 추가한 확장형 문법으로 이해하시면 됩니다.

리액트 엔진은 JSX의 XML 구조를 분석하여 자바스크립트 함수 코드로 변환합니다.
React.createElement()함수는 리액트 엔진에 포함되어 있으며,
기존 자바스크립트의 document.createElement() 함수를 사용하여 객체모델을 생성합니다.

리액트를 사용하면 개발자는 JSX만 작성하고 , 리액트 엔진은 JSX를 기존 자바스크립트로 해석하는 역할만 하게 됩니다
이를 선언형 화면(Declarative View) 기술 이라고 부릅니다.

import React from 'react';

class App extends React.Component {
  render() {
    return (
      <div>
        <img src=".../image.png" />
        <div>안녕하세요</div>
      </div>
    );
  }
}
// 위 구문을 아래로 해석해주는 작업을 리액트 엔진이 합니다.
return React.createElement(
    'div',
    null,
    React.createElement('img', { src: './image.png' }),
    React.createElement('div', null, '안녕하세요'),
  );

컴포넌트와 구성요소

컴포넌트 란?

  • 기존의 MVC의 경우 코드 관리를 효율적으로 가능하나 서로의 의존성이 높아 재활용이 어려웠음
  • 웹사이트 화면의 각 요소가 비슷하고 반복적으로 사용한 경우가 많음
  • 컴포넌트는 MVC의 뷰를 독립적으로 구성하여 재사용도 할 수 있고 컴포넌트를 통해 새로운 컴포넌트를 만들기 또한 쉽고 간단함

컴포넌트 구성요소

데이터 구성요소 특징
프로퍼티 상위 컴포넌트에서 하위 컴포넌트로 전달되는 읽기 전용 데이터
state 컴포넌트의 상태를 저장하고 변경할 수 있는 데이터
컨텍스트 부모 컴포넌트에서 생성하여 모든 자식 컴포넌트에 전달하는 데이터입니다.

컴포넌트에 데이터를 전달하는 프로퍼티

  • 프로퍼티는 위에서 보듯이 상위 컴포넌트에서 하위 컴포넌트 즉 단방향으로 데이터가 흐르는 특징을 가지고 있습니다.
  • 프로퍼티에서는 모든 자료형 사용이 가능합니다. (이때 프로퍼티의 자료형은 미리 선언해주는것이 좋습니다.) 자료형 선언은 prop-types로 가능합니다.
  • 프로퍼티로 값을 전달할때 문자열의 경우 "", 그외에 자료형은 {} 중괄호로 값을 넣어주어야 합니다.
import React, { Component } from 'react';
import PropTypes from 'prop-types';

class ChildComponent extends Component {
  render() {
    const { boolValue, numValue, arrayValue, objValue, nodeValue, funcValue } = this.props;
    return (
      <div>
        <span>{boolValue}</span>
        <span>{numValue}</span>
        <span>{arraylValue}</span>
        <span>{objValue}</span>
        <span>{nodeValue}</span>
        <span>{funcValue}</span>
      </div>
    );
  }
}

ChildComponent.propTypes = {
  boolValue: PropTypes.bool,
  numValue: PropTypes.number,
  arrayValue: PropTypes.array,
  objValue: PropTypes.object,
  nodeValue: PropTypes.node,
  funcValue: PropTypes.func,
  //필수 자료형 지정 방법 : PropTypes.자료형.isRequired;
};
//default 기본값 지정방법
ChildComponent.defaultProps = {
  boolValue: false,
};

export default ChildComponent;

//app.js childComponent의 상위 컴포넌트
import React from 'react';
import ChildComponent from './03/ChildComponent';

class App extends Component {
  render() {
    return (
      <ChildComponent
        boolValue={true}
        numValue={3}
        arrayValue={[1, 2, 3]}
        objValue={{ name: 'name', age: 30 }}
        nodeValue={<h1>Node</h1>}
        funcValue={() => {
          console.log('message');
        }}
      ></ChildComponent>
    );
  }
}

컴포넌트 상태 관리하기

state를 통한 상태 관리하기
state란 프로퍼티와 달리 값을 변경이 가능하여 클릭을 통한 값변경 이벤트 즉 수량 등 변해야 되는 값에 사용하기 좋습니다.
Ex: 4초뒤 state를 변경하는 예제

import React, { Component } from 'react';

class StateExample extends Component {
  constuctor(props) {
    super(props);
    //state 정의
    this.state = {
      loading: true,
      formData: 'no data',
    };
    this.handleData = this.handleData.bind(this);

    setTimeout(this.handleData, 4000);
  }
  handleData() {
    const data = 'new data';
    const { formData } = this.state;
    this.setState({
      loading: false,
      formData: data + formData,
    });
    console.log('loading값', this.state.loading);
    //render 함수전 loading 값은 아직 변하지 않은 true입니다.
  }
  render() {
    return (
      <div>
        <span>{String(this.state.loading)}</span>
        <span>{this.state.formData}</span>
      </div>
    );
  }
}

export default StateExample;

state를 사용할 때 주의할 점

  • 생성자에서 반드시 초기화 해야합니다.
    ㄴ> 내부함수에서 state 값에 접근하기 위해선 필수입니다. 마땅한 초깃값이 없다면 빈 객체({})라도 넣어야 합니다.
  • state값을 변경할 때는 setState() 함수(상태관리함수)를 반드시 사용해야 합니다.
    ㄴ> render()로 화면을 그려주는 시점을 리액트 엔진이 정하기 때문입니다. 직접 state값을 변경하여도 render() 함수는 호출되지 않습니다. 이후에 배울 생명주기와도 깊게 연관되어 있습니다.
  • setState() 함수는 비동기로 처리되며 setState() 코드 이후로 연결된 함수들의 실행이 완료된 시점에 화면 동기화 과정을 거칩니다.

setState() 함수의 인자로 함수를 전달하면 이전 state값을 쉽게 얻을수 있습니다.

handleData(data) {
    this.setState(function(prevState){
        const newState = {
            loading : false,
            formData : data + prevState.formData,
        };
        return newState;
    });
}

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

클래스 인스턴스 변수와 forceUpdate() 함수로 state 관리하기

forceUpdate() 함수 : 클래스 인스턴스 변수와 화면을 강제로 출력해주는 함수, 기존에 setState로 값을 변경하고 리액트 엔진에게 화면출력을 맡겼다면 이 함수를 통해 강제로 출력해주는 것이 가능 하지만 리액트 성능에 제약이 있으므로 매번 새롭게 화면을 출력하는 경우가 아니라면 가급적 사용하지 않기를 권합니다.

import React, { Component } from 'react';

class StateExample extends Component {
  constuctor(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 StateExample;

컴포넌트의 생명주기

컴포넌트의 생성부터 소멸까지의 과정을 컴포넌트의 생명주기 라고 합니다. 생명주기마다 함수를 가지고 있는데 함수들을 이용하면 특정 시점에 원하는 동작을 하도록 만들 수도 있습니다.

그림을 참고하면 생성 과정에서는 4개의 함수가 사용되고 생성 완료후 갱신(update)될때 5개의 함수가 사용된다. shouldComponent() 함수의 반환값에 따라 이후 과정이 생략될지 진행될지 선택 됩니다.소멸 과정에는 1개의 함수가 사용됩니다.

  1. constructor(props) 함수
    맨 처음 생성될 때 단 한번만 호출 되며, 상태를 선언할 때 사용됩니다. 이 함수를 정의할때 항상 super() 함수를 가장 상단에 호출해야 하며 이 과정은 프로퍼티와 생명 주기 상태 등을 초기화 하는 중요한 과정을 포함합니다.
  2. render() 함수
    이 함수는 데이터가 변경되어 새 화면을 그려야 할 때 자동으로 호출되는 함수입니다. 이 함수가 반환하는 JSX를 화면에 그려줍니다.
  3. static getDerivedStateFromProps(props,state) 함수
    이 함수는 정적 함수 입니다. 따라서 함수 안에서 this.props나 this.stat와 같은 방법으로 프로퍼티나 state값에 접근할 수 없습니다. 만약 각 값에 접근해야 하는 경우 반드시 인자로 전달된 props,state를 이용해야 합니다. 이 함수는 상위 컴포넌트에서 전달받은 프로퍼티로 state값을 연동할 때 주로 사용되며, 반환값으로 state를 변경합니다.
  4. componentDidMount() 함수
    이 함수는 render() 함수가 화면을 그린이후에 호출되는 함수입니다.
  5. shouldComponentUpdate(nextProps,nextState) 함수
    이 함수는 화면을 새로 출력할지 말지 판단하며, 데이터 변화를 비교하는 작업을 포함하므로 리액트 성능에 영향을 많이 줍니다. forceUpdate() 함수를 호출시 해당 함수를 호출하지 않습니다.
  6. getSnapshotBeforeUpdate(prevProps,prevState) 함수
    이 함수는 컴포넌트의 변경된 내용이 가상화면에 완성된 이후 호출되는 함수입니다. 실제 화면에 컴포넌트가 출력되기 전 DOM 정보에 접근하기 위해서 사용됩니다.
  7. componentDidUpdate(prevProps,prevState,snapshot) 함수
    실제 화면에 출력된 이후에 호출되는 함수 입니다. 이전 프로퍼티와 이전 state값 그리고 이전에 호출된 getSnapshotBeforeUpdate() 함수의 리턴값 snapshot을 인자로 전달 받습니다. 이값들을 이용해 DOM정보를 변경할때 사용됩니다.
  8. componentWillUnmount() 함수
    컴포넌트가 소멸되기전 호출되는 함수입니다. 보통 컴포넌트에서 감시되고 있는 작업들을 해제하기 위해 사용되는 함수입니다.

클래스형 컴포넌트

리액트 생명주기 함수와 컴포넌트 구성요소를 모두 포함하고 있는 특징이있다.

  • Component
import React from 'react';
class MyComponent extends React.Component{
  constructor(props){
    super(props);
    console.log('생성 함수');
  }
  componentDidMount(){//상속받은 생명주기 함수}
  myMethod() {//추가 확장 함수}
  render() {//상속박은 화면 출력 함수}
    
    
  • PureComponent
    PureComponent 클래스는 Component클래스를 상속받은 클래스 입니다.
    차이점은 shouldComponentUpdate() 함수를 "얕은비교" 하도록 재정의했습니다. 얕은 비교를 통해 성능을 향상 시킬수 있고, 불변 변수를 통한 성능 향상 또한 가능합니다.
  • 얕은비교 란?
    shallow-equal 라이브러리의 shallowEqual() 함수를 사용하여 비교합니다.
const obj = {name:'park'};
const mylist = [1,2,3,obj];
const list1 = [1,2,3,obj];
const list2 = [1,2,3,{name:'park'};

mylist === list1;
// false ===같은 경우 깊은 비교를 함
shallowEqual(mylist,list1);
//true로 다른 배열이지만 내용물이 같아서 동일 취급             
shallowEqual(list1,list2);// false obj라는 객체 내용은 같지만 
//list1 = 1,2,3,obj | list2= 1,2,3,{name:'park'} 이라 다르게 인식

함수형 컴포넌트

함수형 컴포넌트는 조금 길게 표현 하자면 state가 없는 함수형 컴포넌트 SFC라고 부릅니다.그렇기 때문에 단순한 UI 구조의 컴포넌트를 만들때 사용합니다.

처리 과정
데이터(프로퍼티,컨텍스트) -> 함수 컴포넌트 -> 출력할 컴포넌트(JSX)

간단한 구조

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

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

배열 컴포넌트

게시판이나 유튜브 영상 목록 등은 어떻게 구현될까요?
자바스크립트의 배열로 구성 됩니다. 다양한 자료형을 저장할 수 있다는 특징을 가지고 있는데 이것을 이용하면 XML과 JSX또한 저장가능 하기에 반복적으로 처리해야 될 경우 배열 컴포넌트를 사용하기 쉽습니다.
map() 함수를 이용한 방법

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;

map 을 이용하여 반복적인 배열 컴포넌트 처리가 가능합니다.
배열 요소의 개수만큼 반복하므로 성능에 영향을 많이 주기에 키값을 key로 꼭 정의해주어야 합니다. 키값을 정의하여 출력한 배열 컴포넌트는 다시 출력해야 하는 경우 리액트 엔진이 기존의 컴포넌트를 재활용하여 성능을 높일 수 있기 때문입니다.

그리고 단순히 키값에 반복 횟수(i)값으로 줄경우 이후에 filter함수를 사용할 경우 걸러지는 경우 키값이 매번 변경되어 재활용하지 못하고 새로 그리게 되어 비효율 문제가 발생합니다.

{todoList.filter(todo => todo.finished).map((todo, i)=>(
  <div key={`tl_${i}`}>{todo.taskName}</div>
))}

컴포넌트에서 콜백 함수와 이벤트 처리하기

프로퍼티의 경우 상위 컴포넌트에서 하위 컴포넌트로 흐르는 단방향 흐름을 보여준다고 했습니다 만약 하위 컴포넌트에서 변경하기 위해선 어떻게 해야될까요? 프로퍼티의 원본을 수정가능한 함수를 하위 컴포넌트에 제공하면 됩니다.
콜백 함수란 정의된 위치에서 실행되지 않고 이후 특정상황에서 실행되는 함수를 의미합니다. 즉, 콜백 함수를 프로퍼티로 전달하면 됩니다.

import React from 'react';
import Counter from './03/Counter';

class App extends React.Component{
  constructor(props){
	super(props);
    this.state = {
      	count: 1,
    };
  }
  increaeCount() {
	this.setState(({ count}) => ({ count: count +1}));
  }
  render() {
    	return (
          //<Counter count={this.state.count} onAdd={this.increaseCount} />
          // 콜백함수 선언
          <Counter count={this.state.count} 
  		   onAdd={this.increaseCount.bind(this)} />
// 콜백함수 선언시 bind를 해주지 않으면 상위 App컴포넌트에는 this.setState(...)
// 에 정의되어 있는데 실행은 하위 컴포넌트에서 실행되기 때문에 
// 해당 함수를 찾을수 없다는 문제가 발생합니다.
         
          );
  }
}
export default App;

//Counter 구현
class Counter Extends React.Component {
  render() {
    return (
      <div>
      현재 카운트: {this.props.count}
  	<button onClick=(() => this.props.onAdd()}>카운트 증가</button>
	</div>
);
}
}

컴포넌트에 DOM 객체 함수 사용하기
컴포넌트에서 DOM 객체 함수를 사용할려면 특수 프로퍼티(ref)를 사용해야 합니다. 단 ref 프로퍼티는 DOM 객체 함수가 필요한 엘리먼트에 콜백 함수 형태로 전달 됩니다.

import React, { PureComponent } from 'react';

export default class ScrollSpy extends PureComponent {
  constructor(props) {
    super(props);
    this.setRef = this.setRef.bind(this); 
    this.checkPosition = this.checkPosition.bind(this); 
    
  }
  setRef(ref) {
    this.ref = ref;
    //ref 프로퍼티에서 반환된 객체를 컴포넌트 변수에 할당했습니다.
  }
  checkPosition() {
    if (this.ref.getBoundingClienRect().top < window.innerHeight) {
      console.log('enter');
    } else {
      console.log('exit');
    }
  }
  componentDidMount() {
    this.checkPosition();
    window.addEventListener('scroll', this.checkPosition); // 윈도우 스크롤 이동 이벤트를 감지하여 checkPosition함수가 실행 되도록
  }
  componentWillUnmount() {
    window.removeEventListener('scroll', this.checkPosition);
  }
  render() {
    return <div ref={this.setRef}></div>;
  }
}

Input 컴포넌트를 만들면서 복습하기

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Input extends Component {
  constructor(props) {
    super(props);
    this.setRef = this.setRef.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(e) {
    const { name, onChange } = this.props;

    if (onChange) {
      onChange(name, e.target.value);
    }
  }
//autoFocus 값이  true 일경우 ref = document.ElementById().focus();
  componentDidMount() {
    if (this.props.autoFocus) {
      this.ref.focus();
    }
  }
  componentDidUpdate() {
    if (this.props.autoFocus) {
      this.ref.focus();
    }
  }
  setRef(ref) {
    this.ref = ref;
  }
  render() {
    const { errorMessage, label, name, value, type, onFocus } = this.props;
    return (
      <label>
        {label}
        <input
          id={`input_${name}`}
          ref={this.ref}
          onChange={this.handleChange} // 값 변경시 handle~ 함수호출 
          onFocus={onFocus} // 상위 컴포넌트의 콜백함수 호출 onFocus시
          value={value}
          type={type}
        />
        {errorMessage && <span className="error">{errorMessage}</span>}
      </label>
    );
  }
}

Input.propTypes = {
  type: PropTypes.oneOf(['text', 'number', '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,
};
Input.defaultProps = {
  onChange: () => {},
  onFocus: () => {},
  autofocus: false,
  type: 'text',
};

export default Input;
profile
모든건 기록으로

0개의 댓글

관련 채용 정보