React.createElement(component, props, ...children) => ReactElement
- 첫 번째 매개변수 component는 일반적으로 문자열이나 리액트 컴포넌트다. component의 인수가 문자열이면 HTML 태그에 해당하는 돔 요소가 생성
- 두 번째 매개변수 props는 컴포넌트가 사용하는 데이터를 나타낸다. 돔 요소의 경우 style, className 등의 데이터가 사용될 수 있다
- 세 번째 매개변수 children은 해당 컴포넌트가 감싸고 있는 내부의 컴포넌트를 가리킨다.
- 대부분의 리액트 개발자는 createElement를 직접 작성하지 않는다. 일반적으로 바벨(Babel)의 도움을 받아서 JSX 문법을 사용
<div>
<p>hello</p>
<p>world</p>
</div>
createElement(
'div',
null,
createElement('p',null,'hello'),
createElement('p',null,'world')
)
바벨(Babel)은 자바스크립트 코드를 변환해 주는 컴파일러
- 최신 자바스크립트 문법을 지원하지 않는 환경에서도 최신 문법 사용 가능
- 리액트에서는 JSX 문법을 사용하기 위해 바벨을 사용
- 바벨이 jSX문법으로 작성된 코드를 createElement 함수를 호출하는 코드로 변환
- 다양한 기능 제공
- 파일 내용을 기반으로 파일 이름에 해시값 추가 => 효율적으로 브라우저 캐싱 이용
- 사용되지 않은 코드 제거
- 자바스크립트 압충
- JS에서 CSS, JSON, 텍스트 파일 등을 일반 모듈처럼 불러오기
- 환경 변수 주입
- 웹팩을 사용하는 가장 큰 이유 => 모듈 시스템(ESM, commonJS)을 사용하고 싶어서
- AS-IS
<html>
<head>
<script type="text/javascript" src="javascript_file_1.js"></script>
<script type="text/javascript" src="javascript_file_2.js"></script>
...
<script type="text/javascript" src="javascript_file_999.js"></script>
</head>
</html>
- TO-BE
<html>
<head>
<script type="text/javascript" src="app.js"></script>
</head>
</html>
- 일반적인 CSS 파일로 작성하기 (CSS 파일명 ex) Box.css)
- CSS 빌드 시 같은 이름으로 정의된 CSS가 덮어씌워질 수 있다 (충돌 발생)
- css-module로 작성하기 (CSS 파일명 ex) Box.module.css)
- 각 CSS의 속성 값에 해시 값이 부여되어서 각 클래스명은 고유한 이름을 갖게 되어 충돌X
- Sass로 작성하기 (CSS 파일명 ex) Box.module.scss)
- import를 통해 CSS에서 변수를 다룰 수 있어 공통 CSS를 잡기 편함
- css in js로 작성하기 (styled-components)
- CSS코드를 Javascript 코드에 넣어 재사용 가능한 구조로 작성 (Javascript 파일로 관리)
- CSS를 잘 아는 JS개발자가 있다면 이 방법 추천
- CSS를 담당하는 Mark Up개발자가 있다면 이방법 비추천
- SPA가 가능한 조건
- 자바스크립트에서 브라우저로 페이지 전환 요청을 보낼 수 있음
- 단, 브라우저는 서버로 요청을 보내지 않아야 함
- 브라우저의 뒤로가기와 같은 사용자의 페이지 전환 요청을 자바스크립트에서 처리할 수 있음
- 이때도 브라우저는 서버로 요청을 보내지 않아야 함
- 자바스크립트에서 브라우저로 페이지 전환 요청을 보낼 수 있음
- 위 조건을 만족시켜주는 브라우저 API
- 자바스크립트가 페이지 전환을 하고싶을 때 브라우저에게 페이지 전환 이벤트를 알려주는 방법 : pushState, replaceState 함수
- 반대로, 브라우저에서 사용자가 브라우저 UI를 통해서 페이지 전환을 하려고할 때 : popState 이벤트
const [todoList, setTodoList] = useState([]);
- useState의 매개변수는 상태값의 초기값을 의미
- useState는 배열을 반환 (배열의 첫번째 아이템 => 상태값, 두번째 아이템 => 상태값 변경 함수)
- state : 불변성 X, 하지만 불변으로 관리하는게 좋음
- 객체를 불변변수로 관리 : 전개 연산자(...) 사용
- props : 불변성 O
- 부모 컴포넌트 렌더링하면 자식 컴포넌트도 따라서 렌더링
- 속성값이 변경될 때만 렌더링 -> React.memo
function Title({title}){} export default React.memo(Title); //속성값 title이 변경될 때만 이 컴포넌트가 렌더링
- React 요소 ( div, p, h1 등)
- 문자열, 숫자, 배열(key값 필요)
- Fragment(요소의 순서가 key역할을 해서 key값 필요 x)
- 원하지 않는 div 대신 fragment 사용 (<React.Fragment> or <>)
- {null}, {false}, {true} -> 조건부 렌더링할 때 유용
- 리액트 Portal
- public > index.html에 말고 다른 div 만들어서 렌더링 가능
- ReactDOM.createPortal() : 보통 Modal을 위해 사용
- 컴포넌트에 기능을 추가할 때 사용하는 함수
- ex) 컴포넌트에 상태값 추가, 자식 요소에 접근
- 리액트 16.8에 추가
- 이전에는 클래스형 컴포넌트 사용
- 클래스형 컴포넌트보다 장점이 많으며 리액트팀에서도 훅(Hook)에 집중
- useState : 상태값 추가
- 비동기 방식이면서 배치(Batch)로 처리 (이유 : 동기화 방식이면 상태값이 변할 때 마다 다시 렌더링하므로 성능저하 발생)
- 동기화 방식처럼 사용 방법 : 상태값 변경 함수에 함수를 입력하는 것 (ex) setCount(v => v+1))
- useEffect : 부수효과(외부의 상태를 변경하는 것) 처리
- 서버 API호출, 이벤트 핸들러 등록 등
- 렌더링 후에 비동기식으로 작동
- 두번째 인자에 배열(의존성 배열)을 넣을 수 있어, 이 배열에 있는 값이 변경될 때에만 부수효과 함수 실행 / 이 배열에는 지역함수, 지역변수 모두 넣어줘야한다.
- 부수함수의 return 값을 부수함수로 한다면 이것은 컴포넌트가 unmount 될 때 한번 실행한다. (보통 event handler를 등록/해제 할 때 사용)
- 리액트에서 관리하지 않는 외부에서 호출하는 경우에는 배치(batch)로 처리 안됨
- 상태값 변경 함수 호출할 때 마다 렌더링
- 외부에서 호출할 때도 배치로 처리되길 원한다면 batchedUpdate 함수 호출
- concurrent Mode에서는 외부에서 호출되는 함수도 배치처리 될 예정
- 커스터마이징 함수로 꺼내서 사용 가능
- 하나의 컴포넌트에서 훅을 호출하는 순서는 항상 같아야 함
- if문/for문/함수 안에서 훅을 사용하면 안됨
- 훅은 함수형 컴포넌트 혹은 커스텀 훅 안에서만 호출되어야 함
- props를 부모 컴포넌트에서 자식 컴포넌트로 전달해 줄 때 자식 컴포넌트와 부모 컴포넌트가 멀리 떨어져 있을 경우 중간에 계속 props를 전달해야 하는 불편함을 제거하기 위한 함수
- 초기에 createContext('초기값')을 통해 객체를 반환
- 부모 : Provider / 자식 : Consumer로 구분
- Consumer입장에서는 가장 가까운 Provider의 value값을 참조한다.
- root까지 Provider를 찾지 못한 경우 createContext('초기값')으로 설정된 초기값을 참조한다.
- Provider value값이 변하면 Consumer는 렌더링 된다.
- 이 때, 중간층 컴포넌트는 렌더링 되지 않는다.
- Consumer 대신에 Hook을 이용해서 useContext()로 대체 가능
- 따라서 Consumer 컴포넌트를 사용하는 일은 거의 없다. useContext이용
- 컴포넌트 코드로부터 상태 관리 코드를 분리할 수 있음
- 미들웨어를 활용한 다양한 기능 추가
- 강력한 미들웨어 라이브러리 (redux-saga)
- 로컬 스토리지에 데이터 저장하기 및 불러오기
- SSR 시 데이터 전달이 간편
- react context보다 효율적인 렌더링 가능
store.dispatch({type: 'todo/ADD', title: '영화 보기', priority: 'high'});
- dispatch 함수는 액션이 발생했다는 것을 리덕스에게 알려주는 함수 (매개변수 = 액션 객체)
- 액션 객체에 type 속성값 말고 원하는 대로 속성을 정의해서 전달할 수 있음 (데이터를 리듀서에서 받아서 처리)
- 액션을 구분하기 위해서 type 속성값을 사용하므로 type 속성값은 유니크 해야함 (위의 예제 처럼 주로 prefix를 붙여서 많이 사용)
function addTodo({title, priority}){
return {type: 'todo/ADD', title, priority};
}
store.dispatch(addTodo({title:'영화 보기', priority: 'high'}));
- 위의 예제와 같이 action creator를 만들어서 사용하는 이유 : 각 액션 객체의 구조를 일관성 있게 만들기 위해서
const ADD = 'todo/ADD'
function addTodo({title, priority}){
return {type: ADD, title, priority};
}
store.dispatch(addTodo({title:'영화 보기', priority: 'high'}));
- 위의 예제와 같이 type 속성은 action creator에서 사용하기도 하지만 리듀서에서도 사용하기 때문에 상수 변수로 만드는 것이 좋음
const myMiddelware = store => next => action => next(action);
- 위의 예제와 같이 여러 함수가 엉켜있는 이유 : 결론적인 함수 next(action)에서 store 와 next를 사용하기 위해서
- 미들웨어를 작성할 때 스토어가 필요한 경우가 많음
const reportCrash = store => next => action =>{
try{
return next(action)
} catch(err){
//서버로 예외 정보 전송
}
}
- 위의 예제와 같이 예외처리를 할 수 있음
const delayAction = store => next => action =>{
const delay = action.meta?.delay;
if (!delay){
return next(action);
}
const timeoutId = setTimeout(() => next(action), delay);
return function cancel(){
clearTimeout(timeoutId);
}
}
- 위의 예제와 같이 delayAction을 만들 수 있음 (action.meta에 delay라는 값이 있을 때 딜레이를 해줘서 리듀서를 늦게 실행 시킴)
const saveToLocalStorage = store => next => action => {
if (action.meta?.localStorageKey){
localStorage.setItem(action.meta?.localStorageKey, JSON.stringify(action));
}
return next(action);
}
- 위의 예제와 같이 localStorage에 저장해주는 미들웨어도 가능함
- 리듀서는 액션이 발생했을 때 새로운 상태값을 만드는 함수
- 리듀서 작성 중 주의해야할 점
function reducer(state = INITIAL_STATE, action) { return produce(state, draft => { switch(action.type){ case SET_SELECTED_PEOPLE: draft.selectedPeople = draft.peopleList.find( item => item.id === action.id ); break; case EDIT_PEOPLE_NAME: const people = draft.peopleList.find(item => item.id === action.id); people.name = action.name; break; } }); }
- 위의 예제에서 EDIT_PEOPLE_NAME에서 people객체가 만들어지고 people의 name을 변경해도 SET_SELECTED_PEOPLE에서는 예전의 객체 레퍼런스를 들고 있기 때문에 name은 변경되지 않은채로 예전값을 참조
- 따라서, 객체의 레퍼런스가 아니라 객체의 고유한 id값을 참조해서 이 값을 활용하는게 좋다.
- 리듀서는 순수 함수로 작성해야 함 (부수효과(외부 상태 변경) 없어야함)
- 즉, 서버 API를 리듀서에서 호출하면 안됨
- 입력이 같을 때 같은 결과를 출력해야함 (random 함수 사용하면 안됨)
- createReducer
function createReducer(initialState, handlerMap){ return function (state = initialState, action){ return produce(state, draft => { const handler = handlerMap[action.type]; if (handler){ handler(draft, action); } }); }; }
- createStore
- 리덕스에서 store를 만들 때 사용되는 함수 : createStore(reducer)
- store는 상태값을 저장하는 역할도 있고 액션 처리가 끝났다는 것을 외부에 알려주는 역할도 함 - store.subscribe()
- Provider 컴포넌트에서는 리액트에서 액션이 처리됐을 떄 이벤트를 받아서 하위에 있는 다른 컴포넌트가 다시 렌더링 될 수 있도록 도와줌
- 리덕스에서 데이터를 가져올 때는 useSelector Hook을 사용
- useSelect(state => state.friend.friends) : 리덕스의 상태값이 매개변수로 넘어오고 사용하려는 데이터를 가져오면 됨 -> 안의 함수의 반환 값이 Hook의 반환값이 됨
- 리덕스에 저장된 데이터를 화면에 보여줄 때 다양한 형식으로 가공할 필요 있음 => 이 때 도움되는 reselect 라이브러리
- 메모이제이션 기능을 지원되어 특정한 값이 변경되었을 때만 연산이 되도록 할 수 있음
- 리덕스에서 액션이 발생한 이후에 비동기 처리를 통해서 상태값을 변경하고 싶은 경우 (api로 서버에서 데이터 가져올 때)
- redux-thunk
- 비동기 로직이 간단할 때 사용
- 가장 간단하게 시작
- redux-observable
- 비동기 코드가 많을 때 사용
- RxJS 패키지를 기반으로 만들어짐
- 리액티브 프로그래밍을 공부해야하므로 진입 장벽이 가장 높음
- redux-saga
- 비동기 코드가 많을 때 사용
- 제너레이터를 적극적으로 활용
- 테스트 코드 작성이 쉬움