공부블로그

리액트 기초 다지기 본문

리액트/리액트 공부

리액트 기초 다지기

떠어영 2021. 7. 24. 18:48
< JSX 기본 규칙 >
  • 태그는 꼭 닫는다.
  • 두 개 이상의 태그는 무조건 하나의 태그로 감싼다. ( 이 때 리액트의 Fragment를 이용하기도 한다. )
  • 자바스크립트 변수를 보여줘야 할 때에는 { } 중괄호로 감싸준다.
  • 태그에 style과 CSS class를 설정할 때 인라인 스타일은 객체형태로 작성하고 ( class style = { ... } ),  CSS class를 설정할 때에는 className= 으로 설정해준다.

 

props를 통해 컴포넌트에게 값 전달하기 

 

1. Hello 컴포넌트에 name과 color라는 props (속성)을 전달해주고 싶을 때

 

App. js 에서 props 전달

<Hello name="react" color="red"/>

Hello. js 에서 받아서 작성

function Hello(props) {
  return <div style={{ color: props.color }}>안녕하세요 {props.name}</div>
}

// 비구조화 할당과 초기값 설정도 가능

function Hello({ color, name }) {
  return <div style={{ color }}>안녕하세요 {name}</div>
}

//초기값 설정
Hello.defaultProps = {
  name: '이름없음'
}

 

2.  컴포넌트 태그 사이에 넣은 값을 조회하고 싶을 때는 props. children 사용

 

App. js 에서 이렇게 태그 사이에 값을 넣으면

 <Wrapper>
      <Hello name="react" color="red"/>
      <Hello color="pink"/>
 </Wrapper>

Wrapper. js 에서 children을 인자로 받아서 자바스크립트 객체 형태 { } 로 작성

function Wrapper({ children }) { //children을 인자로 받고
  return (
    <div>
      {children} //자바스크립트 객체 형태로 작성
    </div>
  )
}

 

 

조건부 렌더링 

 

컴포넌트에 isSpecial= { true }라는 props를 설정하고 이 값이 true일 때만 특정 값이 보이도록 하고 싶을 때,

삼항연산자 또는 논리연산자( && )를 사용한다.

 

App. js 

<Hello name="react" color="red" isTrue={false} />

Hello. js

function Hello({ color, name, isTrue }) {
  return (
  	<div>
    { isSpecial ? <b>*</b> : null } //참이면 *가 보이고 아니면 null
		//or
	{isSpecial && <b>*</b>}
    </div>
  );
}

 

 

useState 사용해서 Counter 구현하기

 

Counter. js에서 제목태그로 숫자 띄우고, +, - button에 이벤트 등록

상태 (state)를 관리하기 위해 import { useState } 필요

useState 함수는 현재상태( 0 )와 Setter 함수를 배열로 반환 ( 여기에서는 number가 현재상태, setNumber가 Setter 함수 )

버튼 이벤트에 등록할 증가, 감소함수를 각각 선언하여 그 안에서 setNumber함수로 값을 변경( 관리 )

이벤트에 등록할 때는 on이벤트이름 = { 실행하고 싶은 함수 } 형태로 작성

 

기존 값을 업데이트하는 함수형 업데이트도 있다.

 

( Counter. js )

더보기
import React, { useState } from "react";

