특정 DOM 선택하기
리액트에서는 ref를 사용
- 함수용 컴포넌트에서
- useRef 사용
- 클래스형 컴포넌트에서
- React.creasteRef() 또는 callback 함수를 사용
커서를 name input란으로 옮기기
input DOM을 선택하여 커서를 옮기자.
const nameInput = useRef();
- useRef 메소드를 사용한다.
<input name = "name" onChange = {onChange} value = {inputs.name} ref = {nameInput}/>
- 이 input란으로 DOM 선택을 한다.
nameInput.current.focus();
- reset 시 현재 선택한 DOM으로 이동한다.
전체 코드
import React,{useState, useRef} from "react"; function InputSample(){ const [inputs, setText] = useState({ name: '', nickname: '', }); const nameInput = useRef(); const {name, nickname} = inputs; const onChange = (e) =>{ const {name, value} = e.target; setText({ ...inputs,//불변성을 지킨다. [name]: value, }); } const onReset = (e) => { setText({ name: '', nickname: '', }); nameInput.current.focus(); } return( <div> <input name = "name" onChange = {onChange} value = {inputs.name} ref = {nameInput} /> <input name = "nickname" onChange = {onChange} value = {inputs.nickname} /> <button onClick = {onReset}>초기화</button> <div> <b>값: </b> {inputs.name} <br/> <b>값: </b> {inputs.nickname} </div> </div> ); } export default InputSample;
useRef는 언제 쓰는가?
- useRef로 관리되는 값은 바뀌어도 컴포넌트가 리렌더링 되지 않는다.
- 위에서의 경우 말고 또한 어떤 변수를 기억하고 싶을 때 사용하기도 한다.
- 그 변수의 값이 바뀐다고 하더라도 그 컴포넌트는 리렌더링 되지 않는다.
- 언제 쓰일 수 있는가?
- setTimeout
- setInerval의 id
- 외부 라이브러리를 사용하여 생성된 인스턴스
- Scroll 위치 etc...
배열 렌더링하기
직접 가져와서 쓰기
import React from 'react'; function User({user}){ return( <div> <b>{user.username}</b><span>{user.email}</span> </div> ); } function UserList(){ const users = [ { id: 1, username: 'velopert', email: 'public.velopert@gmail.com' }, { id: 2, username: 'tester', email: 'tester@example.com' }, { id: 3, username: 'MyTest', email: 'MyTest@example.com' } ]; return ( <div> <User user = {users[0]}/> <User user = {users[1]}/> <User user = {users[2]}/> </div> ) } export default UserList;
map 함수 사용
import React from 'react'; function User({user}){ return( <div> <b>{user.index}<br/></b><b>{user.username}</b><span>{user.email}</span> </div> ); } function UserList(){ const users = [ { id: 1, username: 'velopert', email: 'public.velopert@gmail.com' }, { id: 2, username: 'tester', email: 'tester@example.com' }, { id: 3, username: 'MyTest', email: 'MyTest@example.com' } ]; return ( <div> { users.map( (user, index) => (<User user={user} key={user.id}/>) ) } </div> ) } export default UserList;
- map은 key와 value로 이루어져 있다.
- 따라서 key를 설정해주는 것을 권장하는데, 할 것이 없다고 index로 하는 것은 매우 비효율적이다.
배열 추가하기
CreateUser.js
import React from 'react'; function CreateUser({username, email, onChange, onCreate}){ return( <div> <input name = "username" placeholder = "계정명" onChange = {onChange} value = {username} /> <input name = "email" placeholder = "이메일" onChange = {onChange} value = {email} /> <button onClick = {onCreate}>등록</button> </div> ); } export default CreateUser;
App.js
import React, {useRef, useState} from 'react'; import CreateUser from './CreateUser'; import UserList from './UserList'; function App() { const [inputs, setInputs] = useState({ username: '', email: '' }); const {username, email} = inputs; const onChange = (e) =>{ const {name, value} = e.target; console.log(name); console.log(value); setInputs({ ...inputs, [name]: value }); } const [users, setUsers] = useState([ { id: 1, username: 'velopert', email: 'public.velopert@gmail.com' }, { id: 2, username: 'tester', email: 'tester@example.com' }, { id: 3, username: 'MyTest', email: 'MyTest@example.com' } ]); const nextId = useRef(4); const onCreate = ()=>{ const user = { id: nextId.current, username, email, }; // 여기는 배열을 새로 생성하여 복사하는 방법 //setUsers([...users, user]); // 여기는 concat 함수 사용 setUsers(users.concat(user)); //push 와 같은 연산으로는 업뎃이 안됨 setInputs({ username: '', email: '' }); console.log(nextId.current); nextId.current += 1; } return ( <> <CreateUser username={username} email = {email} onChange = {onChange} onCreate = {onCreate} /> <UserList users = {users}/> </> ); } export default App;
요소를 추가하는데 두 가지 방법이 있다.
- 배열을 새로 생성하여 복사하는 방법
setUsers([...users, user]);
- users라는 기존 배열을 복사하고, user이라는 값을 추가하여 배열을 생성한다.
- 그 후 setUsers를 통해 배열을 갱신한다.
- concat 함수 사용
- concat: 두 개의 배열을 합칠 때 사용
- 이를 이용하여 기존 배열에 user이라는 값을 배열삼아 결합한다.
- 배열을 새로 생성하여 복사하는 방법
배열 항목 제거하기
UserList.js
import React from 'react'; function User({user, onRemove}){ return( <div> <b>{user.username}</b> <span>{user.email}</span> <button onClick = {()=>onRemove(user.id)}>삭제</button> </div> ); } function UserList({users, onRemove}){
return (
<div>
{
users.map(
(user, index) => (
<User
user={user}
key={user.id}
onRemove = {onRemove}
/>)
)
}
</div>
)
}
export default UserList;
```
- button에 `onClick = {()=>onRemove(user.id)}`으로 해야한다.
- `onClick = {onRemove(user.id)}`는 렌더링 할 때 메소드가 호출되어 먼저 삭제가 되는 상황이 발생한다.
- 따라서, 함수를 생성해주고 해당 함수 안에 onRemove를 호출한다.
- App.js
- 위 코드에서 다음 메소드를 삽입한다.
const onRemove = id => { setUsers(users.filter(user => user.id !== id)); };
- 그 후 UserList에 onRemove를 넣어준다.
- filter: 주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환한다.
- 따라서, 입력받은 id와 다른 모든 값을 통과시켜 배열로 반환해주고
- 같은 id는 해당 배열에 속하지 않고 배열이 업데이트 된다.
- 위 코드에서 다음 메소드를 삽입한다.
배열 항목 수정하기
map을 통해 해당 요소를 찾아 그 요소의 값을 수정한다.
username의 색상을 바꾸는 코드를 구성해보자.
App.js 에서 다음의 코드를 추가한다.
- 각 배열의 요소 active를 추가한다.
const onToggle = id => { setUsers(users.map( user => user.id === id ? {...user, active: !user.active} : user )); }
- map을 통해 입력된 id와 같은 id를 찾는다.
- 그 후 user의 저장된 데이터를 복사하고 active 요소만 반전시킨다.
- 그 외의 값들은 이전 데이터를 그대로 넘겨준다.
- 각 배열의 요소 active를 추가한다.
UserLIst.js
import React from 'react'; function User({user, onRemove, onToggle}){ const {username, email, id, active} = user; const style = { color: active ? 'red' : 'yellow', cursor: `pointer` }; return( <div> <b style = {style} onClick = {() => onToggle(id)} > {username} </b> <span>{email}</span> <button onClick = {()=>onRemove(id)}>삭제</button> </div> ); } function UserList({users, onRemove, onToggle}){ return ( <div> { users.map( (user, index) => ( <User user={user} key={user.id} onRemove = {onRemove} onToggle = {onToggle} />) ) } </div> ) } export default UserList;
useEffect
화면이 나타날 때 혹은 사라질 때 특정 작업을 수행함
컴포넌트의 props나 상태가 바뀌어서 업데이트가 되기 전 또는 후에도 특정 작업을 수행함
즉, 리렌더링 될 때마다 작업을 수행함
example -> UserList.js
function User({user, onRemove, onToggle}){ const {username, email, id, active} = user; useEffect(()=>{ console.log('컴포넌트가 화면에 나타남'); return () => { console.log('컴포넌트가 화면에서 사라짐'); } }, []); const style = { color: active ? 'red' : 'yellow', cursor: `pointer` }; return( <div> <b style = {style} onClick = {() => onToggle(id)} > {username} </b> <span>{email}</span> <button onClick = {()=>onRemove(id)}>삭제</button> </div> ); }
[]
안에 데이터를 넣으면 해당 데이터가 변경 감지가 되었을 때 내용을 실행한다.- 만일
[]
없이 한다면, 부모 컴포넌트의 변경 감지를 하게 됨
- 만일
useEffect 마운트 되면서 작업하는 내용
- props로 받은 값을 컴포넌트의 state로 설정할 수 있음
- 외부 API 요청을 할 수 있음
- 라이브러리를 사용할 경우에도 내부에서 사용 가능
- setInterval, setTimeout 또한 가능
useEffect 언마운트 되면서 작업하는 내용
- clearInterval, clearTimeout 여기서 작업함
- 라이브러리 인스턴스 제거
useMemo
이전에 연산된 값을 재사용 함
주로 성능을 최적화 하는 곳에서 사용함
- App.js에 다음과 같은 함수를 추가한다.
function countActiveUsers(users){ console.log('활성 사용자 수를 세는 중...'); return users.filter(user => user.active).length; }
- 이는 users의 active상태가 true인 값들만 찾아서 길이를 리턴한다.
- 그 후 count 변수를 통해 해당 값을 출력해보자.
const count = countActiveUsers(users);
- 이렇게 해서 출력을 해보면 제대로 원하는 값이 나온다.
- 하지만 input란에 입력할 때마다 호출이 된다.
- 이는 가장 루트 컴포넌트가 변화를 감지했기 때문인데, 우리는 단지 등록을 통해 원하는 경우에 해당 메소드를 호출하고 싶다.
- 이를 위해 useMemo를 이용한다.
const count = useMemo(() => countActiveUsers(users), [users]);
- 이로써 등록을 통해 users가 변화가 생겼을 때 해당 함수를 호출한다.
useCallback
이전에 만들었던 함수를 재사용
위 모든 함수들은 컴포넌트가 리렌더링 될 때마다 새로 만들어진다.
하지만 같은 작업을 반복해야 하는 상황인데 리렌더링 될 때마다 새로 만들어지는 것은 비효율 적이다.
물론 메모리 및 리소스를 많이 차지하지는 않으나 계속 생성되는 작업은 불필요하다.
따라서, useCallback을 통해 한번 만들어 두고 계속 사용할 수 있도록 하자.
- App.js
const onChange = useCallback( (e) =>{ const {name, value} = e.target; console.log(name); console.log(value); setInputs({ ...inputs, [name]: value }); }, [inputs]);
- 위 모든 함수를 이렇게 useCallback으로 묶는다.
React.memo
컴포넌트에서 리렌더링이 불필요 할 때 이전에 썻던 결과를 재사용 할 수 있도록 함
리렌더링 성능 최적화
- 항상 리렌더링이 불필요한 요소에 React.memo를 감싸준다.
- 예를 들어 User 컴포넌트는 한번 만들고 다른 값이 바뀔 때 이 값까지 리렌더링이 될 필요는 없다.
따라서, User컴포넌트는 React.memo를 감싸줄 필요가 있다.
- 예를 들어 User 컴포넌트는 한번 만들고 다른 값이 바뀔 때 이 값까지 리렌더링이 될 필요는 없다.
useReducer
컴포넌트 상태를 업데이트
useState: 설정하고 싶은 다음 상태를 직접 지정해주는 방식
useReducer: Action이라는 객체를 기반으로 상태를 업데이트
Action 객체: 업데이트 할 때 참조하는 객체
useReducer는 상태 업데이트 로직을 컴포넌트 밖으로 분리가 가능하다.
다른 파일에 작성 후 불러와 사용이 가능하다.
reducer: 현재상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 업데이트 하는 함수
function reducer(state, action){ return nextState; }
- reducer가 반환하는 상태는 곧 컴포넌트가 지닐 새로운 상태이다.
- action은 업데이트를 위한 정보를 가지고 있다.
- 주로 type 값을 지닌 객체 형태로 사용한다.
useReducer 사용
const [state, dispatch] = useReducer(reducer, initialState);
- state: 컴포넌트에서 사용할 수 있는 상태
- dispatch: 액션을 발생시키는 함수
Counter.js
import React, {useReducer} from 'react'; function reducer(state, action){ switch(action.type){ case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: throw new Error('Unhandled action'); } } function Counter(){ const [number, dispatch] = useReducer(reducer, 0); const Increase = () => { dispatch({ type: 'INCREMENT' }); } const Decrease = () => { dispatch({ type: 'DECREMENT' }); }; return ( <div> <h1>{number}</h1> <button onClick={Increase}>+1</button> <button onClick={Decrease}>-1</button> </div> ) } export default Counter;
useReducer를 이용하여 App.js수정
import React, {useRef, useState, useMemo, useCallback, useReducer} from 'react';
import CreateUser from './CreateUser';
import UserList from './UserList';
function countActiveUsers(users){
console.log('활성 사용자 수를 세는 중...');
return users.filter(user => user.active).length;
}
const initialState = {
inputs:{
username: '',
email: '',
active: true,
},
users: [
{
id: 1,
username: 'velopert',
email: 'public.velopert@gmail.com',
active: true
},
{
id: 2,
username: 'tester',
email: 'tester@example.com',
active: true
},
{
id: 3,
username: 'MyTest',
email: 'MyTest@example.com',
active: true
}
]
}
function reducer(state, action){
switch(action.type){
case 'CHANGE_INPUT':
return{
...state,
inputs: {
...state.inputs,
[action.name]: action.value,
}
}
case 'CREATE_USER':
return{
inputs: initialState.inputs,
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 'REMOVE_USER':
return{
...state,
users: state.users.filter(
user => user.id !== action.id
)
}
default:
throw new Error('Unhandled Action');
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
const nextId = useRef(4);
const {users} = state;
const {username, email} = state.inputs;
const onChange = useCallback( (e) =>{
const {name, value} = e.target;
dispatch({
type: 'CHANGE_INPUT',
name,
value,
})
}, []);
const onCreate = useCallback( ()=>{
dispatch({
type: 'CREATE_USER',
user: {
id: nextId.current,
username,
email,
active: true,
}
})
}, [username, email]);
const onRemove = useCallback( id => {
dispatch({
type: 'REMOVE_USER',
id
})
}, [])
const onToggle = useCallback(id => {
dispatch({
type: 'TOGGLE_USER',
id
})
}, []);
const count = useMemo(() => countActiveUsers(users), [users]);
return (
<>
<CreateUser
username={username}
email = {email}
onChange = {onChange}
onCreate = {onCreate}
/>
<UserList users = {users} onRemove = {onRemove} onToggle = {onToggle}/>
<div>활성 사용자 수: {count}</div>
</>
);
}
export default App;
커스텀 Hook 만들기
useInput 만들기
uesInput.js
import {useState, useCallback} from React; function useInput(initialForm){ const [form, setForm] = useState(initialForm); const onChange = useCallback(e => { const {name, value} = e.target; setForm(form => ({ ...form, [name]: value})); }, []); const reset = useCallback(() =>{ setForm(initialForm) }, []); return [form, onChange, reset]; } export default useInput;
'WebStudy' 카테고리의 다른 글
React API (0) | 2021.02.18 |
---|---|
React 기본 3 (0) | 2021.02.15 |
React 기본 1 (0) | 2021.02.12 |
Web Server 및 WAS (0) | 2021.02.06 |
Browser (0) | 2021.02.06 |
최근댓글