๐
39์ผ์ฐจ
Part 10. React ๋ก ์ผํ๋ชฐ ๋ง๋ค๊ธฐ (React ๊ธฐ๋ณธ)
Ch 8. React Testing
Ch 9. React Advanced
Ch 8. React Testing
๐ JavaScript Unit Test & Jest ์ฌ์ฉํ๊ธฐ
https://github.com/testing-library/jest-dom
$ npm i jest -D
$ npm test # test ์คํ
$ npx jest --watchAll # test ์๋ ์คํ
// pakeage.json
{
"scripts": {
"test": "jset"
},
...
// ./example.test.js
describe("expect test", () => {
it("37 to equal 37", () => {
expect(37).toBe(37);
});
it("{age: 39} to equal {age: 39}", () => {
expect({ age: 39 }).toEqual({ age: 39 });
});
it(".toHaveLength", () => {
expect("hello").toHaveLength(5);
});
it(".toHaveProperty", () => {
expect({ name: "Mark" }).toHaveProperty("name");
expect({ name: "Mark" }).toHaveProperty("name", "Mark");
});
it(".toBeDefined", () => {
expect({ name: "Mark" }.name).toBeDefined();
});
it(".toBeFalsy", () => {
expect(false).toBeFalsy();
expect(0).toBeFalsy();
expect("").toBeFalsy();
expect(null).toBeFalsy();
expect(undefined).toBeFalsy();
expect(NaN).toBeFalsy();
});
it(".toBeGreaterThan", () => {
expect(10).toBeGreaterThan(9);
});
it(".toBeGreaterThanOrEqual", () => {
expect(10).toBeGreaterThanOrEqual(10);
});
it(".toBeInstanceOf", () => {
class Foo {}
expect(new Foo()).toBeInstanceOf(Foo);
});
});
๐ React Component Test
$ npm test # test ์คํ
๐ testing-library/react ํ์ฉํ๊ธฐ
https://reactjs.org/docs/testing-recipes.html#act
// ./components/Button.test.js
import { act, fireEvent, getByText, render } from "@testing-library/react";
import Button from "./Button";
describe("Button ์ปดํฌ๋ํธ (@testing-library/react)", () => {
it("์ปดํฌ๋ํธ๊ฐ ์ ์์ ์ผ๋ก ์์ฑ๋๋ค.", () => {
const button = render(<Button />);
expect(button).not.toBe(null);
});
it('"button"์ด๋ผ๊ณ ์ฐ์ฌ์๋ ์๋ฆฌ๋จผํธ๋ HTMLButtonElement ์ด๋ค.', () => {
const { getByText } = render(<Button />);
const buttonElement = getByText("button");
expect(buttonElement).toBeInstanceOf(HTMLButtonElement);
});
it('๋ฒํผ์ ํด๋ฆญํ๋ฉด, p ํ๊ทธ ์์ "๋ฒํผ์ด ๋ฐฉ๊ธ ๋๋ ธ๋ค." ๋ผ๊ณ ์ฐ์ฌ์ง๋ค.', () => {
const { getByText } = render(<Button />);
const buttonElement = getByText("button");
fireEvent.click(buttonElement);
const p = getByText("๋ฒํผ์ด ๋ฐฉ๊ธ ๋๋ ธ๋ค.");
expect(p).not.toBeNull();
expect(p).toBeInstanceOf(HTMLParagraphElement);
});
it('๋ฒํผ์ ํด๋ฆญํ๊ธฐ ์ ์๋, p ํ๊ทธ ์์ "๋ฒํผ์ด ๋๋ฆฌ์ง ์์๋ค." ๋ผ๊ณ ์ฐ์ฌ์ง๋ค.', () => {
const { getByText } = render(<Button />);
const p = getByText("๋ฒํผ์ด ๋๋ฆฌ์ง ์์๋ค.");
expect(p).not.toBeNull();
expect(p).toBeInstanceOf(HTMLParagraphElement);
});
it('๋ฒํผ์ ํด๋ฆญํ๊ณ 5์ด ๋ค์๋, p ํ๊ทธ ์์ "๋ฒํผ์ด ๋๋ฆฌ์ง ์์๋ค." ๋ผ๊ณ ์ฐ์ฌ์ง๋ค.', () => {
jest.useFakeTimers();
const { getByText } = render(<Button />);
const buttonElement = getByText("button");
fireEvent.click(buttonElement);
// 5์ด ํ๋ฅธ๋ค.
act(() => {
jest.advanceTimersByTime(5000);
});
const p = getByText("๋ฒํผ์ด ๋๋ฆฌ์ง ์์๋ค.");
expect(p).not.toBeNull();
expect(p).toBeInstanceOf(HTMLParagraphElement);
});
it("๋ฒํผ์ ํด๋ฆญํ๋ฉด, 5์ด ๋์ ๋ฒํผ์ด ๋นํ์ฑํ ๋๋ค.", () => {
jest.useFakeTimers();
const { getByText } = render(<Button />);
const buttonElement = getByText("button");
fireEvent.click(buttonElement);
// ๋นํ์ฑํ
// expect(buttonElement.disabled).toBeTruthy();
expect(buttonElement).toBeDisabled();
// 5์ด ํ๋ฅธ๋ค.
act(() => {
jest.advanceTimersByTime(5000);
});
// ํ์ฑํ
// expect(buttonElement.disabled).toBeFalsy();
expect(buttonElement).not.toBeDisabled();
});
});
// ./components/Button.jsx
import { useEffect, useRef, useState } from "react";
const BUTTON_TEXT = {
NORMAL: "๋ฒํผ์ด ๋๋ฆฌ์ง ์์๋ค.",
CLICKED: "๋ฒํผ์ด ๋ฐฉ๊ธ ๋๋ ธ๋ค.",
};
export default function Button() {
const [message, setMessage] = useState(BUTTON_TEXT.NORMAL);
const timer = useRef();
useEffect(() => {
return (
() => {
if (timer.current) {
clearTimeout(timer.current);
}
},
[]
);
});
return (
<div>
<button onClick={click} disabled={message === BUTTON_TEXT.CLICKED}>
button
</button>
<p>{message}</p>
</div>
);
function click() {
setMessage(BUTTON_TEXT.CLICKED);
timer.current = setTimeout(() => {
setMessage(BUTTON_TEXT.NORMAL);
}, 5000);
}
}
// ./App.js
import logo from "./logo.svg";
import "./App.css";
import Button from "./components/Button";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
<Button />
</header>
</div>
);
}
export default App;
๐ enzyme ํ์ฉํ๊ธฐ
Ch 9. React Advanced
๐ Optimizing Performance : ํ์ํ ๋๋ง ๋ ๋ํ๋ค. = ๋ถํ์ํ ๋ ๋๋ ํ์ง ์๋๋ค.
- Reconciliation (ํํด)
- ๋ ๋ ์ ํ์ ์ผ์น ์ฌ๋ถ๋ฅผ ํ๋จํ๋ ๊ท์น
- ์๋ก ๋ค๋ฅธ ํ์ ์ ๋ ์๋ฆฌ๋จผํธ๋ ์๋ก ๋ค๋ฅธ ํธ๋ฆฌ๋ฅผ ๋ง๋ค์ด๋ธ๋ค.
- ๊ฐ๋ฐ์๊ฐ key prop์ ํตํด, ์ฌ๋ฌ ๋ ๋๋ง ์ฌ์ด์์ ์ด๋ค ์์ ์๋ฆฌ๋จผํธ๊ฐ ๋ณ๊ฒฝ๋์ง ์์์ผ ํ ์ง ํ์ํด ์ค ์ ์๋ค
// ./App.js
// Class Component
import "./App.css";
import React from "react";
class Person extends React.PureComponent {
// shouldComponentUpdate(previousProps) {
// for (const key in this.props) {
// if (previousProps[key] !== this.props[key]) {
// return true;
// }
// }
// return false;
// }
render() {
console.log("Person render");
const { name, age } = this.props;
return (
<div>
{name} / {age}
</div>
);
}
}
class App extends React.Component {
state = {
text: "",
persons: [
{ id: 1, name: "Mark", age: 39 },
{ id: 2, name: "Hanna", age: 28 },
],
};
render() {
const { text, persons } = this.state;
return (
<div>
<input type="text" value={text} onChange={this._change} />
<ul>
{persons.map((person) => {
return (
<Person
{...person}
key={person.id}
onClick={this.toPersonClick}
/>
);
})}
</ul>
</div>
);
}
_change = (e) => {
this.setState({
...this.state,
text: e.target.value,
});
};
toPersonClick = () => {};
}
export default App;
// ./App.js
// Functional Component
import "./App.css";
import React, { useState, useCallback } from "react";
const Person = React.memo(({ name, age }) => {
console.log("Person render");
return (
<div>
{name} / {age}
</div>
);
});
function App() {
const [state, setState] = React.useState({
text: "",
persons: [
{ id: 1, name: "Mark", age: 39 },
{ id: 2, name: "Hanna", age: 28 },
],
});
const toPersonClick = React.useCallback(() => {}, []);
const { text, persons } = state;
return (
<div>
<input type="text" value={text} onChange={change} />
<ul>
{persons.map((person) => {
return <Person {...person} key={person.id} onClick={toPersonClick} />;
})}
</ul>
</div>
);
function change(e) {
setState({
...state,
text: e.target.value,
});
}
}
export default App;
๐ React.createPortal
https://ko.reactjs.org/docs/portals.html
// ./public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<div id="modal"></div> // โญ
...
</body>
</html>
// ./components/modal.jsx
import ReactDOM from "react-dom";
const Modal = ({ children }) =>
ReactDOM.createPortal(children, document.querySelector("#modal"));
export default Modal;
// ./App.js
import "./App.css";
import React, { useState, useCallback } from "react";
import Modal from "./components/modal";
function App() {
const [visible, setVisible] = useState(false);
const open = () => {
setVisible(true);
};
const close = () => {
setVisible(false);
};
return (
<div>
<button onClick={open}>open</button>
{visible && (
<Modal>
<div
style={{
width: "100vw",
height: "100vh",
background: "rgba(0,0,0,0.5",
}}
onClick={close}
>
Hello
</div>
</Modal>
)}
</div>
);
}
export default App;
๐ React.forwardRef
https://ko.reactjs.org/docs/forwarding-refs.html
// ./App.js
import "./App.css";
import React, { useState, useCallback, useRef } from "react";
import MyInput from "./components/MyInput";
function App() {
const myInputRef = useRef();
const click = () => {
console.log(myInputRef.current.value);
};
return (
<div>
<MyInput ref={myInputRef} />
<button onClick={click}>send</button>
</div>
);
}
export default App;
// ./components/MyInput.jsx
import React from "react";
export default React.forwardRef(function MyInput(props, ref) {
return (
<div>
<p>MyInput</p>
<input ref={ref} />
</div>
);
});
'๐ฌ > ใ ใ ใ ใ ใ ใ ์ฑ๋ฆฐ์ง' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
useMemo ์ useCallback ์ ๋ฌด์์ด ๋ค๋ฅธ ๊ฒ์ธ๊ฐ...... (0) | 2022.03.25 |
---|---|
40์ผ์ฐจ (0) | 2022.03.24 |
38์ผ์ฐจ (0) | 2022.03.23 |
37์ผ์ฐจ (0) | 2022.03.23 |
36์ผ์ฐจ (0) | 2022.03.21 |