
React - ๊ธฐ๋ณธ์ ์ธ ์ด๋ก ์ปจ์
Interested in ReactJS, RxJS and ReasonML.
์ฐธ๊ณ : ์ด ๋ฌธ์๋ react-basic ์ ๋ฒ์ญํ ๊ฒ์ผ๋ก ์์ญ ํน์ ์ค์ญ์ด ์์ ์ ์์์ ์ฐธ๊ณ ๋ฐ๋๋๋ค.
์ด ๋ฌธ์๋ ํ์์ React ๋ชจ๋ธ์ ๋ํ ์ดํด๋ฅผ ์ค๋ช ํ๊ธฐ ์ํ ๋ฌธ์์ด๋ค. ์ด ๋์์ธ์ ์ด๋๋ ์ฐ์ญ์ ์ถ๋ก ์ ๊ด์ ์์ ์ด๋ฅผ ์ค๋ช ํ๊ณ ์ ํ๋ค.
๋ถ๋ช ๋ ผ์์ ์ฌ์ง๊ฐ ์๋ ๋ช ๊ฐ์ง ์ ์ ๊ฐ ์์ ์ ์์ผ๋ฉฐ, ์์ ์ ์ค์ ๋์์ธ์ ๋ฒ๊ทธ๋ ํ์ด ์์ ์ ์๋ค. ์ด ์์ ๋ ๋จ์ง ๊ณต์ํ์ ์์์ด๋ฉฐ, ์ด๋ป๊ฒ ๊ณต์ํํ๋ ์ง์ ๋ํ ๋ ์ข์ ์์ด๋์ด๊ฐ ์๋ค๋ฉด ๋ง์ค์ด์ง ๋ง๊ณ Pull Request๋ฅผ ๋ณด๋ด์ฃผ๊ธธ ๋ฐ๋๋ค. ๋จ์ > ๋ณต์ก์ผ๋ก์ ์งํ์ ๋๋ฌด ๋ง์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ธ๋ถ์ฌํญ ์์ด๋ ํฉ๋นํด์ผ ํ๋ค.
๋ฆฌ์กํธ์ ์ค์ ๊ตฌํ์ ์ค์ฉ์ ์ธ ์๋ฃจ์ , ์ ์ง์ ๋จ๊ณ, ์๊ณ ๋ฆฌ์ฆ ์ต์ ํ, ๋ ๊ฑฐ์ ์ฝ๋, ๋๋ฒ๊น ํด ๋ฐ ์ค์ ๋ก ์ ์ฉํ๊ฒ ๋ง๋๋ ๋ฐ ํ์ํ ๊ฒ๋ค๋ก ๊ตฌ์ฑ๋์ด์๋ค. ๊ทธ๋ฌํ ๊ฒ๋ค์ ์๊ฐ์ ์ด๋ฉฐ, ์๊ฐ์ด ํ๋ฆ์ ๋ฐ๋ผ ๋ ํจ์จ์ ์ด๊ณ ๋์ ์ฐ์ ์์๋ฅผ ๊ฐ์ง๋ ๊ฒ๋ค๊ณผ ๋ฐ๋ ์ ์๋ค. ์ค์ ๊ตฌํ์ ์ถ๋ก ํ๊ธฐ๊ฐ ๋งค์ฐ ์ด๋ ต๋ค.
๋๋ ๋ด๊ฐ ์๊ฐํ ์ ์๋ ๋ ๋จ์ํ ์ดํด๋ฅผ ํ๊ณ ์ถ๋ค.
๋ณํ
๋ฆฌ์กํธ์ ํต์ง ์ ์ ๋ UI๊ฐ ๋จ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฅธ ํ์์ ๋ฐ์ดํฐ๋ก ํฌ์ํ๋ค๋ ๊ฒ์ด๋ค. ๋์ผํ ์ ๋ ฅ์ ํญ์ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅํ๋ฉฐ, ๋จ์ํ ์์ ํจ์์ด๋ค.
function NameBox(name) {
return { fontWeight: 'bold', labelContent: name };
}'Sebastian Markbรฅge' ->
{ fontWeight: 'bold', labelContent: 'Sebastian Markbรฅge' };์ถ์
ํ๋์ ํจ์์ ๋ณต์กํ UI๋ฅผ ๋ฃ์ ์ ์๋ค. UI๋ฅผ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ถ๋ถ์ผ๋ก ์ถ์ํํ๋ ๊ฒ์ด ์ค์ํ๋ฉฐ, ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ถ๋ถ์ ๊ตฌํ ์ธ๋ถ ์ฌํญ์ ๋์คํ์ง ์๋๋ค. ํ๋์ ํจ์๋ฅผ ๋ค๋ฅธ ํจ์์์ ํธ์ถํ๋ค.
function FancyUserBox(user) {
return {
borderStyle: '1px solid blue',
childContent: [
'Name: ',
NameBox(user.firstName + ' ' + user.lastName)
]
};
}{ firstName: 'Sebastian', lastName: 'Markbรฅge' } ->
{
borderStyle: '1px solid blue',
childContent: [
'Name: ',
{ fontWeight: 'bold', labelContent: 'Sebastian Markbรฅge' }
]
};๊ตฌ์ฑ
์ง์ ์ผ๋ก ์ฌ์ฌ์ฉํ ์ ์๋ ๊ธฐ๋ฅ์ ๊ตฌ์ฑํ๊ธฐ ์ํด์๋ ๋จ์ํ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๊ฒ๋ค์ ์ฌ์ฉํ๋ ๊ฒ๋ง์ผ๋ก๋ ์ถฉ๋ถํ์ง ์๋ค. ๊ทธ๋ค์ ์ํ ์๋ก์ด ์ปจํ ์ด๋๋ฅผ ๊ตฌ์ฑํด์ผ ํ๋ค. ๋ํ, ๋ค๋ฅธ ์ถ์ํ๋ฅผ ๊ตฌ์ฑํ๋ ์ปจํ ์ด๋์์ ์ถ์ํ๋ฅผ ๊ตฌ์ฑํ ์ ์์ด์ผ ํ๋ค. ๋ด๊ฐ ๊ตฌ์ฑ์ ๋ํด ์๊ฐํ๋ ๋ฐฉ์์ ๋ ์ด์์ ๋ค๋ฅธ ์ถ์ํ๋ฅผ ์๋ก์ด ์ถ์ํ๋ก ๊ฒฐํฉํ๋ ๊ฒ์ด๋ค.
function FancyBox(children) {
return {
borderStyle: '1px solid blue',
children: children
};
}
function UserBox(user) {
return FancyBox([
'Name: ',
NameBox(user.firstName + ' ' + user.lastName)
]);
}์ํ
UI๋ ๋จ์ํ ์๋ฒ์ ๋น์ง๋์ค ๋ก์ง์ ์ํ์ ๋ํ ์ฌ๋ณธ์ด ์๋๋ค. ์ค์ ๋ก ์ ํํ ํฌ์๊ณผ ๊ด๋ จ์ด ์๋ ์ํ๊ฐ ๋ง์ด ์์ผ๋ฉฐ, ๋ค๋ฅธ ํฌ์๊ณผ๋ ๊ด๋ จ์ด ์๋ค. ์๋ฅผ ๋ค์ด ํ ์คํธ ํ๋์ ์ ๋ ฅ์ ํ๋ฉด, ๋ค๋ฅธ ํญ์ด๋ ๋ชจ๋ฐ์ผ ๊ธฐ๊ธฐ์ ๋ณต์ ๋๊ฑฐ๋ ๋ณต์ ๋์ง ์์ ์ ์๋ค. ์คํฌ๋กค ์์น๋ ์ฌ๋ฌ ํฌ์ ์ฌ์ด์ ๊ฑฐ์ ๋ณต์ ๋์ง ์๋ ์ ํ์ ์ธ ์์ด๋ค.
์ฐ๋ฆฌ๋ ๋ฐ์ดํฐ ๋ชจ๋ธ์ด ๋ถ๋ณํ๊ธฐ๋ฅผ ์ ํธํ๋ ๊ฒฝํฅ์ด ์๋ค. ์ฐ๋ฆฌ๋ ๋งจ ์์ ์๋ ๋จ์ผ ์์๋ก์จ ์ํ๋ฅผ ์ ๋ฐ์ดํธ ํ ์ ์๋ ํจ์๋ฅผ ์ฐ๋ ๋ํ๋ค.
function FancyNameBox(user, likes, onClick) {
return FancyBox([
'Name: ', NameBox(user.firstName + ' ' + user.lastName),
'Likes: ', LikeBox(likes),
LikeButton(onClick)
]);
}
// Implementation Details
var likes = 0;
function addOneMoreLike() {
likes++;
rerender();
}
// Init
FancyNameBox(
{ firstName: 'Sebastian', lastName: 'Markbรฅge' },
likes,
addOneMoreLike
);์ฐธ๊ณ : ์ด ์์ ๋ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๊ธฐ ์ํด ์ฌ์ด๋ ์ดํํธ๋ฅผ ์ฌ์ฉํ๋ค. ์ค์ ๋ฆฌ์กํธ์ ๋ชจ๋ธ์ ์ ๋ฐ์ดํธ ๊ณผ์ ์์ ๋ค์ ๋ฒ์ ์ ์ํ๋ฅผ ๋ฐํํ๋ค. ์์ ์ฝ๋์ฒ๋ผ ์ค๋ช ํ๋ ๊ฒ์ด ๋ ๊ฐ๋จํ์ง๋ง, ๋ฏธ๋์ ์ด ์์ ๋ฅผ ๋ฐ๊พธ๊ณ ์ ํ๋ค.
๋ฉ๋ชจ์ด์ ์ด์
์ด๋ค ํจ์๊ฐ ์์ํ๋ค๋ ๊ฒ์ ์๋ ์ํ์์ ๊ทธ ํจ์๋ฅผ ๋ฐ๋ณตํด์ ํธ์ถํ๋ ๊ฒ์ ๋ญ๋น์ด๋ค. ๋ง์ง๋ง ์ธ์์ ๋ง์ง๋ง ๊ฒฐ๊ณผ๋ฅผ ์ถ์ ํ๋ ๋ฉ๋ชจ์ด์ฆ๋ ๋ฒ์ ์ ํจ์๋ฅผ ๋ง๋ค ์ ์๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๊ฐ์ ๊ฐ์ ๋๊ฒจ๋ฐ๋ ํจ์๋ฅผ ๋ค์ ์คํํ ํ์๊ฐ ์๋ค.
function memoize(fn) {
var cachedArg;
var cachedResult;
return function(arg) {
if (cachedArg === arg) {
return cachedResult;
}
cachedArg = arg;
cachedResult = fn(arg);
return cachedResult;
};
}
var MemoizedNameBox = memoize(NameBox);
function NameAndAgeBox(user, currentTime) {
return FancyBox([
'Name: ',
MemoizedNameBox(user.firstName + ' ' + user.lastName),
'Age in milliseconds: ',
currentTime - user.dateOfBirth
]);
}์ญ์ ์ฃผ : ๋ฉ๋ชจ์ด์ ์ด์ ์ ๋ํด ์์ฑํ ๊ธ์ ๋ณด๋ฉด ๋ ์์ธํ ์ ์ ์๋ค.
๋ฆฌ์คํธ
๋๋ถ๋ถ์ UI๋ ๋ชฉ๋ก์ ์๋ ๊ฐ ํญ๋ชฉ์ ๋ํด ์ฌ๋ฌ ๊ฐ์ ๋ง๋๋ ์ด๋ค ํํ์ ๋ชฉ๋ก์ด๋ค. ์ด๊ฒ์ ์์ฐ์ ์ธ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๋ง๋ ๋ค.
๋ชฉ๋ก์ ์๋ ๊ฐ ํญ๋ชฉ์ ๋ํ ์ํ๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด ํน์ ํญ๋ชฉ์ ์ํ๋ฅผ ์ ์งํ๋ Map์ ๋ง๋ค ์ ์๋ค.
function UserList(users, likesPerUser, updateUserLikes) {
return users.map(user => FancyNameBox(
user,
likesPerUser.get(user.id),
() => updateUserLikes(user.id, likesPerUser.get(user.id) + 1)
));
}
var likesPerUser = new Map();
function updateUserLikes(id, likeCount) {
likesPerUser.set(id, likeCount);
rerender();
}
UserList(data.users, likesPerUser, updateUserLikes);์ฐธ๊ณ : ์ด์ FancyNameBox์ ์ ๋ฌ๋๋ ์ฌ๋ฌ ๋ค๋ฅธ ์ธ์๋ค์ด ์๋ค.
์ฌ๋ฌ ๋ค๋ฅธ ์ธ์๋ค๊ณผ ํจ๊ป ํธ์ถ๋๋ ํจ์๋ก ํ์ฌ๊ธ ๋ฉ๋ชจ์ด์ ์ด์
์ ๊นจ์ง๊ฒ ํ๋๋ฐ, ํ ๋ฒ์ ํ๋์ ๊ฐ๋ง ๊ธฐ์ตํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
์๋์ ๋ ์์ธํ ๋ด์ฉ์ด ํ์ ๋๋ค.
์ฐ์์ฑ
๋ถํํ๋, UI ๋ชฉ๋ก์๋ ๋๋ฌด ๋ง์ ๋ชฉ๋ก์ด ์์ด์ ๋ช ์์ ์ผ๋ก ๊ด๋ฆฌํด์ผํ๋ ์์ฉ๊ตฌ ์ฝ๋๊ฐ ๋ง์์ง๋ค.
ํจ์์ ์คํ์ ์ฐ๊ธฐํจ์ผ๋ก์จ ์์ฉ๊ตฌ ์ฝ๋์ ์ผ๋ถ๋ฅผ ์ค์ ๋น์ง๋์ค ๋ก์ง ๋ฐ๊นฅ์ผ๋ก ์ฎ๊ธฐ๋ ๊ฒ์ด ๊ฐ๋ฅํ๋ค.
์๋ฅผ ๋ค์ด, Partial Application์ ์ฌ์ฉํ ์ ์๋ค.
๊ทธ ๋ค์, ์์ฉ๊ตฌ ์ฝ๋๋ก ๋ถํฐ ์์ ๋ก์์ง ํต์ฌ ํจ์์ ์ธ๋ถ๋ก๋ถํฐ ์ํ๋ฅผ ์ ๋ฌํ๋ค.
์ญ์ ์ฃผ : ์ ๊ธ์์๋ Partial Application์ด ์๋ Currying์ด๋ผ๊ณ ๋์ด์๋ค. ํ์ง๋ง ์ปค๋ง์ด๋ผ๋ฉด, box.children(likesPerUser)(updateUserLikes)๊ณผ ๊ฐ์ด ์ฝ๋๊ฐ ์์ฑ๋์ด์ผ ํ๋ค. ์ด ์ธ์๋ bind ์ ๋ํ ๋งํฌ๊ฐ ์ถ๊ฐ๋์ด ์๋๋ฐ, ๋ณ ๊ด๋ จ์ฑ์ด ์๋ค๊ณ ์๊ฐํ๊ธฐ ๋๋ฌธ์ ์ ๊ฑฐํ์๋ค. ์ปค๋ง๊ณผ ํ์
์ ํ๋ฆฌ์ผ์ด์
์ ์ฐจ์ด๋ Javascript- Currying VS Partial Application์์ ๋ ์์ธํ๊ฒ ๋ณผ ์ ์๋ค.
์ด๊ฒ์ ์์ฉ๊ตฌ ์ฝ๋๋ฅผ ์ค์ด์ง ์์ง๋ง, ์ค์ํ ๋น์ง๋์ค ๋ก์ง ๋ฐ๊นฅ์ผ๋ก ๋ฒ์ด๋์ผ ํ๋ค.
function FancyUserList(users) {
return FancyBox(
UserList.bind(null, users)
);
}
const box = FancyUserList(data.users);
const resolvedChildren = box.children(likesPerUser, updateUserLikes);
const resolvedBox = {
...box,
children: resolvedChildren
};์ํ Map
์์ ์ฐ๋ฆฌ๋ ๋ฐ๋ณต๋๋ ํจํด์ ๋ณผ ๋ ๊ฐ์ ํจํด์ ๋ค์ ๊ตฌํํ๋ ๊ฒ์ ํผํ๊ธฐ ์ํด ๊ตฌ์ฑ์ ํ ์ ์๋ค๋ ๊ฒ์ ์๋ค. ์ํ๋ฅผ ์ถ์ถํ๊ณ ์ ๋ฌํ๋ ๋ก์ง์ ์ฌ์ฌ์ฉ๋๋ ์ ์์ค ํจ์๋ก ์ฎ๊ธธ ์ ์๋ค.
function FancyBoxWithState(
children,
stateMap,
updateState
) {
return FancyBox(
children.map(child => child.continuation(
stateMap.get(child.key),
updateState
))
);
}
function UserList(users) {
return users.map(user => ({
continuation: FancyNameBox.bind(null, user),
key: user.id
}));
}
function FancyUserList(users) {
return FancyBoxWithState.bind(null,
UserList(users)
);
}
const continuation = FancyUserList(data.users);
continuation(likesPerUser, updateUserLikes);๋ฉ๋ชจ์ด์ ์ด์ Map
๋ชฉ๋ก์ ์๋ ์ฌ๋ฌ ํญ๋ณต์ ๋ฉ๋ชจ์ด์ฆํ๋ ๊ฒ์ ์๋นํ ์ด๋ ต๋ค. ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋๊ณผ ๋น๋ ์ฌ์ด์ ๊ท ํ์ ๋ง์ถ๋ ๋ณต์กํ ์บ์ฑ ์๊ณ ๋ฆฌ์ฆ์ ์ฐพ์์ผ ํ๋ค.
๋คํํ๋, UI๋ ๋์ผํ ์์น์์ ์์ ๋๋ ค๋ ๊ฒฝํฅ์ด ์๋ค. ํธ๋ฆฌ์ ๊ฐ์ ์์น๋ ํญ์ ๊ฐ์ ๊ฐ์ ๊ฐ์ง๋ฉฐ, ์ด ํธ๋ฆฌ๋ ๋ฉ๋ชจ์ด์ ์ด์ ์ ์ ์ฉํ ์ ๋ต์์ด ๋ฐํ์ก๋ค.
์ฐ๋ฆฌ๋ ์ํ์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋์ผํ ํธ๋ฆญ์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ๊ตฌ์ฑ ๊ฐ๋ฅํ ํจ์๋ฅผ ํตํด ๋ฉ๋ชจ์ด์ ์ด์ ์บ์๋ฅผ ์ ๋ฌํ ์ ์๋ค.
function memoize(fn) {
return function(arg, memoizationCache) {
if (memoizationCache.arg === arg) {
return memoizationCache.result;
}
const result = fn(arg);
memoizationCache.arg = arg;
memoizationCache.result = result;
return result;
};
}
function FancyBoxWithState(
children,
stateMap,
updateState,
memoizationCache
) {
return FancyBox(
children.map(child => child.continuation(
stateMap.get(child.key),
updateState,
memoizationCache.get(child.key)
))
);
}
const MemoizedFancyNameBox = memoize(FancyNameBox);๋์ํ ํจ๊ณผ
์ฌ๋ฌ ์์ค์ ์ถ์ํ๋ฅผ ํตํด ํ์ํ ๋ชจ๋ ์์ ๊ฐ์ ์ ๋ฌํ๋ ๊ฒ์ PITA์ ๊ฐ์ ๊ฒ์ผ๋ก ๋ฐํ์ก๋ค.
์ค๊ฐ ์์ฑ์๋ฅผ ํฌํจํ์ง ์์ ์ฑ ๋ ์ถ์ํ ์ฌ์ด์์ ๋ฌด์ธ๊ฐ๋ฅผ ์ ๋ฌํ๊ธฐ ์ํ ์ง๋ฆ๊ธธ์ ๊ฐ๋ ๊ฒ์ด ๋๋๋ก ๋ฐ๋์งํ๋ค.
๋ฆฌ์กํธ์์๋ ์ด๊ฒ์ ์ปจํ
์คํธ๋ผ๊ณ ๋ถ๋ฅธ๋ค.
์ญ์ ์ฃผ : PITA์ ๋ป์ด ๏ฟฝ๏ฟฝ๏ฟฝ์ง ์ฐพ์๋ดค๋๋ฐ, ์์๋ด์ง ๋ชปํ๋ค. ์๋ฌธ์ ๋ค์๊ณผ ๊ฐ๋ค.
It turns out that it is kind of a PITA to pass every little value you might need through several levels of abstractions.
๋๋ก๋ ๋ฐ์ดํฐ ์ข ์์ฑ์ด ์ถ์ํ ํธ๋ฆฌ๋ฅผ ์์ ํ ๋ฐ๋ฅด์ง ์๋ ๊ฒฝ์ฐ๊ฐ ์๋ค. ์๋ฅผ ๋ค์ด, ๋ ์ด์์ ์๊ณ ๋ฆฌ์ฆ์์ ํ์์ ํฌ๊ธฐ๋ฅผ ์์์ผ ๊ทธ๋ค์ ์์น๋ฅผ ์์ ํ๊ฒ ์ดํํ ์ ์๋ค.
์ด์ , ์ด ์์ ๋ ์กฐ๊ธ ๋ฐ๊นฅ์ ์๋ค. ECMAScript์ ์ ์๋ ๋์ํ ํจ๊ณผ๋ฅผ ์ฌ์ฉํ๊ฒ ๋ค. ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ์ต์ํ๋ค๋ฉด ๋ชจ๋๋์ ์ํด ๋ถ๊ณผ๋ ์ค๊ฐ ์์ ํผํ ๊ฒ์ด๋ค.
function ThemeBorderColorRequest() { }
function FancyBox(children) {
const color = raise new ThemeBorderColorRequest();
return {
borderWidth: '1px',
borderColor: color,
children: children
};
}
function BlueTheme(children) {
return try {
children();
} catch effect ThemeBorderColorRequest -> [, continuation] {
continuation('blue');
}
}
function App(data) {
return BlueTheme(
FancyUserList.bind(null, data.users)
);
}