백엔드 개발자 프론트 도전기 feat.리엑트 [1]

     

 

react.vlpt.us/

 

 

벨로퍼트와 함께하는 모던 리액트 · GitBook

벨로퍼트와 함께하는 모던 리액트 본 강의자료는 패스트캠퍼스 온라인 강의에서 제공하는 리액트 강의에서 사용되는 강의 문서입니다. 이 튜토리얼은 여러분들이 JavaScript 의 기초를 잘 알고있

react.vlpt.us

벨로퍼트와 함께하는 모던 리액트로 리액트 시작.

 

 

 

useState 뭐냐...?

 

동적인 값을 할당하기 위해 useState라는걸 사용하는데 고놈 참 신기한 동작을 한다.

 

일단 선언은 아래와 같다.

const [number, setNumber] = useState(0);

 

useState는 함수고 인자값은 초기값이다. 이놈은 return으로 두 개의 변수가 담긴 배열을 반환한다. 이 배열에 첫번째놈은 value이고 두번째놈은 setter이다. 위에서 number는 value고 이 value값을 할당해주는 setter가 setNumber다.

 

아래 예제는 +버튼과 -버튼 두개를 만들고 누르면 값이 올라가고 내려가는 count를 구현한 코드다.

 

import React, {useState} from "react";

function Count(){
    const [number, setNumber] = useState(0);

    const onAdd = () => {
        setNumber(prev => prev+1)
    }

    const onMin = () => {
        setNumber(prev => prev-1)
    }


    return(
        <div>
            <h1>{number}</h1>
            <button onClick={onAdd}>+1</button>
            <button onClick={onMin}>-1</button>
        </div>
    );
}

export default Count

 

최근에 python 코드만 봤더니 매우 어색하다 ㅋㅋㅋㅋ

일단 Count()라는 애가 return하는건 html이다. 그 안에 onAdd랑 onMin함수가 있는데, onAdd의 경우 setNumber를 이용해 값을 1 더해주는 함수고, onMin은 반대이다. 

 

아니 무슨 함수 선언이 () => {} 요래? ㅋㅋㅋㅋ

자바스크립트의 화살표 함수 선언은 아직까지 너무 생소한것 같다.

 

아무튼 setNumber를 보면 괄호안에 인자값으로 함수가 들어가는데, 그냥 숫자를 넣어도 된다.

setNumber(number+1)이렇게.

 

근데 숫자를 넣는것보다는 함수를 넣는것이 최적화에 효율적이라고 한다.

왜그런지는 나중에 알아본다고 하니.. 기억하고 있어야지 ㅋㅋㅋ

 

 

 

 

근데 이게 string도 된다. ㅋㅋ 아래는 텍스트를 입력하고 초기화 할수있게 만든 버전.

import React, {useState} from "react";

function InputSample() {
    const [text, setText] = useState('');

    const onChange = (e) => {
        setText(e.target.value);
    }

    const onReset = () => {
        setText('');
    }

    return (
        <div>
            <input onChange={onChange} value={text} />
            <button onClick={onReset}> 초기화 </button>
            <div>
                <b> 값: {text} </b>
            </div>
        </div>
    );
}

export default InputSample

 

input 태그의 onChange를 활용해서 특정 이벤트가 발생하면 그 이벤트의 target에서 value를 끄집어 내서 text에 세팅한다. onChange의 인자값이 e는 아마도 input 태그를 말하는것 같다. e.target.value하면 input태그의 value값이 나오는거 보니 ㅋㅋ

 

 

 

useState 여러개를 사용할 때는?

 

동적으로 변하는 변수가 여러개일 때는 어떻게 할까?

여러개의 useState를 써도 되지만, 하나의 useState를 객체형태로 관리할 수 도 있다. 

 

import React, {useState} from "react";

function InputSample() {
    const [inputs, setInputs] = useState({
        name: '',
        nickname: ''
    });

    const { name, nickname } = inputs;

    const onChange = (e) => {
        const {value, name} = e.target;
        setInputs({
            ...inputs,
            [name]: value
        });
    }

    const onReset = () => {
        setInputs({
            name: '',
            nickname: ''
        })
    };

    return (
        <div>
            <input name="name" placeholder="이름" onChange={onChange} value={name} />
            <input name="nickname" placeholder="닉네임" onChange={onChange} value={nickname} />
            <button onClick={onReset}> 초기화 </button>
            <div>
                <b> 값: </b>
                {name} ({nickname})
            </div>
        </div>
    );
}

export default InputSample

 

위에 코드에서 useState의 초기값으로 {name:'', nickname:''}이 사용되었다. 즉 useState의 초기값은 name과 nickname을 갖는 객체가 된다.

 

다른건 다 이해가 가는데 처음보는 스타일의 코드가 있다.

 

...input,

 

이게 뭐냐 ㅋㅋㅋㅋ 어떻게 검색해야되는지 감도 안옴 ㅋㅋㅋ 일단은 'javascript dotdotdot'으로 검색하니까 나오긴 함. 

 

Threedot이라고 불리기도 하는데 배열이나 원소의 나머지(rest)를 가리키거나 전체 원소(spread)를 가리키는데 사용되는 거 같습니다. 

 

