1장. React 입문 요약

hyebin Jo·2022년 7월 10일
0

4. JSX의 기본 규칙

✅ JSX
javascript에서 XML을 확장한 문법입니다.
리액트에서 XML형식의 코드를 리턴할 수 있습니다.
브라우저에서 실행시 Bable도구를 사용하여 일반 Javascript 코드로 변환합니다.

📌 태그는 꼭 닫혀있어야 합니다.

📌 하나의 태그로 감싸야 합니다.

📌 JSX 내부에 자바스크립트 변수를 보여줘야 할 때는 {}로 감싸서 보여줍니다.

📌 태그에 style과 className을 설정하는 방법

  • 인라인 스타일은 객체로 작성
  • CSS속성을 camelCase형태로 네이밍
  • class를 설정할 때는 className="클래스명"으로 설정

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

📌props 의 기본 사용법

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

📌여러개의 props, 비구조화 할당 (구조분해할당)

함수의 파라미터에서 비구조화 할당 문법을 사용하여 props를 더 간결하게 사용할 수 있습니다.

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

📌defaultProps 로 기본값 설정

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

Hello.defaultProps = {
  name: '이름없음'
}
function App() {
  return (
    <>
      <Hello color="pink"/>
    </>
  );
} 
//props로 name을 지정해주지 않으면 Hello컴포넌트에서 지정해준 defaultProps의 name이 렌더링

📌props.children

컴포넌트 태그 사이에 넣은 값을 조회하고 싶을 땐, props.children 을 조회하면 됩니다.

function Wrapper() {
  return (
    <div></div>
  )
}
import Hello from './Hello';
import Wrapper from './Wrapper';

function App() {
  return (
    <Wrapper>
      <Hello />
    </Wrapper>
  );
}

이때, Wrapper컴포넌트에서 props.children을 렌더링하지 않았으므로 <Hello />컴포넌트가 보이지 않습니다.
Wrapper 내부의 내용이 보여지게 하기 위해서는 Wrapper 에서 props.children 을 렌더링해주어야 합니다.

function Wrapper({ children }) {
  return (
    <div>{children}</div>
  )
}

6. 조건부 렌더링

✅ 조건부 렌더링
특정 조건에 따라 다른 결과물을 렌더링 하는 것

function App() {
  return (
    <Wrapper>
      <Hello isSpecial={true}/>
      //<Hello isSpecial />  props값을 생략하면 ={true}
    </Wrapper>
  )
}
function Hello({isSpecial}) {
  return (
    <div>
      {isSpecial && <p>안녕</p>} //isSpecial이 true 일때만 <p>안녕</p>이 렌더링
    </div>
  );
}

7. useState 를 통해 컴포넌트에서 바뀌는 값 관리하기

📌동적인 값 끼얹기, useState

import React, { useState } from 'react';
//React의 useState라는 함수로 컴포넌트의 상태를 관리한다.

function Counter() {
  const [number, setNumber] = useState(0);
  //number를 0으로 초기화
  //setNumber(새로운값)로 number상태 변경

  const onIncrease = () => {
    setNumber(number + 1);
  }

  const onDecrease = () => {
    setNumber(number - 1);
  }

  return (
    <div>
      <h1>{number}</h1>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
      //주의!!
      //함수 형태를 넣어주어야 한다.
      //onClick={onIncrease()} 처럼 함수를 바로 실행시키면 안됨.
    </div>
  );
}

📌함수형 업데이트

setter함수를 사용할때 업데이트 하고 싶은 새로운 값을 파라미터로 넣어주는 방식이 아니라, 기존 값을 어떻게 업데이트 할 지에 대한 함수를 등록하는 방식으로도 값을 업데이트 할 수 있습니다.
setNumber(prevNumber => prevNumber + 1);

8. input 상태 관리하기

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

  const onChange = (e) => {
    setText(e.target.value);
  };
  //e.target은 input DOM을 가리킨다.
  //Input에 값을 입력할때마다(onChange될때마다) text를 Input태그의 value값으로 상태업데이트

  const onReset = () => {
    setText('');
  };
  //onReset이 실행되면(버튼이 onClick되면) text=''이 되므로 Input안의 값이 지워진다

  return (
    <div>
      <input onChange={onChange} value={text}  /> 
      //Input태그의 value를 text상태로 관리
      //상태가 바뀌었을때 input 의 내용도 업데이트
      <button onClick={onReset}>초기화</button>
      <p>: {text}</p>
    </div>
  );
}

