Optimizing Performance
Immutable.js
immer
createPortal
Lead Software Engineer @ProtoPie
Microsoft MVP
TypeScript Korea User Group Organizer
Marktube (Youtube)
이 웅재
필요할 때만 랜더한다.
서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.
개발자가 key prop 을 통해,
여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.
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 (
<div>
<Foo />
</div>
);
}
return (
<span>
<Foo />
</span>
);
}
}
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);
}
render() {
if (this.state.count % 2 === 0) {
return <div className="before" title="stuff" />;
}
return <div className="after" title="stuff" />;
}
}
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 <div style={{ color: "red", fontWeight: "bold" }} />;
}
return <div style={{ color: "green", fontWeight: "bold" }} />;
}
}
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 <Foo name="Mark" />;
}
return <Foo name="Anna" />;
}
}
class Foo extends React.Component {
state = {};
componentDidMount() {
console.log("Foo componentDidMount");
}
componentWillUnmount() {
console.log("Foo componentWillUnmount");
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log("Foo getDerivedStateFromProps", nextProps, prevState);
return {};
}
render() {
console.log("Foo render");
return <p>Foo</p>;
}
}
class App extends React.Component {
state = {
count: 0
};
componentDidMount() {
setTimeout(() => {
this.setState({
count: this.state.count + 1
});
}, 3000);
}
render() {
if (this.state.count % 2 === 0) {
return (
<ul>
<Foo>first</Foo>
<Foo>second</Foo>
</ul>
);
}
return (
<ul>
<Foo>first</Foo>
<Foo>second</Foo>
<Foo>third</Foo>
</ul>
);
}
}
class Foo extends React.Component {
state = {};
componentDidMount() {
console.log("Foo componentDidMount", this.props.children);
}
componentWillUnmount() {
console.log("Foo componentWillUnmount");
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log("Foo getDerivedStateFromProps", nextProps, prevState);
return {};
}
render() {
console.log("Foo render", this.props.children);
return <p>{this.props.children}</p>;
}
}
class App extends React.Component {
state = {
count: 0
};
componentDidMount() {
setTimeout(() => {
this.setState({
count: this.state.count + 1
});
}, 3000);
}
render() {
if (this.state.count % 2 === 0) {
return (
<ul>
<Foo>second</Foo>
<Foo>third</Foo>
</ul>
);
}
return (
<ul>
<Foo>first</Foo>
<Foo>second</Foo>
<Foo>third</Foo>
</ul>
);
}
}
class Foo extends React.Component {
state = {};
componentDidMount() {
console.log("Foo componentDidMount", this.props.children);
}
componentWillUnmount() {
console.log("Foo componentWillUnmount");
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log("Foo getDerivedStateFromProps", nextProps, prevState);
return {};
}
render() {
console.log("Foo render", this.props.children);
return <p>{this.props.children}</p>;
}
}
class App extends React.Component {
state = {
count: 0
};
componentDidMount() {
setTimeout(() => {
this.setState({
count: this.state.count + 1
});
}, 3000);
}
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>
);
}
}
class Foo extends React.Component {
state = {};
componentDidMount() {
console.log("Foo componentDidMount", this.props.children);
}
componentWillUnmount() {
console.log("Foo componentWillUnmount");
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log("Foo getDerivedStateFromProps", nextProps, prevState);
return {};
}
render() {
console.log("Foo render", this.props.children);
return <p>{this.props.children}</p>;
}
}
class App extends React.Component {
state = {
text: "",
persons: [
{ id: 1, name: "Mark", age: 37 },
{ id: 2, name: "Anna", age: 26 },
]
};
render() {
console.log("App render");
const { text, persons } = this.state;
return (
<div>
<input type="text" value={text} onChange={this._change} />
<button onClick={this._click}>click</button>
<ul>
{persons.map(p => (
<Person {...p} key={p.id} />
))}
</ul>
</div>
);
}
_change = e => {
this.setState({
...this.state,
text: e.target.value
});
};
_click = () => {
console.log(this.state.text);
};
}
class Person extends React.Component {
render() {
console.log("Person render");
const { name, age } = this.props;
return (
<ul>
{name} / {age}
</ul>
);
}
}
class App extends React.Component {
state = {
text: "",
persons: [
{ id: 1, name: "Mark", age: 37 },
{ id: 2, name: "Anna", age: 26 },
]
};
render() {
console.log("App render");
const { text, persons } = this.state;
return (
<div>
<input type="text" value={text} onChange={this._change} />
<button onClick={this._click}>click</button>
<ul>
{persons.map(p => (
<Person {...p} key={p.id} />
))}
</ul>
</div>
);
}
_change = e => {
this.setState({
...this.state,
text: e.target.value
});
};
_click = () => {
console.log(this.state.text);
};
}
class Person extends React.Component {
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 (
<ul>
{name} / {age}
</ul>
);
}
}
class App extends React.Component {
state = {
text: "",
persons: [
{ id: 1, name: "Mark", age: 37 },
{ id: 2, name: "Anna", age: 26 },
]
};
render() {
console.log("App render");
const { text, persons } = this.state;
return (
<div>
<input type="text" value={text} onChange={this._change} />
<button onClick={this._click}>click</button>
<ul>
{persons.map(p => (
<Person {...p} key={p.id} />
))}
</ul>
</div>
);
}
_change = e => {
this.setState({
...this.state,
text: e.target.value
});
};
_click = () => {
console.log(this.state.text);
};
}
class Person extends React.PureComponent {
render() {
console.log("Person render");
const { name, age } = this.props;
return (
<ul>
{name} / {age}
</ul>
);
}
}
class App extends React.Component {
state = {
text: "",
persons: [
{ id: 1, name: "Mark", age: 37 },
{ id: 2, name: "Anna", age: 26 },
]
};
render() {
console.log("App render");
const { text, persons } = this.state;
return (
<div>
<input type="text" value={text} onChange={this._change} />
<button onClick={this._click}>click</button>
<ul>
{persons.map(p => (
<Person {...p} key={p.id} onClick={() => {}} />
))}
</ul>
</div>
);
}
_change = e => {
this.setState({
...this.state,
text: e.target.value
});
};
_click = () => {
console.log(this.state.text);
};
}
class Person extends React.PureComponent {
render() {
console.log("Person render");
const { name, age } = this.props;
return (
<ul>
{name} / {age}
</ul>
);
}
}
class App extends React.Component {
state = {
text: "",
persons: [
{ id: 1, name: "Mark", age: 37 },
{ id: 2, name: "Anna", age: 26 },
]
};
render() {
console.log("App render");
const { text, persons } = this.state;
return (
<div>
<input type="text" value={text} onChange={this._change} />
<button onClick={this._click}>click</button>
<ul>
{persons.map(p => (
<Person {...p} key={p.id} onClick={() => {}} />
))}
</ul>
</div>
);
}
_change = e => {
this.setState({
...this.state,
text: e.target.value
});
};
_click = () => {
console.log(this.state.text);
};
}
const Person = React.memo(props => {
console.log("Person render");
const { name, age } = props;
return (
<ul>
{name} / {age}
</ul>
);
});
<!DOCTYPE html>
<html lang="en">
<head>
...
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<div id="modal"></div>
</body>
</html>
body {...}
#modal {
position: absolute;
top: 0;
left: 0;
}
import ReactDOM from 'react-dom';
const Modal = ({ children }) =>
ReactDOM.createPortal(children, document.querySelector('#modal'));
export default Modal;
const Home = () => {
const [visible, setVisible] = useState(false);
const show = () => setVisible(true);
const hide = () => setVisible(false);
return (
<div>
<h1>Home</h1>
<button onClick={click}>open</button>
<NaviContainer />
<BooksContainer />
{visible && (
<Modal>
<div
style={{
width: '100vw',
height: '100vh',
background: 'rgba(0, 0, 0, 0.5)',
}}
onClick={hide}
>
Hello
</div>
</Modal>
)}
</div>
);
};
npm i immutable
import { Map } from "immutable";
const object = Map({ a: 1, b: 2, c: 3 });
console.log(object);
const nested = Map({ foo: Map({ name: "Mark", age: 37 }), bar: "bar" });
console.log(nested);
import { List, Map } from "immutable";
const list1 = List([1, 2, 3]);
console.log(list1);
console.log(list1.toJS());
const list2 = List([
Map({ name: "Mark", age: 37 }),
Map({ name: "Anna", age: 26 })
]);
console.log(list2);
console.log(list2.toJS());
const list3 = list1.push(4, 5);
const list4 = list3.unshift(0);
const list5 = list4.concat(list2, list3);
console.log(list1.size);
console.log(list3.size);
console.log(list4.size);
console.log(list5.size);
import { List, Map } from "immutable";
const object = Map({ a: 1, b: 2, c: 3 });
const list = List([1, 2, 3]);
const newObject = object.set("a", 5);
console.log(object === newObject, newObject.get("a"));
const newList = list.set(0, 5);
console.log(list === newList, newList.get(0));
import { List, Map } from "immutable";
const object = Map({ foo: Map({ name: "Mark", age: 37 }), bar: "bar" });
const list = List([
Map({ name: "Mark", age: 37 }),
Map({ name: "Anna", age: 26 })
]);
const newObject = object.setIn(["foo", "name"], "Anna");
console.log(object === newObject, newObject.getIn(["foo", "name"]));
const newList = list.setIn([0, "name"], "Anna");
console.log(list === newList, newList.getIn([0, "name"]));
import { Map } from "immutable";
const object = Map({ a: 1, b: 2, c: 3 });
const nested = Map({ foo: Map({ name: "Mark", age: 37 }), bar: "bar" });
const newObject = object.update("a", value => value + 1);
console.log(object === newObject, newObject.get("a"));
const newNested = nested.updateIn(["foo", "age"], value => value + 1);
console.log(object === newNested, newNested.getIn(["foo", "age"]));
import { Map } from "immutable";
const object = Map({ a: 1, b: 2, c: 3 });
const newObject = object.delete("c");
console.log(object === newObject, newObject.toJS(), newObject.get("c"));
// src/reducers/books.js
export default function books(state = initialState, action) {
switch (action.type) {
case RECEIVE_BOOKS:
return [...state, ...action.books];
case DELETE_BOOK: {
const newState = [...state];
for (let i = 0; i < newState.length; i++) {
if (newState[i].bookId === action.bookId)
newState[i].deletedAt = new Date().toISOString();
}
return newState;
}
case UNDO_DELETE_BOOK: {
const newState = [...state];
for (let i = 0; i < newState.length; i++) {
if (newState[i].bookId === action.bookId) newState[i].deletedAt = null;
}
return newState;
}
default:
return state;
}
}
// src/reducers/books.js
const initialState = List([]);
export default function books(state = initialState, action) {
switch (action.type) {
case RECEIVE_BOOKS: {
return state.concat(action.books.map(book => Map(book)));
}
case DELETE_BOOK: {
return state.map(book => {
if (book.get(bookId) === action.bookId)
book.set(deletedAt, new Date().toISOString());
return book;
});
}
case UNDO_DELETE_BOOK: {
return state.map(book => {
if (book.get(bookId) === action.bookId) book.set(deletedAt, null);
return book;
});
}
default:
return state;
}
}
npm i immer
import produce from 'immer';
const state = {
name: 'Mark',
age: 39,
};
const nextState = produce(state, (draft) => {
draft.age += 1;
});
console.log(state, nextState, state === nextState);
import produce from 'immer';
const state = {
books: [
{ title: '책 이름', author: { name: 'Mark', age: 39 } },
{ title: '책 이름', author: { name: 'Hanna', age: 28 } },
],
};
const nextState = produce(state, (draft) => {
draft.books[0].author.age += 1;
});
console.log(state, nextState, state === nextState);
console.log(state.books[1] === nextState.books[1]);
import produce from 'immer';
const state = {
books: [
{ title: '책 이름', author: { name: 'Mark', age: 39 } },
{ title: '책 이름', author: { name: 'Hanna', age: 28 } },
],
};
console.log(
produce((draft) => {
draft.books[0].author.age += 1;
}),
);
function App() {
const [state, setState] = useState({
books: [
{ title: '책 이름', author: { name: 'Mark', age: 39 } },
{ title: '책 이름', author: { name: 'Hanna', age: 28 } },
],
});
useEffect(() => {
setState(
produce((draft) => {
draft.books[0].author.age += 1;
}),
);
}, []);
console.log(state);
return (
<div>{JSON.stringify(state)}</div>
);
}