function Counter() {
  const [number, setNumber] = useState(0);
  // [ 현재상태, Setter함수 ]배열 반환
  const onIncrease = () => {
    //화살표 함수로 구현
    setNumber(number + 1);
    //Setter함수를 사용해 업데이트 하고 싶은 새로운 값을 파라미터로
  };
  const onDecrease = () => {
    setNumber((prevNumber) => prevNumber - 1);
    //함수형 업데이트: 기존 값을 어떻게 업데이트 할 지
  };
  return (
    <div>
      <h1>{number}</h1>
      <button onClick={onIncrease}>+1</button>
      {/* on이벤트이름 = {실행하고 싶은 함수} */}
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}
export default Counter;

 

 

input 상태 관리하기

 

사용자가 입력할 수 있는 input태그 만들기

InputSample.js에서 input태그, 초기화 버튼, 입력한 값을 보여주는 bold태그 작성

const [ text, setText ] = useState( ' ' ); 로 현재상태와 Setter함수를 반환

input태그의 onChange이벤트로 이벤트 객체인 e를 받아오면 

e. target. value로 입력한 값을 조회해서 onChange함수에서 setText로 등록

( 이때 input태그의 value값도 text로 설정해야 상태가 바뀌었을때 input 의 내용도 업데이트 )

초기화 버튼의 onClick이벤트에 onReset함수를 등록

 

( InputSample. js )

더보기
import React, { useState } from "react";

function InputSample() {
  const [text, setText] = useState(""); //현재상태, Setter함수
  const onChange = (e) => {
    setText(e.target.value); //이벤트가 발생한 DOM의 값으로 text를 업데이트
  };
  const onReset = () => {
    setText(""); //reset
  };
  return (
    <div>
      <input onChange={onChange} value={text} />
      {/* input태그의 value값도 설정해주어야 내용이 업데이트됨 */}
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: {text}</b>
      </div>
    </div>
  );
}
export default InputSample;

 

 

여러개의 input 상태 관리하기

 

input에 name을 설정하여 값을 참조!

두개의 input태그에 각각 name, placeholder, onChange, value 속성과 이벤트를 작성

useState를 사용할 때도 input이 여러 개이므로 객체 형태로 초기 값을 넣어주고,

현재 상태값 변수인 inputs는 비구조화 할당해준다. ( const { name, nickname } = inputs; )

onChange함수에서 받아온 e객체에서 value와 name값을 추출하고 ( const { value, name } = e.target; )

 

setInputs함수에서 기존의 inputs객체를 복사하고, name키를 가진 값을 value로 설정 ( ...inputs, [ name ] : value )

 

//객체를 직접 수정하면 안되고 새로운 객체에 변화를 주고, 이를 상태로 사용해야 한다.

 

( InputSample. js )

더보기
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;
    //e객체에서 value, name을 추출 (비구조화 할당)

    setInputs({
      ...inputs, //기존 input 객체 복사
      [name]: value, //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;

( 연습으로 만든 Inputs. js )

더보기
import React, { useState } from "react";

function Input() {
  const [inputs, setInputs] = useState({ //상태 변수 값도 객체
    title: "",
    singer: "",
    album: "",
  }); 
  const { title, singer, album } = inputs; //여기서 비구조화 할당

  const onChange = (e) => {
    const { value, name } = e.target;//이벤트 객체e에서 값 추출
    setInputs({
      ...inputs,//복제 후
      [name]: value,//키 값에 해당하는 곳에 value할당
    });
  };
  return (
    <div>
      <input name="title" placeholder="제목" onChange={onChange} value={title}/>
      <input name="singer"placeholder="가수" onChange={onChange} value={singer}/>
      <input name="album" placeholder="앨범" onChange={onChange} value={album}/>
      <div>
        {title} by.{singer} ({album}){" "}
      </div>
    </div>
  );
}
export default Input;

 

 

useRef로 특정 DOM 선택

Ref 객체를 만들고 ( const nameInput = useRef(); )

원하는 DOM의 ref 값으로 Ref 객체를 넣어줌 ( <input name="name" ... ref={ nameInput } /> )

초기화 버튼을 눌렀을 때 원하는 DOM을 선택해서 focus하도록 ( const onReset = ( ) => { ... nameInput.current.focus( ) ; } ; )

 

 

배열 렌더링하기

동적으로 변화하는 배열을 렌더링하기 위해서 map( )함수를 사용

( map함수는 배열안에 있는 각 원소를 변환하여 새로운 배열을 만든다. )

또한, 배열을 렌더링할 때는 key라는 props를 설정해서 각각 원소들마다 고유값을 설정해줘야 한다.

( key값을 통해 수정되지 않는 기존의 값은 그대로 두고 원하는 곳에 내용을 삽입하거나 삭제 가능 => 효율적 )

 

( UserList. js )

더보기
import React from "react";

function User({ user }) {
  // USer 라는 컴포넌트 (UserList 컴포넌트에서 사용)
  return (
    <div>
      <b>{user.username}</b> <span>({user.email})</span>
    </div>
  );
}

function UserList() {
  const users = [
    { 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", }, 
  ];
  
  return (
    <div>
      {/* <User user={users[0]} /> 
          <User user={users[1]} />
          <User user={users[2]} /> */}

      {
        users.map((user) => (
          <User user={user} key={user.id} /* 또는 key ={index} */ />
        ))
        /* map = 배열안에 있는 각 원소(user)를 반환하여 새로운 배열을 생성 */
      }
    </div>
  );
}

export default UserList;

 

 

useRef 로 컴포넌트 안의 변수 만들기

useRef Hook 은 DOM 을 선택하는 용도 외에도, 컴포넌트 안에서 조회, 수정 할 수 있는 변수를 관리 할 수 있다.

( const nextId = useRef( 4 ); // 4가 nextId .current 의 기본값 )

 

 

배열에 항목 추가하기

CreateUser. js : input태그 두개와 등록 button을 작성

{ username, email, onChange, onCreate } 를 받아와서 input태그의 name props 과 onChange이벤트, value props 에 할당

 

App. js : CreateUser과 UserList컴포넌트에 props들을 할당하여 렌더링

  1. 1 ) username과 email를 받아오는 onChange 함수

const [ inputs, setInputs ] = useState( { username: ' ', email: ' ' } ); // inputs이라는 현재 상태 변수와 Setter함수 선언

const { username, email } = inputs ;

이벤트 객체 e를 받아오는 onChange 함수에서 비구조화 할당으로 name, value를 받아와 name에 해당하는 곳에 value 할당

  1. 2 ) 배열에 값을 추가하는 onCreate 함수

useState에 초기값으로 user배열을 넣어 선언해주고 ( const [ users, setUsers ] = useState( [ ... ] ); )

새로운 객체를 만들어서 spread방식이나 concat으로 users배열에 병합해준다.

이때 새로운 객체의 id는 위에서 useRef로 만들어 놓은 nextId가 된다. ( const user = { id: nextId. current, username, email }; ) 

마지막으로 setInputs함수로 입력값을 초기화해주고, nextId. current는 +1해준다.

 

( CreateUser. js )

더보기
import React from "react";

function CreateUser({ username, email, onChange, onCreate }) {
  return (
    <div>
      <input name="username" placeholder="계정명" onChange={onChange} value={username} />
      <input name="email" placeholder="이메일" onChange={onChange} value={email} />
      <button onClick={onCreate}>등록</button>
    </div>
  );
}
export default CreateUser;

( App. js )

더보기
import React, { useRef, useState } from "react";
import CreateUser from "./CreateUser";
import UserList from "./UserList";

function App() {
  const [inputs, setInputs] = useState({ username: "", email: "", });
  const { username, email } = inputs;

  const onChange = (e) => { //input태그의 이벤트
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value, //키 값에 해당하는 value 할당
    });
  };
  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",},
  ]);
  const nextId = useRef(4); //nextId.current의 기본값 = 4
  //useRef로 컴포넌트 안에서 조회 및 수정 할 수 있는 변수를 관리
  const onCreate = () => {
    const user = { id: nextId.current, username, email, };
    setUsers([...users, user]); //users배열 복사후 user객체 추가
    //setUsers(users.concat(user)); //concat 사용 시
    setInputs({ username: "", email: "", }); //입력칸 초기화
    nextId.current += 1;
  };
  return (
    <>
      <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} />
      <UserList users={users} /* users라는 props값에 users배열 객체를 넣어줌 */ />
    </>
  );
}
export default App;

 

 

