$ npm run debug src={no}
State 란
컴포넌트 내부의 현재 상태를 나타내는, 쓰기 가능한 관리 데이터
상태의 변경은 UI를 다시 랜더링 하게 한다.
클래스 컴포넌트에서는 contructor에서 default 값 세팅으로 생성한다.
함수 컴포넌트에서는 useState라는 후크 함수를 사용해서 초기화 한다.
대체적으로 사용자 이벤트로 변경되거나 통신으로 변경된다.
컴포넌트는 this.state안에 여러 데이터를 가질 수 있다.(class component)
this.state는 특정 컴포넌트 전용 데이터이며 변경을 위해서는 setState() 함수를 사용한다.(class component)
상태가 업데이트되면 컴포넌트의 반응형(Reactive) Rendering이 트리거가 되고 컴포넌트와 자식 컴포넌트가 다시 렌더링 된다.
컴포넌트가 동작(event)과 상호작용을 수행할 수 있는 메커니즘을 제공한다.
부모의 상태(State)가 변하면 자식을 포함하여 다시 랜더링한다.
Top-Down property 전달은 Data Flow의 정형화된 방법이다.
Bottom-Up Flow로 데이터 전달을 원할 때에는 property가 아닌 callback 함수를 사용한다.
컴포넌트 구조화를 잘 설계해야 하는 이유, state Component를 관련 있는 컴포넌트들로 묶어 부모화한다. 하나의 state Component에 모두 올려버리게되면 rendering되는 범주가 넓어지므로, 부분적으로 wrapping 처리를 잘해서 처리해야한다. rendering되는 부분이 세분화되어 최대한 구조화하는 것이 좋다.
예제
begin(초기값) step(증가치) 를 props로 넘겨 이벤트 발생시 초기값이 증가하는 예제
Class Component
import React, {Component} from 'react';
export default class extends Component{
constructor(){
super(...arguments);
this.state = {
value: this.props.begin
}
}
onClickEventHandler() {
// this.state.value = this.state.value + this.props.step
// 위와 같이 하면 렌더가 되지 않는다.
this.setState({
value: this.state.value + this.props.step
});
}
render(){
return(
<div>
<button onClick={ this.onClickEventHandler.bind(this) }>
<strong>+</strong>
</button>
{ ' ' }
<span>{ this.state.value }</span>
</div>
)
}
}
function Component
import React, { useState } from 'react';
export default function({ begin, step }){
const [ value, setValue ] = useState(begin);
// 값에 접근할 수 있는 이름과, 값을 변경할 수 있는 함수를 던져준다.
// 배열의 객체 분해를 활용한다.
// props 또한 객체 분해를 활용하여 가독성을 높인다.
const onClickEventHandler = function() {
setValue( value + step );
}
return(
<div>
<button onClick={ onClickEventHandler }>
<strong>+</strong>
</button>
{ ' ' }
<span>{ value }</span>
</div>
)
}
<input>, <textarea>, <option>
과 같은 폼 컴포넌트 중에 사용자 입력에 따라 state값이 변경되고 렌더링하는 컴포넌트를 제어(Controlled) 컴포넌트라고 한다.[src/02] 제어 컴포넌트
import React, { useState } from 'react';
import './assets/Form.css';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheckCircle, faTimesCircle } from '@fortawesome/free-solid-svg-icons';
export default function Form() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [validEmail, setValidEmail] = useState(false);
const [password, setPassword] = useState('');
const [gender, setGender] = useState('female');
const [bithYear, setbirthYear] = useState(1984);
const [selfDescription, setSelfDescription] = useState('');
const [agreeProv, setAgreeProv] = useState('no');
const onChangeInputName = function(e) {
// setName(e.target.value);
// 10 자 제한(Validation)
setName(e.target.value.substr(0,10));
}
const onChangeInputEmail = function (e){
setEmail(e.target.value);
const re = /[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]$/i;
// Ajax
// const result = await fetch('/user/emailcheck?email' + e.target.value);
// setValidEmail(!result.data);
setValidEmail(re.test(e.target.value));
}
const onChangeInputPassword = function(e){
setPassword(e.target.value.substr(0,10));
}
const onChangeInputGender = function (e){
setGender(e.target.value);
}
const onChangeInputProv = function (e){
// API 호출
const url = `/prov/agree?status=${e.target.value === 'no' ? 'yes' : 'no'}`;
console.log(url);
if(true){
setAgreeProv(e.target.value === 'no' ? 'yes' : 'no');
}
}
return (
<form id="joinForm" name="joinForm" method="post" action="/do/not/post">
{/* name */}
<label htmlFor="name">이름</label>
<input id="name" name="name" type="text" value={name} onChange={onChangeInputName}/>
{/* email */}
<label htmlFor="email">이메일</label>
<input id="email" name="email" type="text" value={email} onChange={onChangeInputEmail}/>
{/* etc */}
{
email === '' ? null :
validEmail ?
<FontAwesomeIcon icon={faCheckCircle} style={{marginLeft: 5, color: 'blue'}} size='lg'/> :
<FontAwesomeIcon icon={faTimesCircle} style={{marginLeft: 5, color: 'red'}} size='lg'/>
}
{/* password */}
<label htmlFor="password">패스워드</label>
<input id="password" name="password" type="password" value={password} onChange={onChangeInputPassword}/>
{/* gender, radio box */}
<fieldset>
<legend>성별</legend>
<label>여</label> <input type="radio" name="gender" value={"female"} checked={gender === 'female'} onChange={ onChangeInputGender }/>
<label>남</label> <input type="radio" name="gender" value={"male"} checked={ gender === 'male'} onChange={ onChangeInputGender }/>
</fieldset>
{/* select/options */}
<label htmlFor="birthYear">생년</label>
<select id="birthYear" name='birthYear' value={ bithYear } onChange={ e => setbirthYear(e.target.value) }>
<option value={ 1984 }>1984년</option>
<option value={ 1985 }>1985년</option>
<option value={ 1986 }>1986년</option>
<option value={ 1987 }>1987년</option>
<option value={ 1988 }>1988년</option>
<option value={ 1989 }>1989년</option>
<option value={ 1990 }>1990년</option>
</select>
{/* textarea */}
<label htmlFor="birthYear">자기소개</label>
<textarea id='selfDescription' name='selfDescription' value={ selfDescription } onChange={ e => setSelfDescription(e.target.value.substr(0,50))}/>
<fieldset>
<legend>약관동의</legend>
<input
id="agree-prov"
type="checkbox"
name="agreeProv"
value={ agreeProv }
checked={ agreeProv === 'yes'}
onChange={ onChangeInputProv }
/>
<label>서비스 약관에 동의합니다.</label>
</fieldset>
<input type="submit" value="가입"/>
</form>
);
}
[src/03] 비제어 컴포넌트
Stateful Comonent
Pure Component
애플리케이션의 컴포넌트들은 상태 컴포넌트와 순수 컴포넌트로 분리하여 만드는 것이 좋다.
어떤 컴포넌트가 상태 컴포넌트인지 인지하는 방법()
예제) Emaillist(list part, props)
예제) Emaillist(search part, callback)