17.1 작업 환경 설정

//redux, react-redux라이브러리 설치
yarn add redux react-redux

//.prettierrc
{
  "singleQuote": true,
  "semi": true,
  "useTabs": false,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 80
}

17.2 UI 준비하기

17.3 리덕스 관련 코드 작성하기

액션 타입, 액션 생성 함수, 리듀서 코드를 작성. 작성 방법은 아래 두 가지로 나뉨.

  • actions, constants, reducers로 각각 디렉터리를 구분해서 작성하는 방법
  • Ducks패턴 : 액션 타입, 액션 생성함수, 리듀서 함수를 기능별로 파일 하나에 몰아서 다 작성하는 방식

  1. 액션 타입 정의하기
  2. 액션 생성 함수 만들기
  3. 초기 상태 및 리듀서 함수 만들기
  4. 루트 리듀서 만들기

17.4 리액트 애플리케이션에 리덕스 적용하기

17.4.1 스토어 만들기

17.4.2 Provider 컴포넌트를 사용하여 프로젝트에 리덕스 적용하기

17.4.3 Redux DevTools의 설치 및 적용 

//크롬 확장 프로그램 설치(Redux DevTools)
1.
const store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

2. 
패키지를 설치하여 적용(코드 훨씬 깔끔해짐)
yarn add redux-devtools-extension

import {composeWithDevTools} from "redux-devtools-extension";
const store = createStore(rootReducer, composeWithDevTools());

//index.js
import rootReducer from "./modules";
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import {composeWithDevTools} from "redux-devtools-extension";

const store = createStore(rootReducer, composeWithDevTools());

ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>,
    document.getElementById('root')
);

17.5 컨테이너 컴포넌트 만들기

const makeContainer = connect(mapStateToProps, mapDispatchToProps);
makeContainer(타깃 컴포넌트);

//connect 함수사용
const mapStateToProps = state => ({
    number: state.data.number
});

const mapDispatchToProps = dispatch => ({
    increase: () => dispatch(increase()),
    decrease: () => dispatch(decrease())
});

export default connect(mapStateToProps, mapDispatchToProps)(Container);


//bindActionCreators함수 사용
export default connect(
    state => ({
        number: state.data.number
    }),
    dispatch => bindActionCreators(
        {
            increase,
            decrease,
        }, dispatch)
)(Container);


//mapDispatchToProps 파라미터에 액션 생성함수 객체 삽입 (connect 함수가 내부적으로 bindActionCreators 작업 대신 해줌)
export default connect(
    state => ({
        number: state.data.number
    }),
    {
        increase,
        decrease
    }
)(Container);

17.6 리덕스 더 편하게 사용하기

17.6.1 redux-actions

//라이브러리 설치
yarn add redux-actions


//createAction, handleActions를 사용해서 가독성 향상
import {createAction, handleActions} from "redux-actions";

const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);

const initialState = {
    number: 0
};

const counter = handleActions(
    {
        [INCREASE]: (state, action) => ({number: state.number + 1}),
        [DECREASE]: (state, action) => ({number: state.number - 1}),
    },
    initialState
)

export default counter;

17.6.2 immer

//spread, 배열 내장 함수 대신 immer를 사용한 불변성 유지 업데이트

//전
({...state, input: data}),

//후
produce(state, draft => {
	draft.input = data
}),

17.7 Hooks를 사용하여 컨테이너 컴포넌트 만들기

17.7.1 useSelector로 상태 조회하기

const result = useSelector(state => state.data.num);

17.7.2 useDispatch를 사용하여 액션 디스패치하기

const dispatch = useDispatch();
dispatch({type : 'TEST_ACTION'});
useDispatch를 사용할 때는 성능 최적화를 위해 useCallback을 함께 사용하는 것을 권장.

17.7.3 useStore를 사용하여 리덕스 스토어 사용하기

const store = useStore();
store.dispatch({type : 'TEST_ACTION'});
store.getState();

17.7.5 useActions 유틸 Hook을 만들어서 사용하기

17.7.6 connect 함수와의 주요 차이점

useSelector를 사용할 때는 성능 최적화를 위해 React.memo를 컨테이너 컴포넌트에 사용해 주어야 함.

16.1 개념 미리 정리하기

16.1.1 액션

상태에 어떠한 변화가 필요하면 액션이란 것이 발생함. 

{
	type : 'TOGGLE_VALUE'
}

