Optimizing Performance
필요할 때만 render하며 불필요한 render 줄이기
=> Reconciliation
- render 전후의 일치 여부를 판단하는 규칙
import logo from "./logo.svg";
import "./App.css";
import React from "react";
class Foo extends React.Component {
componentDidMount() {
console.log("Foo componentDidMount");
}
componentWillUnmount() {
console.log("Foo componentWillUnmount");
}
render() {
return <p>Foo</p>;
}
}
class App extends React.Component {
state = {
count: 0,
};
componentDidMount() {
setInterval(() => {
this.setState({ count: this.state.count + 1 });
}, 1000);
}
// 상위 타입이 다르므로 Unmount했다가 mount했다가 한다.
render() {
if (this.state.count % 2 === 0) {
return (
<div>
<Foo />
</div>
);
}
return (
<span>
<Foo />
</span>
);
}
}
export default App;
- 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.
import logo from "./logo.svg";
import "./App.css";
import React from "react";
class App extends React.Component {
state = {
count: 0,
};
componentDidMount() {
setInterval(() => {
this.setState({ count: this.state.count + 1 });
}, 1000);
}
// 같은 dom 엘리먼트인데 다른 속성을 갖게 되면
// 달라진 속성 값만 바뀌면서 다시 표현된다.
render() {
if (this.state.count % 2 === 0) {
return (
<div className={{ color: "red", fontWeight: "bold" }} title="stuff" />
);
}
return (
<div className={{ color: "green", fontWeight: "bold" }} title="stuff" />
);
}
}
export default App;
import logo from "./logo.svg";
import "./App.css";
import React from "react";
class Foo extends React.Component {
componentDidMount() {
console.log("Foo componentDidMount");
}
componentWillUnmount() {
console.log("Foo componentWillUnmount");
}
static getDerivedStateFromProps(nextProps, prevProps) {
console.log("Foo getDerivedStateFromProps", nextProps, prevProps);
return {};
}
render() {
console.log("Foo render");
return <p>Foo</p>;
}
}
class App extends React.Component {
state = {
count: 0,
};
componentDidMount() {
setInterval(() => {
this.setState({ count: this.state.count + 1 });
}, 1000);
}
// 다른 props면 컴포넌트가 업데이트 된다.
render() {
if (this.state.count % 2 === 0) {
return <Foo name="Mark" />;
}
return <Foo name="Hanna" />;
}
}
export default App;
- key prop을 통해, 여러 랜더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해줄 수 있다.
import logo from "./logo.svg";
import "./App.css";
import React from "react";
class Foo extends React.Component {
componentDidMount() {
console.log("Foo componentDidMount", this.props.children);
}
componentWillUnmount() {
console.log("Foo componentWillUnmount");
}
static getDerivedStateFromProps(nextProps, prevProps) {
console.log("Foo getDerivedStateFromProps", nextProps, prevProps);
return {};
}
render() {
console.log("Foo render", this.props.children);
return <p>Foo</p>;
}
}
class App extends React.Component {
state = {
count: 0,
};
componentDidMount() {
setInterval(() => {
this.setState({ count: this.state.count + 1 });
}, 1000);
}
render() {
if (this.state.count % 2 === 0) {
return (
<ul>
<Foo key="2">second</Foo>
<Foo key="3">third</Foo>
</ul>
);
}
return (
<ul>
<Foo key="1">first</Foo>
<Foo key="2">second</Foo>
<Foo key="3">third</Foo>
</ul>
);
}
}
export default App;
class 버전
import logo from "./logo.svg";
import "./App.css";
import React from "react";
// class Person extends React.PureComponent {
// render() {
// console.log("Person render");
// const { name, age } = this.props;
// return (
// <div>
// {name} / {age}
// </div>
// );
// }
// }
class Person extends React.Component {
shouldComponentUpdate(previousProps) {
for (const key in this.props) {
if (previousProps[key] !== this.props[key]) {
return true;
}
}
return false; // 완전 같음, 불필요한 render 하지 않음
}
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: "Yejin", age: 22 },
],
};
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;
function 버전
import logo from "./logo.svg";
import "./App.css";
import React from "react";
// props가 같을 때, 다시 render안 하게 함
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: "Yejin", age: 22 },
],
});
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
Portal은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식은 렌더링하는 최고의 방법을 제공한다.
public/index.html
<div id="root"></div>
<div id="modal"></div> // 추가
index.css
#modal {
position: absolute;
top: 0;
left: 0;
}
components/Modal.jsx
import ReactDOM from "react-dom";
// children이 createPortal로 생성돼서 id가 modal인 element에 render
const Modal = ({ children }) =>
ReactDOM.createPortal(children, document.querySelector("#modal"));
export default Modal;
App.js
import logo from "./logo.svg";
import "./App.css";
import React, { useState } 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
style={{
width: "100vw",
height: "100vh",
background: "rgba(0,0,0,0.5)",
}}
>
<div onClick={close}>Hello</div>
</Modal>
)}
</div>
);
}
export default App;
React.forwardRef
하위 컴포넌트에 있는 레퍼런스를 상위 컴포넌트에서 이용하는 것이다.
특정 input에 대해서 컴포넌트를 만들때 혹은 다른 이유에서든 reference를 나의 하위 컴포넌트 어디에선가 지정을 해주고 싶다면 forwardRef로 전달을 해줄 수 있다.
App.js
import logo from "./logo.svg";
import "./App.css";
import React, { useRef, useState } from "react";
import MyInput from "./components/MyInput";
function App() {
const myInputRef = useRef();
// MyInput.jsx의 input ref를 가져오는 것
const click = () => {
console.log(myInputRef.current.value);
};
return (
<div>
<MyInput ref={myInputRef} />
<button onClick={click}>콘솔에 출력</button>
</div>
);
}
export default App;
MyInput.jsx
import React from "react";
export default React.forwardRef(function MyInput(props, ref) {
return (
<div>
<p>MyInput</p>
<input ref={ref} />
</div>
);
});
댓글남기기