๊ณ ์ฐจ ์ปดํฌ๋ํธ๋, ์๋์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๋ฉด์ ์ถ๊ฐ์ ์ธ ๊ธฐ๋ฅ์ ํฌํจ์ํค๋๋ก ํ๋ ํจ์์ด๋ค. ์ฝ๋ ์ฌ์ฌ์ฉ, ์ถ์ํ์ ๊ด๋ จ์ด ์๋ค.
๋ฆฌ์กํธ์์ ๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ ๊ณ ์ฐจ ํจ์์ ๊ฐ๋ ์ด ๋ฐ์๋ ๊ฒ์ด๋ผ๊ณ ํ ์ ์๋ค. ๋ฐ๋ผ์ ๊ณ ์ฐจ ํจ์๊ฐ ๋ฌด์์ธ์ง์ ๋ํด ๋จผ์ ์ง๊ณ ๋์ด๊ฐ๋ณด์.
์๋ฐ์คํฌ๋ฆฝํธ์์ ํจ์๋ ์ผ๊ธ ๊ฐ์ฒด์ด๋ค. ๊ทธ๋์ ๊ฐ๋ฅํ ๊ฐ๋ ์ด ๊ณ ์ฐจ ํจ์์ด๋ค. ๊ณ ์ฐจ ํจ์๋ ์๋์ ์กฐ๊ฑด๋ค ์ค ํ ๊ฐ์ง ์ด์์ ๋ง์กฑํ๋ ํจ์๋ฅผ ๋งํ๋ค.
- ํจ์์ ์ธ์๋ก ํจ์๋ฅผ ๋ฃ์ ์ ์๋ค.
- ํจ์๋ ํจ์๋ฅผ ๋ฐํํ ์ ์๋ค.
์, ์ด๊ฒ ๊ณ ์ฐจ ํจ์๊ตฌ๋. ๊ทธ๋ผ ๊ณ ์ฐจ ํจ์๋ ์ด๋ป๊ฒ ์ฐ์ผ๊น?
์ฐธ๊ณ : ์ฑ [๋ชจ๋์๋ฐ์คํฌ๋ฆฝํธ ์ ๋ฌธ]
/* ์์ด์ ํ์ํ๋ ํ๋ก๊ทธ๋จ */
let digits = '';
for (var i = 0; i < 10; i++) {
digits += i;
}
console.log(digits); // 0123456789
/* ๋ฌด์์ ์ํ๋ฒณ์ ํ์ํ๋ ํ๋ก๊ทธ๋จ */
let randomChars = '';
for (var i = 0; i < 8; i++) {
randomChars += String.fromCharCode(Math.floor(Math.random() * 26) + 'a'.charCodeAt(0));
}
console.log(randomChars);
์ ๋ ํ๋ก๊ทธ๋จ์ ์ดํด๋ณด๋ฉด ํ๋ ์ผ์ ๋ค๋ฅด์ง๋ง ์ฌ์ฉํ๋ ๋ก์ง์ด ๊ฐ๋ค. ํต์ฌ ๊ด์ฌ์ฌ๋ ์์ด, ๋ฌด์์ ์ํ๋ฒณ์ ๋ํ ๊ฒ์ผ๋ก ๊ฐ๊ฐ ๋ค๋ฅด์ง๋ง, ๊ด์ฌ์ฌ์ ํด๋นํ๋ ๋ฌธ์๋ฅผ ๋ชจ์ ๋ฌธ์์ด๋ก ๋์ถํ๋ ๋ถ๋ถ, ์ฆ ํก๋จ ๊ด์ฌ์ฌ๊ฐ ๊ฐ๋ค๊ณ ๋ณผ ์ ์๋ค. ๊ทธ๋ผ '๋ฌธ์๋ฅผ ๋ชจ์ ๋ฌธ์์ด๋ก ๋์ถํ๋ ๋ถ๋ถ'์ ์ถ์ํํ์ฌ ํํํ๋ค๋ฉด ํ๋ก๊ทธ๋จ์ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ํฅ์์ํฌ ์ ์๋ค. ๋ค์๊ณผ ๊ฐ์ด ๋ง์ด๋ค!
// ๊ณ ์ฐจ ํจ์ joinStrings๋ฅผ ํ์ฉํด ํก๋จ ๊ด์ฌ์ฌ ์ถ์ํ
function joinStrings(n, f) {
var s = '';
for (var i = 0; i < n; i++) {
s += f(i);
}
return s;
}
let digits = joinStrings(10, function (i) {
return i;
});
let randomChars = joinStrings(8, function (i) {
return String.fromCharCode(Math.floor(Math.random() * 26) + 'a'.charCodeAt(0));
});
console.log(digits); // 0123456789
console.log(randomChars); // mzobequt
๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ ์์ ์ค๋ช ํ ๊ณ ์ฐจ ํจ์์ ๊ฐ๋ ์ ์ปดํฌ๋ํธ์ ๋ฐ์ํ๋ค๊ณ ์๊ฐํ ์ ์๋ค. ๊ทธ๋ผ React ์ปดํฌ๋ํธ ์์๋ฅผ ์ดํด๋ณด๋ฉฐ ์ดํดํด๋ณด์.
- ์ธ๋ถ๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๊ตฌ๋
ํ์ฌ ๋๊ธ ๋ชฉ๋ก์ ๋ ๋๋งํ๋
CommentList
์ปดํฌ๋ํธ - ๋ธ๋ก๊ทธ ํฌ์คํธ๋ฅผ ๊ตฌ๋
ํ๊ธฐ ์ํ
BlogPost
์ปดํฌ๋ํธ DataSource
๋ ๊ธ๋ก๋ฒ ๋ฐ์ดํฐ ์์ค
class CommentList extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
comments: DataSource.getComments(),
};
}
componentDidMount() {
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
comments: DataSource.getComments(),
});
}
render() {
return (
<div>
{this.state.comments.map((comment) => (
<Comment comment={comment} key={comment.id} />
))}
</div>
);
}
}
class BlogPost extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
blogPost: DataSource.getBlogPost(props.id),
};
}
componentDidMount() {
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
blogPost: DataSource.getBlogPost(this.props.id),
});
}
render() {
return <TextBlock text={this.state.blogPost} />;
}
}
์ ์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด, ๊ฐ ์ปดํฌ๋ํธ๋ ํต์ฌ ๊ด์ฌ์ฌ๊ฐ ๋งค์ฐ ๋ค๋ฅด์ง๋ง ๋์ผํ ํก๋จ ๊ด์ฌ์ฌ๋ฅผ ๊ฐ์ง๊ณ ์๋ค๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
componentDidMount
:DataSource
์ change ๋ฆฌ์ค๋ ์ถ๊ฐhandleChange
:setState
ํธ์ถcomponentWillUnmount
:DataSource
์ change ๋ฆฌ์ค๋ ์ ๊ฑฐ
์ ํก๋จ ๊ด์ฌ์ฌ๋ ๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํด ์ถ์ํํ ์ ์๋ค. ๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ ์ปดํฌ๋ํธ๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ ์๋ก์ด ์ปดํฌ๋ํธ๋ฅผ ๋ฐํํ๋ ํจ์์ด๋ค. ๊ณ ์ฐจ ์ปดํฌ๋ํธ withSubscription
์ฝ๋๋ฅผ ์ดํด๋ณด์.
function withSubscription(WrappedComponent, selectData) {
return class extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
data: selectData(DataSource, props),
};
}
componentDidMount() {
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
data: selectData(DataSource, this.props),
});
}
render() {
// ๋ํ๋ ์ปดํฌ๋ํธ๋ ์๋ก์ด props, data์ ํจ๊ป ์ปจํ
์ด๋์ ๋ชจ๋ props๋ฅผ ์ ๋ฌ๋ฐ๋๋ค.
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
const CommentListWithSubscription = withSubscription(CommentList, (DataSource) =>
DataSource.getComments()
);
const BlogPostWithSubscription = withSubscription(BlogPost, (DataSource, props) =>
DataSource.getBlogPost(props.id)
);
์ด๋ ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ์ฌ ํก๋จ ๊ด์ฌ์ฌ๋ฅผ ๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ก์ ์ ์ ํ ์ถ์ํํ์๊ณ , ์ด๋ ์ฝ๋ ์ฌ์ฌ์ฉ๊ณผ ์ ์ง๋ณด์์ฑ์ ํฐ ๊ธฐ์ฌ๋ฅผ ํ ๊ฒ์ด๋ค.
- ์ ๋ ฅ๋ ์ปดํฌ๋ํธ๋ฅผ ์์ ํ์ง ์์ผ๋ฉฐ ์์์ ์ฌ์ฉํ์ฌ ๋์์ ๋ณต์ฌํ์ง๋ ์๋๋ค.
- ๊ทธ์ ์๋ณธ ์ปดํฌ๋ํธ๋ฅผ ์ปจํ ์ด๋ ์ปดํฌ๋ํธ๋ก ํฌ์ฅ(wrapping)ํ์ฌ ์กฐํฉ(compose)ํ๋ค.
- ๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ ์ฌ์ด๋ ์ดํํธ๊ฐ ์ ํ ์๋ ์์ ํจ์์ด๋ค.
๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ ์ปดํฌ๋ํธ์ ํ๋กํ ํ์ ์ ์์ ๋๋ ๋ณ๊ฒฝํ์ง ์์์ผ ํ๋ค. ๋ณ๊ฒฝ(mutation)๋ ๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ ๋์ถ๋ ์ถ์ํ(leaky abstraction)์ด๋ผ๊ณ ํ๋ค. HOC๋ ๋ณ๊ฒฝ(mutation) ๋์ ์ ์ ๋ ฅ ์ปดํฌ๋ํธ๋ฅผ ์ปจํ ์ด๋ ๊ตฌ์ฑ ์์๋ก ๊ฐ์ธ์ ์กฐํฉ(composition)์ ์ฌ์ฉํด์ผ ํ๋ค.
function logProps(WrappedComponent) {
return class extends React.Component {
componentDidUpdate(prevProps) {
console.log('Current props: ', this.props);
console.log('Previous props: ', prevProps);
}
render() {
// ์
๋ ฅ component๋ฅผ ๋ณ๊ฒฝํ์ง ์๋ container ๐ค
return <WrappedComponent {...this.props} />;
}
};
}
๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ ์ปดํฌ๋ํธ์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ๋ฟ, ๋ณ๊ฒฝํด์๋ ์ ๋๋ค. ๊ด๋ จ ์๋ props๋ ๊ทธ๋๋ก ์ปดํฌ๋ํธ์ ์ ๋ฌํ๋ค.
render() {
const { extraProp, ...passThroughProps } = this.props;
const injectedProp = someStateOrInstanceMethod;
return (
<WrappedComponent
injectedProp={injectedProp}
{...passThroughProps}
/>
);
}
๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ Component๋ฅผ ์ ๋ ฅ๋ฐ์ Component๋ฅผ ๋ฐํํ๋ค. ์ถ๋ ฅ ํ์ ์ด ์ ๋ ฅ ํ์ ๊ณผ ๋์ผํ ํจ์๋ ์ ๋ง ์ฝ๊ฒ ์กฐํฉํ ์ ์๋ค.
const ConnectedComment = connect(commentSelector, commentActions)(CommentList);
์ ์ฝ๋๋ฅผ ๋ถํดํด๋ณด๋ฉด connect
๋ ๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ฅผ ๋ฐํํ๋ ๊ณ ์ฐจ ํจ์๋ผ๋ ๊ฒ์ ์ ์ ์๋ค.
// connect๋ ๋ค๋ฅธ ํจ์๋ฅผ ๋ฐํํ๋ ํจ์
const enhance = connect(commentListSelector, commentListActions);
// ๋ฐํ๋ ํจ์๋ Redux store์ ์ฐ๊ฒฐ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ฐํํ๋ ๊ณ ์ฐจ ํจ์ ์ปดํฌ๋ํธ
const ConnectedComment = enhance(CommentList);
๊ฐ๋ฐ ๋๊ตฌ ๋๋ฒ๊น ์ ์ํ์ฌ HOC์ ๊ฒฐ๊ณผ์์ ์๋ฆฌ๋ displayName์ ์์ฑํ๋ ๊ฒ์ด ์ข๋ค. ๋ค์ด๋ฐ์ HOC์ ์ด๋ฆ์ผ๋ก ๋ด๋ถ ์ปดํฌ๋ํธ๋ช ์ ๊ฐ์ธ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ค.
function withSubscription(WrappedComponent) {
class WithSubscription extends React.Component {
/* ... */
}
WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`; // WithSubscription(CommentList)
return WithSubscription;
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
๊ณ ์ฐจ ์ปดํฌ๋ํธ ์ฉ๋
- Enhancer : ์๋ก์ด props๋ฅผ ํตํด ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ณ ์ถ์ ๋ (์ธ๋ถ์์ property ๋ฐ์)
- Injector : ํ์ํ ๊ธฐ๋ฅ์ ๋จผ์ ๊ตฌํํด๋จ๋ค๊ฐ ์ฌ์ฉํ๊ณ ์ถ์ ๋ (์ฌ์ ์์ฒ๋ผ)