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;

 

라이플 사이크 메서드는 클래스형 컴포넌트에서만 사용할 수 있음. 함수형 컴포넌트에서는 사용할 수 없음. 대신 Hooks 기능을 사용하여 비슷한 작업을 처리.

7.1 라이프사이클 메서드의 이해

Will 접두사가 붙은 메서드  : 작업을 처리하기 전에 실행되는 메서드
Did 접두사가 붙은 메서드 : 작업을 작동한 후에 실행되는 메서드

종류 : 마운트, 업데이트, 언마운트

마운트

  • 호출 : DOM이 생성되고 웹 브라우저상에 나타날 때
  • 순서 :
    1. constructor :  컴포넌트를 새로 만들 때마다 호출되는 클래스 생성자 메서드
    2. getDerivedStateFromProps  : props에 있는 값을 state에 넣을 때 사용하는 메서드
    3. render : 준비한 UI를 렌더링하는 메서드
    4. componentDidMount : 컴포넌트가 웹 브라우저상에 나타난 후 호출하는 메서드

업데이트

  • 호출
    1. props가 바뀔 때
    2. state가 바뀔 때
    3. 부모 컴포넌트가 리렌더링될 때
    4. this.forceUpdate로 강제로 렌더링을 트리거할 때
  • 순서
    1. getDerivedStateFromProps
    2. shouldComponentUpdate : 컴포넌트가 리랜더링을 할지를 결정하는 메서드 (true -> render호출, false -> 여기서 작업 취소)
    3. render
    4. getSnapshotBeforeUpdate : 컴포넌트 변화를 DOM에 반영하기 바로 직전에 호출
    5. componentDidUpdate : 컴포넌트의 업데이트 작업이 끝난 후 호출하는 메서드

언마운트 

  • 호출
    • 컴포넌트를 DOM에서 제거할 때
  • 순서
    • componentWillUnmount : 컴포넌트가 웹 브라우저상에서 사라지기 전에 호출

7.2 라이프사이클 메서드 살펴보기

7.3 라이프사클 메서드 사용하기

 

'개발 도서 > 리액트를 다루는 기술(개정판)' 카테고리의 다른 글

9장 컴포넌트 스타일링  (0) 2020.05.24
8장 Hooks  (0) 2020.05.23
6장 컴포넌트 반복  (0) 2020.05.23
5장 ref:DOM에 이름 달기  (0) 2020.05.23
4장 이벤트 핸들링  (0) 2020.05.22

6.1 자바스크립트 배열의 map() 함수

arr.map(callback, [thisArg])

  • callback : 새로운 배열의 요소를 생성하는 함수
    • currentValue : 현재 처리하고 있는 요소
    • index : 현재 처리하고 있는 요소의 index 값
    • array : 현재 처리하고 있는 원본 배열
//map변환 예시
var numbers = [1, 2, 3, 4, 5];
var processed = numbers.map(function (num) {
	return num * num;
});
console.log(processed);

//ES6
var numbers = [1, 2, 3, 4, 5];
var processed = numbers.map(num => num * num);
console.log(processed);
        
        

6.2 데이터 배열을 컴포넌트 배열로 변환하기, 6.3 key

고유한 값이 없을 때 index값을 key로 사용해야 함. 그러나, 이 경우 배열이 변경될 때 효율적으로 리렌더링 하지 못 함.

//key값을 설정해주지 않으면 콘솔에 에러 표시됨. Virtual DOM을 비교하는 과정에서 key로 변화를 빠르게 감지
import React from "react";

const IterationSample = () => {
    const names = ['김', '이', '박', '최'];
    const nameList = names.map((name, index) => <li key={index}>{name}</li>);
    return <ul>{nameList}</ul>;
}

export default IterationSample;

6.4 응용

  • 상태 안에서 배열을 변형할 때 배열에 직접 접근하여 수정하는 것이 아니라 concat, filter 등의 배열 내장 함수를 사용하여 새로운 배열을 만든 후 이를 새로운 상태로 설정해 주어야 한다.

    • 배열 추가 시 push <-> concat : 불변성 X <-> O

    • 배열 변경 시 pop <-> filter : 불변성 X <-> O

import React, {useState} from "react";

