React Hooks 및 Context API로 CRUD 앱을 빌드하는 방법
소개
이 기사에서는 React 후크(버전 16.8에 도입됨)를 다룰 것입니다.
Context API의 도입으로 한 가지 주요 문제인 소품 드릴링이 해결되었습니다. 중첩된 심층 구성 요소 계층을 통해 한 구성 요소에서 다른 구성 요소로 데이터를 가져오는 프로세스입니다. React 후크를 사용하면 클래스 기반 구성 요소가 아닌 기능적 구성 요소를 사용할 수 있습니다. 수명 주기 방법을 활용해야 하는 경우에는 클래스 기반 접근 방식을 사용해야 했습니다. 이제 더 이상 super(props)
를 호출하거나 바인딩 메서드 또는 this
키워드에 대해 걱정할 필요가 없습니다.
이 기사에서는 Context API와 React 후크를 함께 사용하여 직원 목록을 에뮬레이트하는 완전한 기능의 CRUD 애플리케이션을 빌드합니다. 직원 데이터를 읽고, 새 직원을 만들고, 직원 데이터를 업데이트하고, 직원을 삭제합니다. 이 자습서에서는 외부 API 호출을 사용하지 않습니다. 데모를 위해 상태로 사용할 하드 코딩된 개체를 사용합니다.
전제 조건
이 자습서를 완료하려면 다음이 필요합니다.
- Node.js용 로컬 개발 환경입니다. Node.js 설치 및 로컬 개발 환경 생성 방법을 따르십시오.
- React 구성 요소 가져오기, 내보내기 및 렌더링에 대한 이해. React.js에서 코딩하는 방법 시리즈를 살펴볼 수 있습니다.
이 튜토리얼은 Node v15.3.0, npm
v7.4.0, react
v17.0.1, react-router-dom
v5.2.0, tailwindcss-cli
v0.1.2 및 tailwindcss
v2.0.2.
1단계 - 프로젝트 설정
먼저 다음 명령과 함께 Create React App을 사용하여 React 프로젝트를 설정하는 것으로 시작합니다.
- npx create-react-app react-crud-employees-example
새로 생성된 프로젝트 디렉토리로 이동합니다.
- cd react-crud-employees-example
다음으로 다음 명령을 실행하여 react-router-dom
을 종속 항목으로 추가합니다.
- npm install react-router-dom@5.2.0
참고: React Router에 대한 추가 정보는 React Router 튜토리얼을 참조하십시오.
그런 다음 src
디렉토리로 이동합니다.
cd src
다음 명령을 사용하여 Tailwind CSS의 기본 빌드를 프로젝트에 추가합니다.
- npx tailwindcss-cli@0.1.2 build --output tailwind.css
참고: Tailwind CSS에 대한 자세한 내용은 Tailwind CSS 자습서를 참조하세요.
그런 다음 코드 편집기에서 index.js
를 열고 tailwind.css
및 BrowserRouter
를 사용하도록 수정합니다.
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './tailwind.css';
import './index.css';
import App from './App';
ReactDOM.render(
<BrowserRouter>
<App />
<BrowserRouter>
document.getElementById('root')
);
이 시점에서 Tailwind CSS 및 react-router-dom
이 포함된 새로운 React 프로젝트가 생성됩니다.
2단계 — AppReducer 및 GlobalContext 빌드
먼저 src
디렉토리 아래에 새 context
디렉토리를 만듭니다.
이 새 디렉터리에서 새 AppReducer.js
파일을 만듭니다. 이 감속기는 ADD_EMPLOYEE
, EDIT_EMPLOYEE
및 REMOVE_EMPLOYEE
와 같은 CRUD 작업을 정의합니다. 코드 편집기에서 이 파일을 열고 다음 코드 줄을 추가합니다.
export default function appReducer(state, action) {
switch (action.type) {
case "ADD_EMPLOYEE":
return {
...state,
employees: [...state.employees, action.payload],
};
case "EDIT_EMPLOYEE":
const updatedEmployee = action.payload;
const updatedEmployees = state.employees.map((employee) => {
if (employee.id === updatedEmployee.id) {
return updatedEmployee;
}
return employee;
});
return {
...state,
employees: updatedEmployees,
};
case "REMOVE_EMPLOYEE":
return {
...state,
employees: state.employees.filter(
(employee) => employee.id !== action.payload
),
};
default:
return state;
}
};
ADD_EMPLOYEES
는 새 직원이 포함된 페이로드 값을 가져와 업데이트된 직원 상태를 반환합니다.
EDIT_EMPLOYEE
는 페이로드 값을 가져와 id
를 직원과 비교합니다. 일치하는 항목이 있으면 새 페이로드 값을 사용하고 업데이트된 직원 상태를 반환합니다.
REMOVE_EMPLOYEE
는 페이로드 값을 가져와 id
를 직원과 비교합니다. 일치하는 항목이 있으면 해당 직원을 제거하고 업데이트된 직원 상태를 반환합니다.
context
디렉터리에 남아 있는 동안 새 GlobalState.js
파일을 만듭니다. 요청에서 반환된 직원 데이터를 에뮬레이트하기 위해 초기 하드 코딩된 값이 포함됩니다. 코드 편집기에서 이 파일을 열고 다음 코드 줄을 추가합니다.
import React, { createContext, useReducer } from 'react';
import appReducer from './AppReducer';
const initialState = {
employees: [
{
id: 1,
name: "Sammy",
location: "DigitalOcean",
designation: "Shark"
}
]
};
export const GlobalContext = createContext(initialState);
export const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(appReducer, initialState);
function addEmployee(employee) {
dispatch({
type: "ADD_EMPLOYEE",
payload: employee
});
}
function editEmployee(employee) {
dispatch({
type: "EDIT_EMPLOYEE",
payload: employee
});
}
function removeEmployee(id) {
dispatch({
type: "REMOVE_EMPLOYEE",
payload: id
});
}
return (
<GlobalContext.Provider
value={{
employees: state.employees,
addEmployee,
editEmployee,
removeEmployee
}}
>
{children}
</GlobalContext.Provider>
);
};
이 코드는 각 작업에 해당하는 사례를 전환하기 위해 리듀서 파일로 이동하는 작업을 디스패치하는 몇 가지 기능을 추가합니다.
이 시점에서 AppReducer.js
및 GlobalState.js
가 포함된 React 애플리케이션이 있어야 합니다.
애플리케이션이 제대로 작동하는지 확인하기 위해 EmployeeList
구성 요소를 생성해 보겠습니다. src
디렉토리로 이동하여 새 components
디렉토리를 만듭니다. 해당 디렉터리에서 새 EmployeeList.js
파일을 만들고 다음 코드를 추가합니다.
import React, { useContext } from 'react';
import { GlobalContext } from '../context/GlobalState';
export const EmployeeList = () => {
const { employees } = useContext(GlobalContext);
return (
<React.Fragment>
{employees.length > 0 ? (
<React.Fragment>
{employees.map((employee) => (
<div
className="flex items-center bg-gray-100 mb-10 shadow"
key={employee.id}
>
<div className="flex-auto text-left px-4 py-2 m-2">
<p className="text-gray-900 leading-none">
{employee.name}
</p>
<p className="text-gray-600">
{employee.designation}
</p>
<span className="inline-block text-sm font-semibold mt-1">
{employee.location}
</span>
</div>
</div>
))}
</React.Fragment>
) : (
<p className="text-center bg-gray-100 text-gray-500 py-5">No data.</p>
)}
</React.Fragment>
);
};
이 코드는 모든 직원
의 employee.name
, employee.designation
및 employee.location
을 표시합니다.
그런 다음 코드 편집기에서 App.js
를 엽니다. 그리고 EmployeeList
및 GlobalProvider
를 추가합니다.
import { EmployeeList } from './components/EmployeeList';
import { GlobalProvider } from './context/GlobalState';
function App() {
return (
<GlobalProvider>
<div className="App">
<EmployeeList />
</div>
</GlobalProvider>
);
}
export default App;
애플리케이션을 실행하고 웹 브라우저에서 관찰합니다.
EmployeeList
구성 요소는 GlobalState.js
에 설정된 하드 코딩된 값을 표시합니다.
3단계 - AddEmployee 및 EditEmployee 구성 요소 구축
이 단계에서는 새 직원 만들기 및 기존 직원 업데이트를 지원하는 구성 요소를 빌드합니다.
이제 components
디렉토리로 다시 이동합니다. 새 AddEmployee.js
파일을 만듭니다. 이는 양식 필드의 값을 상태로 푸시하는 onSubmit
핸들러를 포함하는 AddEmployee
구성 요소 역할을 합니다.
import React, { useState, useContext } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { GlobalContext } from '../context/GlobalState';
export const AddEmployee = () => {
let history = useHistory();
const { addEmployee, employees } = useContext(GlobalContext);
const [name, setName] = useState("");
const [location, setLocation] = useState("");
const [designation, setDesignation] = useState("");
const onSubmit = (e) => {
e.preventDefault();
const newEmployee = {
id: employees.length + 1,
name,
location,
designation,
};
addEmployee(newEmployee);
history.push("/");
};
return (
<React.Fragment>
<div className="w-full max-w-sm container mt-20 mx-auto">
<form onSubmit={onSubmit}>
<div className="w-full mb-5">
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor="name"
>
Name of employee
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:text-gray-600"
value={name}
onChange={(e) => setName(e.target.value)}
type="text"
placeholder="Enter name"
/>
</div>
<div className="w-full mb-5">
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor="location"
>
Location
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:text-gray-600 focus:shadow-outline"
value={location}
onChange={(e) => setLocation(e.target.value)}
type="text"
placeholder="Enter location"
/>
</div>
<div className="w-full mb-5">
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor="designation"
>
Designation
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:text-gray-600"
value={designation}
onChange={(e) => setDesignation(e.target.value)}
type="text"
placeholder="Enter designation"
/>
</div>
<div className="flex items-center justify-between">
<button className="mt-5 bg-green-400 w-full hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
Add Employee
</button>
</div>
<div className="text-center mt-4 text-gray-500">
<Link to="/">Cancel</Link>
</div>
</form>
</div>
</React.Fragment>
);
};
이 코드에서 setName
, setLocation
및 setDesignation
은 사용자가 양식 필드에 입력하는 현재 값을 사용합니다. 이러한 값은 고유한 id
(총 길이에 1을 추가함)를 사용하여 새 상수인 newEmployee
로 래핑됩니다. 그러면 새로 추가된 직원을 포함하여 업데이트된 직원 목록이 표시되는 메인 화면으로 경로가 변경됩니다.
AddEmployee
구성 요소는 내장된 React Hooks 중 하나인 GlobalState
및 useContext
를 가져와 기능 구성 요소가 컨텍스트에 쉽게 액세스할 수 있도록 합니다.
employees
개체, removeEmployee
및 editEmployees
는 GlobalState.js
파일에서 가져왔습니다.
여전히 components
디렉토리에 있는 동안 새 EditEmployee.js
파일을 만듭니다. 이것은 상태에서 기존 개체를 편집하는 기능을 포함하는 editEmployee
구성 요소 역할을 합니다.
import React, { useState, useContext, useEffect } from 'react';
import { useHistory, Link } from 'react-router-dom';
import { GlobalContext } from '../context/GlobalState';
export const EditEmployee = (route) => {
let history = useHistory();
const { employees, editEmployee } = useContext(GlobalContext);
const [selectedUser, setSelectedUser] = useState({
id: null,
name: "",
designation: "",
location: "",
});
const currentUserId = route.match.params.id;
useEffect(() => {
const employeeId = currentUserId;
const selectedUser = employees.find(
(currentEmployeeTraversal) => currentEmployeeTraversal.id === parseInt(employeeId)
);
setSelectedUser(selectedUser);
}, [currentUserId, employees]);
const onSubmit = (e) => {
e.preventDefault();
editEmployee(selectedUser);
history.push("/");
};
const handleOnChange = (userKey, newValue) =>
setSelectedUser({ ...selectedUser, [userKey]: newValue });
if (!selectedUser || !selectedUser.id) {
return <div>Invalid Employee ID.</div>;
}
return (
<React.Fragment>
<div className="w-full max-w-sm container mt-20 mx-auto">
<form onSubmit={onSubmit}>
<div className="w-full mb-5">
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor="name"
>
Name of employee
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:text-gray-600 focus:shadow-outline"
value={selectedUser.name}
onChange={(e) => handleOnChange("name", e.target.value)}
type="text"
placeholder="Enter name"
/>
</div>
<div className="w-full mb-5">
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor="location"
>
Location
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:text-gray-600 focus:shadow-outline"
value={selectedUser.location}
onChange={(e) => handleOnChange("location", e.target.value)}
type="text"
placeholder="Enter location"
/>
</div>
<div className="w-full mb-5">
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor="designation"
>
Designation
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:text-gray-600 focus:shadow-outline"
value={selectedUser.designation}
onChange={(e) => handleOnChange("designation", e.target.value)}
type="text"
placeholder="Enter designation"
/>
</div>
<div className="flex items-center justify-between">
<button className="block mt-5 bg-green-400 w-full hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:text-gray-600 focus:shadow-outline">
Edit Employee
</button>
</div>
<div className="text-center mt-4 text-gray-500">
<Link to="/">Cancel</Link>
</div>
</form>
</div>
</React.Fragment>
);
};
이 코드는 구성 요소가 마운트될 때 호출되는 useEffect
후크를 사용합니다. 이 후크 내에서 현재 경로 매개변수는 상태의 employees
개체에 있는 동일한 매개변수와 비교됩니다.
onChange
이벤트 리스너는 사용자가 양식 필드를 변경할 때 트리거됩니다. userKey
및 newValue
는 setSelectedUser
로 전달됩니다. selectedUser
가 확산되고 userKey
가 키로 설정되고 newValue
가 값으로 설정됩니다.
4단계 - 경로 설정
이 단계에서는 EmployeeList
를 업데이트하여 AddEmployee
및 EditEmployee
구성 요소에 연결합니다.
EmployeeList.js
를 다시 방문하여 Link
및 removeEmployee
를 사용하도록 수정합니다.
import React, { useContext } from 'react';
import { Link } from 'react-router-dom';
import { GlobalContext } from '../context/GlobalState';
export const EmployeeList = () => {
const { employees, removeEmployee } = useContext(GlobalContext);
return (
<React.Fragment>
{employees.length > 0 ? (
<React.Fragment>
{employees.map((employee) => (
<div
className="flex items-center bg-gray-100 mb-10 shadow"
key={employee.id}
>
<div className="flex-auto text-left px-4 py-2 m-2">
<p className="text-gray-900 leading-none">
{employee.name}
</p>
<p className="text-gray-600">
{employee.designation}
</p>
<span className="inline-block text-sm font-semibold mt-1">
{employee.location}
</span>
</div>
<div className="flex-auto text-right px-4 py-2 m-2">
<Link
to={`/edit/${employee.id}`}
title="Edit Employee"
>
<div className="bg-gray-300 hover:bg-gray-400 text-gray-800 font-semibold mr-3 py-2 px-4 rounded-full inline-flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
</div>
</Link>
<button
onClick={() => removeEmployee(employee.id)}
className="block bg-gray-300 hover:bg-gray-400 text-gray-800 font-semibold py-2 px-4 rounded-full inline-flex items-center"
title="Remove Employee"
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-trash-2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
</button>
</div>
</div>
))}
</React.Fragment>
) : (
<p className="text-center bg-gray-100 text-gray-500 py-5">No data.</p>
)}
</React.Fragment>
);
};
이 코드는 직원 정보 옆에 두 개의 아이콘을 추가합니다. 연필과 종이 아이콘은 "편집\을 나타내고 EditEmployee
구성 요소에 대한 링크를 나타냅니다. 휴지통 아이콘은 "제거\를 나타내며 이 아이콘을 클릭하면 removeEmployee
가 실행됩니다.
다음으로 Heading
및 Home
이라는 두 개의 새 구성 요소를 만들어 EmployeeList
구성 요소를 표시하고 사용자에게 AddEmployee에 대한 액세스 권한을 제공합니다.
구성 요소.
components
디렉토리에서 새 Heading.js
파일을 만듭니다.
import React from "react";
import { Link } from "react-router-dom";
export const Heading = () => {
return (
<div>
<div className="flex items-center mt-24 mb-10">
<div className="flex-grow text-left px-4 py-2 m-2">
<h5 className="text-gray-900 font-bold text-xl">Employee Listing</h5>
</div>
<div className="flex-grow text-right px-4 py-2 m-2">
<Link to="/add">
<button className="bg-green-400 hover:bg-green-500 text-white font-semibold py-2 px-4 rounded inline-flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
<span className="pl-2">Add Employee</span>
</button>
</Link>
</div>
</div>
</div>
);
};
components
디렉터리에서 새 Home.js
파일을 만듭니다.
import React from "react";
import { Heading } from "./Heading";
import { EmployeeList } from "./EmployeeList";
export const Home = () => {
return (
<React.Fragment>
<div className="container mx-auto">
<h3 className="text-center text-3xl mt-20 text-base leading-8 text-black font-bold tracking-wide uppercase">
CRUD with React Context API and Hooks
</h3>
<Heading />
<EmployeeList />
</div>
</React.Fragment>
);
};
App.js
를 다시 방문하여 react-router-dom
에서 Route
및 Switch
를 가져옵니다. Home
, AddeEmployee
및 EditEmployee
구성 요소를 각 경로에 할당합니다.
import { Route, Switch } from 'react-router-dom';
import { GlobalProvider } from './context/GlobalState';
import { Home } from './components/Home';
import { AddEmployee } from './components/AddEmployee';
import { EditEmployee } from './components/EditEmployee';
function App() {
return (
<GlobalProvider>
<div className="App">
<Switch>
<Route path="/" component={Home} exact />
<Route path="/add" component={AddEmployee} exact />
<Route path="/edit/:id" component={EditEmployee} exact />
</Switch>
</div>
</GlobalProvider>
);
}
export default App;
앱을 컴파일하고 브라우저에서 관찰합니다.
Heading
및 EmployeeList
구성 요소가 있는 Home
구성 요소로 라우팅됩니다.
직원 추가 링크를 클릭합니다. AddEmployee
구성 요소로 라우팅됩니다.
신입 사원에 대한 정보를 제출하면 홈
구성 요소로 다시 라우팅되며 이제 신입 사원이 나열됩니다.
직원 편집 링크를 클릭합니다. EditEmployee
구성 요소로 라우팅됩니다.
직원에 대한 정보를 수정한 후 홈
구성 요소로 다시 라우팅되며 이제 업데이트된 세부 정보와 함께 새 직원이 나열됩니다.
결론
이 기사에서는 Context API와 React 후크를 함께 사용하여 완전히 작동하는 CRUD 애플리케이션을 빌드했습니다.
React에 대해 자세히 알아보려면 연습 및 프로그래밍 프로젝트에 대한 React 주제 페이지를 살펴보세요.