(node.js랑 npx 설치되어있어야함)
npx create-react-app 프로젝트이름 (vue에서 vue-cli 역할)
크롬 확장프로그램에서 React developer tools 라는 확장프로그램 받기
import React, { useState } from 'react';
const [count, setCount] = useState(0);
useState를 쓸때는 위와같이 useState를 react에서 가져와야함
상위 컴포넌트에서 템플릿구문에 props를 내려줌.
<Counter initialValue={number} />
컴포넌트를 children으로 아래와같이 템플릿안에 내용을 넣을때
//상위
<Container>
<div className="App">
<Counter {...counterProps} />
</div>
</Container>
//껍데기 컴포넌트
const Container = ({children}) =>{
return(
<div style={{margin:20, padding:20, border:"1px solid #222"}}>
{children}
</div>
)
}
export default Container
포커스 기능 쓸때 사용
const contentInput = useRef();
contentInput.current.focus();
태그 속성으로
ref={contentInput}
가져온 배열을 map으로 하나씩 보여줌 vue에서 v-for=item in List와 같은 역할
{diaryList.map((it) =>(
<div>
<div>작성자 : {it.author}</div>
</div>
))}
함수에 defaultProps 추가해서 설정해주기
DiaryList.defaultProps={
diaryList : [],
}
가장 상위 div에
key={it.id} 추가
id값이 배열중에 없다면 그냥 idx을 넣어도 무방
{diaryList.map((it) =>(
<div key={it.id}>
<div>작성자 : {it.author}</div>
<div>일기 : {it.content}</div>
<div>감정 : {it.emotion}</div>
<div>작성 시간(ms) : {it.create_date}</div>
</div>
))}
라이프사이클을 관리하는 기능
컴포넌트가 MOUNT되었을때 주는 기능
const [count,setCount] = useState(0);
const [text,setText] = useState("");
//처음 mount시 (vue:mounted)
useEffect(()=>{
console.log("Mount!");
},[]);
//업데이트시 (vue:updated)
useEffect(() =>{
console.log("update");
});
(vue:watch)
//usestate가 변할때만 감지해서 작동시킬수 있음
useEffect(()=>{
console.log(`count is update : ${count}`);
if(count > 5){
alert("count가 5를 넘었습니다 따라서 1로 초기화합니다.");
setCount(1);
}
},[count]);
//component가 사라질때(unmounted 역할)
useEffect(()=>{
return () => console.log("unmounted");
},[])
연산최적화 역활, 어떤값이 변화할때만 함수를 작용하고싶을때 쓴다
단, 주의할점은 useMemo를 쓸 경우 변수가 함수가 아니라 그대로 값을 return해서
불러올때 func()가 아니라 func 자체로 가져와야한다.
const getDiaryAnalysis = useMemo(() =>{
console.log("일기 분석 시작");
const goodCount = data.filter((it)=>it.emotion>=3).length;
const badCount = data.length - goodCount;
const goodRatio = (goodCount / data.length) * 100;
return {goodCount,badCount,goodRatio};
},[data.length]);
const {goodCount, badCount, goodRatio} = getDiaryAnalysis;
React.memo(({text})=>{})
를 통해서 text값이 바뀌면 리렌더링 하도록 설정
const Textview = React.memo(({text}) => {
useEffect(()=>{
console.log(`update :: Text : ${text}`);
})
return <div>{text}</div>;
});
const CountView = React.memo(({count}) => {
useEffect(()=>{
console.log(`update :: Count : ${count}`);
})
return <div>{count}</div>;
});
const OptimizeTest = () => {
const [count,setCount] = useState(1);
const [text,setText] = useState("");
return(
<div style={{padding:50}}>
<div>
<h2>count</h2>
<CountView count={count} />
<button onClick={()=>setCount(count+1)}>+</button>
</div>
<div>
<h2>text</h2>
<Textview text={text} />
<input value={text} onChange={(e)=>setText(e.target.value)} />
</div>
</div>
)
}
obj가 단일 값이 아닌 객체로 이루어졌을 경우 안의 내용이 같아도 다르게 인식한다
한마디로 객체형식으로 업데이트가 되지않아도 계속 리렌더링이 일어날 수 있다는 소리다
그래서 아래와같이 prevProps === nextProps로 체크를 해주고 true와 false값으로 구분해서
React.memo(호출컴포넌트,구분함수) 이렇게 써줘야한다.
const [obj,setObj] = useState({
count : 1
})
const CounterB = ({obj})=>{
useEffect(()=>{
console.log(`CounterB update - obj : ${obj}`);
})
return <div>{obj.count}</div>
};
const areEqual = (prevProps, nextProps)=>{
if(prevProps.obj.count === nextProps.obj.count){
return true;
}
return false;
}
const MemoizedCounterB = React.memo(CounterB,areEqual);
메모이제이션된 콜백을 반환한다.
const onCreate = useCallback((author,content,emotion) => {
const created_date = new Date().getTime();
const newItem = {
author,
content,
emotion,
created_date,
id:dataId.current,
}
dataId.current++;
setData((data)=>[newItem,...data]);
// setData로 기존의 data를 넘겨주기때문에 뒤에 []값을 비워서 렌더링을 막을수 있다.
},[]);
useState가 여러개 쓰일때 쓸 수 있는 상태관리 기능
const [data,dispatch] = useReducer(reducer,[]);
const reducer = (state,action) =>{
switch(action.type){
case 'INIT' :{
return action.data;
}
case 'CREATE':
const created_date = new Date().getTime();
const newItem = {
...action.data,
created_date
}
return [newItem,...state];
case 'REMOVE':
return state.filter((it)=>it.id !== action.targetId);
case 'EDIT':
return state.map((it) => it.id === action.targetId ? {...it,content: action.newContent} : it);
default:
return state;
}
}
const onCreate = useCallback((author,content,emotion) => {
dataId.current++;
dispatch({type:'CREATE',data:{
author,
content,
emotion,
id:dataId.current,
}});
},[]);
const onRemove = useCallback((targetId) => {
dispatch({type:'REMOVE',data:targetId});
},[]);
const onEdit = useCallback((targetId,newContent) => {
dispatch({type:'EDIT',targetId,newContent});
},[]);
export const DiaryStateContext = React.createContext();
return (
<DiaryStateContext.Provider value={data}>
<div className="App">
<DiaryEditor onCreate={onCreate} />
<div>전체 일기 : {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 나쁜 일기 개수 : {badCount}</div>
<div>기분 좋은 일기 비율 : {goodRatio}</div>
<DiaryList onRemove={onRemove} onEdit={onEdit} />
</div>
</DiaryStateContext.Provider>
);
전역 상태 관리 위와같은 createContext로 객체 생성 가장 상위에 provider로 감싸주기 위에서 value값으로 넣은 데이터를 useContext로 가져온다
const data = useContext(DiaryStateContext);
provider에서 아래와같이 하면 함수도 전역상태로 쓸 수 있다(dispatch역할)
<DiaryDispatchContext.Provider value={{
onCreate,
onEdit,
onRemove,
}}>
npm i react-router-dom@6
app에서 view로 아래와같이 넣어준다,
<Route path='/edit' element={<Edit />} />
컴포넌트에서 링크 부분은 아래와같이 넣어준다
import { Link } from 'react-router-dom';
<Link to={'/edit'}>Edit</Link>
<Route path='/diary/:id' element={<Diary />} />
import { useParams } from "react-router-dom";
const {id} = useParams();
useSearchParams를 통해서 가져올 수 있다. setSearchParams를 통해 쿼리를 변경시킬수도있다.
import { useSearchParams } from "react-router-dom";
const [searchParams, setSearchParams] = useSearchParams();
const id = searchParams.get('id');
<button onClick={()=>setSearchParams({who:"juyeon"})}>QS 바꾸기</button>
useNavigate를 통해서 링크를 바꿔줄수있다.
const navigate = useNavigate();
const hoHome = () => {
navigate("/home");
}
<button onClick={goHome}>
홈으로가기
</button>
뒤로가기
navigate(-1);
process.env.PUBLIC_URL
<img src={process.env.PUBLIC_URL + `/assets/emotion1.png`} />
class -> className for -> htmlFor 로 바꿔줘야한다 jsx는 js기반이라 html을 제대로 인식하지 않을 수 있기때문에 DOM 문제가 생긴다. 이와같이 js의 기본옵션과 겹치는 html 속성은 따로 써줘야한다.
// jsconfig.json
{
"compilerOptions": {
"baseUrl": "src",
}
}
{} 중괄호 안에 삼항연산자로 넣어준다.
ex: {idx > 0 ? idx : ''}
ref() = useState()
mounted() = useEffect()
unmounted() = useEffect()에 return값으로 넘기기
ref = ref/useRef()
npm i @reduxjs/toolkit redux react-redux
useSelector와 useDispatch로 store을 사용할 수 있다. store는 slice로 이루어져있으며 여러개의 slice를 name을 지정해 쓸 수 있다.
const Todos = createSlice({
name: "todos",
initialState: { list: [] },
reducers: {
addTodo: (state, action) => {
state.list.push(action.todo);
},
},
});
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();