16.1.2 액션 생성 함수

액션 객체를 만들어 주는 함수

//일반함수 형태
function addTest(data) {
	return {
    	type : 'ADD_TEST',
        data
    }
}

//화살표함수 형태
const addTest = text => ({
	type : 'ADD_TEST',
    text
});

16.1.3 리듀서

리듀서는 변화를 일으키는 함수. 액션을 만들어서 발생시켜면 리듀서가 현재 상태와 전달받은 액션 객체를 파라미터로 받아 옴. 그리고 두 값을 참고하여 새로운 상태를 반환해줌.

const initialState = {
	data : 1
};

function reducer(state = initialState, action) {
	switch (action.type) {
    	case ADD : 
        	return {
            	data : state.data + 1
            };
        default :
        	return state;
    }
}

16.1.4 스토어

리덕스를 적용하기 위해 스토어를 만듬. 한 개의 프로젝트는 단 하나의 스토어만 가질 수 있음. 스토어 안에는 현재 애플리케이션 상태와 리듀서가 들어가 있으며, 그 외에 몇 가지 중요한 내장 함수를 지님.

16.1.5 디스패치

스토어의 내장 함수 중 하나. 액션을 발생시키는 것. dispatch(action)과 같은 형태로 액션 객체를 파라미터로 넣어서 호출함. 이는 리듀서 함수를 실행시켜서 새로운 상태를 만들어 줌.

16.1.6 구독

스토어의 내장 함수 중 하나. subscribe 함수 안에 리스터 함수를 파라미터로 넣어서 호출해 주면 이 함수가 액션이 디스패치되어 상태가   업데이트될 때마다 호출됨.

16.2 리액트 없이 쓰는 리덕스

16.3 리덕스의 세 가지 규칙

16.3.1 단일 스토어

하나의 애플리케이션 안에는 하나의 스토어가 들어 있음.

16.3.2 읽기 전용 상태

상태를 업데이트할 때 기존 객체를 건드리지 않고 새로운 객체를 생성해 주어야 함. (리덕스가 내부적으로 데이터 변경 감지를 위해 얕은 비교 검사를 하기 때문)

16.3.3 리듀서는 순수한 함수

순수한 함수는 다음 조건을 만족해야함.

  • 리듀서 함수는 이전 상태와 액션 객체를 파라미터로 받음.
  • 파라미터 외의 값에는 의존하면 안 됨.
  • 이전 상태는 건드리지 않고, 변화를 준 새로운 상태 객체를 만들어서 반환함.
  • 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과 값을 반환해야 함.

15.1 Context API를 사용한 전역 상태 관리 흐름 이해하기

15.2 Context API 사용법 익히기

15.2.1 새 Context 만들기

import {createContext} from 'react';

const TestContext = createContext({data: 'black'});

export default TestContext;

 

15.2.2 Consumer 사용하기

import React from "react";
import TestContext from "../context/test";

const TestBox = () => {
    return (
        <TestContext.Consumer>
            {value =>(
            	<div>value.data</div>
            )}
        </TestContext.Consumer>
    );
};

export default TestBox;

15.2.3 Provider

Provider를 사용하여 Context value 변경

import React from 'react';
import TestContext from "./context/Test";

function App() {
    return (
        <TestContext.Provider value={{data:'red'}}>
			...
        </TestContext.Provider>
    );
}

export default App;

15.3 동적 Context 사용하기

15.4 Consumer 대신 Hook 또는 static contextType 사용하기

15.4.1 useContext Hook 사용하기

children에 함수를 전달하는 Render Props 패턴이 불편하다면, useContext Hook을 사용하여 훨씬 편하게 Context 값을 조회할 수 있음.

15.4.2 static contextType 사용하기

클래스형 컴포넌트에서 Context를 좀더 쉽게 사용하고 싶다면 static contextType을 정의하는 방법이 있음.

14.1 비동기 작업의 이해

14.1.1 콜백 함수
14.1.2 Promise
14.1.3 asnyc/await

화살표 함수에 async/await을 적용할 때는 async () => {}와 같은 형식으로 적용함.

const onClick = async () => {
	try {
    	const response = await axios.get(...);
        setData(response.data);
    } catch(e) {
    ...
    }
};

14.2 axios로 API 호출해서 데이터 받아 오기

14.3 newsapi API 키 발급받기

14.4 뉴스 뷰어 UI 만들기

14.5 데이터 연동하기