9. 여러개의 input 상태 관리하기

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

  const { name, nickname } = inputs; // 비구조화 할당을 통해 값 추출

  const onChange = (e) => {
    const { value, name } = e.target; 
    // 우선 e.target 에서 name 과 value 를 추출
    setInputs({
      ...inputs, // 기존의 input 객체를 복사한 뒤 (전개연산자(spread 문법))
      [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>
  );
}

🌟 리액트 상태에서 '객체'를 수정해야 할 때에는 객체를 직접 수정하면 안되고, 새로운 객체를 만들어서 그 객체에 변화를 주고 이를 상태로 사용해주어야 합니다.
이러한 작업을, "🌟불변성을 지킨다🌟 라고 부릅니다. 불변성을 지켜주어야만 리액트 컴포넌트에서 상태가 업데이트가 됐음을 감지 할 수 있고 이에 따라 필요한 리렌더링이 진행됩니다.
inputs[name] = value; 이렇게 객체를 직접 수정하면 값을 바꿔도 리렌더링이 되지 않습니다.

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

10. useRef 로 특정 DOM 선택하기

리액트를 사용하는 프로젝트에서 DOM 을 직접 선택해야 할 때 ref라는 것을 사용합니다. 함수형 컴포넌트에서 ref 를 사용 할 때에는 useRef 라는 Hook 함수를 사용합니다.

function InputSample() {
  const nameInput = useRef(); //useRef() 를 사용하여 Ref 객체를 만든다.

  const onReset = () => {
    nameInput.current.focus();
    //Ref객체의 .current는 우리가 원하는 DOM(밑에서 설정해준 input) 을 가리키게 됩니다.
  };

  return (
    <div>
      <input
        placeholder="이름"
        ref={nameInput} //nameInput객체를 이 DOM에 ref 값으로 설정
      />
      <button onClick={onReset}>초기화</button>
    </div>
  );
}

11. 배열 렌더링하기

📌동적인 배열을 렌더링해야 할 때에는 자바스크립트 배열의 내장함수 map() 을 사용합니다.
📌리액트에서 배열을 렌더링 할 때에는 key 라는 props 를 설정해야합니다. key 값은 각 원소들마다 가지고 있는 고유값으로 설정을 해야합니다.

🌟 왜 key props를 설정해야 할까?

- key props가 없을 경우

const array = ['a', 'b', 'c', 'd'];
위 배열을 렌더링한다고 하면,
array.map(item => <div>{item}</div>);
이 때 b 와 c 사이에 z 를 삽입하게 된다면, 리렌더링을 하게 될 때 <div>b</div><div>c</div> 사이에 새 div 태그를 삽입을 하게 되는 것이 아니라, 기존의 c 가 z 로바뀌고, d 는 c 로 바뀌고, 맨 마지막에 d 가 새로 삽입됩니다. => 비효율적!!

- key props가 있는 경우

[ { id: 0, text: 'a' }, { id: 1, text: 'b' }, { id: 2, text: 'c' }, { id: 3, text: 'd' } ];
위 배열을 렌더링 할때 고유한 값인 id를 key props으로 전달해주면,
array.map(item => <div key={item.id}>{item.text}</div>);
배열이 업데이트 될 떄 key 가 없을 때 처럼 비효율적으로 업데이트 하는 것이 아니라, 수정되지 않는 기존의 값은 그대로 두고 원하는 곳에 내용을 삽입하거나 삭제합니다.

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

useRef Hook 은 DOM 을 선택하는 용도 외에도, 다른 용도가 한가지 더 있는데요, 바로, 컴포넌트 안에서 조회 및 수정 할 수 있는 변수를 관리하는 것 입니다.
useRef 로 관리하는 변수는 값이 바뀐다고 해서 컴포넌트가 리렌더링되지 않습니다. 따라서 useRef 로 관리하고 있는 변수는 설정 후 바로 조회 할 수 있습니다.

이 변수를 사용하여 다음과 같은 값을 관리 할 수 있습니다.

  • setTimeout, setInterval 을 통해서 만들어진 id
  • 외부 라이브러리를 사용하여 생성된 인스턴스
  • scroll 위치

useRef() 를 사용 할 때 파라미터를 넣어주면, 이 값이 .current 값의 기본값이 됩니다. 그리고 이 값을 수정 할때에는 .current 값을 수정하면 되고 조회 할 때에는 .current 를 조회하면 됩니다.

13~15. 배열에 항목 추가, 제거, 수정하기


import React from "react";

function User({ user, onRemove, onToggle }) {
  return (
    <div>
      <b
        style={{
          cursor: "pointer",
          color: user.active ? "green" : "black"
        }}
        onClick={() => onToggle(user.id)}
      >
        {user.username}
      </b>
      &nbsp;
      <span>({user.email})</span>
      <button onClick={() => onRemove(user.id)}>삭제</button>
    </div>
  );
}

export default User;
import React from "react";
import User from "./User";

function UserList({ users, onRemove, onToggle }) {
  return (
    <div>
      {users.map((user) => (
        <User
          user={user}
          key={user.id}
          onRemove={onRemove}
          onToggle={onToggle}
        />
      ))}
    </div>
  );
}

export default UserList;
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;
import React, { useRef, useState } from "react";
import UserList from "./UserList";
import CreateUser from "./CreateUser";

function App() {
  const [inputs, setInputs] = useState({
    username: "",
    email: ""
  });
  
  const { username, email } = inputs;
  
  const onChange = (e) => {
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value
    });
  }; //Input태그에 입력할때마다 inputs상태 업데이트
  
  const [users, setUsers] = useState([
    {
      id: 1,
      username: "user1",
      email: "user1@gmail.com",
      active: true
    },
    {
      id: 2,
      username: "user2",
      email: "user2@gmail.com",
      active: false
    },
    {
      id: 3,
      username: "user3",
      email: "user3@gmail.com",
      active: false
    }
  ]);

  const nextId = useRef(4);
  
  const onCreate = () => {
    const user = {
      id: nextId.current,
      username,
      email
    };
    setUsers(users.concat(user));
    //전개연산자 외에 concat을 쓰는 방법도 있습니다.
    //concat 함수는 기존의 배열을 수정하지 않고, 새로운 원소가 추가된 새로운 배열을 만들어줍니다.
    
    setInputs({
      username: "",
      email: ""
    }); //추가 후 Inputs상태 초기화
    nextId.current += 1;
  }; //'등록'버튼 클릭시 setUsers(newArr), setInputs로 inputs 초기화

  const onRemove = (id) => {
    setUsers(users.filter((user) => user.id !== id));
  };//'삭제'버튼 클릭시 해당 User컴포넌트의 id를 받아와서 삭제
  
  const onToggle = (id) => {
    setUsers(
      users.map((user) =>
        user.id === id ? { ...user, active: !user.active } : user
      )
    );
  };//이름 클릭시 해당 User컴포넌트의 id를 받아와서 active상태 바꿈
  
  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onRemove={onRemove} onToggle={onToggle} />
    </>
  );
}

