스토리북(Stroybook)을 활용.
스토리 북이란 UI 컴포넌트 개발 도구이다. 이는 데모용 코드를 작성하는데 도움을 주고 공통적으로 사용될 컴포넌트를 팀원들과 편리하게 공유하는 도구 활용
vs code 의 새로운 프로젝트 디렉터리를 생성하고 터미널을 열어 스토리북 설치.
•npx–p storybook sb init
스토리북 실행 명령어
•npm run storybook
작성한 코드와 실행 결과
import React, { Component } from "react";
import PropTypes from "prop-types";
export function Text({ children, color, italic, underline }) {
const style = {
color: color,
fontStyle: italic ? "italic" : "normal",
textDecoration: underline ? "underline" : "none",
};
return <span style={style}>{children}</span>;
}
Text.propTypes = {
children: PropTypes.string.isRequired,
color: PropTypes.string,
italic: PropTypes.bool,
underline: PropTypes.bool,
};
Text.defaultProps = {
color: "black",
italic: false,
underline: false,
};
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);
}
}
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.setRef}
onChange={this.handleChange}
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,
autoFocus: PropTypes.bool,
};
Input.defaultProps = {
onChange: () => {},
onFocus: () => {},
autoFocus: false,
type: "text",
};
export default Input;
수업에서 사용된 함수들의 의미가 이해가 잘 되지 않았고 이에 따라 활용을 할 때 주의 할 점에 대해 좀 더 학습할 필요가 있다고 생각이 들었다.
https://ko.reactjs.org/docs/react-component.html#mounting 참조하며 좀더 학습해 보았다.
React를 사용할 때는 컴포넌트를 class 또는 함수로 정의할 수 있습니다. class로 정의된 컴포넌트는 아래에 자세히 설명하고 있듯 보다 많은 기능을 제공합니다. React 컴포넌트 class를 정의하려면 React.Component를 상속받아야 합니다.
아래 메서드들은 컴포넌트의 인스턴스가 생성되어 DOM 상에 삽입될 때에 순서대로 호출됩니다.
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
props 또는 state가 변경되면 갱신이 발생합니다. 아래 메서드들은 컴포넌트가 다시 렌더링될 때 순서대로 호출됩니다.
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
주의
아래 메서드는 기존에 사용되었지만 이제는 사용하면 안 됩니다.
- UNSAFE_componentWillUpdate()
- UNSAFE_componentWillReceiveProps()
render() 메서드는 클래스 컴포넌트에서 반드시 구현돼야하는 유일한 메서드입니다.
이 메서드가 호출되면 this.props와 this.state의 값을 활용하여 아래의 것 중 하나를 반환해야 합니다.
React 엘리먼트. 보통 JSX를 사용하여 생성됩니다.
render() 함수는 순수해야 합니다. 즉, 컴포넌트의 state를 변경하지 않고, 호출될 때마다 동일한 결과를 반환해야 하며, 브라우저와 직접적으로 상호작용을 하지 않습니다.
render() {
const { errorMessage, label, name, value, type, onFocus } = this.props;
return (
<label>
{label}
<input
id={"input_${name}"}
ref={this.setRef}
onChange={this.handleChange}
onFocus={onFocus}
value={value}
type={type}
/>
{errorMessage && <span className="error">{errorMessage}</span>}
</label>
);
}
브라우저와 상호작용하는 작업이 필요하다면, 해당 작업을 componentDidMount()이나 다른 생명주기 메서드 내에서 수행하세요. render()를 순수하게 유지하여야 컴포넌트의 동작을 이해하기 쉽습니다.
React에서 생성자는 보통 아래의 두 가지 목적을 위하여 사용됩니다.
this.state에 객체를 할당하여 지역 state를 초기화
인스턴스에 이벤트 처리 메서드를 바인딩
constructor() 내부에서 setState()를 호출하면 안 됩니다. 컴포넌트에 지역 state가 필요하다면 생성자 내에서 this.state에 초기 state 값을 할당하면 됩니다.
constructor(props) { super(props); // 여기서 this.setState()를 호출하면 안 됩니다! this.state = { counter: 0 }; this.handleClick = this.handleClick.bind(this); }```
생성자는 this.state를 직접 할당할 수 있는 유일한 곳입니다. 그 외의 메서드에서는 this.setState()를 사용해야 합니다.
생성자 내에서는 부수 효과를 발생시키거나 구독 작업(subscription)을 수행하면 안 됩니다. 해당 경우에는 componentDidMount()를 대신 사용하세요.
componentDidMount()는 컴포넌트가 마운트된 직후, 즉 트리에 삽입된 직후에 호출됩니다. DOM 노드가 있어야 하는 초기화 작업은 이 메서드에서 이루어지면 됩니다. 외부에서 데이터를 불러와야 한다면, 네트워크 요청을 보내기 적절한 위치입니다.
이 메서드는 데이터 구독을 설정하기 좋은 위치입니다. 데이터 구독이 이루어졌다면, componentWillUnmount()에서 구독 해제 작업을 반드시 수행하기 바랍니다.
componentDidMount()에서 즉시 setState()를 호출하는 경우도 있습니다. 이로 인하여 추가적인 렌더링이 발생하지만, 브라우저가 화면을 갱신하기 전에 이루어질 것입니다. 이 경우 render()가 두 번 호출되지만, 사용자는 그 중간 과정을 볼 수 없을 것입니다. 이런 사용 방식은 성능 문제로 이어지기 쉬우므로 주의가 필요합니다. 대부분의 경우, 앞의 방식을 대신하여 constructor() 메서드에서 초기 state를 할당할 수 있습니다. 하지만 모달(Modal) 또는 툴팁과 같이 렌더링에 앞서 DOM 노드의 크기나 위치를 먼저 측정해야 하는 경우 이러한 방식이 필요할 수 있습니다.
componentDidUpdate()는 갱신이 일어난 직후에 호출됩니다. 이 메서드는 최초 렌더링에서는 호출되지 않습니다.
컴포넌트가 갱신되었을 때 DOM을 조작하기 위하여 이 메서드를 활용하면 좋습니다. 또한, 이전과 현재의 props를 비교하여 네트워크 요청을 보내는 작업도 이 메서드에서 이루어지면 됩니다 (가령, props가 변하지 않았다면 네트워크 요청을 보낼 필요가 없습니다).
componentDidUpdate(prevProps) { // 전형적인 사용 사례 (props 비교를 잊지 마세요) if (this.props.userID !== prevProps.userID) { this.fetchData(this.props.userID); } }
componentDidUpdate()에서 setState()를 즉시 호출할 수도 있지만, 위의 예시처럼 조건문으로 감싸지 않으면 무한 반복이 발생할 수 있다는 점에 주의하세요. 또한 추가적인 렌더링을 유발하여, 비록 사용자는 눈치채지 못할지라도 컴포넌트 성능에 영향을 미칠 수 있습니다. 상위에서 내려온 prop을 그대로 state에 저장하는 것은 좋지 않으며, 그 대신 prop을 직접 사용하는 것이 좋습니다. 이와 관련된 자세한 정보는 props를 state에 복사하는 것이 버그를 유발하는 이유에서 확인할 수 있습니다.
컴포넌트에서 getSnapshotBeforeUpdate()를 구현한다면, 해당 메서드가 반환하는 값은 componentDidUpdate()에 세 번째 “snapshot” 인자로 넘겨집니다. 반환값이 없다면 해당 인자는 undefined를 가집니다.
주의
componentDidUpdate()는 shouldComponentUpdate()가 false를 반환하면 호출되지 않습니다.