userEffect 내부에서 async/await을 사용하고 싶다면, 함수 내부에 async 키워드가 붙은 다른 함수를 반들어 사용해야함.

useEffect(() => {
	const fetchData = async () => {
      setLoading(true);
      try {
        ...
        const response = await axios.get(...);
        setData(response.data.xxx);
      } catch (e) {
      	console.log(e);
      }
      setLoading(false);
    }
    fetchData();
}, [category]);

14.6 카테고리 기능 구현하기

14.7 리액트 라우터 구현하기

리액트 라우터의 Router, NavLink등을 사용하여 라우터 구현

//리액트 라우터 설치
yarn add react-router-dom

14.8 usePromise 커스텀 Hook 만들기

13.1 SPA란?

13.1.1 SPA의 단점

  • 앱의 규모가 커지면 자바스크립트 파일이 너무 커짐(실제로 방문하지 않을 수도 있는 페이지의 스크립트까지 불러오기 때문) => 코드 스플리팅을 통해 속도 개선
  • 자바스크립트를 실행하지 않는 일반 크롤러에서 페이지의 정보를 제대로 수집해 가지 못함 => 서버 사이드 렌더링을 통해 해결

13.2 프로젝트 준비 및 기본적인 사용법

13.2.1 프로젝트 생성 및 라이브러리 설치

yarn create react-app router-tutorial
cd router-tutorial
yarn add react-router-dom

13.2.2 프로젝트에 라우터 적용

import React from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import {BrowserRouter} from 'react-router-dom';

ReactDOM.render(
    <BrowserRouter>
        <App/>
    </BrowserRouter>,
    document.getElementById('root')
);

serviceWorker.unregister();

13.2.3 페이지 만들기

13.2.4 Route 컴포넌트로 특정 주소에 컴포넌트 연결

import React from 'react';
import {Route} from 'react-router-dom';
import Home from "./Home";
import About from "./About";

const App = () => {
    return (
        <div>
            <Route path="/" component={Home} exact={true}/>
            <Route path="/about" component={About}/>
        </div>
    );
}

export default App;

13.2.4 Link 컴포넌트를 사용하여 다른 주소로 이동하기

<Link to="주소">내용</Link>

13.3 Route 하나에 여러 개의 path 설정하기

import React from 'react';
import {Route} from 'react-router-dom';
import Home from "./Home";
import About from "./About";

const App = () => {
    return (
        <div>
            <Route path="/" component={Home} exact={true}/>
            <Route path={['/about', '/info']} component={About}/>
        </div>
    );
}

export default App;

13.4 URL 파라미터와 쿼리

13.4.1 URL 파라미터

//router 설정
<Route path="/profile/:username" component={Profile}/>

//Link 설정
<Link to="/profile/test">test 프로필</Link>

//component 설정
import React from "react";

const Profile = ({match}) => {
    const {username} = match.param;
...
}

13.4.2 URL 쿼리

//쿼리 문자열을 객체로 변환하기 위한 qs 라이브러리 설치
yarn add qs

location 객체가 라우트로 사용된 컴포넌트에게 props로 전달됨
{
 "pathname" : "/about",
 "search" : "?detail=true",
 "hash" : ""
}

import React from "react";
import qs from 'qs';

const About = ({location}) => {
    const query = qs.parse(location.search, {
        ignoreQueryPrefix: true //문자열 맨 앞의 ?를 생략 
    });
    
    return (
    ...
    );
};

export default About;

13.5 서브 라우트

라우트 내부에 또 라우트를 정의하는 것
Route 컴포넌트에는 componet 대신 render라는 prop로 보여 주고 싶은 JSX를 넣어 줄 수 있음.
JSX에서 props를 설정할 때 값을 생략하면 자동으로 true로 설정됨. (ex> exact={true} 와 exact는 동일 표현)

<Route path="/profiles" exact render={()=><div>사용자를 선택해 주세요.</div>}/>

13.6 리액트 라우터 부가 기능

13.6.1 history

match, location과 함께 전달되는 props중 하나로,  이 객체를 통해 컴포넌트 내에 구현하는 메서드에서 라우터  API를 호출할 수 있음.

13.6.2 withRouter

라우트로 사용된 컴포넌트가 아니어도 match, location, history 객체를 접근할 수 있게 해 줌

import React from 'react';
import {withRouter} from 'react-router-dom';

