Woongjae Lee
NHN Dooray - Frontend Team
Hooks NEW in React 16.8
컴포넌트 간 통신
Context API
Lead Software Engineer @ProtoPie
Microsoft MVP
TypeScript Korea User Group Organizer
Marktube (Youtube)
이 웅재
useState
useEffect
useContext (Context API 애서 다룹니다.)
npx create-react-app react-hooks-example
import React from 'react';
export default class Example1 extends React.Component {
state = {
count: 0,
};
render() {
const { count } = this.state;
return (
<div>
<p>You clicked {count} times</p>
<button onClick={this.click}>Click me</button>
</div>
);
}
click = () => {
this.setState({ count: this.state.count + 1 });
};
}
import React, { useState } from 'react';
const Example2 = () => {
const [count, setCount] = useState(0);
function click() {
setCount(count + 1);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={click}>Click me</button>
</div>
);
};
export default Example2;
const [스테이트 값, 스테이트 변경 함수] = useState(스테이트 초기값);
import React, { useState } from 'react';
const Example3 = () => {
const [state, setState] = useState({ count: 0 });
function click() {
setState({ count: state.count + 1 });
}
return (
<div>
<p>You clicked {state.count} times</p>
<button onClick={click}>Click me</button>
</div>
);
};
export default Example3;
const [스테이트 값, 스테이트 변경 함수] = useState(스테이트 초기값);
컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어렵습니다.
컨테이너 방식 말고, 상태와 관련된 로직
복잡한 컴포넌트들은 이해하기 어렵습니다.
Class 는 사람과 기계를 혼동시킵니다.
컴파일 단계에서 코드를 최적화하기 어렵게 만든다.
this.state 는 로직에서 레퍼런스를 공유하기 때문에 문제가 발생할 수 있다.
좋은 것일까 ?
import React from 'react';
export default class Example4 extends React.Component {
state = { count: 0 };
componentDidMount() {
console.log('componentDidMount', this.state.count);
}
componentDidUpdate() {
console.log('componentDidUpdate', this.state.count);
}
render() {
const { count } = this.state;
return (
<div>
<p>You clicked {count} times</p>
<button onClick={this.click}>Click me</button>
</div>
);
}
click = () => {
this.setState({ count: this.state.count + 1 });
};
}
import React, { useState, useEffect } from 'react';
const Example5 = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('componentDidMount & componentDidUpdate', count);
});
function click() {
setCount(count + 1);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={click}>Click me</button>
</div>
);
};
export default Example5;
import React from 'react';
export default class Example6 extends React.Component {
state = {
time: new Date(),
};
_timer = null;
componentDidMount() {
this._timer = setInterval(() => {
this.setState({ time: new Date() });
}, 1000);
}
componentWillUnmount() {
clearInterval(this._timer);
}
render() {
const { time } = this.state;
return <div>{time.toISOString()}</div>;
}
}
import React, { useState, useEffect } from 'react';
const Example7 = () => {
const [time, setTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return <div>{time.toISOString()}</div>;
};
export default Example7;
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
function click() {
setCount(count + 1);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={click}>Click me</button>
</div>
);
}
from 리액트
"컴포넌트야,
state가 0 일 때의 UI를 보여줘."
from 컴포넌트
from 리액트
"좋아. UI를 업데이트 하겠어.
이봐 브라우저, 나 DOM에 뭘 좀 추가하려고 해."
from 브라우저
"좋아, 화면에 그려줄게."
리액트
좋아, 이제 컴포넌트 컴포넌트가 준 이펙트를 실행할거야.
() => { document.title = 'You clicked 0 times' } 를 실행.
from 컴포넌트
"이봐 리액트, 내 상태를 1 로 변경해줘."
from 리액트
"상태가 1 일때의 UI를 줘."
from 컴포넌트
"여기 랜더링 결과물로 <p>You clicked 1 times</p> 가 있어."
"그리고 모든 처리가 끝나고 이 이펙트를 실행하는 것을 잊지 마."
() => { document.title = 'You clicked 1 times' }.
from 리액트
"좋아. UI를 업데이트 하겠어.
이봐 브라우저, 나 DOM에 뭘 좀 추가하려고 해."
from 브라우저
"좋아, 화면에 그려줄게."
리액트
좋아, 이제 컴포넌트 컴포넌트가 준 이펙트를 실행할거야.
() => { document.title = 'You clicked 1 times' } 를 실행.
useSomething
// hooks/useWindowWidth.js
import { useState, useEffect } from 'react';
export default function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const onResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', onResize);
return () => {
window.removeEventLister('resize', onResize);
};
}, []);
return width;
}
// hocs/withHasMounted.js
import React from 'react';
export default function withHasMounted(Component) {
class WrapperComponent extends React.Component {
state = {
hasMounted: false,
};
componentDidMount() {
this.setState({
hasMounted: true,
});
}
render() {
const { hasMounted } = this.state;
return <Component {...this.props} hasMounted={hasMounted} />;
}
}
WrapperComponent.displayName = `withHasMounted(${Component.name})`;
return WrapperComponent;
}
// hooks/useHasMounted.js
import { useState, useEffect } from 'react';
export default function useHasMounted() {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
return hasMounted;
}
useReducer
useCallback, useMemo
useRef, useImperativeHandle,
useLayoutEffect
useDebugValue
다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우
다음 state가 이전 state에 의존적인 경우
Redux 를 안다면 쉽게 사용 가능
import React, { useReducer, useEffect } from 'react';
const Example8 = ({ count }) => {
const [state, dispatch] = useReducer(reducer, { count });
useEffect(() => {
setTimeout(() => {
dispatch({ type: 'PLUS' });
}, 2000);
}, []);
function click() {
dispatch({ type: 'PLUS' });
}
return (
<div>
<p>You clicked {state.count} times</p>
<button onClick={click}>Click me</button>
</div>
);
};
export default Example8;
const reducer = (state, action) => {
if (action.type === 'PLUS') {
return {
count: state.count + 1,
};
}
return state;
};
import React, { useState } from 'react';
function sum(persons) {
console.log('sum...');
return persons.map(person => person.age).reduce((l, r) => l + r, 0);
}
const Example9 = () => {
const [value, setValue] = useState('');
const [persons] = useState([{ name: 'Mark', age: 38 }, { name: 'Hanna', age: 27 }]);
function change(e) {
setValue(e.target.value);
}
const count = sum(persons);
return (
<div>
<input value={value} onChange={change} />
<p>{count}</p>
</div>
);
};
export default Example9;
import React, { useState, useMemo } from 'react';
function sum(persons) {
console.log('sum...');
return persons.map(person => person.age).reduce((l, r) => l + r, 0);
}
const Example9 = () => {
const [value, setValue] = useState('');
const [persons] = useState([{ name: 'Mark', age: 38 }, { name: 'Hanna', age: 27 }]);
function change(e) {
setValue(e.target.value);
}
const count = useMemo(() => sum(persons), [persons]);
return (
<div>
<input value={value} onChange={change} />
<p>{count}</p>
</div>
);
};
export default Example9;
import React, { useState } from 'react';
const Example10 = () => {
const [value, setValue] = useState('');
const [persons, setPersons] = useState([
{ id: 0, name: 'Mark', age: 38 },
{ id: 1, name: 'Hanna', age: 27 },
]);
function change(e) {
setValue(e.target.value);
}
function click(id) {
setPersons(
persons => persons.map(person =>
person.id === id
? { ...person, age: person.age + 1 }
: { ...person },
)
);
}
return (
<div>
<input value={value} onChange={change} />
{persons.map(person => (
<Person {...person} key={person.id} click={click} />
))}
</div>
);
};
export default Example10;
const Person = React.memo(({ id, name, age, click }) => {
console.log('Person...');
function onClick() {
click(id);
}
return (
<div>
{name}, {age} <button onClick={onClick}>+</button>
</div>
);
});
import React, { useState } from 'react';
const Example10 = () => {
const [value, setValue] = useState('');
const [persons, setPersons] = useState([
{ id: 0, name: 'Mark', age: 38 },
{ id: 1, name: 'Hanna', age: 27 },
]);
function change(e) {
setValue(e.target.value);
}
const click = useCallback(id => {
setPersons(persons => {
return persons.map(person =>
person.id === id
? {
...person,
age: person.age + 1,
}
: {
...person,
},
);
});
}, []);
return (
<div>
<input value={value} onChange={change} />
{persons.map(person => (
<Person {...person} key={person.id} click={click} />
))}
</div>
);
};
export default Example10;
const Person = React.memo(({ id, name, age, click }) => {
console.log('Person...');
function onClick() {
click(id);
}
return (
<div>
{name}, {age} <button onClick={onClick}>+</button>
</div>
);
});
import React, { useRef, useEffect, useState } from 'react';
const Example11 = () => {
const [count, setCount] = useState(0);
const inputCreateRef = React.createRef();
const inputUseRef = useRef();
console.log(inputCreateRef.current);
console.log(inputUseRef.current);
useEffect(() => {
setTimeout(() => {
setCount(count => count + 1);
}, 1000);
});
return (
<div>
<p>{count}</p>
<input ref={inputCreateRef} />
<input ref={inputUseRef} />
</div>
);
};
export default Example11;
npx create-react-app component-communication
<A /> 컴포넌트에서 button 에 onClick 이벤트를 만들고,
button 을 클릭하면, <A /> 의 state 를 변경하여, <B /> 로 내려주는 props 를 변경
<B /> 의 props 가 변경되면, <C /> 의 props 에 전달
<C /> 의 props 가 변경되면, <D /> 의 props 로 전달
<D /> 의 props 가 변경되면, <E /> 의 props 로 전달
// A 컴포넌트
<div>
<B />
<button>클릭</button>
</div>
// B 컴포넌트
<div>
<C />
</div>
// C 컴포넌트
<div>
<D />
</div>
// D 컴포넌트
<div>
<E />
</div>
// E 컴포넌트
<div>
{props.value}
</div>
import React from "react";
class A extends React.Component {
state = {
value: "아직 안바뀜"
};
render() {
console.log("A render");
return (
<div>
<B {...this.state} />
<button onClick={this._click}>E 의 값을 바꾸기</button>
</div>
);
}
_click = () => {
this.setState({
value: "E 의 값을 변경"
});
};
}
export default A;
const B = props => (
<div>
<p>여긴 B</p>
<C {...props} />
</div>
);
const C = props => (
<div>
<p>여긴 C</p>
<D {...props} />
</div>
);
const D = props => (
<div>
<p>여긴 D</p>
<E {...props} />
</div>
);
const E = props => (
<div>
<p>여긴 E</p>
<h3>{props.value}</h3>
</div>
);
<A /> 에 함수를 만들고, 그 함수 안에 state 를 변경하도록 구현, 그 변경으로 인해 p 안의 내용을 변경.
만들어진 함수를 props 에 넣어서, <B /> 로 전달
<B /> 의 props 의 함수를 <C /> 의 props 로 전달
<C /> 의 props 의 함수를 <D /> 의 props 로 전달
<D /> 의 Props 의 함수를 <E /> 의 props 로 전달, <E /> 에서 클릭하면 props 로 받은 함수를 실행
// A 컴포넌트
<div>
<B />
<p>{state.value}</p>
</div>
// B 컴포넌트
<div>
<C />
</div>
// C 컴포넌트
<div>
<D />
</div>
// D 컴포넌트
<div>
<E />
</div>
// E 컴포넌트
<div>
<button>클릭</button>
</div>
import React from "react";
class A extends React.Component {
state = {
value: "아직 안바뀜"
};
render() {
console.log("A render");
return (
<div>
<h3>{this.state.value}</h3>
<B change={this.change} />
</div>
);
}
change = () => {
this.setState({
value: "A 의 값을 변경"
});
};
}
export default A;
const B = props => (
<div>
<p>여긴 B</p>
<C {...props} />
</div>
);
const C = props => (
<div>
<p>여긴 C</p>
<D {...props} />
</div>
);
const D = props => (
<div>
<p>여긴 D</p>
<E {...props} />
</div>
);
const E = props => {
function click() {
props.change();
}
return (
<div>
<p>여긴 E</p>
<button onClick={click}>클릭</button>
</div>
);
};
npx create-react-app react-context-example
데이터를 Set 하는 놈
가장 상위 컴포넌트 => 프로바이더
데이터를 Get 하는 놈
모든 하위 컴포넌트에서 접근 가능
컨슈머로 하는 방법
클래스 컴포넌트의 this.context 로 하는 방법
펑셔널 컴포넌트의 useContext 로 하는 방법
일단 컨텍스트를 생성한다.
컨텍스트.프로바이더 를 사용한다.
value 를 사용
import React from 'react';
const PersonContext = React.createContext();
export default PersonContext;
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import PersonContext from './contexts/PersonContext';
const persons = [
{ id: 0, name: 'Mark', age: 38 },
{ id: 1, name: 'Hanna', age: 27 },
];
ReactDOM.render(
<PersonContext.Provider value={persons}>
<App />
</PersonContext.Provider>,
document.getElementById('root'),
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
컨텍스트를 가져온다.
컨텍스트.컨슈머를 사용한다.
value 를 사용
import React from 'react';
import PersonContext from '../contexts/PersonContext';
const Example1 = () => (
<PersonContext.Consumer>
{value => <ul>{JSON.stringify(value)}</ul>}
</PersonContext.Consumer>
);
export default Example1;
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Example1 from './components/Example1';
export default function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Example1 />
</header>
</div>
);
}
static contextType 에 컨텍스트를 설정한다.
this.context => value 이다.
import React from 'react';
import PersonContext from '../contexts/PersonContext';
export default class Example2 extends React.Component {
static contextType = PersonContext;
render() {
return <ul>{JSON.stringify(this.context)}</ul>;
}
}
// Example2.contextType = PersonContext;
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Example1 from './components/Example1';
import Example2 from './components/Example2';
export default function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Example1 />
<Example2 />
</header>
</div>
);
}
useContext 로 컨텍스트를 인자로 호출한다.
useContext 의 리턴이 value 이다.
import React, { useContext } from 'react';
import PersonContext from '../contexts/PersonContext';
const Example3 = () => {
const value = useContext(PersonContext);
return <ul>{JSON.stringify(value)}</ul>;
};
export default Example3;
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Example1 from './components/Example1';
import Example2 from './components/Example2';
import Example3 from './components/Example3';
export default function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Example1 />
<Example2 />
<Example3 />
</header>
</div>
);
}
By Woongjae Lee
Fast Campus Frontend Developer School 17th