React.js의 PropTypes , 컴포넌트 분류, 통신, 수명주기, 불변성📚

Jihee·2020년 8월 25일
1
post-thumbnail

🎵React.js의 기초에 대해서 정리하는 두번째 글이다.

🎈PropTypes

컴포넌트를 문서화하는 데 도움을 주며 아래 두가지 장점을 제공한다.
1. 언제든지 컴포넌트를 열고 어떤 속성이 필요한지, 그리고 속성의 형식이 무엇인지 알 수 있다.
2. 문제가 발생한 경우 리액트가 콘솔에 오류 메시지를 출력해 어떤 속성이 잘못됐는지, 어떤 render 메소드에서 문제가 발생했는지 알려준다.
컴포넌트를 생성할 때 곧바로 선언하는 것이 좋다.

아래와 같이 정의한다. 선택적 요소라면 isRequired 생략하면 된다.

Greeter.propTypes={
	description: PropTypes.string.isRequired
}

속성 기본값

속성값이 별도로 제공되지 않을 때 기본값 지정하려면 defaultProps 객체로 정의. 위의 PropTypes를 지정하고 밑에 작성하는 것이 좋다.

Greeter.defaultProps = {
	description : "This is a description."
}



기본 PropTypes 유효성 검사기

기본형

PropTypes.array / PropTypes.bool / PropTypes.func / PropTypes.number
PropTypes.object / PropTypes.string

조합 기본형

PropTypes.oneOfType([PropTypes.string, PropTypes.number]) : 여러형식 중 하나
PropTypes.arrayOf(PropTypes.number) : 속성이 숫자 형식의 배열이어야 함.
PropTypes.objectOf(PropTypes.number) : 속성이 숫자형식의 속성 값을 가진 객체여야 함.
PropTypes.shape({color : PropTypes.string , fontSize:PropTypes.number}) :
속성이 특정 형태를 가진 객체여야 함.

특수 PropTypes

PropTypes.node :속성이 렌더링 가능한 어떤 값이라도 될 수 있다.(숫자,문자열,요소,배열)
PropTypes.element : 속성이 리액트 요소여야 함.
PropTypes.instanceOf(Message) : 속성이 지정한 클래스의 인스턴스여야 한다.
PropTypes.oneOf(['News','Photos']) : 속성이 열거형과 같이 특정한 범위의 값으로 한정돼야 한다.

커스텀 PropTypes 유효성 검사기

let titlePropType = (props, propName, componentName) => {
	if(props[propName]){
    	let value = props[propName];
        if(typeof value !== 'string' || value.length > 80){
        	return new Error(
            	`${propName} in ${componentName} is longer than 80 characters`
            )
        }	
    }
}

Card.propTypes = {
	title : titlePropType
}




🎈상태 저장 컴포넌트와 순수 컴포넌트

상태 저장 컴포넌트 : 상태(State)를 관리하는 컴포넌트. 일반적으로 컴포넌트 계층에서 상위를 차지하며, 상태저장 컴포넌트나 순수 컴포넌트를 하나 이상 래핑한다.
순수 컴포넌트 : 내부 상태가 없고 속성을 받아서 데이터를 표시하는 역할만 하는 컴포넌트. 재사용하거나 테스트 하기가 수월하다.

애플리케이션의 상태를 다수의 컴포넌트로 분산하면 관리하기 힘들고 문제가 생길 가능성이 높으니 대부분 상태 비저장 컴포넌트로 만드는 것이 좋다.

상태 저장 컴포넌트의 적절성 검사

  1. 해당하는 상태를 기준으로 무언가를 렌더링하는 모든 컴포넌트를 찾는다.
  2. 공통 소유자 컴포넌트를 찾는다(계층에서 상태를 필요로 하는 모든 컴포넌트의 상위에 있는 단일 컴포넌트.)
  3. 공통 소유자나 계층에서 더 상위에 있는 다른 컴포넌트가 상태를 소유해야 한다.
  4. 해당 상태를 소유하기에 적절한 컴포넌트를 찾을 수 없는 경우 단순히 상태를 저장하기 위한 컴포넌트를 새로 만들고 계층에서 공통 소유자 컴포넌트 위쪽에 추가한다.





🎈부모와 자식컴포넌트 간의 통신

콜백함수 이용

ContactsApp.js

class ContactsApp extends Component{
	constructor(){
    	super();
        this.state = {
        	filterText : ''
        };
    }
    
    handleUserInput(searchTerm){
    	this.setState({filterText : searchTerm})
    }
    
    render(){
    	return(
        	<div>
            	<SearchBar filterText={this.state.filterText}
                	onUserInput={this.handleUserInput.bind(this)} />
            </div>
        )
    }
}
ContactsApp.propTypes={
    contacts: PropTypes.arrayOf(PropTypes.object);
}

SearchBar.js

class SearchBar extends Components{
	handleChange(event){
    	this.props.onUserInput(event.target.value)
    }
    
    render(){
    	return <input type="search" value={this.props.filterText}
        		onChange={this.handleChange.bind(this)} />
    }
}
SearchBar.propTypes={
	filterText : PropTypes.string.isRequired,
    onUserInput : PropTypes.func.isRequired
}





🎈컴포넌트 수명주기

수명주기를 이용하여 속성이나 상태 변경에 적절하게 대응할 수 있다.

마운팅 시

클래스 생성자

componentWillMount : 초기 렌더링을 수행하기 직전 한 번 호출.

render

componentDidMount : 초기 렌더링을 수행한 직후 한 번 호출

언마운팅 시

componentWillUnMount : 컴포넌트가 DOM에 언마운팅되기 직전에 호출. 리스너의 타이머 제거 등 정리 작업을 할 때 유용.

속성,상태 변경 시