위에 코드에서는 전체 원소로 사용되었는데, inputs={name, nickname}이니까 ...input이라고 하면 {name, nickname}을 말합니다. 즉 이전값을 불러오기 위해서 굳이 {name, nickname}을 한번씩 더 써준게 아니라 단순히 ...을 이용해서 써준거죠. 아마 ...을 사용하지 않았다면 아래와 같이 되었겠죠?

 

setInputs({
    // ...inputs,
    name:inputs.name,
    nickname:inputs.nickname,
    [name]: value
});

 

 

useRef 사용하기

프론트만들다보면 javascript를 붙이기 위해 DOM에 접근해서 위치를 불러오곤 했는데, 예를들면 어떤 텍스트값에 변화를 주기 위해 Document.getElementById 등의 함수를 사용해서 특정 위치를 찾곤했다. 

 

리엑트에서는 useRef라는것을 사용해서 위치를 찾을 수 있는데, ref태그를 이용해 id를 할당하고 검색하는 것 같은 느낌이다.

 

위에 코드에서 useRef를 더해 초기화 한 후 이름을 입력하는곳에 focus가 가도록 추가했다. 여기서 focus는 마지막으로 사용자가 눌른 객체를 말한다. 나만 focus가 먼지 몰랐던거 아니지??

 

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

function InputSample() {
    const [inputs, setInputs] = useState({
        name: '',
        nickname: ''
    });

    const nameInput = useRef();

    const { name, nickname } = inputs;

    const onChange = (e) => {
        const {value, name} = e.target;
        setInputs({
            // ...inputs,
            name:inputs.name,
            nickname:inputs.nickname,
            [name]: value
        });
    }

    const onReset = () => {
        setInputs({
            name: '',
            nickname: ''
        });
        nameInput.current.focus();
    };

    return (
        <div>
            <input name="name" placeholder="이름" onChange={onChange} value={name} ref={nameInput} />
            <input name="nickname" placeholder="닉네임" onChange={onChange} value={nickname} />
            <button onClick={onReset}> 초기화 </button>
            <div>
                <b> 값: </b>
                {name} ({nickname})
            </div>
        </div>
    );
}

export default InputSample

 

return에서 name을 입력하는 input태그에 ref가 추가된것을 확인할 수 있다. 이렇게 ref에 nameInput이라는것을 할당하고, 소스에서 nameInput = useRef()라고 할당하면, 나중에 nameInput을 통해 name을 입력하는 input tag에 접근할 수 있게 된다. 여기서는 onReset 함수 제일 끝에 nameInput.current.focus();를 사용해서 초기화 버튼을 누를 경우 input으로 focus가 이동하도록 만들었다.

 

 

동적으로 변하는 배열 사용하기

useState를 사용해 동적으로 변하는 배열을 만들 수 있다.

배열 자체를 State로 생성하여 setState를 사용해 배열이 추가, 변경될 때마다 자동적으로 화면을 다시 그릴 수 있다.

 

const [users, setUsers] = useState([
        {
            id: 1,
            username: 'velopert',
            email: 'public.velopert@gmail.com'
        },
        {
            id: 2,
            username: 'tester',
            email: 'tester@example.com'
        },
        {
            id: 3,
            username: 'liz',
            email: 'liz@example.com'
        }
    ]);

 

물론 useState()에 인자값(초기화값)으로 배열을 선언해줘야한다. 

useState에서 setState가 값을 추가하는게 아니라 상태값을 새로 만드는 것이기 때문에 배열에 특정값이 추가될 때 기존에 배열에 하나가 추가되는게 아니라 기존배열 + 새로운값을 이용해 새로운 배열을 만들게 된다. 새로운 배열을 만드는 방법은 두 가지가 있다.

setUsers([...users, user]);  // 첫번째 방법
setUsers(users.concat(user)); // 두번째 방법

첫번째방법은 ...연산자를 사용하여 기존에 있는 배열 + 새로운 값으로 새로운 배열을 만드는것이고, 두번째 방법은 concat함수를 이용해 새로운 값을 붙인 배열을 만드는 것이다.

 

 

배열을 삭제하는건 추가하는것보다 살짝 복잡할 수 있는데, 삭제하려는 원소값만 빼고 배열을 새로 만들면 된다.

처음에 내가 짠 코드는 아래인데,, 강의에서는 더 간단하게 만들었네 ㅋㅋㅋ

    const onRemove = (userid) => {
        const idx = users.findIndex(function (user){return user.id === userid})
        users.splice(idx, 1);
        setUsers([...users]);
    }

삭제할 id 들고와서 index값 찾은 다음에, splice를 이용해서 삭제. 그 다음 setUser로 삭제된 배열을 할당해주는건데, 강의에서는 한줄로 만들었다.

 

const onRemove = (userid) => {
   	setUsers(users.filter(user => user.id !== userid));
}

filter를 이용해서 user.id가 입력받은 userid가 아닌것만 새로 배열로 만들었다.

 

 

 

 

원소의 값을 수정하기 위해서는 onRemove랑 똑같이 수정할 대상을 찾은 다음 원소객체 안에 있는 값을 바꿔주면 된다. 

 

