2024 최신 학습 도구React는 사용자 인터페이스를 작성하기위한 JavaScript 라이브러리입니다. 이를 통해 개발자는 재사용 가능한 UI 구성 요소를 구축하고 최적의 성능을 위해 가상 DOM을 사용하여 DOM을 효율적으로 업데이트 할 수 있습니다.
React와 함께 새로운 앱이나 웹 사이트를 완전히 만들려면 커뮤니티에서 인기있는 RECT 구동 프레임 워크 중 하나를 선택하는 것이 좋습니다.
프레임 워크없이 React를 사용할 수 있지만 대부분의 앱과 사이트는 결국 코드 분할, 라우팅, 데이터 페치 및 HTML 생성과 같은 일반적인 문제를 해결해야한다는 것을 발견했습니다. 이러한 과제는 React에게 고유 한 것이 아니라 모든 UI 라이브러리에 공통적입니다.
프레임 워크로 시작하면 React로 빠르게 일어나고 나중에 자신의 프레임 워크를 개발할 필요가 없습니다.
Next.js의 페이지 라우터는 풀 스택 반응 프레임 워크입니다. 다재다능하며 대부분 정적 블로그에서 복잡한 동적 응용 프로그램에 이르기까지 모든 크기의 React 앱을 만들 수 있습니다. 새로운 Next.js 프로젝트를 만들려면 터미널에서 실행하십시오.
npx create-next-app@latest
다음 .js는 Vercel에 의해 유지됩니다. 다음 node.js 또는 Serverless 호스팅 또는 자신의 서버에 Next.js 앱을 배포 할 수 있습니다. Next.js는 서버가 필요하지 않은 정적 내보내기도 지원합니다.
o 앱 라우터 do next.js는 다음 팀의 풀 스택 아키텍처 비전을 충족하는 것을 목표로 다음 Next.js API의 재 설계입니다. 서버에서 또는 빌드 중에 실행되는 비동기 구성 요소로 데이터를 가져올 수 있습니다.
다음 .js는 Vercel에 의해 유지됩니다. 다음 node.js 또는 Serverless 호스팅 또는 자신의 서버에 Next.js 앱을 배포 할 수 있습니다. Next.js는 서버가 필요하지 않은 정적 내보내기도 지원합니다.
개요 vite ( "Quick", "VEET"과 같은 발음 /vit /에 대한 프랑스어 단어)는 현대 웹 프로젝트에 더 빠르고 더 거친 개발 경험을 제공하는 빌드 도구입니다. 두 가지 주요 부분으로 구성됩니다.
기본 ES 모듈 (예 : HMR)과 같은 기본 ES 모듈에 대한 풍부한 기능 향상을 제공하는 개발자 서버.
생산을 위해 고도로 최적화 된 정적 자산을 출력하도록 사전 구성된 롤업과 함께 코드를 묶는 빌드 명령.
Vite는 의견이 있으며 상자 밖에서 현명한 기본값이 제공됩니다. 기능 가이드에서 가능한 것에 대해 읽으십시오. 플러그인을 통해 프레임 워크 지원 또는 다른 도구와의 통합이 가능합니다. 구성 섹션은 필요한 경우 프로젝트에 Vite를 조정하는 방법을 설명합니다.
시작하기 전에 시스템에 Node.js가 설치되어 있는지 확인하십시오. 아직 없다면 공식 Node.js 웹 사이트에서 다운로드 할 수 있습니다. 정말 간단합니다.
npx create-vite your-project-name --template react
프로젝트에 원하는 이름으로 프로젝트를 교체하십시오. VITE는 많은 프레임 워크를 지원하지만이 경우 --- template React 옵션을 사용하여 React 템플릿을 지정합니다.
cd your-project-name
프로젝트 이름을 프로젝트를 위해 선택한 실제 이름으로 대체하는 것을 잊지 마십시오 (물론 프로젝트의 이름을 유지하려는 한).
npm
npm run dev
이 명령을 실행 한 후에는 터미널에 RECT 웹 사이트가 특정 포트에서 실행되고 있음을 나타내는 메시지가 표시되며 일반적으로 http : // localhost : 5173과 같은 '랜덤'포트 번호입니다. 이제 브라우저를 열고 제공된 URL을 방문하여 React 웹 사이트를 실제로 확인하십시오.
React 개발자 도구를 사용하여 React 구성 요소를 검사하고 소품 및 상태를 편집하고 성능 문제를 식별하십시오.
브라우저 확장 React로 구축 된 웹 사이트를 디버그하는 가장 쉬운 방법은 React 개발자 도구 브라우저 확장을 설치하는 것입니다. 몇 가지 인기있는 브라우저에서 사용할 수 있습니다.
다른 브라우저 (예 : Safari)의 경우 React-DevTools NPM 패키지를 설치하십시오.
# Yarn
yarn global add react-devtools
# Npm
npm install -g react-devtools
다음으로 터미널에서 개발자 도구를 엽니 다.
react-devtools
그런 다음 웹 사이트의 시작 부분에 다음 <Script> 태그를 추가하여 웹 사이트를 연결하십시오.
<html>
<head>
<script src="http://localhost:8097"></script>
JSX는 XML 또는 HTML과 유사한 JavaScript의 구문 연장입니다. 이를 통해 개발자는 JavaScript 파일 내에서보다 간결하고 읽기 쉬운 방식으로 HTML 요소 및 구성 요소를 작성할 수 있습니다.
import React from 'react';
function App() {
return (
<div>
<h1>Hello, React!</h1>
</div>
);
}
export default App;
기능 구성 요소는 소품을 인수로 받아들이고 JSX 요소를 반환하는 간단한 기능입니다. 반응 후크를 사용하면 기능 구성 요소에 상태 및 부작용이 발생할 수 있습니다.
import React from 'react';
const FunctionalComponent = () => {
return <p>This is a functional component.</p>;
}
export default FunctionalComponent;
클래스 구성 요소는 React 구성 요소를 확장하는 ES6 클래스입니다. 그들은 로컬 상태를 유지하고 관리 할 수 있으며 수명주기 방법에 액세스 할 수있어 기능 구성 요소보다 기능이 풍부합니다.
import React, { Component } from 'react';
class ClassComponent extends Component {
render() {
return <p>This is a class component.</p>;
}
}
export default ClassComponent;
소품은 부모 구성 요소에서 React의 자식 구성 요소로 데이터를 전달하는 방법입니다. 그것들은 불변이며 구성 요소를 역동적이고 재사용 할 수있는 방법을 제공합니다.
import React from 'react';
const PropsExample = (props) => {
return <p>{props.message}</p>;
}
export default PropsExample;
상태 반응은 구성 요소의 변화 상태를 나타냅니다. 이를 통해 구성 요소는 자체 데이터를 관리하고 업데이트하여 동적 및 대화식 사용자 인터페이스를 만듭니다.
import React, { Component } from 'react';
class StateExample extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Increment
</button>
</div>
);
}
}
export default StateExample;
수명주기 방법 상자 메토 도스 effessias em componentes de classe que são invocados em diferentes fases do ciclo de vida de um componente. ComponentDidmount é um método de ciclo de vida comummente utilizado, executado depois de um componente ser renderizado no dom.
import React, { Component } from 'react';
class LifecycleExample extends Component {
componentDidMount() {
console.log('Component is mounted!');
}
render() {
return <p>Lifecycle Example</p>;
}
}
export default LifecycleExample;
React는 Camelcase를 사용하여 이벤트를 처리합니다. 함수는 클릭, 변경 등과 같은 이벤트를 처리하여 구성 요소와의 상호 작용을 제공하기 위해 정의 할 수 있습니다.
import React from 'react';
const EventHandlingExample = () => {
const handleClick = () => {
alert('Button clicked!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
export default EventHandlingExample;
반응 후크는 기능 구성 요소가 상태 및 부작용을 관리 할 수있는 기능입니다. 이들은 React 16.8에 도입되었으며 기능성 구성 요소에서 상태 및 수명주기 방법으로보다 간결한 방법을 제공했습니다.
import React, { useState } from 'react';
const UseStateExample = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
export default UseStateExample;
RECT의 제어 된 구성 요소에는 입력이 있으며 해당 상태는 React에 의해 제어됩니다. 그들은 현재 값과 OnChange 핸들러를 소품으로 받고 DOM이 아닌 React에 의해 제어되었습니다.
import React, { useState } from 'react';
const ControlledComponent = () => {
const [inputValue, setInputValue] = useState('');
const handleChange = (e) => {
setInputValue(e.target.value);
}
return (
<input
type="text"
value={inputValue}
onChange={handleChange}
placeholder="Type here"
/>
);
}
export default ControlledComponent;
오류 경계는 자식 구성 요소 트리의 어느 곳에서나 JavaScript 오류를 감지하고 오류를 기록하거나 폴백 UI를 나타내거나 다른 작업을 수행하는 반응 구성 요소입니다.
import React, { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <p>Something went wrong.</p>;
}
return this.props.children;
}
}
export default ErrorBoundary;
고차 구성 요소 (HOC)는 구성 요소를 취하고 추가 기능으로 새로운 구성 요소를 반환하는 기능입니다. 그들은 구성 요소의 논리를 재사용하는 방법입니다.
import React from 'react';
const WithLogger = (WrappedComponent) => {
return class extends React.Component {
componentDidMount() {
console.log('Component is mounted!');
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
export default WithLogger;
// Usage
import React from 'react';
import WithLogger from './WithLogger';
const MyComponent = () => {
return <p>My Component</p>;
}
const EnhancedComponent = WithLogger(MyComponent);
React는 map 기능을 제공하여 항목 목록을 동적으로 렌더링합니다. 배열의 각 항목은 React 요소에 매핑되어 동적 컨텐츠를 더 쉽게 렌더링 할 수 있습니다.
import React from 'react';
const RenderingList = () => {
const items = ['Item 1', 'Item 2', 'Item 3'];
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}
export default RenderingList;
React의 컨텍스트 API는 각 레벨에서 수동으로 소품을 전달하지 않고 구성 요소 트리를 통해 데이터를 전송하는 방법을 제공합니다. 테마 또는 인증 상태와 같은 값을 공유하는 데 유용합니다.
import React from 'react';
const ThemeContext = React.createContext('light');
export default ThemeContext;
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
const ThemedComponent = () => {
const theme = useContext(ThemeContext);
return <p style={{ color: theme === 'light' ? 'black' : 'white' }}>Themed Component</p>;
}
export default ThemedComponent;
React의 키는 변경, 추가 또는 제거 된 항목을 식별하는 데 도움이됩니다. 목록 내에서 독특하고 효율적인 업데이트에 반응하는 데 도움이됩니다.
import React from 'react';
const KeysExample = () => {
const data = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
];
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default KeysExample;
React의 양식 처리에는 상태를 사용하여 양식 데이터를 관리하고 이벤트 처리기를 통한 양식 제출을 처리하는 것이 포함됩니다. 제어 구성 요소는 형태 요소를 React의 상태와 동기화하는 데 사용됩니다.
import React, { useState } from 'react';
const FormExample = () => {
const [formData, setFormData] = useState({ username: '', password: '' });
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
}
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted:', formData);
}
return (
<form onSubmit={handleSubmit}>
<label>
Username:
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
/>
</label>
<label>
Password:
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
</label>
<button type="submit">Submit</button>
</form>
);
}
export default FormExample;
인라인 스타일 : React를 사용하면 인라인 스타일을 사용하여 구성 요소를 스타일링 할 수 있으며, 여기서 스타일은 객체로 정의되어 요소에 직접 적용됩니다.
import React from 'react';
const InlineStyleExample = () => {
const styles = {
color: 'blue',
fontSize: '18px',
};
return <p style={styles}>Styled with inline styles</p>;
}
export default InlineStyleExample;
Render Props는 값이 함수 인 Prop를 사용하여 React 구성 요소간에 코드를 공유하는 기술입니다. 이것은 성분의 동적 구성을 허용합니다.
import React, { useState } from 'react';
const MouseTracker = ({ render }) => {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
}
return (
<div style={{ height: '100vh' }} onMouseMove={handleMouseMove}>
{render(position)}
</div>
);
}
export default MouseTracker;
// Usage
import React from 'react';
import MouseTracker from './MouseTracker';
const App = () => {
return (
<MouseTracker
render={(position) => (
<p>
Mouse position: {position.x}, {position.y}
</p>
)}
/>
);
}
export default App;
CSS 모듈은 글로벌 스타일 충돌을 피하고 특정 구성 요소의 스타일 범위를 정의하는 데 도움이됩니다. 각 구성 요소에는 로컬 스코프 스타일의 자체 CSS 모듈이있을 수 있습니다.
.myComponent {
color: green;
}
import React from 'react';
import styles from './CSSModulesExample.module.css';
const CSSModulesExample = () => {
return <p className={styles.myComponent}>Styled with CSS Modules</p>;
}
export default CSSModulesExample;
특징:
import React, { useState } from 'react';
const TodoApp = () => {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
const addTask = () => {
setTasks([...tasks, { text: newTask, completed: false }]);
setNewTask('');
};
const toggleTask = (index) => {
const updatedTasks = [...tasks];
updatedTasks[index].completed = !updatedTasks[index].completed;
setTasks(updatedTasks);
};
const removeTask = (index) => {
const updatedTasks = [...tasks];
updatedTasks.splice(index, 1);
setTasks(updatedTasks);
};
return (
<div>
<input
type="text"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
<button onClick={addTask}>Add Task</button>
<ul>
{tasks.map((task, index) => (
<li key={index}>
<input
type="checkbox"
checked={task.completed}
onChange={() => toggleTask(index)}
/>
<span style={{ textDecoration: task.completed ? 'line-through' : 'none' }}>
{task.text}
</span>
<button onClick={() => removeTask(index)}>Remove</button>
</li>
))}
</ul>
</div>
);
};
export default TodoApp;
이 날씨 적용 예는 상태 관리, 부작용에 대한 사용률, 이벤트 처리, API 상호 작용 및 조건부 렌더링을 포함한 React 개념의 실제 적용을 보여줍니다. 사용자는 기능적 날씨 응용 프로그램을 작성하는 방법을 배울 수 있고 React 후크를 실제 시나리오에 통합하는 것을 이해할 수 있습니다.
태아 :
기능적 구성 요소 및 상태 후크 :
weather 와 city 대한 useState 훅을 사용하여 제어됩니다.사용 기능을 사용하여 데이터를 얻습니다.
useEffect 후크는 OpenWeatherMap API의 날씨 데이터를 가져 오는 것과 같은 부작용을 수행하는 데 사용됩니다.
fetchWeatherData 함수는 비동기식이며 fetch API를 사용하여 선택한 도시를 기반으로 날씨 데이터를 가져옵니다.
조건부 렌더링 :
날씨 데이터는 존재하는 경우에만 조건부로 렌더링됩니다 ( weather && ... ).
이벤트 처리 :
onChange 이벤트에서 setCity 기능이 호출됩니다.API 상호 작용 :
동적으로 업데이트 컨텐츠 :
스타일링 :
온도 변환 :
// src/RealWorldExamples/WeatherApp.js
import React, { useState, useEffect } from 'react';
const WeatherApp = () => {
const [weather, setWeather] = useState(null);
const [city, setCity] = useState('New York');
const apiKey = 'YOUR_OPENWEATHERMAP_API_KEY';
useEffect(() => {
// Fetch weather data from OpenWeatherMap API
const fetchWeatherData = async () => {
try {
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}`
);
if (!response.ok) {
throw new Error('Failed to fetch weather data');
}
const data = await response.json();
setWeather(data);
} catch (error) {
console.error(error.message);
}
};
fetchWeatherData();
}, [city, apiKey]);
return (
<div>
<h1>Weather App</h1>
<label>
Enter City:
<input
type="text"
value={city}
onChange={(e) => setCity(e.target.value)}
/>
</label>
{weather && (
<div>
<h2>{weather.name}, {weather.sys.country}</h2>
<p>Temperature: {Math.round(weather.main.temp - 273.15)}°C</p>
<p>Weather: {weather.weather[0].description}</p>
</div>
)}
</div>
);
};
export default WeatherApp;
모범 사례 :
/src
/components
/Button
Button.js
Button.test.js
Button.css
/features
/Todo
TodoList.js
TodoItem.js
TodoForm.js
/styles
global.css
/tests
/unit
Button.test.js
/integration
TodoIntegration.test.js
모범 사례 :
// Using React.memo
const MemoizedComponent = React.memo(({ data }) => {
// Component logic
});
// Using Code Splitting and Lazy Loading
const MyComponent = React.lazy(() => import('./MyComponent'));
// In your component
const App = () => (
<React.Suspense fallback={<LoadingSpinner />}>
<MyComponent />
</React.Suspense>
);
모범 사례 :
예:
// Jest Unit Test
test('renders correctly', () => {
const { getByText } = render(<Button label="Click me" />);
expect(getByText('Click me')).toBeInTheDocument();
});
// Jest Integration Test
test('increments count on button click', () => {
const { getByText } = render(<Counter />);
fireEvent.click(getByText('Increment'));
expect(getByText('Count: 1')).toBeInTheDocument();
});
// React Testing Library
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('clicking button increments count', () => {
render(<MyComponent />);
userEvent.click(screen.getByRole('button'));
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
모범 사례 :
단일 페이지 응용 프로그램에서 클라이언트 측 라우팅에 React 라우터를 사용하십시오.
응용 프로그램의 다른 뷰 또는 섹션으로의 경로를 정의하십시오.
<Link> 와 같은 탐색 구성 요소를 구현하여 경로간에 쉽게 탐색 할 수 있습니다.
예:
// React Router
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
const App = () => (
<Router>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
</nav>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Router>
);
모범 사례 :
// Using Local Component State
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
// Using Context API
const ThemeContext = React.createContext('light');
const ThemedComponent = () => {
const theme = useContext(ThemeContext);
return <p style={{ color: theme === 'light' ? 'black' : 'white' }}>Themed Component</p>;
};
// Using Redux
// (Assuming you have a Redux store configured)
import { useSelector, useDispatch } from 'react-redux';
const CounterRedux = () => {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
</div>
);
};
모범 사례 :
예:
모범 사례 :
예:
// Error Boundary
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <p>Something went wrong. Please try again.</p>;
}
return this.props.children;
}
}
모범 사례 :
예:
// Semantic HTML Elements
<article>
<h2>Article Title</h2>
<p>Article content...</p>
</article>
// ARIA Roles and Attributes
<button aria-label="Close" onClick={handleClose}>
✕
</button>
// Keyboard Navigation
<input type="text" onKeyDown={handleKeyDown} />
모범 사례 :
예:
// Using React.memo
const MemoizedComponent = React.memo(({ data }) => {
// Component logic
});
// Using Code Splitting and Lazy Loading
const MyComponent = React.lazy(() => import('./MyComponent'));
// In your component
const App = () => (
<React.Suspense fallback={<LoadingSpinner />}>
<MyComponent />
</React.Suspense>
);
// Using PureComponent
class PureCounter extends React.PureComponent {
render() {
return <p>Count: {this.props.count}</p>;
}
정기적으로 종속성을 업데이트하여 새로운 기능, 버그 수정 및 보안 패치의 혜택을받습니다.
프로젝트에 사용 된 라이브러리 및 패키지에 대한 시맨틱 버전을 따르십시오.
업데이트하기 전에 주요 업데이트에주의를 기울이고 철저히 테스트하십시오.
예:
# Regularly update dependencies
npm update
# Follow semantic versioning
# Example: Major.Minor.Patch
# ^1.2.3 means any version that is compatible with 1.2.3
최적화로 생산 빌드를위한 웹 팩 구성 :
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
// Other webpack configurations...
};
이 놀라운 반응 자습서에 기여하는 데 관심을 가져 주셔서 감사합니다! 귀하의 기여는 모든 수준의 학생들 에게이 자원을 더욱 가치있게 만드는 데 도움이됩니다. 버그를 고치거나 기존 기능을 개선하거나 완전히 새로운 것을 추가하든 관계없이 노력이 차이를 만듭니다.
** 저장소 포크 :이 저장소의 오른쪽 상단에있는 "포크"버튼을 클릭하여 사본을 만듭니다.
** 저장소 복제 : git clone https://github.com/DaveSimoes/React-Tutorial-2024.git 사용하여 로컬 컴퓨터에 저장소를 복제하십시오.
** 지점 만들기 : 설명 이름으로 기여할 새 지점을 만듭니다. git checkout -b your-feature
** 변경 사항 : 적절한 파일을 변경하십시오. 기존 예제를 개선하고, 새로운 예제를 추가하거나, 오타를 올바르게하거나, 문서를 개선하십시오.
** 변경 사항 : 명확하고 간결한 메시지로 변경 사항을 제출하십시오. git commit -m "Your message here" .
** 푸시 변경 : 포크 리포지토리에 변경 사항을 보내십시오 : git push origin your-feature .
풀 요청 생성 : 원래 저장소에서 풀 요청을 엽니 다. Forneça um título claro, descreva suas alterações e envie o pull request.
일관된 코딩 스타일을 따르십시오. 특히 새로운 예제 나 기능을 추가하는 경우 코드가 잘 문서화되어 있는지 확인하십시오.
이 React 튜토리얼에 기여하는 것을 고려해 주셔서 감사합니다. 이 자원을 개선하기위한 당신의 헌신은 대단히 감사합니다. 배우고 함께 성장합시다!
이 반응 자습서가 유용하다고 생각되면 별을주는 것이 좋습니다! 귀하의 지원은 엄청나게 동기 부여이며 다른 사람들 이이 자원을 발견하도록 도와줍니다. 우리 커뮤니티와 HAP의 일원이되어 주셔서 감사합니다!
공식 도서관의 반응