const IterationSample = () => {
    const [names, setNames] = useState([
        {id: 1, text: '김'},
        {id: 2, text: '이'},
        {id: 3, text: '박'},
        {id: 4, text: '최'},
    ]);
    const [inputText, setInputText] = useState('');
    const [nextId, setNextId] = useState(5);

    const onChange = e => setInputText(e.target.value);
    const onClick = () => {
        const nextNames = names.concat({
            id: nextId,
            text: inputText
        })
        setNextId(nextId + 1);
        setNames(nextNames);
        setInputText('');
    };
    const onRemove = id => {
        const nextNames = names.filter(name => name.id !== id);
        setNames(nextNames);
    };

    const nameList = names.map(name => (<li key={name.id} onDoubleClick={() => onRemove(name.id)}>{name.text}</li>));
    return (
        <>
            <input value={inputText} onChange={onChange}/>
            <button onClick={onClick}>추가</button>
            <ul>{nameList}</ul>
        </>
    );
}

export default IterationSample;

'개발 도서 > 리액트를 다루는 기술(개정판)' 카테고리의 다른 글

8장 Hooks  (0) 2020.05.23
7장 컴포넌트의 라이프사이클 메서드  (0) 2020.05.23
5장 ref:DOM에 이름 달기  (0) 2020.05.23
4장 이벤트 핸들링  (0) 2020.05.22
3장 컴포넌트  (0) 2020.05.17

리액트에서 태그를 특정화하는데 id대신 ref를 사용해야함. 같은 컴포넌트를 여러 번 사용할 때 중복id가 가진 DOM이 발생하기 때문

5.1 ref는 어떤 상황에서 사용해야 할까?

  • DOM을 꼭 직접적으로 건드려야 할 때
  • DOM을 꼭 사용해야 하는 상황
    • 특정 input에 포커스 주기
    • 스크롤 박스 조작하기
    • Canvas 요소에 그림 그리기 등

5.2 ref 사용

5.2.1 콜백 함수를 통한 ref 설정

//ref를 달고자 하는 요소에 ref라는 콜백 함수를 props로 전달. 이 콜백 함수는 ref값을 파라미터로 전달받음.  
//함수 내부에서 파라미터로 받은 ref를 컴포넌트의 멤버 변수로 설정. this.input은 input 요소의 DOM을 가리킴.(ref이름 자유 지정)

<input ref={(ref)=>{this.input=ref}}/>

5.2.2 createRef를 통한 ref 설정

리액트에 내장되어 있는 createRef함수를 사용하면 더 적은 코드로 ref를 쉽게 사용할 수 있음. (v16.3부터 도입)

import React, {Component} from "react";

class RefSample extends Component {
    input = React.createRef();

    handleFocus = () => {
        this.input.current.focus();
    }

    render() {
        return (
            <div>
                <input ref={this.input}/>
            </div>
        )
    }
}

export default RefSample;

5.3 컴포넌트에 ref 달기

component에도 ref를 달 수 있음. (DOM과 방법 동일).
내부의 ref(메서드 및 멤버 변수)에도 접근할 수 있게 됨.

  • 스크롤 박스를 아래로 내리는 예시
//App.js
import React, {Component} from 'react';
import ScrollBox from "./ScrollBox";

class App extends Component {
    render() {
        return (
            <div>
                <ScrollBox ref={(ref) => this.scrollBox = ref}/>
                <button onClick={()=>this.scrollBox.scrollToBottom()}>
                    맨 밑으로
                </button>
            </div>
        );
    }
}

export default App;


//ScrollBox.js
import React, {Component} from "react";

class ScrollBox extends Component {
    scrollToBottom = () => {
        const {scrollHeight, clientHeight} = this.box;
        this.box.scrollTop = scrollHeight - clientHeight;
    }

    render() {
        const style = {
            border: '1px solid black',
            height: '300px',
            width: '300px',
            overflow: 'auto',
            position: 'relative'
        }
        const innerStyle = {
            width: '100%',
            height: '500px',
            background: 'linear-gradient(white, black)'
        }
        return (
            <div style={style} ref={(ref) => {
                this.box = ref
            }}>
                <div style={innerStyle}/>
            </div>
        );
    }
}

export default ScrollBox;

 

'개발 도서 > 리액트를 다루는 기술(개정판)' 카테고리의 다른 글

7장 컴포넌트의 라이프사이클 메서드  (0) 2020.05.23
6장 컴포넌트 반복  (0) 2020.05.23
4장 이벤트 핸들링  (0) 2020.05.22
3장 컴포넌트  (0) 2020.05.17
2장 JSX  (0) 2020.05.16

4.1 리액트의 이벤트 시스템