배열에 항목 제거하기

 

User컴포넌트에 삭제 버튼을 만들어 클릭 시 onRemove함수를 실행하여 삭제

이 때, filter함수를 사용해서 id가 일치하지 않는 원소만 추출해서 새로운 배열을 만든다. (id에 해당하는 원소만 제거)

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

 

 

배열에 항목 수정하기

 

users 배열 안의 객체에 active라는 속성을 추가, 계정명에 마우스를 올리면 손가락 모양으로 변하도록 style추가

( style={{ cursor: "pointer", color: user.active ? "green" : "black", }} )

 

User 컴포넌트에 계정명을 클릭했을때 onToggle함수가 실행

( const onToggle = ( id ) => { setUsers( users.map( (user) => (user .id === id ? { ...user, active: !user.active } : user) ) ); }; ) 

 

( UserList. js )

더보기
import React from "react";

function User({ user, onRemove, onToggle }) { //User컴포넌트: 배열의 각 요소를 보여준다
  return (
    <div>
      <b 
      style={{ cursor: "pointer", //마우스를 올리면 손가락 모양으로 변한다.
          color: user.active ? "green" : "black", //active가 참이면 초록색, 아니면 검정색
        }}
        onClick={() => onToggle(user.id)} //클릭되면 onToggle함수 실행
      >
        {user.username}
      </b>{" "}
      <span>({user.email})</span>
      <button onClick={() => onRemove(user.id)}>삭제</button>
    </div>
  );
}

function UserList({ users, onRemove, onToggle }) {
  return (
    <div>
      {users.map((user) => (
        //map() 함수는 users라는 배열의 요소를 돌면서
        //인자로 전달된 함수를 사용하여 처리 된 새로운 결과를 새로운 배열에 담아 반환
        <User user={user} key={user.id} onRemove={onRemove} onToggle={onToggle} />
      ))}
    </div>
  );
}
export default UserList;

 