const withRouterSample = ({location, match, history}) => {
    return (
        <div>
            <h4>location</h4>
			
            <textarea
                value={JSON.stringify(location, null, 2)}//2, 3번째 파라미터를 null, 2로 설정해주면 JSON에 들여쓰기가 적용된 상태로 문자열이 만들어짐
                rows={7}
                readOnly={true}/>
            <h4>match</h4>
            <textarea
                value={JSON.stringify(match, null, 2)}
                rows={7}
                readOnly={true}/>
            <button onClick={() => history.push('/')}>홈으로</button>
        </div>
    );
};

export default withRouter(withRouterSample);

13.6.3 Switch

Switch 컴포넌트는 여러 Router를 감싸서 그중 일치하는 단 하나의 라우트만을 렌더링 시켜 줌.  Swtich를 사용하면 모든 규칙과 일치하지 않을 때 보여 줄 Not Fount 페이지를 구현할 수 있음.

import React from 'react';
import {Route, Link, Switch} from 'react-router-dom';
import Home from "./Home";
import About from "./About";
import Profile from "./Profile";

const App = () => {
    return (
        <div>
            <ul>
                <li><Link to="/">홈</Link></li>
                <li><Link to="/about">소개</Link></li>
                <li><Link to="/profiles">프로필</Link></li>
                <li><Link to="/test">test</Link></li>
            </ul>
            <hr/>
            <Switch>
                <Route path="/" component={Home} exact={true}/>
                <Route path={['/about', '/info']} component={About}/>
                <Route path="/profile/:username" component={Profile}/>
                <Route render={({location}) => (
                    <div>
                        <h2>이 페이지는 존재하지 않습니다.</h2>
                        <p>{location.pathname}</p>
                    </div>
                )}>

                </Route>
            </Switch>
        </div>
    );
}

export default App;

13.6.4 NavLink

현재 경로와 Link에서 사용하는 경로가 일치하는 경우 특정 스타일 혹은 CSS 클래스를 적용할 수 있는 컴포넌트. NavLink에서 링크가 활성화되었을 때의 스타일을 적용할 때는  activeStyle값을, CSS 클래스를 적용할 때는 activeClassName 값을 prop로 넣어 주면 됨.

import React from "react";
import {NavLink} from "react-router-dom";

const Profile = ({match}) => {
    const activeStyle = {
        background: 'black',
        color: 'white'
    };
    return (
        <div>
            <h3>사용자 목록</h3>
            <ul>
                <li>
                    <NavLink activeStyle={activeStyle} to="/profiles/test1" active>
                        test1
                    </NavLink>
                </li>
                <li>
                    <NavLink activeStyle={activeStyle} to="/profiles/test2">
                        test2
                    </NavLink>
                </li>
            </ul>
        </div>
    );
}

export default Profile;
  • immer를 사용하면 객체의 불변성을 유지하는 작업을 매우 간단하게 처리할 수 있음. 단, concat, filter등을 통해 간결하게 불변성이 유지되는 경우는  굳이 immmer를 사용하지 않아도 됨.
  • immer를 사용하면 컴포넌트 상태를 작성할 때 객체 안에 있는 값을 직접 수정하거나, 배열에 직접적인 변화를 일으키는 push, slice등의 함수를 사용해도 무방함.

12.1 immer를 설치하고 사용법 알아보기

yarn add immer

produce(originalState, draft)

  • originalState  : 수정하고 싶은 상태
  • draft : 상태를 어떻게 업데이트할지 정의하는 함수
import produce from 'immer';

const nextState = produce(originalState, draft => {
	draft.somewhere.depp.isuue = 5;
    }
);

immer에서 제공하는 produce 함수를 호출할 때, 첫 번째 파라미터가 함수 형태라면 업데이트 함수를 반환함.

const update = (draft => {
	draft.value = 2;
});

cont originalState = {
	value : 1,
    foo : 'bar',
}

const nextState = upate(originalState);
console.log(nextState); // {value : 2, foo : 'bar'}

11.1 많은 데이터 렌더링하기

11.2 크롬 개발자 도구를 통한 성능 모니터링

개발자 도구의 Performance 탭을 사용하여 성능 측정이 가능함. Performance -> 녹화 탭

11.3 느려지는 원인 분석

11.4 React.memo를 사용하여 컴포넌트 성능 최적화

React.memo 함수를 통해서 컴포넌트의 props가 바뀌지 않았다면 리랜더링하지 않도록 설정하여 함수형 컴포넌트의 리랜더링 성능 최적화가 가능함.