4.1.1 이벤트를 사용할 때 주의 사항

  • 이벤트 이름은 카멜 표기법으로 작성
  • 이벤트에 실행할 자바스크립트 코드를 전달하는 것이 아니라, 함수 형태의 값을 전달. 
  • DOM 요소에만 이벤트를 설정할 수 있음. (직접 만든 컴포넌트에는 이벤트를 설정할 수 없음.)

4.1.2. 이벤트 종류

4.2 예제로 이벤트 핸들링 익히기

4.2.1 컴포넌트 생성 및 불러오기

4.2.2 onChange 이벤트 핸들링하기

import React, {Component} from "react";

class EventPractice extends Component {
    state = {
        message: ''
    }

    render() {
        return (
            <div>
                <h1>이벤트 연습</h1>
                <input type="text" name="message" placeholder="아무거나 입력해 보세요"
                       value={this.state.message}
                       onChange={(e) => {
                           this.setState({message: e.target.value})
                       }}/>
                <button onClick={() => {
                    alert(this.state.message);
                    this.setState({
                        message: ''
                    });
                }}>확인</button>
            </div>
        );
    }

}

export default EventPractice;

4.2.3 임의 메서드 만들기

//함수 주입
//함수가 호출될 때 this는 호출부에 따라 결정되므로, 클래스의 임의 메서드가 특정 html요소의 이벤트로 등록되는 과정에서
//메서드와 this의 관계가 끊어져 버림.
import React, {Component} from "react";

class EventPractice extends Component {
    state = {
        message: ''
    }

    constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
        this.handleClick = this.handleClick.bind(this);
    }

    handleChange(e) {
        this.setState({
            message: e.target.value
        });
    }

    handleClick() {
        alert(this.state.message);
        this.setState({
            message: ''
        });
    }

    render() {
        return (
            <div>
                <h1>이벤트 연습</h1>
                <input type="text" name="message" placeholder="아무거나 입력해 보세요"
                       value={this.state.message}
                       onChange={this.handleChange}/>
                <button onClick={this.handleClick}>확인
                </button>
            </div>
        );
    }

}

export default EventPractice;


// 바벨의 transform-class-properties 문법을 사용하여 화살표 함수 형태로 메소드를 정의
import React, {Component} from "react";

class EventPractice extends Component {
    state = {
        message: ''
    }

    constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
        this.handleClick = this.handleClick.bind(this);
    }

    handleChange(e) {
        this.setState({
            message: e.target.value
        });
    }

    handleClick() {
        alert(this.state.message);
        this.setState({
            message: ''
        });
    }

    render() {
        return (
            <div>
                <h1>이벤트 연습</h1>
                <input type="text" name="message" placeholder="아무거나 입력해 보세요"
                       value={this.state.message}
                       onChange={this.handleChange}/>
                <button onClick={this.handleClick}>확인
                </button>
            </div>
        );
    }

}

export default EventPractice;

4.2.4 이벤트 여러개 다루기

    handleChange = (e) => {
        this.setState({
            [e.target.name]: e.target.value
        });
    }

4.2.5 onKeyPress 이벤트 핸들링

4.3 함수형 컴포넌트로 구현해 보기

import React, {useState} from "react";

const EventPractice = () => {
    const [form, setForm] = useState({username: '', message: ''})
    const {username, message} = form;

    const onChange = e => {
        const nextForm = {
            ...form,
            [e.target.name]: e.target.value
        }
        setForm(nextForm);
    }

    const onClick = () => {
        alert(username + ':' + message);
        setForm({
            username: '',
            message: ''
        })
    }

    const onKeyPress = e => {
        if (e.key === 'Enter') {
            onClick();
        }
    }

    return (
        <div>
            <h1>이벤트 연습</h1>
            <input type="text" name="username" placeholder="사용자명" value={username}
                   onChange={onChange}/>
            <input type="text" name="message" placeholder="아무거나 입력해 보세요"
                   value={message}
                   onChange={onChange}
                   onKeyPress={onKeyPress}/>
            <button onClick={onClick}>확인
            </button>
        </div>
    );

}

export default EventPractice;

'개발 도서 > 리액트를 다루는 기술(개정판)' 카테고리의 다른 글

6장 컴포넌트 반복  (0) 2020.05.23
5장 ref:DOM에 이름 달기  (0) 2020.05.23
3장 컴포넌트  (0) 2020.05.17
2장 JSX  (0) 2020.05.16
1장 리액트 시작  (0) 2020.05.16

+ Recent posts