[2019.08.11] Mobx-react 기본 개념 및 사용법 2 (살짝 어려움 주의)
# Mobx-react 기본 개념 및 사용법 1
앞에서 구현한 Counter 컴포넌트를 이용하여 Mobx에 대해 더 알아보도록 하자.
# observable 값 변경에 의한 연산 처리 computed
Counter 값을 변경할 때마다, 자동으로 제곱된 값이 나오도록 해보자.
src/Counter.js
import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
@inject('counter')
@observer
class Counter extends Component {
render() {
const { counter } = this.props;
return (
<div>
<h1>제곱된 값 : {counter.total}</h1>
<h3>현재 값 : {counter.number}</h3>
<button onClick={counter.increase}>+1</button>
<button onClick={counter.decrease}>-1</button>
</div>
);
}
}
export default Counter;
기존의 Counter 컴포넌트에서, 제곱된 값을 나타내는 counter.total 이라는 값이 추가되었다.
src/stores/counter.js
import {observable, action, computed} from 'mobx';
export default class CounterStore {
@observable number = 0;
@action increase = () => {
this.number++;
}
@action decrease = () => {
this.number--;
}
@computed
get total() {
return this.number * this.number;
}
}
mobx에서 computed 기능을 추가로 선언하였다.
observable으로 지정한 값의 상태가 변할 때마다, computed 함수가 실행된다.
따라서, number 값 증가 및 감소를 할 때마다 total() 함수가 실행되어 number의 제곱된 값이 return 된다.
# 여러 Store 사용 시 관계 형성하기
현재 CounterStore만 사용되고 있지만, 다른 Store도 생성하여 사용, 그리고 해당 스토어에서 다른 스토어의 값을 참조하여 사용하는 법을 알아보자.
위와 같이 변화되어 있는 number 값이, reset 버튼을 누르면 0으로 초기화가 되는 작업을 또 다른 store로 만들어서 사용해 볼 것이다.
여러 스토어끼리 관계를 형성하기 위해서는 하나의 공통 스토어로 관리해야 한다.
src/stores/index.js
import CounterStore from './counter';
import ResetStore from './reset';
class RootStore {
constructor(){
this.counter = new CounterStore(this);
this.reset = new ResetStore(this);
}
}
export default RootStore;
stores 폴더에서, 모든 store를 하나로 묶을 공통 store 파일을 생성한다.
그리고 위와 같이 store의 인스턴스를 생성 시, 자신을 가리키는 this를 매개변수로 넣는다.
src/stores/reset.js
import {action} from 'mobx';
export default class ResetStore {
constructor(root){
this.root = root;
}
@action resetFunc = () => {
this.root.counter.number = 0;
}
}
reset이라는 새로운 store를 생성하여 위와 같이 작성한다.
src/stores/index.js 내에서 store의 인스턴스 생성 시 매개변수로 넣었던 this를, 해당 store의 constructor에서 받아 root에 저장한다.
resetFunc 함수는 상위의 공통 스토어를 통해, counter 인스턴스 내부의 number 값에 직접 접근한다.
src/stores/counter.js
import {observable, action, computed} from 'mobx';
export default class CounterStore {
@observable number = 0;
constructor(root){
this.root = root;
}
@action increase = () => {
this.number++;
}
@action decrease = () => {
this.number--;
}
@computed
get total() {
return this.number * this.number;
}
}
기존에 가지고 있던 counter 스토어에도 reset 스토어와 같이 constructor를 만들어서 root를 저장하도록 한다.
src/index.js
기존에는,프로젝트의 루트 엔트리인 index.js 내에서 store를 사용하기 위해, Provider를 아래와 같이 적용했었다.
import CounterStore from './stores/counter';
const counter = new CounterStore();
<Provider counter={counter}>
<App />
</Provider>
그러나 이제는 counter 스토어가 아닌, 공용 스토어를 적용시켜야 하므로, 아래와 같이 적용한다.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
import App from './App';
import * as serviceWorker from './serviceWorker';
import RootStore from './stores';
const root = new RootStore();
ReactDOM.render(
<Provider {...root}>
<App />
</Provider>
, document.getElementById('root'));
serviceWorker.unregister();
src/Counter.js
마지막으로, reset 스토어에 선언한 reset 함수를, 새로운 버튼에 적용시킨다.
import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
@inject(stores => ({
counter: stores.counter,
reset: stores.reset
}))
@observer
class Counter extends Component {
render() {
const { counter, reset } = this.props;
return (
<div>
<h1>제곱된 값 : {counter.total}</h1>
<h3>현재 값 : {counter.number}</h3>
<button onClick={counter.increase}>+1</button>
<button onClick={counter.decrease}>-1</button>
<button onClick={reset.resetFunc}>reset</button>
</div>
);
}
}
export default Counter;
inject로 스토어를 가져오는 부분에 주의한다.
하나의 스토어만 가져오는게 아닌, 하나 이상의 스토어를 가져오는 것이므로, 위와 같이 inject로 사용한다.
값을 증가 및 감소 후, reset 버튼을 클릭하면, number 값이 0으로 초기화 되는 것을 확인할 수 있다.
# Mobx 리액트 컴포넌트 최적화 작성 방식
1. 렌더링 될 대상은 최대한 분리해서 observer 적용시키기
@observer class MyComponent extends Component {
render() {
const {todos, user} = this.props;
return (<div>
{user.name}
<ul>
{todos.map(todo => <TodoView todo={todo} key={todo.id} />)}
</ul>
</div>)
}
}
위와 같은 코드에서, MyComponent 전체가 observer로 적용되어 있다.
이렇게 되면, user.name이 바뀔때도, 전체가 리렌더링이 된다. 이러한 구조 대신 아래와 같은 방식으로 하는 것이 성능에 좋다.
@observer class MyComponent extends Component {
render() {
const {todos, user} = this.props;
return (<div>
{user.name}
<TodosView todos={todos} />
</div>)
}
}
@observer class TodosView extends Component {
render() {
const {todos} = this.props;
return <ul>
{todos.map(todo => <TodoView todo={todo} key={todo.id} />)}
</ul>)
}
}
2. 세부 참조는 최대한 늦게하기
여기서 세부참조는 stores.counter.number 와 같은 방식의 참조를 말한다.
const MyConponent1 = () => (
<MyConponent2 name={item.name} price={item.price} />
)
----------------------------------
const MyConponent2 = ({name, price}) => (
<div>
이름 : {name} <br/>
가격 : {price}
</div>
)
위 코드는, MyComponent2로 props를 보낼 때, item 내부의 name과 price를 미리 세부참조하여 보냈다.
이러한 방식보다, 아래의 코드처럼 세부참조를 최대한 늦게 하는게 성능 최적화를 이룰 수 있다.
const MyConponent1 = () => (
<MyConponent2 item={item} />
)
----------------------------------
const MyConponent2 = ({item}) => (
<div>
이름 : {item.name} <br/>
가격 : {item.price}
</div>
)
3. 바인딩 시킬 함수는 최대한 밖에서 선언
render() {
return <MyComponent onClick={()=>{alert('hello')}} />
}
위 코드는 Onclick에 바인딩 시킬 함수를 직접 안에다가 선언하였다.
이러한 방식보다 아래의 코드의 방식이 성능의 최적화 시킬 수 있다.
handleClick = () => {
alert('hello');
}
render() {
return <MyComponent onClick={handleClick} />
}
4. 바인딩 시킬 함수의 파라미터는 최대한 내부에서 적용
handleClick = (name, price) => {
alert(hello);
alert(price);
}
render() {
return <MyComponent handle={()=>{handleClick(item.name, item.price)}} />
}
const MyComponent = ({handle}) => {
return (
<div onClick={handle}>
hello!
</div>
)
}
위 코드에서는, handleClick 함수에 미리 파라미터를 적용 후에 props로 내렸다.
이러한 방식보다, 아래 코드처럼 파라미터 값과 바인딩할 함수를 같이 props로 내린 후, 해당 컴포넌트 내부에서 파라미터를 적용시키는게 성능을 최적화 시킬 수 있다.
handleClick = (name, price) => {
alert(hello);
alert(price);
}
render() {
return <MyComponent {...item} handle={this.handleClick} />
}
const MyComponent = ({name, price, handle}) => {
return (
<div onClick={()=>{handle(name, price)}}>
hello!
</div>
)
}
(성능 최적화에 대한 근거는 벨로퍼트님의 velog를 참조)
헥헥...!!
mobx에 대한 기술적인 블로그를 정리하느라 반나절은 걸린 것 같다..😫
그래도 정리를 하면서 감이 좀 많이 잡혔다!
이제는 내가 가지고 있던 기존 프로젝트에 한번 mobx로 리펙토링 해보면 될 것 같다!
다음에는 mobx + TypeScript를 이용한 블로깅을 해보는 것도 좋을 것 같다.