React
20210609 React07 : HOC (고차 컴포넌트), Controlled vs Uncontrolled Component, Hooks(useState, useEffect)
React 07
HOC (Higher-Oder Components)
- 고차 컴포넌트
- Hook이 나오면서 사용성이 줄어들고 있음
- 컴포넌트를 재사용 할수 있는 기술
- React 에서만 있는 기술이 아닌 일종의 패턴 개념임
- HOC는 컴포넌트를 인자로 받아 새로운 컴포넌트를 리턴하는 함수
컴포넌트 vs HOC
- input : props ->
컴포넌트
-> output : UI - input : 컴포넌트 ->
HOC
-> output : 새로운 컴포넌트
사용법
- cross-cutting concerns (횡단 관심사)로 사용함
- 여러 페이지에서 지속적으로 일어 날수 있는 일 (ex. 로그인, 인증, 로깅)
- 그런 일을 전문적으로 하나로 분리하여 관리하여 여러 페이지에서 사용함
- HOC에 인자로 들어가는 컴포넌트를 변경하지 말아야 함 -> 컴포지션을 사용
- React : composition vs inheritance : react에서는 상속보다는 강력한 합성 방법을 추천함
- Composition (컴포지션, 합성) : 컴포넌트를 인자로 해서 다른 컴포넌트에 집어 넣어 return하는 방식
- 어떤 자식 엘리먼트가 들어올지 모르는 컴포넌트의 경우 children prop을 사용하여 자식 엘리먼트를 출력 그대로 전달 가능
- 우리가 넣는 props (unrelated props)와 HOC가 만든 props가 오염되지 않도록, 우리가 넣는 props는 JSX에 제대로 전달 해야함
- Composability (조합하는 것을) 최대화 해라
- 쉬운 디버깅을 위해서 새로 만들어진 컴포넌트(인자X)에 HOC 임을 알려주는 컴포넌트 Display Name을 꼭 넣어주어라
한계
- HOC에 대해서는 아직 잘 감이 오질 않는다. 필요한 상태에서 사용하는게 가장 이해가 잘되는듯 한데 아직인듯 하다.
Controlled Component vs Uncontrolled Component
- React의 원칙 : 신뢰 가능한 단일 소스로 내부의 상태를 관리
- 자식 컴포넌트가 data가 필요할 경우, 해당 data는 가장 가까운 공통 부모 컴포넌트에게서만 props의 형태로 전달받아서 사용해야 한다.
- -> form 관련한 태그들(input, textarea, select)은 정보를 가지기 때문에 내부적인 state에 의해 정보가 관리되지 않고 real Dom에 의해서 관리됨
- Controlled Component : 엘리먼트의 상태를 컴포넌트가 관리 (PUSH 방식)
- event에 의해서 받아오는 정보를 state에 저장하여 변경하고 해당 state 값을 다시 컴포넌트의 value prop으로 전달 -> dom element의 value를 변경 (state값이 변경될 때마다 Rerendering)
import React from "react";
class ControlledComponent extends React.Component {
state = {
value: "",
};
render() {
const { value } = this.state;
return (
<div>
<input value={value} onChange={this.change} />
<button onClick={this.click}>전송</button>
</div>
);
}
change = (e) => {
this.setState({ value: e.target.value });
};
click = () => {
console.log(this.state.value);
};
}
export default ControlledComponent;
- Uncontrolled Component : 엘리먼트의 상태를 관리 하지 않고, 엘리먼트의 참조만 컴포넌트가 소유 (PULL 방식)
- 실제 DOM의 Ref 속성을 참조하여 엘리먼트에 있는 정보를 사용함
- 컴포넌트에서 실시간으로 정보를 관리하지 않아 실시간 작업은 부적합함
import React from "react";
class UncontrolledComponent extends React.Component {
inputRef = React.createRef();
render() {
console.log("initial render", this.inputRef);
return (
<div>
<input ref={this.inputRef} />
<button onClick={this.click}>전송</button>
</div>
);
}
componentDidMount() {
console.log("componentDidMount", this.inputRef);
}
click = () => {
// input 엘리먼트의 현재상태 값 value 꺼내서 전송
// const input = document.querySelector('#my-input');
// console.log(input.value);
// real dom의 값을 읽어 들이는 것을 지양함
console.log(this.inputRef.current.value);
};
}
export default UncontrolledComponent;
Hooks
- function component도 Life Cycle, State를 사용가능케 해줌
- Component 의 State를 재사용 가능케 함
- 16.8버전 부터 지원
- Hooks를 사용하여 function component를 만든 이유
- class Component는 컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어려움
- 컨테이너 방식(props를 이용해서 재사용하는 방식) 말고, 상태와 관련된 로직
- 복잡한 컴포넌트 이해가 어려움
- Class는 컴파일 단계에서 코드 최적화를 어렵게함
- this.state는 로직에서 레퍼런스를 공유하기 때문에 문제가 발생 할 수 있음
- 즉, Life cycle 사이에 this.state를 공유함 (반대로 function component는 render, Life cycle사이에 공유하지 않음)
- class Component는 컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어려움
Basic Hooks
- ussState
- state를 대체 할 수 있음 (동등하지는 않음)
- useEffect
- 라이프 사이클 훅을 대체 할 수 있음
- componentDidMount
- componentDidUpdate
- componentWillUnmount
- useContext
useState
기존의 Class Component 사용 방식
import React from "react";
export default class Example1 extends React.Component {
state = { count: 0 };
render() {
const { count } = this.state;
return (
<div>
<p>Class : You clicked {count} times</p>
<button onClick={this.click}>Click Me! : classComponent</button>
</div>
);
}
click = () => {
this.setState({ count: this.state.count + 1 });
};
}
Function component + useState 사용 방식01
- state에 하나의 값을 가지는 경우
import React from "react";
export default function Example2() {
const [count, setCount] = React.useState(0);
return (
<div>
<p>Hooks : You clicked {count} times</p>
<button onClick={click}>Click Me! : Hook</button>
</div>
);
function click() {
setCount(count + 1);
}
}
Function component + uesState 사용 방식02
- state에 객체로 여러 값을 가지는 경우
- setState 내부에 함수를 사용하면, 해당 state를 인자로 받기 때문에 state 명에 의존적이지 않고 사용할 수 있음
import React from "react";
// useState => count
// useState => {count: 0}
export default function Example3() {
const [state, setState] = React.useState({ count: 0, count1: 1 });
return (
<div>
<p>Count + 1 : You clicked {state.count} times</p>
<p>Count + 1 : You clicked {state.count1} times</p>
<button onClick={click}>Click Me! : Hook</button>
</div>
);
function click() {
// setState({ count: state.count + 1 });
// 기존의 외부 state의 이름을 몰라도 state를 사용하고자 할때 -> 함수를 많이 사용
// 인자로 해당 state를 들고 오기 때문에
setState((state) => {
return {
count: state.count + 1,
count1: state.count1 + 1,
};
});
}
}
useEffect
- Rinae's devlog : UseEffect 완벽 가이드
- useEffect는 class Component의 Life cycle 에서 componentDidMount, componentDidUpdate 훅의 기능을 대체하고 있음, 또한 componentWillUnmount의 기능도 대체 됨
- 그래서 최초 render 후, state 및 props Update 후에 실행할 명령을 지정하여 때가 되면 실행시켜준다.
class Component 방식
import React from "react";
export default class Example4 extends React.Component {
state = { count: 0 };
render() {
const { count } = this.state;
return (
<div>
<p>Class DidMount,Update: You clicked {count} times</p>
<button onClick={this.click}>Click Me! : classComponent</button>
</div>
);
}
componentDidMount() {
console.log("componentDidMount", this.state.count);
}
componentDidUpdate() {
console.log("componentDidUpdate", this.state.count);
}
click = () => {
this.setState({ count: this.state.count + 1 });
};
}
function component + useEffect
- 기본 구조
- function component에서 JSX return 전에 useEffect 함수안에 callback을 넣어 사용함
import React from "react";
export default function Example5() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("componentDidMount & componentDidUpdate", count);
});
return (
<div>
<p>Hooks : You clicked {count} times</p>
<button onClick={click}>Click Me! : Hook</button>
</div>
);
function click() {
setCount(count + 1);
}
}
- 뒤에 state 옵션을 사용하는 방식에 따라 useEffect가 작동하는 방식이 달라진다.
useEffect : []
가 없는 경우
- 최초 Mount가 된 후 그리고 Update가 된 후 마다 useEffect를 실행
// 최초 그리고 update때 모두 실행
React.useEffect(() => {
console.log("componentDidMount & componentDidUpdate", count);
});
useEffect : []
가 있고 비어 있는 경우
- 최초 Mount가 된 후에 만 useEffect를 실행 (Update시에는 useEffect가 실행 되지 않음)
- 원래는 useEffect callback 안에 state를 사용하게 되면 당연히 update되면 update시켜서 render 시켜주는 것이 원칙이기에
[]
안에 count를 써주는 것이 마땅하다. (의도치 않으면 mount시에만 실행시키도록 하는 것도 상관은 없지만, 통일성을 저해한다.)
React.useEffect(() => {
console.log("componentDidMount & componentDidUpdate", count);
}, []);
useEffect : [state]
가 있는 경우
- 최초 Mount가 된 후 그리고 특정 state만 update되면 useEffect를 실행
React.useEffect(() => {
console.log("componentDidMount & componentDidUpdate", count);
},[count]]);
useEffect : ComponentWillUnmout 대체 기능 (cleanup) 등..
state 옵션이 없어 최초 실행만 되는 useEffect의 경우
- useEffect의 callback 함수의 return 값에 함수를 지정하면 해당 컴포넌트가 완전히 없어지기 전 useEffect의 callback의 return에 있는 함수를 실행킨다.
React.useEffect(() => {
console.log("componentDidMount");
return () => {
// cleanup
// componentWillUnmount
};
}, []);
state 옵션이 있어 mount, update 후 모두 실행되는 uesEffect 경우
- return 부분의 명령은 component가 없어지기 전에 실행도 되고, 또한 update가 되어 rendor 후 uesEffect 실행되기 전에
Update 이전 state 값
을 이용하여 명령을 실행한다. - render -> DidMount(state 무관 useEffect) -> state update(버튼 클릭) -> render ->
cleanUp(state 있는 useEffect return)
-> DidUpdate(state 있는 useEffect)
import React from "react";
// component 정의
export default function Example5() {
// state 정의
const [count, setCount] = React.useState(0);
// 최초 실행 : state 옵션 없는 경우
React.useEffect(() => {
console.log("DidMount");
return () => {
// cleanUp
// componentWillUnmount
console.log("WillUnmount");
};
}, []);
// 최초 실행 및 update 마다 : state 옵션 있는 경우
React.useEffect(() => {
console.log("DidMount & DidUpdate", count);
return () => {
// cleanUp (update 이전 count 값)
console.log("cleanup by count", count);
};
}, [count]);
// JSX render
return (
<div>
{console.log("render")}
<p>Hooks : You clicked {count} times</p>
<button onClick={click}>Click Me! : Hook</button>
</div>
);
// state update
function click() {
setCount(count + 1);
}
}