11.5 onToggle, onRemove 함수가 바뀌지 않게 하기

상태를 참조하는 함수가 있으면 상태가 바뀔 때마다 함수가 새로 만들어지는데, 이를 아래의 두방법으로 방지할 수 있음.

  1. useState의 함수형 업데이트 사용
  2. useReducer 사용 : 상태 업데이트 로직을 분리할 수 있다는 장점이 있음.
//useState의 함수형 업데이트 예시
import React, {useCallback, useRef, useState} from 'react';
import TodoTemplate from "./components/TodoTemplate";
import TodoInsert from "./components/TodoInsert";
import TodoList from "./components/TodoList";

function createBulkTodos() {
    const array = [];
    for (let i = 1; i < 1000; i++) {
        array.push({
            id: i,
            text: `할일 ${i}`,
            checked: false
        })
    }
    return array;
}

const App = () => {
    const [todos, setTodos] = useState(createBulkTodos);

    const nextId = useRef(4);

    const onInsert = useCallback(text => {
        const todo = {
            id: nextId.current,
            text: text,
            checked: false
        }

        setTodos(todos => todos.concat(todo));
        // setTodos(todos.concat(todo));
        nextId.current += 1;
    });

    const onToggle = id => {
        setTodos(todos => todos.map(todo => todo.id === id ? {...todo, checked: !todo.checked} : todo));
        // setTodos(todos.map(todo => todo.id === id ? {...todo, checked: !todo.checked} : todo));
    }

    const onRemove = id => {
        setTodos(todos => todos.filter(todo => todo.id !== id))
        // setTodos(todos.filter(todo => todo.id !== id))
    };

    return (
        <TodoTemplate>
            <TodoInsert onInsert={onInsert}/>
            <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle}/>
        </TodoTemplate>
    );
}

export default App;

11.6 불변성의 중요성

라이브러리 immer를 통한 불변성 유지 참조.
https://immerjs.github.io/immer/docs/introduction

 

Introduction to Immer · Immer

 

 

immerjs.github.io

11.7 TodoList 컴포넌트 최적화하기

11.8 react-virtualized를 사용한 렌더링 최적화

react-virtualized 라이브러리 를 사용하면 리스트 컴포넌트에서 스크롤되기 전에 보이지 않는 컴포넌트는 랜더링하지 않고 크기만 차지하게끔 할 수 있음. https://github.com/bvaughn/react-virtualized

 

10.1 프로젝트 준비하기

yarn create react-app todo-app
cd todo-app
yarn add node-sass classnames react-icons

Prettier 설정(.prettierrc)

{
  "singleQuote": true,
  "semi": true,
  "useTabs": false,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 80
}

index.css 수정

body {
    margin: 0;
    padding: 0;
    background: #e9ecef;
}

App 컴포넌트 초기화

import React from 'react';

const App = () => {
  return <div>Todo App을 만들자</div>;
}

export default App;

10.2 UI 구성하기

예제 소스는 책을 참조

//대상창이 닫혀있을 때 자동완성이 작동하지 않을 경우 최상위 디렉토리에 jsconfig.json파일 추가하고 아래 내용 기입
//ex> <Test 까지 쳐도 자동완성 작동x
{
  "compilerOptions": {
    "target": "es6"
  }
}

10.3 기능 구현하기

예제 소스는 책을 참조

크롬의 확장플러그인 리액트 개발자 도구(React Developer Tools)를 통해 컴포넌트 내의 변화를 관찰할 수 있습니다. 

9.1 가장 흔한 방식, 일반 CSS

9.1.1 이름 짓는 규칙

클래스 이름을 컴포넌트 이름-클래스 형태로 지음으로써 중복을 방지
(+ BEM 네이밍)

9.1.2 CSS Selector

.App {
...
}

.App .logo {
...
}

.App header { //header태그 자체 선택
...
}

.App .link {
...
}

 

9.2 Sass 사용하기

  • .sass : 중괄호와 세미콜론을 사용하지 않음
  • .scss : 기존 CSS문법과 유사
yarn add node-sass

sass-loader 설정 커스터마이징 하기

  1. yarn eject로 세부 설정 호출
  2. conifig/webpack.config.js 에서 sassRegex 부분 편집
