Woongjae Lee
NHN Dooray - Frontend Team
2woongjae@gmail.com
2018.02
import { createSelector } from 'reselect'
const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent
const subtotalSelector = createSelector(
shopItemsSelector,
items => items.reduce((acc, item) => acc + item.value, 0)
)
const taxSelector = createSelector(
subtotalSelector,
taxPercentSelector,
(subtotal, taxPercent) => subtotal * (taxPercent / 100)
)
const totalSelector = createSelector(
subtotalSelector,
taxSelector,
(subtotal, tax) => ({ total: subtotal + tax })
)
let exampleState = {
shop: {
taxPercent: 8,
items: [
{ name: 'apple', value: 1.20 },
{ name: 'orange', value: 0.95 },
]
}
}
console.log(subtotalSelector(exampleState)) // 2.15
console.log(taxSelector(exampleState)) // 0.172
console.log(totalSelector(exampleState)) // { total: 2.322 }
~/Project/workshop-201801
➜ npx create-react-app reselect-ts-quick-start --scripts-version=react-scripts-ts
~/Project/workshop-201801 took 1m 36s
➜ cd reselect-ts-quick-start
Project/workshop-201801/reselect-ts-quick-start is 📦 v0.1.0 via ⬢ v8.9.4
➜ npm i redux -D
+ redux@3.7.2
added 3 packages in 9.771s
Project/workshop-201801/reselect-ts-quick-start is 📦 v0.1.0 via ⬢ v8.9.4 took 11s
➜ npm i react-redux @types/react-redux -D
+ react-redux@5.0.6
+ @types/react-redux@5.0.14
added 3 packages in 8.452s
Project/workshop-201801/reselect-ts-quick-start is 📦 v0.1.0 via ⬢ v8.9.4 took 9s
➜ npm i reselect -D
+ reselect@3.0.1
added 1 package in 7.86s
mapStateToProps 에서 state.persons 를 트랜스폼 시킨다.
그러면 다른 props 로 여겨서 count 가 올라갈때 같이 랜더가 실행된다.
state.persons 가 변경되지 않으면, 트랜스폼 결과를 기억해서 그대로 준다.
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import './index.css';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store;
import { AnyAction, combineReducers } from 'redux';
export const ADD_PERSON = 'ADD_PERSON';
export type ADD_PERSON = typeof ADD_PERSON;
export const CHANGE_COUNT = 'CHANGE_COUNT';
export type CHANGE_COUNT = typeof CHANGE_COUNT;
export interface State {
persons: PersonsState;
count: CounterState;
}
type PersonsState = {
name: string;
age: number;
}[];
type CounterState = number;
export function addPerson(name: string) {
return {
type: ADD_PERSON,
name
};
}
export function changeCount() {
return {
type: CHANGE_COUNT
};
}
function personsReducer(
state: PersonsState = [
{
name: 'Mark',
age: 1
}
],
action: AnyAction
) {
if (action.type === ADD_PERSON) {
return [...state, { name: action.name, age: state.length + 1 }];
} else {
return state;
}
}
function counterReducer(state: CounterState = 0, action: AnyAction) {
if (action.type === CHANGE_COUNT) {
return state + 1;
} else {
return state;
}
}
export default combineReducers<State>({
persons: personsReducer,
count: counterReducer
});
import * as React from 'react';
import './App.css';
import Persons from './Persons';
import Counter from './Counter';
import store from './store';
const initial = store.getState();
const logo = require('./logo.svg');
export default class App extends React.Component {
input: HTMLInputElement;
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<Persons />
<Counter />
<p>{JSON.stringify(initial)}</p>
</div>
);
}
}
import * as React from 'react';
import './App.css';
import { Dispatch, AnyAction } from 'redux';
import { connect } from 'react-redux';
import { State, changeCount } from './reducer';
interface CounterStateProps {
count: number;
}
interface CounterDispatchProps {
changeCount: () => void;
}
class Counter extends React.Component<
CounterStateProps & CounterDispatchProps
> {
input: HTMLInputElement;
componentDidMount() {
setInterval(() => {
this.props.changeCount();
// tslint:disable-next-line:align
}, 2000);
}
render() {
return (
<div>
<p>{this.props.count}</p>
</div>
);
}
}
const mapStateToProps = (state: State): CounterStateProps => {
return {
count: state.count
};
};
const mapDispatchToProps = (
dispatch: Dispatch<AnyAction>
): CounterDispatchProps => ({
changeCount: () => dispatch(changeCount())
});
export default connect<CounterStateProps, CounterDispatchProps>(
mapStateToProps,
mapDispatchToProps
)(Counter);
import * as React from 'react';
import { Dispatch, AnyAction } from 'redux';
import { connect } from 'react-redux';
import { addPerson } from './reducer';
import { makeMapStateToProps } from './selector';
interface PersonsStateProps {
persons: string[];
}
interface PersonsDispatchProps {
addPerson: (name: string) => void;
}
class Persons extends React.Component<
PersonsStateProps & PersonsDispatchProps
> {
input: HTMLInputElement;
render() {
console.log(this.props.persons);
return (
<div className="App">
<p className="App-intro">
<input
type="text"
ref={ref => (this.input = ref as HTMLInputElement)}
/>
<button onClick={() => this.props.addPerson(this.input.value)}>
이름 추가
</button>
</p>
<p className="App-intro">{JSON.stringify(this.props.persons)}</p>
</div>
);
}
}
/*
const mapStateToProps = (state: State): PersonsStateProps => {
return {
persons: state.persons.map(person => person.name)
};
};
*/
const mapDispatchToProps = (
dispatch: Dispatch<AnyAction>
): PersonsDispatchProps => ({
addPerson: name => dispatch(addPerson(name))
});
export default connect<PersonsStateProps, PersonsDispatchProps>(
makeMapStateToProps,
mapDispatchToProps
)(Persons);
import { State } from './reducer';
import { createSelector } from 'reselect';
const makeGetPersons = () =>
createSelector([(state: State) => state.persons], persons =>
persons.map(person => person.name)
);
export const makeMapStateToProps = () => {
const getPersons = makeGetPersons();
return (state: State) => {
console.log('makeMapStateToProps');
return {
persons: getPersons(state)
};
};
};
By Woongjae Lee
코드버스킹 워크샵 - React with TypeScript 세번째 (2018년 1월 버전)