함수형 컴포넌트
리액트에서 컴포넌트를 정의하는 방법중 하나로 javascript 함수를 이용하여 React element를 반환하는 컴포넌트를 말함
컴포넌트를 정의하는 가장 간단한 방법은 Javascript 함수를 작성하는 것function Welcome(props) { return <h1>Hello, {props.name}</h1> }
이 함수는 데이터를 가진 하나의 "props" 객체 인자를 받은 후 React 엘리먼트를 반환하므로 유효한 React 컴포넌트.
이러한 컴포넌트는 Javascript 함수이기 때문에 말 그대로 "함수 컴포넌트"라고 호칭.
state
컴포넌트 내 변경 가능한 데이터 저장소
UI(엘리먼트)에 반영하기 위해 유지해야할 값 묶음
리액트 컴포넌트에 저장한 데이터가 변화하면 UI가 자동으로 갱신됨
리액트 개발의 핵심은 상태값을 효율적으로 관리하는 것, 그리고 상태값에 따라 화면이 불필요하게 업데이트되지 않도록 관리하는 것
각 컴포넌트 안에서만 사용하는 값은 해당 컴포넌트 안에서 생성하고 갱신한다. 여러 컴포넌트에서 사용하는 값은 별도 공간에서 생성하고 갱신한다.
상태값 관리는 getter/setter 함수로 하고, UI 갱신 문제 등이 있으니 직접 변경하는 것은 지양
useState
, useEffect
등과 같은 내장 Hook 메서드들이 존재하며, 필요에 따라서 커스텀 Hook을 제작할 수 있습니다.Hook의 원칙
- 최상위 레벨 에서만 Hook을 호출한다. (반복문이나 조건문 코드블랙 내에서 Hook을 호출하면 안됨)
- 일반 javascript 함수 스코프 내에서 호출하지 않는다 (오직 React 함수 내에서 Hook을 호출한다.)
- custom Hook 내에서는 호출 가능하다.
기본형
const [state, setState] = useState(초기값);
useState
는 컴포넌트에서 state
값을 추가할때 사용된다.state
를 사용할 수 없어, Hook을 사용해 state
와 같은 기능을 할 수 있도록 만듬useState
는 하나의 상태 값만 관리를 할 수 있어 만약에 컴포넌트에서 관리해야 할 상태가 여러 개라면 useState
를 여러번 사용해야 한다import React, { useState } from 'react';
const [inputs, setInputs] = useState({
username: "",
email: "",
});
const [inputs, setInputs]
useState
가 호출되면 배열을 반환하는데, 그 배열의 첫번째 원소는 상태값이고, 두번째 원소는 상태를 업데이트하는 함수이다. 이 함수에 파라미터를 넣어서 호출하게 되면 전달받은 파라미터로 값이 바뀌게 되고 컴포넌트는 정상적으로 리렌더링 된다.inputs
setInputs
inputs
값을 업데이트하는 함수. 클래스 컴포넌트에서의 this.setState
는 이전 state
와 새로운 state
를 합치지만 setInputs
은 이전값을 덮어쓴다.useState(...)
...
값은 초기값을 의미. useState
는 인자로 초기 state
설정값을 하나 받는다. 이 초기값은 첫 번째 렌더링 시에 딱 한 번 사용된다.기본형
const refContainer = useRef(초기값);
useRef
는 .current
property로 전달된 인자(initialValue)로 초기화된 변경 가능한 ref
객체를 반환합니다. 반환된 객체는 컴포넌트의 전 생애주기를 통해 유지된다.
본질적으로 useRef
는 .current
property에 변경 가능한 값을 담고 있는 상자와 같다.
useRef
는 아래와 같은 상황에서 주로 사용 된다.
시점
사용이유
import React, { useRef } from 'react';
JavaScript 를 사용할 때는, 특정 DOM 을 선택해야 하는 상황에
getElementById
,querySelector
와 같은 DOM Selector 함수를 사용해서 DOM 을 선택한다.
함수형 컴포넌트에서 사용할 때는useRef
를 사용하고 클래스형 컴포넌트에서는 콜백 함수를 사용하거나 React.createRef 라는 함수를 사용한다.
useRef
를 사용하여 Ref객체를 만들고, 이 객체를 선택하고 싶은 DOM 에 ref
값으로 설정한다. 그러면, Ref객체의 .current
값은 DOM 을 가리키게 된다.input
에 포커스가 잡히도록 useRef
를 사용하여 기능을 구현하기const nameInput = useRef();
focus()
DOM API 호출nameInput.current.focus();
ref
값 설정<input
name="name"
placeholder="Name"
onChange={onChange}
value={name}
ref={nameInput}
autoComplete="off"
/>
컴포넌트 안에서 조회 및 수정할 수 있는 변수를 관리할 수 있다.
useRef
로 관리하는 변수는 값이 바뀐다고 해서 컴포넌트가 리렌더링되지 않는다.
리액트 컴포넌트에서의 상태는 상태를 바꾸는 함수를 호출하고 난 다음 렌더링 이후로 업데이트된 상태를 조회할 수 있지만,
useRef
로 관리하는 변수는 설정 후 바로 조회할 수 있다.
변수를 사용하여 다음과 같은 값을 관리할 수 있다.
setTimeout
,setInterval
을 통해서 만들어진id
- 외부 라이브러리를 사용하여 생성된 인스턴스
scroll
위치
useRef
를 사용할 때 파라미터를 넣어주면, 이 값이 .current
값의 기본값이 된다..current
값을 수정하면 되고 조회할 때는 .current
를 조회하면 된다.4
지정const nextId = useRef(4);
onCreate()
가 실행될 때마다 nextId
의 값이 1씩 증가const onCreate = () => {
...
nextId.current += 1;
};
컴포넌트가 렌더링 된다는 것은 함수(컴포넌트)를 호출하여 실행되는 것을 의미.
함수가 실행될 때마다 내부에 선언되어 있던 표현식(변수나 또 다른 함수 등)도 매번 다시 선언되어 사용.
컴포넌트는 자신의state
가 변경되거나, 부모에게서 받는props
가 변경되었을 때마다 리렌더링 됨.심지어 하위 컴포넌트에 최적화 설정을 해주지 않으면 부모에게서 받는
props
가 변경되지 않았더라도 리렌더링.
useRef
로 관리하는 변수는 값이 바뀐다고 해서 컴포넌트가 리렌더링되지 않으므로 리렌더링 방지에 활용할 수 있다.
onChange
구현 부분을 ref
값으로 대체해서 단점을 해결할 수 있다. stat
e로 event의 value에 접근하지 않고 refObject.current.value
를 사용하는 방법.const usernameRef = useRef("");
const emailRef = useRef("");
ref
값 설정<input name="username" placeholder="USERNAME" ref={usernameRef} />
<input name="email" placeholder="EMAIL" ref={emailRef} />
refObject.current.value
사용 user
에 값 저장const onCreate = () => {
const user = {
id: nextId.current,
username: usernameRef.current.value,
email: emailRef.current.value,
};
setUsers(users.concat(user)); // users객체에 user 추가
usernameRef.current.value = ""; // input 값 초기화
emailRef.current.value = ""; // input 값 초기화
nextId.current += 1;
}
기본형
useEffect(effect, [, deps]);
effect -
함수의 형태로, 렌더링 이후 실행할 함수(리액트는 함수를 기억 했다가 DOM 업데이트 이후 불러냄)
함수에서 함수를 return 할 경우 그 함수가, 컴포넌트가 unmount 될 때 정리의 개념으로 한번 실행됨() -cleanup
함수
(만약 컴포넌트가 mount 될 때 이벤트 리스너를 통해 이벤트를 추가하였다면 컴포넌트가 unmount 될 때 이벤트를 삭제해주어야한다. 그렇지 않으면 컴포넌트가 Re-rendering 될 때마다 새로운 이벤트 리스너가 핸들러에 바인딩됨.)
deps, 의존값 -
배열의 형태로, 특정한 값이 변경될 때 effect함수를 실행 하고 싶을 경우 배열 안에 그 값을 넣어줌.
빈 배열을 넣을 경우 컴포넌트가 mount 될 때에만 실행
배열을 생략할 경우 렌더링이 될 때 마다 실행
useEffect
는 컴포넌트가 mount, unmount, update 됐을 때 특정 작업을 처리할 수 있다.side Effects 란?
React 컴포넌트 안에서 데이터를 가져오거나 구독하고, DOM을 직접 조작하는 작업을 “side effects”(또는 짧게 “effects”)라고한다.
왜냐하면 이것은 다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서는 구현할 수 없는 작업이기 때문.
Mount - DOM이 생성되고 웹 브라우저상에 나타나는 것을 Mount 라고한다.
- props 로 받은 값을 컴포넌트의 로컬 상태로 설정
- 외부 API 요청 (REST API 등)
- 라이브러리 사용 (D3, Video.js 등...)
- setInterval 을 통한 반복작업 혹은 setTimeout 을 통한 작업 예약
Unmount - Mount 의 반대 과정, 즉 컴포넌트를 DOM에서 제거하는 것을 Unmount 라고 한다.
- setInterval, setTimeout 을 사용하여 등록한 작업들 clear 하기 (clearInterval, clearTimeout)
- 라이브러리 인스턴스 제거
import React, { useEffect } from "react";
useEffect(() => {
console.log("마운트 될 때만 실행")
}, []);
useEffect(() => {
console.log("렌더링 될 때 마다 실행")
});
useEffect(() => {
console.log("state 값이 변경 될 때 실행")
}, [state]);
useEffect
안에서 사용하는 state나, props 가 있다면, useEffect 의 의존값에 넣어주어야 합useEffect
에 등록한 함수가 실행 될 때 최신 props / state를 가르키지 않게 됨import React, { useMemo, useCallback } from 'react';
기본형
const memoizedValue = useMemo(() => computeExpensiveValue(deps), [ deps ]);
첫번째 파라미터에는 어떤 연산을 할지 함수를 정의
두번째에는 useEffect와 마찬가지로 의존값들을 넣어주면 되는데, 이 배열 안에 넣은 내용이 변경되면 등록한 함수를 호출해서 값을 연산
만약 빈 배열을 넣는다면 useEffect와 마찬가지로 마운트 될 때에만 값을 계산하고 그 이후론 계속 Memoization된 값을 꺼내와 사용
메모이제이션된 값을 반환
useMemo
는 성능 최적화를 위하여 연산된 값을 재사용하는 기능을 가진 함수
useMemo
에서 Memo는 Memoization을 뜻함
시점
사용이유
활성 사용자 수 세기
// App.js
...
function countActiveUsers(users) {
console.log("사용자수");
return users.filter((user) => user.active).length;
}
...
const count = countActiveUsers(users);
countActiveUsers
가 호출되는 것이다.users
에 변화가 있을때만 세야되는건데, input 값이 바뀔 때에도 컴포넌트가 리렌더링 되므로 이렇게 불필요할때에도 호출하여서 자원이 낭비된다.// App.js
...
const count = useMemo(() => countActiveUsers(users), [users]);
기본형
const memoizedCallback = useCallback(() =>, [ deps ]);
useCallback
은 useMemo
와 비슷한 Hook. useMemo
는 특정 결괏값을 재사용할 때 사용하는 반면, useCallback
은 특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용하는 함수.(메모이제이션된 콜백을 반환)
useCallback
은 첫 번째 인자로 넘어온 함수를, 두 번째 인자로 넘어온 배열 형태의 함수 실행 조건의 값이 변경될 때까지 저장해놓고 재사용할 수 있게 해줌.
컴포넌트 안에 함수가 선언되어있을 때 이 함수는 해당 컴포넌트가 렌더링 될 때마다 새로운 함수가 생성되는데,
useCallback
을 사용하면 해당 컴포넌트가 렌더링 되더라도 그 함수가 의존하는 값(deps)들이 바뀌지 않는 한 기존 함수를 재사용할 수 있다.
시점
사용이유
onChange
,onCreate
, onRemove
, onToggle
은 컴포넌트가 리렌더링 될 때 마다 새로 만듬
useCallback() 예시
// App.js
...
// useCallback 사용 전
const onToggle = (id) => {
setUsers(
users.map((user) =>
user.id === id ? { ...user, active: !user.active } : user
)
);
};
// useCallback 사용 후
const onToggle = useCallback(
(id) => {
setUsers(
users.map((user) =>
user.id === id ? { ...user, active: !user.active } : user
)
);
},
[users]
);
주의⚠
useCallback
은 useMemo
를 기반으로 만들어졌기 때문에 아래와 같이 표현할 수도 있다.
const onToggle = useMemo(
() => () => {
/* ... */
},
[users]
);
기본형
const MyComponent = React.memo(function MyComponent(props) { /* props를 사용하여 렌더링 */ });
...
export default React.memo(MyComponent);
> `React.memo`는 Memoization(메모이제이션) 기법으로 동작하며, 고차 컴포넌트(Higher Order Component, HOC)이다.<br>
> 컴포넌트가 props로 동일한 결과를 렌더링하면, `React.memo`를 호출하고 결과를 메모이징(Memoizaing) 하도록 래핑하여 경우에 따라 성능 향상을 할 수 있다.<br>
> 즉, React는 컴포넌트를 재렌더링하지 않고 마지막으로 렌더링된 결과를 재사용한다.<br>
> `React.memo`는 props 변화에만 영향을 준다.<br>
> 즉, 함수 컴포넌트 안에서 구현한 state나 context가 변할 때는 재렌더링된다.<br>
> props가 갖는 복잡한 객체에 대하여 얕은 비교만을 수행하는 것이 기본 동작이다.<br>
> 다른 비교 동작을 원한다면, 두번째 인자로 별도의 비교 함수를 제공하면 된다.<br>
- props가 이전과 동일한 값이면 재렌더링하지 않고, 다른 값이면 재렌더링하여 컴포넌트를 다시 만들어 반환한다.
- `React.memo`에 쓰인 컴포넌트 안에서 구현한 state가 변경되면 컴포넌트는 재렌더링이 된다.
- 렌더링 최적화 하지 않을 컴포넌트에 `React.memo` 를 사용하는것은, 불필요한 props 비교만 하는 것이기 때문에 실제로 렌더링을 방지할수있는 상황이 있는 경우에만 사용
- `React.memo`에서 두번째 파라미터에 `propsAreEqual` 이라는 함수를 사용하여 특정 값들만 비교를 하는 것도 가능
```javascript
export default React.memo(
UserList,
(prevProps, nextProps) => prevProps.users === nextProps.users
);
useCallback
, useMemo
, React.memo
는 컴포넌트의 성능을 실제로 개선할수있는 상황에서만 사용!!!기본형
const [state, dispatch] = useReducer(reducer, initialState, init);
state - 컴포넌트에서 사용할 상태
dispatch 함수 - 첫번째 인자인 reducer 함수를 실행시킨다. 컴포넌트 내에서 state의 업데이트를 일으키키 위해 사용하는 함수
reducer 함수 - 컴포넌트 외부에서 state를 업데이트 하는 함수. 현재state, action 객체를 인자로 받아, 기존의 state를 대체하여 새로운 state를 반환하는 함수
initialState - 초기 state
init - 초기 함수 (초기 state를 조금 지연해서 생성하기 위해 사용)
useReducer
는 state(상태)를 관리하고 업데이트하는 Hook인 useState
를 대체할 수 있는 HookseReducer
는 state 업데이트 로직을 분리하여 컴포넌트의 외부에 작성하는 것을 가능하게 함으로써, 코드의 최적화useState vs useReducer
- useState
- 관리해야 할 state가 1개일 경우
- 그 state가 단순한 숫자, 문자열 또는 Boolean 값일 경우
const [value, setValue] = useState(true);
- useReducer
- 관리해야 할 State가 1개 이상, 복수일 경우
- 혹은 현재는 단일 state 값만 관리하지만, 추후 유동적일 가능성이 있는 경우
- 스케일이 큰 프로젝트의 경우
- state의 구조가 복잡해질 것으로 보이는 경우
setUsers(users => users.concat(user)); setInputs({ username: '', email: '' });
import React, { useReducer } from "react";
reducer
함수를 실행 시킨다.action
객체를 인자로 받으며 action
객체는 어떤 행동인지를 나타내는 type 속성과 해당 행동과 관련된 데이터(payload)를 담고 있다. (action 객체의 형태는 자유)action
을 이용하여 컴포넌트 내에서 state의 업데이트를 일으킨다. (업데이트를 위한 정보를 가지고 있음)<button onClick={() => dispatch({ type: "INCREMENT" })}>증가</button>
<button onClick={() => dispatch({ type: "INCREMENT", payload: 1, id : 0, text : "useReducer" })}>증가</button>
action
을 받게 된다.function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
throw new Error("unsupported action type: ", action.type);
}
}
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + action.payload };
case "DECREMENT":
return { count: state.count - action.payload };
default:
throw new Error("unsupported action type: ", action.type);
}
}
Custom Hook의 등장
1. 컴포넌트 사이에서 상태로직을 재사용하기 어렵다.
- Render Props와 HOC 같은 패턴은 코드의 파악이 어렵다
- 상태 관련 로직을 추상화 하기가 어렵다
- Class는 사람과 기계를 혼동시킨다.
- this가 동적으로 결정되기 때문에 클래스 내부에서 파악하기가 어렵다
- class는 코드의 최소화를 힘들게 만들고, 핫 리로딩을 깨지기 쉽고 신뢰할 수 없게 만든다.
- 복잡한 컴포넌트들은 이해하기가 어렵다
- 생명주기 class 메서드가 관련이 없는 로직들은 모아놓고, 관련이 있는 로직들은 여러개의 메서드에 나누어 놓는 경우가 자주 있었다.
useState
와 useEffect
들과 같이, useState
으로 정의하고, 이 state을 변경시킬 함수들을 객체로 담아 리턴하여 캡슐화한다MVC(Model-View-Controller) Pattern
데이터를 다루는 로직(Controller), 데이터(Model), 사용자 인터페이스(View)를 나누어 어플리케이션을 구현하는 하나의 개발 모델
![]()
Controller는 Model의 데이터를 조회하거나 업데이트하는 역할을 하며, Model의 변화는 View에 반영된다. 그리고 사용자가 뷰를 통해 데이터를 입력하면, 모델에 영향을 주면서 데이터를 관리하게 된다.
MVC 구조는 앱이 커지면서 굉장히 복잡해지고 View가 다양한 상호작용을 위해 여러개의 Model을 동시에 업데이트하는 상황이 나타면(많은 의존성을 가지게 되면) 예측불가능한 상황이 많이 나온다.
Flux Pattern
MVC의 문제를 해결할 목적으로 고안한 애플리케이션 아키텍쳐
큰 특징은 단방향 데이터 흐름(Unidirectional data flow).
데이터 흐름은 Dispatcher => Store => View 로 흘러가며 뷰에서 입력되는 데이터가 발생하면 Action을 이용해 디스패처로 향함
데이터를 직접 수정할 수 없고 반드시 액션을 통해서만 수정이 일어나기 때문에 교통정리가 가능
![]()
Action
Action Creater Method, View에서의 사용자 상호작용에서 발생
첫 흐름을 발생시키는 요소이며 Dispatcher에게 해당 액션 메시지를 보내준다.
타입(type)과 데이터(payload)를 가지고 있다.{ type: "EVENT_TYPE", data: { "name": "Huurray" } }
Dispatcher
Flux의 모든 데이터 흐름을 관리하는 중앙허브.
Store들이 등록해놓은 Action Type에 대한 맞춤 Callback이 있다.
Action이 넘어오면 Store들이 타입에 맞는 Store의 Callback을 실행하도록 해줌
Store
Store는 데이터와 데이터를 가공하는 로직을 가지고 있다
Dispatcher에 자신을 등록하고 Callback을 제공
Action이 넘어오면 등록된 Callback을 활용해 타입에 맞는 로직을 실행하고 데이터를 업데이트
Store는 변경된 데이터를 View에게 알려주고 자신의 컴포넌트 트리에 속해 있는 자식 노드 모두를 다시 랜더링하게함
View
Flux에서의 View는 MVC의 뷰와는 달리 화면을 보여주는것 외에도 Controller의 성격또한 가지고 있다.
특히 최상위 View는 스토어에서 데이터를 가져와 이를 자식 View 로 내려보내주는 역할
function App() {
return <GrandParent value="Hello World!" />;
}
function GrandParent({ value }) {
return <Parent value={value} />;
}
function Parent({ value }) {
return <Child value={value} />;
}
function Child({ value }) {
return <GrandChild value={value} />;
}
function GrandChild({ value }) {
return <Message value={value} />;
}
function Message({ value }) {
return <div>Received: {value}</div>;
}
Message
컴포넌트를 열어서, 이 value
값이 어디서 오는건지 파악하려고 한다면 그 상위 컴포넌트로 타고 또 타고 거슬러 올라가야 함value
라는 네이밍을 message
로 변경을 하고 싶어진다면, 통일성을 맞추기 위해서 또 여러 컴포넌트들을 수정해야함createContext
라는 함수를 불러와서 만들 수 있다.import { createContext } from 'react';
const UserDispatch = createContext(null);
createContext
의 파라미터에는 Context 의 기본값을 설정할 수 있다.<UserDispatch.Provider value={dispatch}>...</UserDispatch.Provider>
const value = useContext(Mycontext);
- 원시타입: Boolean, String, Number, null, undefined, Symbol
- 고정된 크기로 메모리에 저장, 실제 데이터가 변수에 할당.
let foo = 1; // 메모리 영역a에 할당. foo = 2; // 다른 메모리 영역b 에 재할당. 즉, 메모리영역b는 a를 대체하는 것이 아니라 새로운 영역에 할당됨.
- 참조타입: Object, Array
- 데이터 크기가 정해지지 않고 메모리에 저장, 데이터의 값이 heap에 저장되며 변수에 heap 메모리의 주소값이 할당.
const bar = { name : "Son" }; bar.name = "Kane"; // 내부 프로퍼티의 값을 변경했을 때 프로퍼티 `name`은 변경되지 않고 값이 변경된다
즉, 불변성은 메모리 영역에서 값이 변하지 않는다 라는 의미
자바스크립트 객체는 가변한다. 리액트에서는 자바스크립트 객체가 가변하기 때문에 문제가 된다.
- 리액트의 상태 업데이트 원리
- 리액트는 상태값을 업데이트 할 때 얕은 비교를 수행한다.
- 즉 배열이나 객체의 속성 하나하나를 비교하는게 아니라 이전 참조값과 현재 참조값만을 비교하여 상태 변화를 감지
const bar = {
name : "Son",
};
const baz = Object.assign({}, bar);
spread operator
, map
, filter
, slice
, reduce
등 새로운 배열을 반환하는 메소드들을 활용const bar = {
name : "Son",
};
const baz = {
...bar,
age: 30,
}
> npm i immer
import produce from 'immer';
produce
함수를 사용 할 때에는 const state = {
number: 1,
dontChangeMe: 2
};
const nextState = produce(state, draft => {
draft.number += 1;
});
console.log(nextState);
// { number: 2, dontChangeMe: 2 }
case "CREATE_USER":
return {
users: state.users.concat(action.user),
};
case "TOGGLE_USER":
return {
...state,
users: state.users.map((user) =>
user.id === action.id ? { ...user, active: !user.active } : user
),
};
case "CREATE_USER":
return produce(state, (draft) => {
draft.users.push(action.user);
});
case "TOGGLE_USER":
return produce(state, (draft) => {
const user = draft.users.find((user) => user.id === action.id);
user.active = !user.active;
});
TOGGLE_USER
액션의 경우 immer를 사용해서 간결해졌지만 CREATE_USER
오히려 더 복잡하다.const [todo, setTodo] = useState({
text: 'Hello',
done: false
});
const onClick = useCallback(() => {
setTodo(todo => ({
...todo,
done: !todo.done
}));
}, []);
setTodo
함수에 업데이트를 해주는 함수를 넣음으로써,
만약 useCallback
을 사용하는 경우 두번째 파라미터인 deps 배열에 todo
를 넣지 않아도됨
produce
함수에 두개의 파라미터를 넣게 되면 첫번째 파라미터에 넣은 상태를 불변성을 유지하면서 새로운 상태를 만들어 주지만
첫번째 파라미터를 생략하고 바로 업데이트 함수를 넣어주게 되면 반환 값은 새로운 상태가 아닌 상태를 업데이트 하는 함수가 됨
즉, produce
가 반환하는 것이 업데이트 함수가 되기 때문에 useState
의 업데이트 함수를 사용할 때 아래와 같이 구현 가능하다.
const [todo, setTodo] = useState({
text: 'Hello',
done: false
});
const onClick = useCallback(() => {
setTodo(
produce(draft => {
draft.done = !draft.done;
})
);
}, []);
React 컴포넌트를 만들때 클래스형 컴포넌트, 함수형 컴포넌트 2가지 방식이 있다
과거에는 클래스형 컴포넌트를 많이 사용했지만
2019년 v16.8 부터 함수형 컴포넌트에 리액트 훅(hook)을 지원해 주어서
현재는 공식 문서에서도 함수형 컴포넌트와 훅(hook)을 함께 사용할 것을 권장하고 있다.
컴포넌트란?
컴포넌트는 단순한 템플릿 이상의 기능을 수행
데이터가 주어졌을 때 이에 맞추어 UI를 만들어 주는 기능을 하는 것은 물론, 라이프 사이클 API를 통해 컴포넌트가 화면에 나타날 때, 사라질 때, 변할 때 작업들을 수행
컴포넌트의 목적에 따라 프리젠테이션(presentational) 컴포넌트와 컨테이너(container) 컴포넌트로 나누기도 한다.
- 프레젠테이셔널 컴포넌트
- View 만을 담당하는 컴포넌트이다 (UI를 작성한다)
- 이 컴포넌트 안에서는 프레젠테이셔널 컴포넌트와 컨테이너 컴포넌트 둘 다 사용할 수 있다
- 리덕스 스토어에 직접적으로 접근하지 않고 props로만 데이터, 함수를 가져온다
- 순수한 컴포넌트로 state를 사용하지 않으며 state가 있을 경우 데이터가 아닌 UI에 대한 state여야 한다.
- 주로 함수형 컴포넌트로 작성된다
- 컨테이너 컴포넌트
- 다른 프레젠테이션 컴포넌트나 컨테이너 컴포넌트를 관리한다
- 내부에 DOM 엘레멘트를 (UI) 작성하지 않는다 (사용하는 경우 감싸는 용도)
- 스타일을 가지고 있지 않는다
- 스타일은 모두 프레젠테이셔널 컴포넌트 내부에 정의되어야 한다
- 상태를 가지고 있고 리덕스에 직접 접근하여 데이터를 가져온다
- dispatch를 하는 함수를 여기서 구현한다
클래스형 컴포넌트와 함수형 컴포넌트의 역할은 동일
import React, { Component } from "react"
class App extends Component {
render() {
const name = "클래스형 컴포넌트"
return <div>{name}</div>
}
}
export default App
import React from "react"
const App = () => {
const name = "함수형 컴포넌트"
return <div>{name}</div>
}
export default App
es6 화살표 함수와 일반 function() 함수의 차이 :
일반 함수(function())는 자신이 종속된 객체를 this로 가리키며, 화살표 함수(() => {})는 자신이 종속된 인스턴스를 가리킨다.
import React, { Component } from "react"
class App extends Component {
constructor(props){
super(props)
this.state = {
test1: [],
test2: "",
number: 1
}
}
testFunction = () => {
this.setState({ number : number +1 })
}
render() {
const name = "클래스형 컴포넌트"
return <div>{name}</div>
}
}
export default App
import React, { useState } from "react"
const App = () => {
const [test1, setTest1] = useState([])
const [test2, setTest2] = useState("")
const [number, setNumber] = useState(0)
const testFunctiond = () => {
setNumber(number +1)
}
const name = "함수형 컴포넌트"
return <div>{name}</div>
}
export default App
Props 란?
- 컴포넌트의 속성을 설정
- 읽기 전용 (컴포넌트 자체 props를 수정하면 안된다.)
- 모든 React 컴포넌트는 자신의 props를 다룰 때 반드시 순수 함수처럼 동작해야한다.
- 수정되는 값은 state 이다.
import React, { Component } from "react"
class App extends Component {
static defaultProps = {
name: "이름 없음"
}
render() {
const {number, testName} = this.props;
const title = "클래스형 컴포넌트";
return <div>{testName}의 나이는 {number}살 입니다.</div>
}
}
// static 키워드와 함께 클래스 내부에 선언 가능
// App.defaultProps = {
// name: "이름 없음"
// }
export default App
import React, { useState } from "react"
const App = ({ number, testName }) => {
const title = "함수형 컴포넌트"
return <div>{testName}의 나이는 {number}살 입니다.</div>
}
import React, { Component } from "react"
class App extends Component {
constructor(props) {
super(props)
this.state = {
number: 1
}
}
onClickFunc = () => {
this.setState({ number : number +1 })
}
render() {
const title = "클래스형 컴포넌트"
return (
<div>
<button onClick={ this.onClickFunc }>++1</button>
</div>
)
}
}
export default
import React, { useState } from "react"
const App = ({ number, testName }) => {
const [number, setNumber] = useState("")
const onClickFunc = () => {
setNumber(number + 1)
}
const title = "함수형 컴포넌트"
return (
<div>
<button onClick={ this.onClickFunc }>++1</button>
</div>
)
}
Life Cycle이란?
React에서 컴포넌트는 여러 종류의 "생명주기 메소드" 를 가지며 이 메소드를 오버라이딩(상속하여 재정의) 하여 특정 시점에 코드가 실행되도록 설정
클래스 컴포넌트에만 해당되는 내용이며, 함수형 컴포넌트는 Hook를 사용하여 생명주기에 원하는 동작을 함
컴포넌트가 인스턴스로 생성되고 DOM 트리에 삽입되어 브라우저상에 나타나는 과정
constructor
- 시점
- 브라우저상에 나타날때 가장 처음 실행되는 함수
- 사용이유
- 생성자 메서드로 this.state의 초기값 설정, 인스턴스에 이벤트 처리 메서드를 바인딩하기 위해 사용한다.
- super(props)를 첫 줄에 필수로 사용한다.
constructor(props) { super(props) this.state = { number: 1 } }
static getDerivedStateFromProps
- 시점
- 컴포넌트가 처음 렌더링 되기 전에도 호출 되고 그 이후 리렌더링 되기 전에도 매번 실행됩니다
- 사용이유
- props로 받은 값을 state에다가 넣고 싶을때 사용 (거의 쓰지 않는 함수.)
static getDerivedStateFromProps(nextProps, prevState) { if (preveState.value !== nextState.value) { return { value: nextState.value } } // 변경 사항이 없거나 아무것도 안할때 return null }
render
- 컴포넌트를 렌더링하는 메서드
컴포넌트 props 또는 state가 바뀌었을 때
시점
사용이유
shouldComponentUpdate(nextProps, nextState) {
return this.props.value !== netxProps.value
}
시점
사용이유
컴포넌트가 브라우저상에서 사라질때
class App extends Component {
state = {
error: false
}
componentDidCatch(error, info) {
this.setState({
error: true
})
}
render() {
if(this.state.error) {
return <p>ERROR</p>
}
return <p>Not ERROR</p>
}
}
export default App