export default App;

16. useEffect를 사용하여 마운트/언마운트/업데이트시 할 작업 설정하기

✅ useEffect는 컴포넌트가 마운트, 언마운트, 업데이트 될 때 특정 함수를 실행할 수 있는 Hook입니다.

import React, { useEffect } from 'react';

function User({ user, onRemove, onToggle }) {
  
  useEffect(() => {
    console.log('컴포넌트가 화면에 나타남');
    return () => {
      console.log('컴포넌트가 화면에서 사라짐');
    };
  }, [user]);
  
  return (
    <div>
      <b
        style={{
          cursor: "pointer",
          color: user.active ? "green" : "black"
        }}
        onClick={() => onToggle(user.id)}
      >
        {user.username}
      </b>
      &nbsp;
      <span>({user.email})</span>
      <button onClick={() => onRemove(user.id)}>삭제</button>
    </div>
  );
}

export default User;

⭐useEffect의 두번째 파라미터는 의존성배열입니다. 의존성배열(deps)안의 특정 값이 바뀔때마다 Hook이 호출되고, 의존성배열이 비어있는 경우 컴포넌트가 최초 렌더링될때 한번만 호출됩니다.
⭐useEffect가 반환하는 함수를 cleanUp 함수라 부릅니다. useEffect 에 대한 뒷정리를 해준다고 이해하시면 되는데요, deps 가 비어있는 경우에는 컴포넌트가 사라질 때 cleanup 함수가 호출됩니다.
⭐deps 파라미터를 생략한다면, 컴포넌트가 리렌더링 될 때마다 호출이 됩니다.

주로, 마운트 시에 하는 작업들

  • props 로 받은 값을 컴포넌트의 로컬 상태로 설정
  • 외부 API 요청 (REST API 등)
  • 라이브러리 사용 (D3, Video.js 등...)
  • setInterval 을 통한 반복작업 혹은 setTimeout 을 통한 작업 예약