componentWillReceiverProps : 새 속성을 받을 때 호출. 상태 변경 시는 호출x.

shouldComponentUpdate : 해당 컴포넌트의 렌더링을 생략할 수 있는 기회 제공

componentWillupdate : 새 속성,상태 수신하고 렌더링 직접에 호출. this.setState 허용x.

render

componentDidUpdate : 컴포넌트 업데이트가 DOM으로 플러시된 직후 호출

cf ) 컨테이너 컴포넌트
어플리케이션에서 이미 다른 역할을 가지고 있는 컴포넌트에 데이터 가져오기 논리를 추가하는 것은 바람직하지 않고, 속성을 통해 데이터와 콜백 전달하는 역할만 수행하는 상태 저장 컴포넌트를 새로 만드는 것이 좋다. 이러한 역할을 하는 컴포넌트를 컨테이너 컴포넌트라고 한다.





🎈불변성

배열로 이뤄진 상태에서 배열을 추가한다고 할 때.

let updatePassengers = this.state.passengers;
updatePassengers.push('Mitchell, Vicent M.');
this.setState({passengers : updatePassengers});

위와 같이 한다면 let updatePassengers = this.state.passengers; 이 부분이 배열의 복사본을 만든것이 아니라 새로운 참조를 만드는 것이라 이렇게 하면 의도치 않은 결과가 나올 수 있다.

자바스크립트에서 실제 배열의 복사본을 만들려면 원하는 변경이 적용된 배열을 반환하는 비파괴 메소드를 이용해야한다.
비파괴 배열 메소드 ex)map, filter, concat

let updatePassengers = this.state.passengers.concat('Mitchell, Vicent M.');
this.setState({passengers : updatePassengers});

아니면 Object.assign을 이용해 변경이 적용된 객체를 새로 생성하는 방법이 있다.

Object.assign( target, source_1, ...., source_n )

let updateTicket = Object.assign({},this.state.ticket, {flightNo:'1010'});
this.setState({passengers : updateTicket});




하지만 자바스크립트에서 배열 안의 배열같이 배열이 중첩된 경우는 비파괴 메소드와 Object.assign 도 깊은 복사를 할 수가 없고 배열안의 배열은 결국 원래 객체를 참조하게 된다.
이 것을 해결하기 위해 리액트 애드온 패키지의 update라는 불변성 도우미를 사용한다.
update는 일반 자바스크립트 객체와 배열을 대상으로 작업하며, 이러한 객체를 변경불가로 조작할 수 있게 해준다. 즉, 실제 객체를 변경하는 대신 변경된 새로운 객체를 반환한다.

npm install --save react-addons-update
import update from 'react-addons-update'

let student = {name:'John Caster', grades:['A','C','B']};

let newStudent = update(student, {grades:{ $push: ['A']}});
//student에서 grades 키를 찾고(위치) , 새로운 값을 push로 넣기(유형)

let newStudent_1 = update(student , {grades:{ $set: ['A','A','B']}})
//배열을 완전히 바꾸기

변경 할 수 있는 중첩 단계에는 제한이 없다.

let newTicket = update(ticket, {arrivals: {airport: {$set: 'MCO'}}});

배열 인덱스를 이용해 변경할 위치를 찾을 수 있다.

let newTicket = update(ticket,{
	codeShare: { 
    	0 : {$set: {company:'AX', no:'1254'}}
     }
   }); // ticket객체안에 codeShare 배열의 첫번째 요소가 변경

사용가능한 명령

$push: 배열 끝 부분에 요소를 하나 이상 추가한다.

$unshift : 배열 앞부분에 요소를 하나 이상 추가한다.

$splice : 요소 제거 및 추가를 통해 배열 내용 변경

let initialArray = [1,2,'a'];
let newArray = update(initialArray , {$splice:[[2,1,3,4]]})
//2번째 요소에서 변경 시작. 1개의 요소 제거 후 3,4 추가한다.
// => [1,2,3,4]

$set : 대상을 완전히 대체한다.

$merge : 지정한 객체의 키를 대상과 병합한다.

let obj = {a:5, b:3};
let newObj = update(obj, {$merge{ b:6, c:7}});
//=> {a:5, b:6, c:7}

$apply : 현재 값을 지정한 함수로 전달하고 새로 반환된 값으로 이를 업데이트한다.

let obj = {a:5, b:3};
let newObj = update(obj, {b: {$apply: (value)=>value*2 }});
//=> {a:5,b:6}

cf ) 낙관적 업데이트
변경이 실제로 데이터베이스 저장됐는지 여부에 대한 서버의 응답을 기다리지 않고 UI를 변경하는 작업. 체감 성능을 높으닌 데 효과적이다.
만약 서버에서 오류가 생긴다면 몇 차례 재시도하고, UI변경을 되돌리며, 사용자에게 알리는 과정이 필요하다. 상태 변경이라면 UI변경 시 그 전 state를 변수로 저장 해놨다가 오류 시에 그 전 state로 다시 사용하면 된다.







정리

  • 리액트 애플리케이션에서 데이터는 항상 부모 컴포넌트에서 자식 컴포넌트로 한 방향으로만 전달된다.
  • 컴포넌트 간 통신을 위해선 부모가 속성을 통해 콜백 함수를 자식으로 전달해 보고 가능하다.
  • 상태 저장 컴포넌트(가급적 적게), 순수 컴포넌트(더 많이) 두 분류로 구분하는 것이 재사용에 바람직하다.
  • 상태는 변경 불가로 취급해 항상 this.setState를 이용해야하고, 불변성 도우미를 이용해 state의 얕은 복사본을 생성한다.















출처 : 프로 리액트(출판사: 위키북스)

profile
개발열정이 넘치는 개발자

0개의 댓글