( App. js )

더보기
import React, { useRef, useState } from "react";
import CreateUser from "./CreateUser";
import UserList from "./UserList";

function App() {
  const [inputs, setInputs] = useState({ username: "", email: "", });
  const { username, email } = inputs;

  const onChange = (e) => { //input태그의 이벤트
    const { name, value } = e.target;
    setInputs({ ...inputs, [name]: value, //키 값에 해당하는 value 할당 });
  };
  const [users, setUsers] = useState([
    { id: 1, username: "velopert", email: "public.velopert@gmail.com", active: true, },
    { id: 2, username: "tester", email: "tester@example.com", active: false, },
    { id: 3, username: "liz", email: "liz@example.com", active: false, },
  ]);
  const nextId = useRef(4); //nextId.current의 기본값 = 4
  //useRef로 컴포넌트 안에서 조회 및 수정 할 수 있는 변수를 관리

  const onCreate = () => {
    const user = { id: nextId.current, username, email, };
    setUsers([...users, user]); //users배열 복사후 user객체 추가
    //setUsers(users.concat(user)); //concat 사용
    setInputs({ username: "", email: "", }); //입력칸 초기화
    nextId.current += 1;
  };
  const onRemove = (id) => {
    //id가 일치하지 않는 원소만 추출해서 새로운 배열을 만든다. (id에 해당하는 원소만 제거)
    setUsers(users.filter((user) => user.id !== id));
  };

  const onToggle = (id) => {
    //User컴포넌트 클릭 시 넘어가는 user.id를 인자로 받아서
    setUsers(
      users.map(
        (user) => (user.id === id ? { ...user, active: !user.active } : user)
        //id가 같으면 active값을 반전, 아니면 그대로
      )
    );
  };
  return (
    <>
      <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} />
      <UserList users={users} onRemove={onRemove} onToggle={onToggle}
        /* users라는 props값에 users배열 객체를 넣어줌 */
      />
    </>
  );
}
export default App;

 

 

useEffect 함수 

 

useEffect 함수는 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 실행할 수 있도록 하는 Hooks

( 컴포넌트가 mount, unmount, update 됐을 때 특정작업을 실행 )

 

기본형태: useEffect( 수행하고자 하는 작업 , deps = 검사하고자 하는 값들의 배열 or 빈배열 )

  1. deps를 생략하면, 리렌더링 될 때마다 실행 ( 마운트, 언마운트 )
  2. deps에 빈배열을 넣으면, 첫 마운트 또는 컴포넌트가 사라질 때만 실행
  3. deps 배열에 값이 있으면,
    1. 처음 마운트 될 때와 해당 값이 바뀔 때 수행하고자 하는 작업이 실행,
    2. 배열 안의 값이 업데이트 되기 직전과 언마운트될 때 return 실행 ( ex. 값이 업데이트 되면 return이 먼저 실행되고 그 뒤에 effect 작업이 실행됨 )

( UserList. js - useEffect함수 )

useEffect(() => { console.log("user 값이 설정됨");  console.log(user);
    return () => { console.log("user 가 바뀌기 전.."); console.log(user); };
  }, [user]);

 

 

useMemo 함수   

 

active값이 true인 값을 세서 보여주도록 countActiveUsers함수를 생성 

function countActiveUsers(users) { //인자로 배열 받아옴
  console.log("활성 사용자 수 세는 중...");
  return users.filter((user) => user.active).length;
  //배열을 순회하며 조건을 만족하는 원소들로 구성된 새로운 배열의 length 리턴
}
const count = countActiveUsers(users);

그런데 이렇게 작성하면 users 배열에 변화가 있을때만 세는 것이 아니라, input 값이 바뀔 때마다 컴포넌트가 리렌더링 됨.

( 값을 입력하면 CreateUser 컴포넌트의 속성(props)가 바뀌게 되는데 이 값은 App 컴포넌트의 state → App의 State가 변경됐으니 App Component가 다시 렌더링 ) 

이 때, useMemo( memorized )라는 Hook함수를 사용하여 성능 최적화

const count = useMemo(() => countActiveUsers(users), [users]);

useMemo 사용법: 두번째 인자인 deps 배열 안에 넣은 내용이 바뀌면, 첫번째 인자로 등록한 함수를 호출해서 값을 연산해주고, 만약에 내용이 바뀌지 않았다면 이전에 연산한 값을 재사용

 

 

useCallback 함수 

useMemo가 특정 값을 재사용하기 위해 쓰였다면, useCallback특정 함수를 재사용할 때 사용.

 

해당 함수 안에서 사용하는 state, props가 있으면 반드시 deps 배열안에 포함시켜야 하고, 컴포넌트에서 props로 함수를 받아왔으면 그 함수도 deps에 포함해야 한다.

함수를 자식 컴포넌트에 props로 넘겨줄 때 는 항상 useCallback을 사용 ( 그렇지 않으면 렌더링할 때마다 계속 함수를 생성 )

useCallback ( 재사용 할 함수, [ 검사할 특정 값들 ] ) ;  // 배열안의 값들이 바뀌면 함수를 호출해서 연산, 아니면 이전 값 사용

const onToggle = useCallback( id => {
   setUsers( users.map( user => user.id === id ? { ...user, active: !user.active } : user )); },
    [users] /* deps 배열 안의 값이 바뀌면 함수를 호출 */
  );

 

 

useReducer 함수

 

컴포넌트의 상태 업데이트를 위한 Hook 함수 ( 로직을 컴포넌트에서 분리할 수 있다 )

 

const [ state, dispatch ] = useReducer( reducer, initialState ); // 상태로 쓰일 변수 이름, 액션을 발생시키는 함수

function reducer( state, action ) { ... } // 현재 상태와 액션 객체를 파라미터로 받아서 새로운 상태를 반환

 

즉, dispatch로 액션을 발생시키고, 그에 맞는 함수를 실행하기 위한 reducer 함수를 작성한다.

 

( App. js )

더보기
import React, { useRef, useReducer, useMemo, useCallback } from "react";
import UserList from "./UserList";
import CreateUser from "./CreateUser";

const initialState = { //초기상태 
  inputs: { username: "", email: "", },
  users: [
    { id: 1, username: "velopert", email: "public.velopert@gmail.com", active: true, },
    { id: 2, username: "tester",  email: "tester@example.com", active: false, },
    { id: 3, username: "liz", email: "liz@example.com", active: false, }, 
    ],
};
function reducer(state, action) { //reducer 함수
  switch (action.type) { //액션에 맞는 작업을 실행하도록 조건문 작성 
    case "CHANGE_INPUT": 
  	//input태그의 onChange이벤트에 설정한 onChange에 의해 dispatch가 해당 타입의 액션 발생시킴
      return {
        ...state, inputs: {...state.inputs, [action.name]: action.value, },
        //state 객체 복사 후 inputs 수정( state.inputs복사 후 액션으로 받아온 값으로 수정 )
      };
    case "CREATE_USER":
      return {
        inputs: initialState.inputs,
        users: state.users.concat(action.user), //추가
      }; //리턴값은 새로운 state
    case "TOGGLE_USER":
      return {
        ...state,
        users: state.users.map((user) =>
          user.id === action.id ? { ...user, active: !user.active } : user
        ),
      };
    case "REMOVE_USER":
      return {
        ...state,
        users: state.users.filter((user) => user.id !== action.id),
      };
    default: return state;
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const nextId = useRef(4);

  //state = { inputs: {username: "", email: ""} users: [{…}, {…}, {…}] }
  const { users } = state;
  //state에서 users추출
  const { username, email } = state.inputs;
  //state의 inputs에서 username과 email추출

  const onChange = useCallback((e) => {
    const { name, value } = e.target; //비구조화 할당
    dispatch({  type: "CHANGE_INPUT", name, value, }); }, []); //action으로 전달

  const onCreate = useCallback(() => {
    dispatch({ type: "CREATE_USER", user: { id: nextId.current, username, email, }, }); 
    nextId.current += 1;
  }, [username, email]);

  const onToggle = useCallback((id) => {
    dispatch({ type: "TOGGLE_USER", id, }); }, []);

  const onRemove = useCallback((id) => {
    dispatch({ type: "REMOVE_USER", id, }); }, []);

  const count = useMemo(() => countActiveUsers(users), [users]);
  
  function countActiveUsers(users) {
  console.log("활성 사용자 수를 세는중...");
  return users.filter((user) => user.active).length;
} 
  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
      <div>활성사용자 수 : {count}</div>
    </>
  );
}

export default App;

 

여기까지 리액트의 기초 문법들과 여러가지 Hooks함수에 대하여 알아보았다.

여러 함수들을 능숙하게 사용할 수 있기까지는 더 많은 시간과 노력이 필요할 것 같다!!!