const onToggle = (userid) =>{
    setUsers(
        users.map(user =>
            user.id === userid ? {...user, active: !user.active, username: '선택됨'} : user
        )
    );
};

예제에서는 active만 바꿨지만 나는 username도 바꿔봄.

뭔가 수정이라길래 이름을 바꾸는 예제를 할 줄 알았는데 그냥 색만 바꿈. 

아마 텍스트를 변경하려면 edittext로 변경하고 새로 입력도 받아야되고 번거로우니까 안한것 같다.

 

 

useMemo, useEffect

 

useEffect는 컴포넌트가 mount, unmount될때 호출됨.

useMemo는 매번 호출할 필요 없이 값이 변경될 때만 실행되는 메소드들에 대해 이전에 기억된 값을 사용할 수 있도록 해줌(최적화)

 

크롬 리액트 개발자도구 

React Developer Tools - Chrome 웹 스토어 (google.com)

 

React Developer Tools

Adds React debugging tools to the Chrome Developer Tools. Created from revision f160547f47 on 12/4/2020.

chrome.google.com

요거 깔면 이제 화면이 리로딩 될때 테두리가 생긴다.

역시 개발은 크롬으로 해야되는....

 

 

 

useCallback

useState를 사용하면 상태가 바뀔때마다 자동으로 랜더링해준다.

그런데 onCreate나 onToggle, onRemove같은 함수들도 상태가 바뀔때마다 재 선언되고 있다.

함수의 내용이 변하지 않으니 기존에 있던거 그냥 써도 되는데 기존꺼 쓰려면 useCallback을 이용해서 감싸줘야된다.

 

 

 

React.memo

함수를 재사용하는거 말고 컴포넌트를 재사용하게 하기 위해서는 React.memo를 사용하면 된다. useMemo랑은 좀 다른거 같은데 사용하는게 비슷해보인다. 뭔차이냐 ㅋㅋㅋ

 

다시 돌아가서 보니 useMemo는 함수를 호출할때 사용되는거고, React.memo는 컴포넌트를 감싸주는것 같다.

 

 

useReducer

요게 이제 좀 핵심인것 같은데, 기존에 쓰던 useState와 비슷하긴 한데 상태업데이트를 setter로 하는게 아니라 외부 함수로 만들어서 할 수 있게 만든다. 선언은 useState랑 비슷한데 살짝 인자값이 다르다.

 

    const [userState, userDispatch] = useReducer(UserReducer, usersInitialState);

 

요런식으로 UserReducer라는 함수를 만들어서 첫번째 인자로 넣고, 두번째 인자로 초기값을 넣는다. 반환되는 값은 상태값과 dispatch라는 함수로, dispatch함수를 이용해 state를 변화시킬 수 있다.

 

Reducer함수는 state와 action값을 인자로 같으며 action안에 있는 type에 따라 state를 변경하는 로직이다.

 

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

reducer({type:'INCREMENT'});

 

요런느낌.

 

 

커스텀 hook

상태를 변경시키는 hook 기능들을 소스 하나에 다 코딩하는게 아니라 따로 모듈화해서 관리할 수 있게 커스텀 hook으로 만들 수 있다. 비슷하게 상태를 생성, 변경한다면 커스텀 hook 하나만 만들어서 재사용이 가능할듯.

 

import { useState, useCallback} from 'react';

function useInputs(initialForm){
    const [form, setForm] = useState(initialForm);

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

    const reset = useCallback( () => setForm(initialForm), [initialForm]);
    return [form, onChange, reset];
}

export default useInputs;

위처럼 useInputs에 대한 커스텀 hook을 만든다음에 useInputs를 이용해서 initialForm, onChange, reset을 한번에 가져올 수 있다.

 

const [{username, email}, onChange, reset] = useInputs({username:'', email:''});

요롷게.

 

 

ContextAPI

컴포넌트를 전달-전달-전달 해서 사용하는데, 언제까지 전달할 수는 없으니 전역으로 선언해서 사용하는 방법이 있다.

 

createContext()랑 useContext()를 사용하면 Context의 Provider를 사용해서 특정 객체를 Provider가 감싸진 영역에서 상요할 수 있게 만들 수 있다.

 

export const UserDispatch = React.createContext(userDispatch);

return(
    <UserDispatch.Provider value={userDispatch}>
    <CreateUser username={username} email={email} onChange={onChange} />
    <UserList users={users}/>
    <div>활성화 사용자 수 = {count}</div>
    </UserDispatch.Provider>
)

 

요롷게 userDispatch로 감싼 CreateUser랑 UserList에서는 userDispatch를 사용할 수 있다.

 

 

 

Immer 불변성 

 

상태값을 업뎃해주면 계속해서 불변성을 맞춰주고 있는데, Immer라는 패키지를 설치하면 자동적으로 불변성을 맞춰준다.

 

근데 성능상 안좋고, 구형 브라우져에서는 지원을 안한다. 

 

쓰라는건지 말라는건지,,, ㅋㅋ

 

 

 

 

반응형

댓글

Designed by JB FACTORY