- function 만들기
- return()안에 html 담기
- <함수명></함수명>쓰기
- 반복적인 html 축약 시
- 큰 페이지들
- 자주 변경되는 것들
단, 컴포넌트의 단점은 state 가져다 쓸 때 문제가 생김
- html css로 미리 디자인 완성
- UI 현재 상태를 state로 저장
- state에 따라 UI 어떻게 보일지 작성
Q. 제목 클릭 시 모달창 띄우기
A. 클릭 시 state만 조절
map()
쓰기
1. 왼족 array 자료만큼 내부 코드 실행
2. return 오른쪽에 있는걸 arr 담아줌
3. 유용한 파리머터 2개 사용
- <자식 컴포넌트 작명 {state이름}/>
- props 파라미터 등록 후 props.작명 사용
props 전송은 부모에서 자식만 가능
- state 자식에 만들면 부모 -> 자식 전송 필요없음
- state가 여러 컴포넌트에서 필요하면 상위에 만들어놔야함
따라서 state만드는 곳은 state 사용하는 최상위 컴포넌트
컴포넌트는
1. 생성이 될 수도 있고 (전문용어로 mount)
2. 재렌더링이 될 수도 있고 (전문용어로 update)
3. 삭제가 될 수도 있습니다. (전문용어로 unmount)
라이플 사이클을 알아야하는 이유는 ?
컴포넌트 라이프 사이클 중간중간에 간섭할 수 있기 때문
즉, 중간중간에 코드 실행이 가능하다.
- useEffect 안에 적은 코드는 html 렌더링 이후에 동작
즉, html을 다 그린 뒤에 실행된다.- 코드의 실행 시점을 조절할 수 있어 조금이라도 html 렌더링이 빠른 사이트를 원하면 쓸데없는 것들은 useEffect 안에 넣는게 좋다.
- 컴포넌트의 핵심 기능은 html 렌더링
- 그 외의 쓸데없는 기능들 :오래걸리는 반복연산, 서버에서 데이터가져오는 작업, 타이머다는 것 등
이런건 useEffect 안에 많이 적습니다.
useEffect(()=>{ 실행할코드 }, [state])
- [ ]에 있는 변수나 state 가 변할 때만 useEffect 안의 코드를 실행
(참고) [ ] 안에 state 여러개 넣을 수 있음
useEffect(()=>{ 실행할코드 }, [])
- 아무것도 안넣으면 컴포넌트 mount시 (로드시) 1회 실행하고 영영 실행안됌
useEffect(()=>{ 실행할코드 return ()=>{} })
- useEffect 동작하기 전에 특정코드를 실행하고 싶으면 return ()=>{} 안에 작성
(참고1) clean up function에는 타이머제거, socket 연결요청제거, ajax요청 중단 이런 코드를 많이 작성
(참고2) clean up function은 mount시엔 실행안됌
그러나 컴포넌트 unmount 시에는 clean up function 안에 있던게 1회 실행 됌
useEffect(()=>{ 실행할코드 })
1. 이러면 재렌더링마다 코드를 실행가능합니다.
useEffect(()=>{ 실행할코드 }, [])
2. 이러면 컴포넌트 mount시 (로드시) 1회만 실행가능합니다.
useEffect(()=>{ return ()=>{ 실행할코드 } })
- 이러면 useEffect 안의 코드 실행 전에 항상 실행됩니다.
useEffect(()=>{ return ()=>{ 실행할코드 } }, [])
- 이러면 컴포넌트 unmount시 1회 실행됩니다.
useEffect(()=>{ 실행할코드 }, [state1])
- 이러면 state1이 변경될 때만 실행됩니다.
- state 변경함수들이 연달아서 여러개 처리되어야한다면 state 변경함수를 다 처리하고 마지막에 한 번만 재렌더링
- 찾아보면 setTimeout 말고 flushSync() 이런거 써도 될 것 같기도 합니다. automatic batching을 막아줌
Q. 최상단 부모에 있던 state를 자식의 자식 컴포넌트에서 사용하고 싶어지면 어떻게하는가 ?
A. 부모에 -> 자식 -> 자식의 자식 props를 2번 전송
A2. Context API 문법을 쓰기
A3. Redux 같은 외부 라이브러리
- Context API 문법으로 props 없이 state 공유하기
- createContext() 함수를 가져와서 context를 하나 만듦
- context를 쉽게 비유해서 설명하자면 state 보관함
- createContext()함수로 만든 컴포터는로 원하는 곳 감싸고 공유를 원하는 state를 value 안에 다 적기
- 만들어둔 Context를 import 해옵니다.
- useContext() 안에 넣으면 그 자리에 공유했던 state가 전부 남음
- state 변경시 쓸데없는 컴포넌트까지 전부 재렌더링
- useContext() 를 쓰고 있는 컴포넌트는 나중에 다른 파일에서 재사용할 때 Context를 import 하는게 귀찮아질 수 있습니다.
그래서 이것 보다는 redux 같은 외부라이브러리 사용
- state를 Redux store에 보관가능하여 모든 컴포넌트가 거기 있던 state를 직접 꺼내쓸 수 있음
- 즉, props 없어도 편리하게 state 공유가 가능
- Redux는 props 없이 state를 공유할 수 있게 도와주는 라이브러리
Redux를 쓰면 js 파일 하나에 state들을 보관해 모든 컴포넌트가 직접 꺼내쓰기에 props 전송이 필요없어짐
import { configureStore } from '@reduxjs/toolkit' export default configureStore({ reducer: { } })
- store.js 파일을 만들어서 위 코드를 적는데 이는 state들을 보관하는 파일
import { Provider } from "react-redux"; import store from './store.js' const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <Provider store={store}> <BrowserRouter> <App /> </BrowserRouter> </Provider> </React.StrictMode> );
- 밑에
<Provider store={import해온거}>
이걸로<App/>
을 감싸기<App/>
과 그 모든 자식컴포넌트들은 store.js에 있던 state를 맘대로 꺼내쓸 수 있음
- store.js 파일 열어 다음과 같이 코드를 짜면 state 하나 만들 수 있음
- createSlice( ) 로 state 만들기
- configureStore( ) 안에 등록
import { configureStore, createSlice } from '@reduxjs/toolkit' let user = createSlice({ name : 'user', initialState : 'kim' }) export default configureStore({ reducer: { user : user.reducer } })
createSlice( ) 상단에서 import
{ name : 'state이름', initialState : 'state값' } 넣으면 state 하나 생성
(createSlice( ) 는 useState( ) 와 용도가 비슷)
state 등록은 configureStore( ) 안에 등록
{ 작명 : createSlice만든거.reducer } 이렇게 등록한 state는 모든 컴포넌트가 자유롭게 사용가능
import { useSelector } from "react-redux" function Cart(){ let a = useSelector((state) => { return state } ) console.log(a) return (생략) }
- 아무 컴포넌트에서 useSelector((state) => { return state } ) 쓰면 store에 있던 모든 state가 그 자리에 나음
let user = createSlice({ name : 'user', initialState : 'kim', reducers : { changeName(state){ return 'john ' + state } } })
slice 안에 reducers : { } 열고 거기 안에 함수 만들기
- 함수 작명
- 파라미터 하나 작명하면 그건 기존 state
- return 우측에 새로운 state 입력하면 그걸로 기존 state를 갈아치움
export let { changeName } = user.actions
- 이런 코드 store.js 밑에 추가
- slice이름.actions 라고 적으면 state 변경함수가 전부 그 자리에 출력
해 변수에 저장했다가 export
import { useDispatch, useSelector } from "react-redux" import { changeName } from "./../store.js" <button onClick={()=>{ dispatch(changeName()) }}>버튼임</button>
- store.js에서 원하는 state변경함수 가져오고
- useDispatch 라는 것도 라이브러리에서 가져오기
- 그리고 dispatch( state변경함수() ) 이렇게 감싸서 실행하면 state 변경
- 컴포넌트 100개에서 직접 'kim' 이라는 state 변경하다가 갑자기 'kim'이 123이 되어버리는 버그가 발생하면 찾기 찾으려고 컴포넌트 100개를 다 뒤져야함
- state 수정함수를 store.js에 미리 만들어두고 컴포넌트는 그거 실행해달라고 부탁만 하는 식으로 코드를 짜면 'kim'이 123이 되어버리는 버그가 발생했을 때 찾기 수월
- 원인은 무조건 store.js에 있으니 (수정함수를 잘 만들어놨다면)
let user = createSlice({
name : 'user',
initialState : {name : 'kim', age : 20},
reducers : {
changeName(state){
state.name = 'park'
}
}
})
state를 직접 수정해도 변경 잘 잘 변경되는 이유는 Immer.js 라이브러리가 state 사본을 하나 더 생성해준 덕분인데 Redux 설치하면 함께 쓸 수 있어서
- array/object 자료의 경우 state변경은 state를 직접 수정
(참고) 그래서 state 만들 때 문자나 숫자하나만 필요해도 redux에선 일부러 object 아니면 array에 담는 경우도 있음
- state 변경함수에도 파라미터문법 사용가능
let user = createSlice({ name : 'user', initialState : {name : 'kim', age : 20}, reducers : { increase(state, a){ state.age += a.payload } } })
- state변경함수의 둘째 파라미터를 작명하면 파라미터입력을 해서 함수사용이 가능
- 파라미터자리에 넣은 자료들은 a.payload 하면 사용 가능
(참고) 파라미터는 action 식으로 작명 / action.type 하면 state 변경함수 이름 / action.payload 하면 파라미터
- 사이트마다 5MB 정도의 문자 데이터를 저장 가능
- object 자료랑 비슷하게 key/value 형태로 저장
- local Storage는 유저가 브라우저 청소를 하지 않는 이상 영구적으로 남아있음
- Session Storage도 똑같은데 브라우저 끄면 삭제되는 휘발성 가짐
- 차례로 추가, 읽기, 삭제 문법입니다.
localStorage.setItem('데이터이름', '데이터');
localStorage.getItem('데이터이름');
localStorage.removeItem('데이터이름')
localStorage에 array/object 자료를 저장하려면?
localStorage.setItem('obj', JSON.stringify({name:'kim'}) );
- JSON.stringify() 라는 함수에 array/object를 집어넣으면 그 자리에 JSON으로 변환된걸 남겨줌
var a = localStorage.getItem('obj'); var b = JSON.parse(a)
JSON -> array/object 변환하고 싶으면 JSON.parse()를 쓰기
localStorage에 state를 자동저장
- redux-persist 라이브러리 설치해서 쓰면 redux store 안에 있는 state를 자동으로 localStorage에 저장
- state 변경될 때마다 그에 맞게 localStorage 업데이트도 알아서 해주지만 셋팅문법 복잡하고 귀찮
ajax 요청 시 추가 기능들
- 몇초마다 자동으로 데이터 다시 가져오기
- 요청실패시 몇초 간격으로 재시도
- 다음 페이지 미리가져오기
- ajax 성공/실패시 각각 다른 html을 보여주기
가령 SNS, 코인거래소같은 실시간 데이터를 보여줘야하는 사이트 시 유용
- 라이브러리 이름이 react-query에서 @tanstack/react-query로 바뀜
npm install @tanstack/react-query
- 설치
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'
- import 해서 사용2
import { QueryClient, QueryClientProvider } from "react-query" //1번 const queryClient = new QueryClient() //2번 const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <QueryClientProvider client={queryClient}> //3번 <Provider store={store}> <BrowserRouter> <App /> </BrowserRouter> </Provider> </QueryClientProvider> );
useQuery(['작명'],
- useQuery쓸 때 '작명' 말고 ['작명']
1. ajax 요청 성공/실패/로딩중 상태를 쉽게 파악
result라는 변수에 ajax 현재 상태가 알아서 저장
- ajax요청이 로딩중일 땐 result.isLoading 이 true
- ajax요청이 실패시엔 result.error 가 true.
- ajax요청이 성공시엔 result.data 안에 데이터가 들어옴
2. 틈만나면 알아서 ajax 재요청해줍니다.
- 페이지 체류하고나서 일정시간이 경과하거나 다른 창으로 갔다가 다시 페이지로 돌아오거나 여러 경우에 알아서 ajax 요청을 다시 해줌
- 재요청 끄는 법, 재요청간격 조절 가능
장점3. 실패시 재시도 알아서 해줌
3. 실패시 재시도 알아서 해줌
- 잠깐 인터넷이 끊겼거나 서버가 죽었을 때 등 ajax 요청이 실패했을 때는 4, 5번 재시도를 알아서 해줌
4. ajax로 가져온 결과는 state 공유 필요없음
- props 전송 필요어지는데 필요한 컴포넌트에다가 ajax 요청하는 코드 똑같이 또 적어서 가져올 수 있음
react-query는 스마트하기 때문에 ajax 요청이 2개나 있으면 1개만 날려주고
5. 캐싱기능
- 이미 같은 ajax 요청을 한 적이 있으면 그걸 가져와서 씀
react-query가 주장하는 장점
- server-state (DB 데이터)를 프론트엔드에서 실시간 동기화해주는걸 도와줌
- 근데 ajax 요청을 몇초마다 계속 날려서 가져오는 방식이라 좀 비효율적일 수도 있습니다.
- 실시간으로 서버에서 데이터를 자주 보내려면 웹소켓이나 Server-sent events 같은 가벼운 방식들도 있음
- react-query는 ajax 관련 기능개발 편하게 할 수 있는데에 의의가 더 있음
React Developer Tools 설치
Components 탭
- 개발중인 리액트사이트를 컴포넌트로 미리볼 수 있음
-왼쪽 컴포넌트구조 파악이 가능- 왼쪽상단 아이콘눌러서 컴포넌트 찍으면 거기 있는 state, props 이런거 조회 및 수정 가능
Profiler 탭
- 성능평가 가능
- Profiler 탭 들어가서 녹화버튼 누르고 페이지 이동이나 버튼조작을 해보고 녹화를 끝내면
방금 렌더링된 모든 컴포넌트의 렌더링시간을 측정
- 하나의 큰 자바스크립트 파일을 잘게 분할하는 방법은 import 문법을 약간 고치기
import Detail from './routes/Detail.js' import Cart from './routes/Cart.js'
import {lazy, Suspense, useEffect, useState} from 'react'
const Detail = lazy( () => import('./routes/Detail.js') )
const Cart = lazy( () => import('./routes/Cart.js') )
- lazy 문법으로도 똑같이 import가 가능한데 "Detail 컴포넌트가 필요해지면 import 해주세요" 라는 뜻
- 그리고 이렇게 해놓으면 Detail 컴포넌트 내용을 다른 js 파일로 쪼개주기에 첫 페이지 로딩속도를 향상
```js
<Suspense fallback={ <div>로딩중임</div> }>
<Detail shoes={shoes} />
</Suspense>
<Suspense>
이걸로 <Routes>
전부 감싸도 가능
- 컴포넌트가 재렌더링되면 거기 안에 있는 자식컴포넌트는 항상 함께 재렌더링 되어 자식컴포넌트가 렌더링시간이 오래 걸리는 무거운 경우가 있음
memo()로 컴포넌트 불필요한 재렌더링 막기
- memo를 'react' 라이브러리로부터 import
- 원하는 컴포넌트 정의부분을 감싸기
단, 컴포넌트let 컴포넌트명 = function( ){ }
식으로 만들어야 감쌀 수 있음
let res = 복잡한함수(){ }
useMemo(() => {
return 함수()
}, [state])
// 컴포넌트 렌터링 시 1회만 실행
-useEffect와 useMemo차이는 ? HTML 보여지는 return이 되면 그 때 실행되지만 useMemo는 렌더링 될때 실행된다 즉 실행 시점의 차이가 있다.
리액트18버전 이후부터 렌더링 성능이 저하되는 컴포넌트에서 쓸 수 있는 혁신적인 기능 추가
automatic batching 기능
setCount(1) setName(2) setValue(3) //여기서 1번만 재렌더링됨
- state변경함수를 연달아서 3개 사용하면 재렌더링도 원래 3번 되어야하지만 재렌더링을 마지막에 1회만 처리
- 일종의 쓸데없는 재렌더링 방지기능을 batching이라 함
- 만약 batching 되는게 싫고 state변경함수 실행마다 재렌더링시키고 싶다면 flushSync라는 함수를 사용
렌더링시간이 매우 오래걸리는 컴포넌트 개선방법
import {useState, useTransition} from 'react'
let a = new Array(10000).fill(0)
function App(){
let [name, setName] = useState('')
let [isPending, startTransition] = useTransition()
return (
<div>
<input onChange={ (e)=>{
startTransition(()=>{
setName(e.target.value)
})
}}/>
{
a.map(()=>{
return <div>{name}</div>
})
}
</div>
)
}
- startTransition() 으로 감싼 코드가 처리중일 때 true로 변하는 변수
{ isPending ? "로딩중기다리셈" : a.map(()=>{ return <div>{name}</div> }) }
-startTransition()와 용도가 똑같은데 state 아니면 변수하나를 집어넣을 수 있어서 변수에 변동사항이 생기면 그것을 늦게 처리
import {useState, useTransition, useDeferredValue} from 'react' let a = new Array(10000).fill(0) function App(){ let [name, setName] = useState('') let state1 = useDeferredValue(name) return ( <div> <input onChange={ (e)=>{ setName(e.target.value) }}/> { a.map(()=>{ return <div>{state1}</div> }) } </div> ) }
- useDeferredValue 안에 state를 집어넣으면 그 state가 변동사항이 생겼을 때 나중에 처리해주고 처리결과는 let state에 저장
- Progressive Web App, 웹사이트를 안드로이드/iOS 모바일 앱처럼 사용할 수 있게 만드는 일종의 웹개발 기술
장점
- 스마트폰, 태블릿 바탕화면에 웹사이트를 설치 가능
- 오프라인에서도 동작할 수 있습니다.
service-worker.js 라는 파일과 브라우저의 Cache storage덕분에- 설치 유도 비용이 매우 적음
npx create-react-app 프로젝트명 --template cra-template-pwa
Q. 프로젝트 다시만들기 싫다면?
- 다른 폴더에 위 명령어를 이용해 프로젝트 새로 하나 만든 다음에
- 기존 프로젝트의 App.js App.css index.js 이런 파일들을 새 프로젝트로 복붙
건드린 파일은 다 복붙하는데 index.js 파일은 많이 바뀐점이 좀 있어서 차이점만 잘 복붙- router, redux 이런 라이브러리를 설치했다면 그것도 새프로젝트에서 다시 설치
function App(){ let [name, setName] = useState('kim') }
- setName을 사용하려면 name이라는 state를 자유롭게 변경가능
EX>setName('park')
이런 식으로 하면 변경- 문제는 setName() 같은 state 변경함수들은 전부 asynchronous (비동기적)으로 처리
- 즉, setName()이 오래걸리면 이거 제껴두고 다른 밑에 있는 코드들부터 실행하여 뭔가 예상치 못한 문제가 생길 수 있음
function App() { let [count, setCount] = useState(0); let [age, setAge] = useState(20); console.log(count, age); return ( <div className="App"> <div>안녕하십니까 전 {age}</div> <button onClick={() => { setCount(count + 1); if (count < 3) { setAge(age + 1); } }} 누르면한살먹기 </button> </div> ); }
- state 변경함수는 async 하게 처리되는 함수기 때문에 완료되기까지 시간이 오래걸리면 제쳐두고 다음 코드를 실행
① 버튼을 세번째 누르면 setCount(count+1); 이걸 실행해서 count를 3을 만들어줌
② 근데 count를 3으로 만드는건 오래걸리니까 제껴두고 if ( count > 3 ) {} 실행
③ 이 때 count는 아직 2라서 if문 안의 setAge(age+1)이 잘 동작
이 모든 문제는 setCount()가 async 함수라서 async함수는 오래걸리면 제껴두고 다음 줄 코드부터 실행
- state1 변경하고나서 state2를 변경하는 코드를 작성할 땐 가끔 문제 생김
- 이걸 정확히 sync스럽게, 순차적으로 실행하고 싶을 때 해결책은 useEffect를 잘 작성하면 특정 state가 변경될 때 useEffect를 실행
function App() { let [count, setCount] = useState(0); let [age, setAge] = useState(20); useEffect(() => { if (count !== 0 && count < 3) { setAge(age + 1); } }, [count]); return ( <div className="App"> <div>안녕하십니까 전 {age}</div> <button onClick={() => { setCount(count + 1); }} 누르면한살먹기 </button> </div> ); }