//원본
{
  test: sassRegex,
  exclude: sassModuleRegex,
  use: getStyleLoaders(
    {
      importLoaders: 3,
      sourceMap: isEnvProduction && shouldUseSourceMap,
    },
    'sass-loader'
  ),
  sideEffects: true,
},


// 'sass-loader'부분을 지우고, 뒷부분에 concnat을 통해 커스터마이징된 sass-loader를 삽입. 책에 있는 options 설정 부가 제대로 작동하지 않음.
// 아래와 같이 변경 
{
  test: sassRegex,
  exclude: sassModuleRegex,
  use: getStyleLoaders(
    {
      importLoaders: 3,
      sourceMap: isEnvProduction && shouldUseSourceMap,
    },
    ).concat({
      loader: require.resolve('sass-loader'),
      options: {
      	prependData: `@import 'utils';`, // 항상 포함시키는 scss추가
        sassOptions: {
          includePaths: [paths.appSrc + '/styles'],
          sassOptions: isEnvProduction && shouldUseSourceMap,
        }
      }
    }),
  sideEffects: true,
},

//출처 : https://velog.io/@madpotato1713/%EB%A6%AC%EC%95%A1%ED%8A%B8-sass-loader-%EC%84%A4%EC%A0%95-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A7%95-%EC%98%A4%EB%A5%98-options-has-an-unknown-property-includePaths.-These-properties-are-valid-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95

node_modules에서 라이브러리 불러오기

//open-color : 편리한 색상 팔레트
//include-media : 반응형 디자인을 쉽게 만들어주는 라이브러리
yarn add open-color include-media

//node_modules내의 라이브러리는 물결 푯를 통해 쉽게 호출할 수 있음. 
@import '~include-media/dist/include-media';
@import '~open-color/open-color';

9.3 CSS Module

CSS를 불러와서 사용할 때, [파일 이름]_[클래스_이름]__[해시값] 형태로 자동으로 만들어서 스타일 클래스 이름 중첩을 방지하는 기술 (*.module.css 확장자로 파일을 저장하기만 하면 CSS Module이 적용됨.)

//className={styles.[클래스이름]} 형태로 전달. 
//:global .something {...} 처럼 :global을 사용하여 전역적으로 선언한 클래스는 그냥 문자열 삽입 
import React from "react";
import styles from './CSSModule.module.css';

const CSSModule = () => {
    return (
        <div className={styles.wrapper}>
            this is <span className="something">CSS Module</span>
        </div>
    );
};

export default CSSModule;


//CSSModule 컴포넌트에 classnames의 bind함수 적용.
import React from "react";
import classNames from "classnames/bind";
import styles from './CSSModule.module.css';

const cx = classNames.bind(styles);

const CSSModule = () => {
    return (
        <div className={cx('wrapper', 'inverted')}>
            this is <span className="something">CSS Module</span>
        </div>
    );
};

export default CSSModule;

9.3.1 classnames

css클래스를 조건부로 설정할 때 유용한 라이브러리

yarn add classnames

//classnames 간략 사용법
classNames('one', 'two'); // = 'one two'
classNames('one', {two : true}); // = 'one two'
classNames('one', {two : false}); // = 'one'
classNames('one', ['two', 'three']); // = 'one two three'

const myClass = 'hello';
classNames('one', myClass, {myCondition : true}); // = 'one hello myCondition'

9.3.2 Sass와 함께 사용하기

*.module.scss 확장자를 사용하면 Sass를 CSS Module로 사용할 수 있음.

9.3.3 CSS Module이 아닌 파일에서 CSS Module 사용하기

:local을 사용하면 일반 .css/.scss 파일에서도 CSS Module 사용할 수 있음.

9.4 styled-components

자바스크립트 파일 안에 스타일을 선언하는 'CSS-in-JS'라이브러리 중 가장 대중적. 이를 사용하면 자바스크립트 파일 하나에 스타일까지 작성할 수 있다는 장점이 있음.

신택싱 하이라이팅이 제대로 이루어지지 않음. 플러그인 추가 설치 필요
ex > VS Code : vscode-styled-components, intellij : styled components & styled jsx

9.4.1 Tagged 템플릿 리터럴

스타일을 작성할 때 사용한 문법을 Tagged 템플릿 리터럴이라고 함. 이를 사용하면 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출할 수 있음. (<-> 일반 템플릿 리터럴)
styled-components는 이런 속성을 사용하여 props를 스타일 쪽에서 쉽게 조회할 수 있도록 해줌.