언마운트 시에 하는 작업들

  • setInterval, setTimeout 을 사용하여 등록한 작업들 clear 하기 (clearInterval, clearTimeout)
  • 라이브러리 인스턴스 제거

17. useMemo 를 사용하여 연산한 값 재사용하기

✅ useMemo는 두번째 파라미터인 배열 안에 넣은 내용이 바뀌면 등록한 함수를 호출해서 값을 연산해주고, 바뀌지 않았다면 이전에 연산한 값을 재사용하여 성능최적화를 할 수 있는 Hook입니다.

function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}

function App() {
  ...
  //const count = countActiveUsers(users);
  //App안의 다른 상태가 바뀔때마다 countActiveUsers함수가 계속 호출됩니다.
  
  const count = useMemo(() => countActiveUsers(users), [users]);
  //useMemo를 쓰면 users상태가 바뀔때에만 호출
  return (<>...</>);
}

18. useCallback 을 사용하여 함수 재사용하기

✅ useCallback 은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용합니다.

import React, { useRef, useState, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';

function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}

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

  const onCreate = useCallback(함수, [users, username, email]);
  const onRemove = useCallback(함수, [users]);
  const onToggle = useCallback(함수, [users]);
  
  const count = useMemo(() => countActiveUsers(users), [users]);
  return (<>...</>);
}

export default App;

주의 하실 점은, 함수 안에서 사용하는 상태 혹은 props 가 있다면 꼭, deps 배열안에 포함시켜야 된다는 것 입니다. 만약에 deps 배열 안에 함수에서 사용하는 값을 넣지 않게 된다면, 함수 내에서 해당 값들을 참조할때 가장 최신 값을 참조 할 것이라고 보장 할 수 없습니다. props 로 받아온 함수가 있다면, 이 또한 deps 에 넣어주어야 해요.

✔ useCallback은 useMemo를 기반으로 만들어졌기 때문에 다음과같이 쓸 수도 있습니다.

const onToggle = useMemo(
  () => () => {
    /* ... */
  },
  [users]
);

19. React.memo 를 사용한 컴포넌트 리렌더링 방지

✅ React.memo는 컴포넌트의 props 가 바뀌지 않았다면, 리렌더링을 방지하여 컴포넌트의 리렌더링 성능 최적화를 해줄 수 있는 함수입니다.

const CreateUser = ({ username, email, onChange, onCreate }) => {
  return (...);
};
export default React.memo(CreateUser); // => 사용법은, 그냥 감싸주면 됩니다!
const User = function User({ user, onRemove, onToggle }) {
  return (...);
};
export default React.memo(User)
function UserList({ users, onRemove, onToggle }) {
  return (...);
}
export default React.memo(UserList);

이렇게 하면 input이 onChange될 때 하단의 userList는 렌더링되지 않습니다.
그런데, User 중 하나라도 수정하면 모든 User 들이 리렌더링되고, CreateUser 도 리렌더링이 됩니다. 이는 onCreate, onToggle,onRemove 의 useCallback dept에 users가 있어서 함수가 새로 만들어지기 때문입니다.

=> 이걸 최적화하고 싶다면? 햠수형 업데이트!!
deps 에서 users 를 지우고, 함수들에서 현재 useState 로 관리하는 users 를 참조하지 않게 하는것입니다. 함수형 업데이트를 하게 되면, setUsers 에 등록하는 콜백함수의 파라미터에서 최신 users 를 참조 할 수 있기 때문에 deps 에 users 를 넣지 않아도 된답니다.

import React, { useRef, useState, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';

function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}

function App() {
  const [inputs, setInputs] = useState({
    username: '',
    email: ''
  });
  
  const { username, email } = inputs;
  
  const onChange = useCallback(e => {
    const { name, value } = e.target;
    setInputs(inputs => ({
      ...inputs,
      [name]: 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);
  const onCreate = useCallback(() => {
    const user = {
      id: nextId.current,
      username,
      email
    };
    setUsers(users => users.concat(user)); //함수형 업데이트!!

    setInputs({
      username: '',
      email: ''
    });
    nextId.current += 1;
  }, [username, email]);

  const onRemove = useCallback(id => {
    setUsers(users => users.filter(user => user.id !== id));
  }, []); //함수형 업데이트!!
  
  const onToggle = useCallback(id => {
    setUsers(users =>
      users.map(user =>
        user.id === id ? { ...user, active: !user.active } : user
      )
    ); //함수형 업데이트!!
  }, []);
  
  const count = useMemo(() => countActiveUsers(users), [users]);
  
  return (...);
}

export default App;

추가적으로, React.memo 에서 두번째 파라미터에 propsAreEqual 이라는 함수를 사용하여 특정 값들만 비교를 하는 것도 가능합니다.

export default React.memo(
  UserList,
  (prevProps, nextProps) => prevProps.users === nextProps.users
);

20. useReducer 를 사용하여 상태 업데이트 로직 분리하기

useReducer Hook 함수를 사용하면 컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있습니다.
✅ reducer 는 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수입니다.

function reducer(state, action) {
  // 새로운 상태를 만드는 로직
  // const nextState = ...
  return nextState;
}

✅ action 은 업데이트를 위한 정보를 가지고 있습니다. 주로 type 값을 지닌 객체 형태로 사용

//action 객체 예시
{
  type: 'ADD_TODO',
  todo: {
    id: 1,
    text: 'useReducer 배우기',
    done: false,
  }
}

✅ useReducer 사용법

const [state, dispatch] = useReducer(reducer, initialState);
//state: 컴포넌트에서 사용할 수 있는 상태
//dispatch: 액션을 발생시키는 함수. 다음과 같이 사용합니다: dispatch({ type: 'INCREMENT' })
import React, { useReducer } from 'react';

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

function Counter() {
  const [number, dispatch] = useReducer(reducer, 0);

  const onIncrease = () => {
    dispatch({ type: 'INCREMENT' });
  }; //dispatch함수로 action만 전달해주면 reducer함수에서 알아서 상태 업데이트

  const onDecrease = () => {
    dispatch({ type: 'DECREMENT' });
  }; //dispatch함수로 action만 전달해주면 reducer함수에서 알아서 상태 업데이트

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

export default Counter;

21. 커스텀 Hooks 만들기

✅ 비슷한 코드가 반복될때에는 커스텀 Hooks 를 만들어서 반복되는 로직을 쉽게 재사용할 수 있습니다.
✅ 커스텀 Hooks 를 만들 때에는 보통 use 라는 키워드로 시작하는 파일을 만들고 그 안에 함수를 작성합니다.
✅ 커스텀 Hooks 를 만드는 방법: 안에서 useState, useEffect, useReducer, useCallback 등 Hooks 를 사용하여 원하는 기능을 구현해주고, 컴포넌트에서 사용하고 싶은 값들을 반환해주면 됩니다.

//커스텀 훅 예시
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;
//App에서 사용
function App() {
  const [{ username, email }, onChange, reset] = useInputs({
    username: '',
    email: ''
  });
  const [state, dispatch] = useReducer(reducer, initialState);
  ...

22. Context API 를 사용한 전역 값 관리

프로젝트에서 상태를 전역적으로 사용할 수 있는 값을 관리하는 API

// UserDispatch 라는 이름으로 내보내줍니다.
export const UserDispatch = React.createContext(null);

function App() {
 ...
  const [state, dispatch] = useReducer(reducer, initialState);
 ...
  return (
    <UserDispatch.Provider value={dispatch}>
      ...
    </UserDispatch.Provider>
  );
}

export default App;

UserDispatch 라는 Context 를 만들어서, 어디서든지 dispatch 를 꺼내 쓸 수 있도록 준비를 해준 것입니다.
이제 onRemove, onToggle등의 함수를 props로 전달, 전달 받지 않고 User컴포넌트에서 바로 dispatch해줄 수 있습니다.

import React, { useContext } from 'react';
import { UserDispatch } from './App';

const User = React.memo(function User({ user }) {
  const dispatch = useContext(UserDispatch);
  //UserDispatch라는 context를 사용하겠다

  return (
    <div>
      <b
        style={{
          cursor: 'pointer',
          color: user.active ? 'green' : 'black'
        }}
        onClick={() => {
          dispatch({ type: 'TOGGLE_USER', id: user.id });
        }} //App에서 UserDispatch.Provider로 전달해준 value={dispatch} 를 사용해서 dispatch함수를 실행
      >
        {user.username}
      </b>
      &nbsp;
      <span>({user.email})</span>
      <button
        onClick={() => {
          dispatch({ type: 'REMOVE_USER', id: user.id });
        }}//App에서 UserDispatch.Provider로 전달해준 value={dispatch} 를 사용해서 dispatch함수를 실행
      >
        삭제
      </button>
    </div>
  );
});

0개의 댓글