9.4.2 스타일링 된 엘리먼트 만들기

import styled from 'styled-components';

const MyComponent = styled.div`
	font-size: 2rem;
`;

//태그의 타입을 styled 함수의 인자로 전달
const MyInput = styled('input')`
	background : gray;
`

//컴포넌트 형식의 값을 넣기
const StyledLink = styled(Link)`
	color : blue;
`

9.4.3 스타일에서 props 조회하기

const Box = styled.div`
    background : ${props => props.color || 'blue'}; // props로 값 전달 가능
    padding : 1rem;
    display : flex;
`;

//in JSX
<Box color='Black'>...</Box>

9.4.4 props에 따른 조건부 스타일링

import React from "react";
import styled, {css} from "styled-components";

const Button = styled.div`
    background : white;
    color : black;
    border-radius : 4px;
    ... 
    ${props => 
    	props.inverted && 
        css` //조건부 스타일링을 할 때 신택스 하이라이팅이 필요하거나, props전달이 필요하면 반드시 CSS로 감싸줘야함.
        background : none;
        border : 2px solid gray;
        `
    }
   `;

//in JSX
<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>

8.1 useState

함수형 컴포넌트에서 상태 관리가 필요할 때 사용.

import React, {Component, useState} from "react";

const Info = () => {
    const [name, setName] = useState('');
    const [nickName, setNickName] = useState('');

    const onChangeName = (e) => {
        setName(e.target.value);
    }
    const onChangeNickName = (e) => {
        setNickName(e.target.value);
    }

    return (
        <div>
            <div>
                <input value={name} onChange={onChangeName}/>
                <input value={nickName} onChange={onChangeNickName}/>
            </div>
            <div>
                <div>
                    <b>이름 : </b> {name}
                </div>
                <div>
                    <b>닉네임 : </b> {nickName}
                </div>
            </div>
        </div>
    );
}

export default Info;

8.2 useEffect

컴포넌트가 랜더링 될 때마다 특정 작업을 수행하도록 설정

import React, {Component, useState, useEffect} from "react";

const Info = () => {
    const [name, setName] = useState('');
    const [nickName, setNickName] = useState('');

    useEffect(() => {
        console.log('렌더링 완료');
        console.log({
            name,
            nickName
        })

    });

    const onChangeName = (e) => {
        setName(e.target.value);
    }
    const onChangeNickName = (e) => {
        setNickName(e.target.value);
    }

    return (
	    ...
    );
}

export default Info;

8.2.1 마운트될 때만 실행하고 싶을 때

useEffect(() => {
	console.log('마운트될 때만 실행');
},[]);

8.2.2 특정 값이 업데이트될 때만 실행하고 싶을 때

useEffect(() => {
	console.log('마운트될 때만 실행');
},[name]);

8.2.3 뒷정리하기

컴포넌트가 언마운트되기 전이나 업데이트되기 직전에 작업 수행

//컴포넌트가 나타날 때 콘솔에 effect가 나타나고, 사라질 때 clean up이 나타남
useEffect(() => {
	console.log('effect');
	console.log(name);
	return () => {
		console.log('clean up');
		console.log(name);
	}
});

// 오직 언마운트될 떄만 뒷정리 함수를 호출하고 싶다면 두 번째 파라미터에 비어있는 배열을 넣으면 됨
useEffect(() => {
	console.log('effect');
	console.log(name);
	return () => {
		console.log('clean up');
		console.log(name);
	}
}, []);

8.3 usereducer

  • useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트해 주고 싶을 때 사용.
  • 리듀서는 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 액션 값을 전달받아 새로운 상태를 반환하는 함수.
  • 리듀서 함수에서 새로운 상태를 만들 때는 반드시 불변성을 지켜 주어야 함.
  • useReducer에서 사용하는 액션 객체는 반드시 type을 지니고 있을 필요 없음( <-> 리덕스는 액션 객체에 type이 필수)
  • 컴포넌트 업데이트로직을 컴포넌트 바깥으로 빼낼 수 있게 해 줌
//
import React, {useReducer} from "react";

function reducer(state, action) {
    switch (action.type) {
        case 'INCREMENT':
            return {value: state.value + 1};
        case 'DECREMENT':
            return {value: state.value - 1};
        default :
            return this.state;
    }
}

const Counter = () => {
    const [state, dispatch] = useReducer(reducer, {value: 0});

    return (
        <div>
            <p>
                현재 카운터 값은 <b>{state.value}</b>입니다.
            </p>
            <button onClick={() => dispatch({type: 'INCREMENT'})}>+1</button>
            <button onClick={() => dispatch({type: 'DECREMENT'})}>-1</button>
        </div>
    );

}

export default Counter;


//
import React, {useReducer} from "react";

function reducer(state, action) {
    return {
        ...state,
        [action.name]: action.value
    }
}

const Info = () => {
    const [state, dispatch] = useReducer(reducer, {
        name: '',
        nickname: ''
    })
    const {name, nickname} = state;
    const onChange = e => {
        dispatch(e.target);
    }
    return (
        <div>
            <div>
                <input name="name" value={name} onChange={onChange}/>
                <input name="nickname" value={nickname} onChange={onChange}/>
            </div>
			...
        </div>
    );
}

export default Info;

8.4 useMemo

함수형 컴포넌트 내부에서 발생하는 연산을 최적화해줌

8.5 useCallback

랜더링 성능을 최적화하기 위해 사용.(useMemo와 유사) 
이벤트 핸들러 함수를 필요할 때만 생성할 수 있게 해 줌.

숫자, 문자열, 객체처럼 일반 값을 재사용하려면 useMemo, 함수를 재사용하려면 useCallback
//useMemo vs useCallback
useMemo(() => {
	const fn = () => {
    	console.log('hello world!');
    }
    return fn;
}, [])

useCallback(() => {
	console.log('hello world');
}, [])


//useMemo, useCallback 사용 예시
import React, {useState, useMemo, useCallback, useRef} from "react";

const getAverage = (numbers) => {
    console.log('평균값 계산 중...');
    if (numbers.length === 0) return 0;
    const sum = numbers.reduce((a, b) => a + b);
    return sum / numbers.length;
}


const Average = () => {
    const [list, setList] = useState([]);
    const [number, setNumber] = useState('');
    const inputEl = useRef(null);

    const onChange = useCallback(e => {
        setNumber(e.target.value);
    }, []); //컴포넌트가 처음 랜더링될 때만 함수 생성

    const onInsert = useCallback(e => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
        inputEl.current.focus();
    }, [number, list]); //number 혹은 list가 바뀌었을 때만 함수 생성

    const avg = useMemo(() => getAverage(list), [list]); //특정 값이 바뀌었을 때만 연산을 실행하도록 useMemo로 연산함수를 감쌈.
    return (
        <div>
            <input value={number} onChange={onChange} ref={inputEl}/>
            <button onClick={onInsert}>등록</button>
            <ul>
                {list.map((value, index) => <li key={index}>{value}</li>)}
            </ul>
            <div><b>평균값 : </b>{avg}</div>
        </div>
    );
}

export default Average;

 

8.6 userRef

  • ref를 설정한 useRef를 통해 만든 객체 안의 current값이 실제 엘러먼트를 가리킴.
  • 컴포넌트 로컬 변수를 사용해야 할 때도 useRef를 활용할 수 있음. (렌더링과 상관없이 바뀔 수 있는 값)
import React, {useRef} from "react";

const RefSample = () => {
    const id = useRef(1);
    const setId = (n) => {
        id.current = n;
    }
    const printId = () => {
        console.log(id.current);
    }

    return (
        <div>
            refsample
        </div>
    )
}
export default RefSample

8.7 커스텀 Hooks 만들기

//커스텀 훅 만들기
import React, {useReducer} from "react";

function reducer(state, action) {
    return {
        ...state,
        [action.name]: action.value
    }
}

export default function useInputs(initialForm) {
    const [state, dispatch] = useReducer(reducer, initialForm);
    const onChange = e => {
        dispatch(e.target);
    }
    return [state, onChange];
}


//커스텀 훅 사용
import React, {useReducer} from "react";
import useInputs from "./useInputs";

const Info = () => {
    const [state, onChange] = useInputs({name: '', nickname: ''})
    const {name, nickname} = state;

    return (
        <div>
            <div>
                <input name="name" value={name} onChange={onChange}/>
                <input name="nickname" value={nickname} onChange={onChange}/>
            </div>
            <div>
                <div>
                    <b>이름 : </b> {name}
                </div>
                <div>
                    <b>닉네임 : </b> {nickname}
                </div>
            </div>
        </div>
    );
}

export default Info;

 

+ Recent posts