์ด๋ฒ ์ฅ์์๋ ๋ฆฌ์กํธ ์ปดํฌ๋ํธ ์ฌํ ๋ด์ฉ์ ๋ค๋ฃน๋๋ค. ๊ณ ์ฐจ ์ปดํฌ๋ํธ(higher order components)๋ฅผ ํ์ตํ ํ ์ค์ ๊ตฌํ์ ํด๋ณผ ๊ฒ์ ๋๋ค. ๋ฆฌ์กํธ ๊ณ ๊ธ ์ฃผ์ ์ ๋ณต์กํ ์ธํฐ๋ ์ ๋ ํจ๊ป ์์๋ด ์๋ค.
์ข
์ข
๋ฆฌ์กํธ์์ DOM ๋
ธ๋๋ฅผ ๋ค๋ฃฐ ์ผ์ด ์์ต๋๋ค. ref
์์ฑ์ผ๋ก DOM ๋
ธ๋๋ฅผ ์ ๊ทผํ ์ ์์ต๋๋ค. ๋ฆฌ์กํธ์ ๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ๊ณผ ์ ์ธ ๋ฐฉ์์ ์๋ฐฐ๋๊ธฐ ๋๋ฌธ์ ์ํฐ ํจํด(anti pattern)์ด๋ผ๊ณ ๋ ํฉ๋๋ค. ๊ฒ์ ์
๋ ฅ ํ๋๋ฅผ ๊ตฌํํ์ ๋, ์ ์ ์ธ๊ธํ์ต๋๋ค. ๋ฆฌ์กํธ ๊ณต์ ๋ฌธ์์๋ ref
๊ฐ ํ์ํ ์ธ ๊ฐ์ง ๊ฒฝ์ฐ๋ฅผ ์๊ฐํฉ๋๋ค.
- DOM API (focus, media playback ๋ฑ)์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ
- ๋ช ๋ นํ ๋ ธ๋ ์ ๋๋ฉ์ด์ ์ ํธ์ถํํ๋ ๊ฒฝ์ฐ
- DOM ๋ ธ๋๊ฐ ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(์: D3.js)๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ
Search ์ปดํฌ๋ํธ๋ฅผ ์์ ๋ก ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ฒ์ ๋ ๋๋ง ๋ ๋ ์ ๋ ฅ ํ๋(input field)๋ฅผ ํฌ์ปค์ค(focus) ํ๊ธฐ ์ํด์๋ DOM API๊ฐ ํ์ํฉ๋๋ค. ์ด ๋ถ๋ถ์ ๋ํด ์์ธํ ์ค๋ช ํ๊ฒ ์ง๋ง ์ฐ๋ฆฌ๊ฐ ๋ง๋ค๊ณ ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์๋ ์ค์ง์ ์ธ ๋์์ ๋์ง ์์ผ๋ฏ๋ก ์ดํ ๋ณ๊ฒฝ ์ฌํญ์ ์๋ตํ๊ฒ ์ต๋๋ค.
์ผ๋ฐ์ ์ผ๋ก ํจ์ํ ๋น ์ํ ์ปดํฌ๋ํธ์ ES6 ํด๋์ค ์ปดํฌ๋ํธ ๋ชจ๋ ref
์์ฑ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํฌ์ปค์ค ์ฌ์ฉ์ ์ํด ์๋ช
์ฃผ๊ธฐ ๋ฉ์๋๊ฐ ํ์ํฉ๋๋ค. ES6 ํด๋์ค ์ปดํฌ๋ํธ์ ํจ๊ป ref
์์ฑ์ ์ฌ์ฉํ์ฌ input ํ๋์ ํฌ์ปค์ค(focus) ํ๊ฒ ๋ง๋ค์ด๋ด
์๋ค.
๋จผ์ ํจ์ํ ๋น ์ํ ์ปดํฌ๋ํธ๋ฅผ ES6 ํด๋์ค ์ปดํฌ๋ํธ๋ก ๋ฆฌํฉํฐ๋ง ํ๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
# leanpub-start-insert
class Search extends Component {
render() {
const {
value,
onChange,
onSubmit,
children
} = this.props;
return (
# leanpub-end-insert
<form onSubmit={onSubmit}>
<input
type="text"
value={value}
onChange={onChange}
/>
<button type="submit">
{children}
</button>
</form>
# leanpub-start-insert
);
}
}
# leanpub-end-insert
ES6 ํด๋์ค ์ปดํฌ๋ํธ์ this
๊ฐ์ฒด๋ ref
์์ฑ์ผ๋ก DOM ๋
ธ๋๋ฅผ ์ฐธ์กฐํ ์ ์์ต๋๋ค.
{title="src/App.js",lang=javascript}
class Search extends Component {
render() {
const {
value,
onChange,
onSubmit,
children
} = this.props;
return (
<form onSubmit={onSubmit}>
<input
type="text"
value={value}
onChange={onChange}
# leanpub-start-insert
ref={(node) => { this.input = node; }}
# leanpub-end-insert
/>
<button type="submit">
{children}
</button>
</form>
);
}
}
์ด์ this
๊ฐ์ฒด, ์๋ช
์ฃผ๊ธฐ ๋ฉ์๋์ DOM API๋ฅผ ์ฌ์ฉํด์ ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ๋์ ๋ input ํ๋์ ํฌ์ปค์ค๋ฅผ ์ค ์ ์์ต๋๋ค.
{title="src/App.js",lang=javascript}
class Search extends Component {
# leanpub-start-insert
componentDidMount() {
this.input.focus();
}
# leanpub-end-insert
render() {
const {
value,
onChange,
onSubmit,
children
} = this.props;
return (
<form onSubmit={onSubmit}>
<input
type="text"
value={value}
onChange={onChange}
ref={(node) => { this.input = node; }}
/>
<button type="submit">
{children}
</button>
</form>
);
}
}
์ ํ๋ฆฌ์ผ์ด์
์ด ๋ ๋๋ง๋ ๋ input ํ๋์ ํฌ์ปค์ค๋ฅผ ์ฃผ์ด์ผ ํฉ๋๋ค. ์ด๋ฅผ ์ํด ๊ธฐ๋ณธ์ ์ผ๋ก ref
์์ฑ์ ์ฌ์ฉํฉ๋๋ค.
๊ทธ๋ฌ๋ this
๊ฐ์ฒด๊ฐ ์๋ ํจ์ํ ๋น ์ํ ์ปดํฌ๋ํธ๋ ref
๋ฅผ ์ด๋ป๊ฒ ์ ๊ทผํ ์ ์์๊น์? ์๋ ํจ์ํ ๋น ์ํ ์ปดํฌ๋ํธ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
const Search = ({
value,
onChange,
onSubmit,
children
}) => {
# leanpub-start-insert
let input;
# leanpub-end-insert
return (
<form onSubmit={onSubmit}>
<input
type="text"
value={value}
onChange={onChange}
# leanpub-start-insert
ref={(node) => input = node}
# leanpub-end-insert
/>
<button type="submit">
{children}
</button>
</form>
);
}
์ด์ DOM ์๋ ๋จผํธ์ ์ ๊ทผํ ์ ์์ต๋๋ค. ํฌ์ปค์ค ์์ ์ ๊ฒฝ์ฐ ๋ณ๋ค๋ฅธ ๋์์ด ๋์ง ์์ ๊ฒ๋๋ค. ํจ์ํ ๋น ์ํ ์ปดํฌ๋ํธ์์ ํฌ์ปค์ค๋ฅผ ํธ๋ฆฌ๊ฑฐํ๋ ์๋ช
์ฃผ๊ธฐ ๋ฉ์๋๊ฐ ์๊ธฐ ๋๋ฌธ์
๋๋ค. ๊ทธ๋ฌ๋ ๊ฐ๊น์ด ๋ฏธ๋์๋ ref
์์ฑ์ ๊ฐ์ง ํจ์ํ ๋น ์ํ ์ปดํฌ๋ํธ๊ฐ ๊ฐ๋ฅํ ์ง๋ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
- [๋ฆฌ์กํธ ๊ณต์ ๋ฌธ์] the ref attribute in general in React
- [๋ฆฌ์กํธ ๊ณต์ ๋ฌธ์] the usage of the ref attribute in React
์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ๋ค์ ๋์๊ฐ ๋ด ์๋ค. ํด์ปค ๋ด์ค API๋ก ๊ฒ์ ์์ฒญํ๋ ๋์ ๋ก๋ฉ ์ธ๋์ผ์ดํฐ๋ฅผ ๋ณด์ฌ์ฃผ๊ณ ์ถ์ ๊ฒ๋๋ค. ๋น๋๊ธฐ ์์ฒญ์์ผ๋ก ์ฌ์ฉ์์๊ฒ ๊ทธ๋์ ๊ธฐ๋ค๋ฆด ๊ฒ์ ์๋ ค์ค์ผ ํฉ๋๋ค. src/App.js ํ์ผ์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ Loading ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
const Loading = () =>
<div>Loading ...</div>
๋ค์ ๋ก๋ฉ ์ํ๋ฅผ ์ ์ฅํ๋ ํ๋กํผํฐ๊ฐ ํ์ํฉ๋๋ค. ๋ก๋ฉ ์ํ์ ๋ฐ๋ผ ์ปดํฌ๋ํธ๊ฐ ๋ณด์ฌ์ง๊ฒ ๋ฉ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: null,
searchKey: '',
searchTerm: DEFAULT_QUERY,
error: null,
# leanpub-start-insert
isLoading: false,
# leanpub-end-insert
};
...
}
...
}
isLoading
ํ๋กํผํฐ์ ์ด๊ธฐ ๊ฐ์ false
์
๋๋ค. App ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋๊ธฐ ์ ์๋ ๋ก๋ฉํ์ง ์์ต๋๋ค.
์์ฒญํ ๋ ๋ก๋ฉ ์ํ ๊ฐ์ true
๋ก ๋ฐ๊พธ๊ณ , ์์ฒญ์ด ์ฑ๊ณต๋๋ฉด ๋ค์ false
๋ก ๋ฐ๊ฟ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
setSearchTopStories(result) {
...
this.setState({
results: {
...results,
[searchKey]: { hits: updatedHits, page }
},
# leanpub-start-insert
isLoading: false
# leanpub-end-insert
});
}
fetchSearchTopStories(searchTerm, page = 0) {
# leanpub-start-insert
this.setState({ isLoading: true });
# leanpub-end-insert
fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}&${PARAM_PAGE}${page}&${PARAM_HPP}${DEFAULT_HPP}`)
.then(response => response.json())
.then(result => this.setSearchTopStories(result))
.catch(e => this.setState({ error: e }));
}
...
}
์ด์ App ์ปดํฌ๋ํธ ์์ Loading ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๊ฒ ์ต๋๋ค. ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ผ๋ก ๋ก๋ฉ ์ํ์ ๋ฐ๋ผ Loading ์ปดํฌ๋ํธ ๋๋ Button ์ปดํฌ๋ํธ ๋ ์ค ํ๋๋ฅผ ๋ณด์ฌ์ค๋๋ค. Button ์ปดํฌ๋ํธ๋ ๋ ๋ง์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฒํผ์ ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
render() {
const {
searchTerm,
results,
searchKey,
error,
# leanpub-start-insert
isLoading
# leanpub-end-insert
} = this.state;
...
return (
<div className="page">
...
<div className="interactions">
# leanpub-start-insert
{ isLoading
? <Loading />
: <Button
onClick={() => this.fetchSearchTopStories(searchKey, page + 1)}>
More
</Button>
}
# leanpub-end-insert
</div>
</div>
);
}
}
์ด๊ธฐ์ componentDidMount()
๋ฉ์๋์์ ์์ฒญํ๊ธฐ ๋๋ฌธ์ ์ ํ๋ฆฌ์ผ์ด์
์ ์์ํ ๋ Loading ์ปดํฌ๋ํธ๊ฐ ํ์๋ฉ๋๋ค. ๋ฆฌ์คํธ๊ฐ ๋น์ด์๊ธฐ ๋๋ฌธ์ Table ์ปดํฌ๋ํธ๋ ๋ณด์ด์ง ์์ต๋๋ค. ํด์ปค ๋ด์ค API์์ ์๋ต์ ๋ฐ์ผ๋ฉด result
๊ฐ ํ์๋๊ณ isLoading
์ํ๋ false
๋ก ์ค์ ๋๋ฉฐ Loading ์ปดํฌ๋ํธ๋ ์ฌ๋ผ์ง๊ณ "More" ๋ฒํผ์ด ๋ํ๋ฉ๋๋ค. ๋ฐ์ดํฐ๋ฅผ ํ๋ฒ ๋ ๊ฐ์ ธ์ค๋ฉด, ์ด ๋ฒํผ์ด ์ฌ๋ผ์ง๊ณ ๋ค์ Loading ์ปดํฌ๋ํธ๊ฐ ๋ณด์
๋๋ค.
- "Loading ..." ํ ์คํธ ๋์ Font Awesome ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด ๋ก๋ฉ ์์ด์ฝ์ ๋ณด์ฌ์ค๋๋ค.
๊ณ ์ฐจ ์ปดํฌ๋ํธ(Higher order components, ์ดํ HOCs)๋ ๋ฆฌ์กํธ์ ๊ณ ๊ธ ์ฃผ์ ์ ๋๋ค. HOCs๋ ๊ณ ์ฐจ ํจ์์ ๋์ผํ ๊ฐ๋ ์ ๋๋ค. HOCs๋ ํจ์์ ๊ฐ์ด ์ ๋ ฅ๊ณผ ์ ํ์ ์ธ์๋ฅผ ๋ฐ์ ์ปดํฌ๋ํธ๋ฅผ ์ถ๋ ฅํฉ๋๋ค. ๋ฐํ๋ ์ปดํฌ๋ํธ๋ ์ ๋ ฅ๋ ์ปดํฌ๋ํธ๋ก JSX์์ ์ฌ์ฉ๋ฉ๋๋ค.
HOCs๋ ์ฌ๋ฌ ์ฌ์ฉ ์ฌ๋ก๊ฐ ์์ต๋๋ค. ํ๋กํผํฐ ์ค๋น, ์ํ ๊ด๋ฆฌ, ์ปดํฌ๋ํธ ํ์ ๋ณ๊ฒฝ์ ํ ์ ์์ต๋๋ค. ๋๋ ์กฐ๊ฑด๋ถ ๋ ๋๋ง์์ HOCs๋ฅผ ํฌํผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด List ์ปดํฌ๋ํธ๊ฐ ์๋๋ฐ, ์ด ์ปดํฌ๋ํธ๋ ๋ฆฌ์คํธ๋ฅผ ๋ฐํํ๊ฑฐ๋ ๋ฆฌ์คํธ๊ฐ ์๊ฑฐ๋ null์ผ ๊ฒฝ์ฐ ์๋ฌด๊ฒ๋ ๋ฐํํ์ง ์๋๋ค๊ณ ํฉ๋๋ค. HOCs๋ ๋ฆฌ์คํธ๊ฐ ์์ ๋ ๋ ๋๋ง ํ์ง ๋ชปํ๊ฒ ํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด List ์ปดํฌ๋ํธ๋ ๋ฆฌ์คํธ๊ฐ ์์ ๊ฒฝ์ฐ๋ฅผ ์ฒดํฌํ์ง ์์๋ ๋ฉ๋๋ค. ๊ทธ์ List ์ปดํฌ๋ํธ๋ ๋ฆฌ์คํธ๋ฅผ ๋ ๋๋ง ํ๋ ์ญํ ์๋ง ์ถฉ์คํ๋ฉด ๋ฉ๋๋ค.
์ปดํฌ๋ํธ๋ฅผ ์ ๋ ฅ์ผ๋ก ์ฌ์ฉํ๊ณ , ์ปดํฌ๋ํธ๋ฅผ ๋ฐํํ๋ ๊ฐ๋จํ HOCs๋ฅผ ๋ง๋ค์ด๋ด ์๋ค. src/App.js ํ์ผ์์ ์์ฑํ๋ฉด ๋ฉ๋๋ค.
{title="src/App.js",lang=javascript}
function withFoo(Component) {
return function(props) {
return <Component { ...props } />;
}
}
๊ด๋ก๋ก HOCs ๋ณ์๋ช
์์ with
์ ๋์ด๋ฅผ ๋ถ์
๋๋ค. ES6 ํ์ดํ ํจ์๋ก HOCs๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ๋ง๋ค๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
const withFoo = (Component) => (props) =>
<Component { ...props } />
์ด ์์ ์์ ์ ๋ ฅ ์ปดํฌ๋ํธ๋ ์ถ๋ ฅ ์ปดํฌ๋ํธ์ ๋์ผํ๊ฒ ์ ์ง๋ฉ๋๋ค. ์๋ฌด ์ผ๋ ์ผ์ด๋์ง ์์ต๋๋ค. ๋์ผํ ์ปดํฌ๋ํธ ์ธ์คํด์ค๋ฅผ ๋ ๋๋งํ๊ณ ๋ชจ๋ props๋ฅผ ์ถ๋ ฅ ์ปดํฌ๋ํธ๋ก ์ ๋ฌํฉ๋๋ค. ๊ทธ๋ฌ๋ ์ ์ฉํ์ง ์์ต๋๋ค. ์ถ๋ ฅ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ ํด๋ด ์๋ค. ์ถ๋ ฅ ์ปดํฌ๋ํธ๋ ๋ก๋ฉ ์ํ๊ฐ true์ผ ๋ Loading ์ปดํฌ๋ํธ๋ฅผ ๋ณด์ฌ์ฃผ๊ณ , ๊ทธ๋ ์ง ์์ ๋ ์ ๋ ฅ ์ปดํฌ๋ํธ๋ฅผ ๋ณด์ฌ์ค์ผ ํฉ๋๋ค. ์กฐ๊ฑด๋ถ ๋ ๋๋ง์์ HOCs์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
{title="src/App.js",lang=javascript}
# leanpub-start-insert
const withLoading = (Component) => (props) =>
props.isLoading
? <Loading />
: <Component { ...props } />
# leanpub-end-insert
๋ก๋ฉ ํ๋กํผํฐ์ ๋ฐ๋ผ ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ์ ์ฉํ ์ ์์ต๋๋ค. ์ด ํจ์๋ Loading ์ปดํฌ๋ํธ ๋๋ ์ ๋ ฅ ์ปดํฌ๋ํธ๋ฅผ ๋ฐํํฉ๋๋ค.
์ผ๋ฐ์ ์ผ๋ก ์ ์์ ์ฒ๋ผ props์ ๊ฐ์ด ์ปดํฌ๋ํธ๋ฅผ ์ ๋ ฅ๊ณผ ๊ฐ์ด ์ ํํ๋ ๊ฒ์ด ๋งค์ฐ ํจ๊ณผ์ ์ ๋๋ค. ์๋ ์ฝ๋๋ฅผ ๋ณด๊ณ ์๋ก ์ฐจ์ด์ ์ ๋น๊ตํด๋ณด์ธ์.
{title="Code Playground",lang="javascript"}
// props๋ฅผ ์ ๋ฌํ๊ธฐ ์ ๊ตฌ์กฐ ํด์ฒด๋ฅผ ํด์ผํฉ๋๋ค.
const { foo, bar } = props;
<SomeComponent foo={foo} bar={bar} />
// ๊ฐ์ฒด ์ ๊ฐ ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํด ๋ชจ๋ ๊ฐ์ฒด ํ๋กํผํฐ๋ฅผ ์ ๋ฌํฉ๋๋ค.
<SomeComponent { ...props } />
isLoading
๋ฅผ ํฌํจํ ๋ชจ๋ props ๊ฐ์ฒด๋ฅผ ์ ๊ฐํด์ ์
๋ ฅ ์ปดํฌ๋ํธ์ ์ ๋ฌํ์ต๋๋ค. ๊ทธ๋ฌ๋ ์
๋ ฅ ์ปดํฌ๋ํธ๋ isLoading
ํ๋กํผํฐ๋ฅผ ์ ๊ฒฝ ์ฐ์ง ์์ต๋๋ค. ์ด๋ฅผ ํผํ๊ธฐ ์ํด ES6 ๊ตฌ์กฐ ํด์ฒด๋ฅผ ์ฌ์ฉํฉ๋๋ค.
{title="src/App.js",lang=javascript}
# leanpub-start-insert
const withLoading = (Component) => ({ isLoading, ...rest }) =>
isLoading
? <Loading />
: <Component { ...rest } />
# leanpub-end-insert
๊ฐ์ฒด์์ ํ๋กํผํฐ ํ๋๋ฅผ ๊ฐ์ ธ์ค์ง๋ง ๋๋จธ์ง ๊ฐ์ฒด๋ ์ ์ง๋ฉ๋๋ค. ๋ํ ์ฌ๋ฌ ํ๋กํผํฐ์ ํจ๊ป ์ฌ์ฉ๋ฉ๋๋ค. ์ด๋ฏธ ์ฌ๋ฌ๋ถ๋ค์ [MDN] ๊ตฌ์กฐ ํด์ฒด ํ ๋น ๋ถ๋ถ์ ์ฝ์์ ๊ฒ๋๋ค.
์ด์ JSX์์ HOCs๋ฅผ ์ฌ์ฉํด๋ด ์๋ค. ์ฐ๋ฆฌ๋ "More" ๋ฒํผ ๋๋ Loading ์ปดํฌ๋ํธ ๋ ์ค ํ๋๋ฅผ ๋ณด์ฌ์ค ๊ฒ๋๋ค. Loading ์ปดํฌ๋ํธ๋ HOCs์ ์บก์ํ๋์ด์์ง๋ง ์ ๋ ฅ ์ปดํฌ๋ํธ๋ ๋น ์ ธ์์ต๋๋ค. ์ ๋ ฅ ์ปดํฌ๋ํธ๋ฅผ Button ์ปดํฌ๋ํธ๋ก, ์ถ๋ ฅ ์ปดํฌ๋ํธ๋ ButtonWithLoading ์ปดํฌ๋ํธ๋ก ์ถ๊ฐํฉ์๋ค.
{title="src/App.js",lang=javascript}
const Button = ({ onClick, className = '', children }) =>
<button
onClick={onClick}
className={className}
type="button"
>
{children}
</button>
const Loading = () =>
<div>Loading ...</div>
const withLoading = (Component) => ({ isLoading, ...rest }) =>
isLoading
? <Loading />
: <Component { ...rest } />
# leanpub-start-insert
const ButtonWithLoading = withLoading(Button);
# leanpub-end-insert
๋ง์ง๋ง์ผ๋ก ButtonWithLoading ์ปดํฌ๋ํธ๊ฐ loading ์ํ๋ฅผ ํ๋กํผํฐ๋ก ๊ฐ์ ธ์ค๊ฒ ๋ง๋ญ์๋ค. HOCs๊ฐ loading ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ๋ ๋์, ๋๋จธ์ง props ์ ์ฒด๋ Button ์ปดํฌ๋ํธ๋ก ์ ๋ฌ๋ฉ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
render() {
...
return (
<div className="page">
...
<div className="interactions">
# leanpub-start-insert
<ButtonWithLoading
isLoading={isLoading}
onClick={() => this.fetchSearchTopStories(searchKey, page + 1)}>
More
</ButtonWithLoading>
# leanpub-end-insert
</div>
</div>
);
}
}
ํ ์คํธ๋ฅผ ์คํํด๋ด ์๋ค. App ์ปดํฌ๋ํธ์ ์ค๋ ์ท ํ ์คํธ๊ฐ ์คํจํจ์ ์ ์ ์์ต๋๋ค. ์ปค๋งจ๋ ๋ผ์ธ์์ diff๊ฐ ์๋์ ๊ฐ์ด ๋ณด์ผ ๊ฒ์ ๋๋ค.
{title="Command Line",lang="text"}
- <button
- className=""
- onClick={[Function]}
- type="button"
- >
- More
- </button>
+ <div>
+ Loading ...
+ </div>
์๋ชป๋์๋ค๊ณ ํ๋จ๋๋ฉด ์ปดํฌ๋ํธ๋ฅผ ์์ ํ๊ฑฐ๋, ์ค๋ ์ท์ ์๋ฝํ ์ ์์ต๋๋ค. Loading ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์๊ธฐ ๋๋ฌธ์ ์ปค๋งจ๋ ๋ผ์ธ์์ ๋ณ๊ฒฝ๋ ์ค๋ ์ท ํ ์คํธ๋ฅผ ์๋ฝํ ์ ์์ต๋๋ค.
๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ ๋ฆฌ์กํธ ๊ณ ๊ธ ํ ํฌ๋์ ๋๋ค. ๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ ์ปดํฌ๋ํธ์ ์ฌ์ฌ์ฉ์ฑ ํฅ์, ๊ฐํ๋ ์ถ์ํ, ์ปดํฌ๋ํธ์ ์กฐํฉ๊ณผ props, ์ํ ๋ฐ ๋ทฐ ์กฐ์ ๋ฑ ์ฌ๋ฌ ๋ชฉ์ ์ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์ง๊ธ ๋ชจ๋ ๊ฒ์ ์ดํดํ์ง ๋ชปํ๋ค๊ณ ํด๋ ๊ฑฑ์ ํ์ง ์๊ธธ ๋ฐ๋๋๋ค. ์ต์ํด์ง๊ธฐ๊น์ง ๊ฝค ์๊ฐ์ด ๊ฑธ๋ฆด ๊ฒ์ ๋๋ค.
์ ์๊ฐ ์ด ๊ณ ์ฐจ ์ปดํฌ๋ํธ ์์ํ๊ธฐ ๊ธ์ ์ฝ์ด๋ณด๊ธธ ๋ฐ๋๋๋ค. ๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ฅผ ๋ฐฐ์ฐ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํ๊ณ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ๊ณผ ์กฐ๊ฑด๋ถ ๋ ๋๋ง ๋ด ๊ณ ์ฐจ ์ปดํฌ๋ํธ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ ๋ฐฉ๋ฒ์ ์๊ฐํ์ต๋๋ค.
- HOCs๋ฅผ ์คํํด๋ด ๋๋ค.
- ๊ทธ ์ธ ๋ ๋ง์ HOCs ์ฌ์ฉ ์ฌ๋ก๋ฅผ ์๊ฐํด๋ด
์๋ค.
- ์ง์ HOCs๋ฅผ ๊ตฌํํด๋ด ์๋ค.
์ง๊ธ๊น์ง ํด๋ผ์ด์ธํธ์ ์๋ฒ ๋ชจ๋ ๊ฒ์ ์ธํฐ๋ ์ ์ ๋ค๋ค๋ดค์ต๋๋ค. ์ด๋ฒ ์ ์์๋ Table ์ปดํฌ๋ํธ๋ก ํ์ธต ์ฌํ๋ ์ธํฐ๋ ์ ์ ๋ค๋ฃจ๊ฒ ์ต๋๋ค. Table ์ปดํฌ๋ํธ์ ๊ฐ ์ฒซ ํ์ ๋ผ๋ฒจ์ ์ถ๊ฐํ๊ณ ์ด๋ฅผ ํ์ฉํด ์ ๋ ฌ ๊ธฐ๋ฅ์ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
์ง์ ์ ๋ ฌ ํจ์๋ฅผ ๋ง๋ค ์ ์์ง๋ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ํธํด์ง๋๋ค. ์ด๋ฒ ์ ์์๋ ๊ฐ์ฅ ๋ํ์ ์ธ ์ ํธ๋ฆฌํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ ๋ก๋์ฌ(Lodash)๋ฅผ ์ฌ์ฉํ๊ฒ ์ต๋๋ค. ๋ฌผ๋ก ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด๋ ๊ด์ฐฎ์ต๋๋ค. ๋จผ์ ๋ก๋์ฌ๋ฅผ ์ค์นํ๊ฒ ์ต๋๋ค.
{title="Command Line",lang="text"}
npm install lodash
src/App.js ํ์ผ์์ ๋ก๋์ฌ์ ์ ๋ ฌ ํจ์๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
{title="src/App.js",lang=javascript}
import React, { Component } from 'react';
import axios from 'axios';
# leanpub-start-insert
import { sortBy } from 'lodash';
# leanpub-end-insert
import './App.css';
Table ์ปดํฌ๋ํธ์๋ title, author, comments, points ์ด์ด ์์ต๋๋ค. ์ ๋ ฌ ํจ์๋ ํน์ ํ๋กํผํฐ์ ๋ฐ๋ผ ์ ๋ ฌ๋ ๋ฆฌ์คํธ๋ฅผ ๋ฐํํฉ๋๋ค. ๋ํ ์ ๋ ฌ๋์ง ์๋ ๋ฆฌ์คํธ๋ฅผ ๋ฐํํ๋ ๊ธฐ๋ณธ ์ ๋ ฌ ํจ์๊ฐ ํ์ํฉ๋๋ค. ๊ธฐ๋ณธ ์ ๋ ฌ ํจ์๋ ์ด๊ธฐ ์ํ ๊ฐ์ด ๋ฉ๋๋ค.
{title="src/App.js",lang=javascript}
...
# leanpub-start-insert
const SORTS = {
NONE: list => list,
TITLE: list => sortBy(list, 'title'),
AUTHOR: list => sortBy(list, 'author'),
COMMENTS: list => sortBy(list, 'num_comments').reverse(),
POINTS: list => sortBy(list, 'points').reverse(),
};
# leanpub-end-insert
class App extends Component {
...
}
...
SORTS ํจ์ ์ค COMMENTS
์ POINTS
ํจ์๋ ๋ด๋ฆผ์ฐจ์ ๋ฐฐ์ด์
๋๋ค. COMMENTS
ํจ์๋ ๋๊ธ ์๊ฐ ๋์ ์, POINTS
ํจ์๋ ์ ์๊ฐ ๋์ ์์ผ๋ก ์ ๋ ฌ๋ฉ๋๋ค.
SORTS
๊ฐ์ฒด๋ก ์ ๋ ฌ ํจ์๋ฅผ ์ฌ์ฉํฉ๋๋ค.
App ์ปดํฌ๋ํธ์ ์ ๋ ฌ ์ํ์ธ sortKey
๋ฅผ ์ ์ฅํฉ๋๋ค. ์ด๊ธฐ ์ ๋ ฌ ์ํ ๊ฐ์ 'NONE'
์ด๋ฉฐ, ์ ๋ ฌ๋์ง ์์ ๋ฆฌ์คํธ๋ฅผ ๋ฐํํฉ๋๋ค.
{title="src/App.js",lang=javascript}
this.state = {
results: null,
searchKey: '',
searchTerm: DEFAULT_QUERY,
error: null,
isLoading: false,
# leanpub-start-insert
sortKey: 'NONE',
# leanpub-end-insert
};
sortKey
์ํ ๊ฐ์ 'AUTHOR'
๋ก ๋ฐ๊พธ๊ฒ ๋๋ฉด, SORTS
๊ฐ์ฒด์ AUTHOR
ํจ์๋ฅผ ํตํด ๋ฆฌ์คํธ๋ฅผ ์ ๋ ฌํฉ๋๋ค.
๋ค์ App ์ปดํฌ๋ํธ์์ ์๋ก์ด ํด๋์ค ๋ฉ์๋๋ฅผ ๋ง๋ค๊ฒ ์ต๋๋ค. ์ด ๋ฉ์๋๋ sortKey
๋ก ์ ๋ ฌ ํจ์๋ฅผ ์ฐพ์ ๋ฆฌ์คํธ์ ์ ์ฉํฉ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
constructor(props) {
...
this.needsToSearchTopStories = this.needsToSearchTopStories.bind(this);
this.setSearchTopStories = this.setSearchTopStories.bind(this);
this.fetchSearchTopStories = this.fetchSearchTopStories.bind(this);
this.onSearchSubmit = this.onSearchSubmit.bind(this);
this.onSearchChange = this.onSearchChange.bind(this);
this.onDismiss = this.onDismiss.bind(this);
# leanpub-start-insert
this.onSort = this.onSort.bind(this);
# leanpub-end-insert
}
# leanpub-start-insert
onSort(sortKey) {
this.setState({ sortKey });
}
# leanpub-end-insert
...
}
๊ทธ๋ฆฌ๊ณ Table ์ปดํฌ๋ํธ์ ๋ฉ์๋์ sortKey
์ ์ ๋ฌํฉ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
render() {
const {
searchTerm,
results,
searchKey,
error,
isLoading,
# leanpub-start-insert
sortKey
# leanpub-end-insert
} = this.state;
...
return (
<div className="page">
...
<Table
list={list}
# leanpub-start-insert
sortKey={sortKey}
onSort={this.onSort}
# leanpub-end-insert
onDismiss={this.onDismiss}
/>
...
</div>
);
}
}
Table ์ปดํฌ๋ํธ๋ ๋ฆฌ์คํธ ์ ๋ ฌํฉ๋๋ค. sortKey
๋ก SORT
ํจ์ ์ค ํ๋๋ฅผ ์ทจํด ๋ฆฌ์คํธ๋ฅผ ์
๋ ฅ์ผ๋ก ์ ๋ฌํ๊ณ , ์ ๋ ฌ๋ ๋ฆฌ์คํธ๋ฅผ ๋ฐํํฉ๋๋ค.
{title="src/App.js",lang=javascript}
# leanpub-start-insert
const Table = ({
list,
sortKey,
onSort,
onDismiss
}) =>
# leanpub-end-insert
<div className="table">
# leanpub-start-insert
{SORTS[sortKey](list).map(item =>
# leanpub-end-insert
<div key={item.objectID} className="table-row">
...
</div>
)}
</div>
์์ ๋งํ๋ฏ์ด ๋ฆฌ์คํธ๋ ํน์ ํจ์์ ์ํด ์ ๋ ฌ๋ฉ๋๋ค. ๊ธฐ๋ณธ ์ ๋ ฌ ๊ฐ์ 'NONE'
์ด๊ธฐ ๋๋ฌธ์ ๋ฆฌ์คํธ๋ ์ ๋ ฌ๋์ง ์์์ต๋๋ค. ์์ง sortKey
๋ฅผ ๋ฐ๊ฟ์ฃผ๋ onSort()
๋ฉ์๋๋ฅผ ์คํํ์ง ์์์ต๋๋ค. ๋ค์์ผ๋ก Sort ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด ์ด ํค๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ๋ ฌํ ์ ์๊ฒ ๋ง๋ค๊ฒ ์ต๋๋ค. ๋จผ์ Table ์์ Sort ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
const Table = ({
list,
sortKey,
onSort,
onDismiss
}) =>
<div className="table">
# leanpub-start-insert
<div className="table-header">
<span style={{ width: '40%' }}>
<Sort
sortKey={'TITLE'}
onSort={onSort}
>
Title
</Sort>
</span>
<span style={{ width: '30%' }}>
<Sort
sortKey={'AUTHOR'}
onSort={onSort}
>
Author
</Sort>
</span>
<span style={{ width: '10%' }}>
<Sort
sortKey={'COMMENTS'}
onSort={onSort}
>
Comments
</Sort>
</span>
<span style={{ width: '10%' }}>
<Sort
sortKey={'POINTS'}
onSort={onSort}
>
Points
</Sort>
</span>
<span style={{ width: '10%' }}>
Archive
</span>
</div>
# leanpub-end-insert
{SORTS[sortKey](list).map(item =>
...
)}
</div>
๋ค์์ผ๋ก Sort ์ปดํฌ๋ํธ๋ฅผ ์ ์ํ๊ฒ ์ต๋๋ค. Sort ์ปดํฌ๋ํธ๋ ํน์ sortKey
์ onSort()
๋ฉ์๋๋ฅผ ๊ฐ๊ณ ์์ต๋๋ค. ๋ด๋ถ์์ ํน์ ํค๋ฅผ ์ค์ ํ๊ธฐ ์ํด sortKey
๋ฉ์๋๋ฅผ ํธ์ถํฉ๋๋ค.
{title="src/App.js",lang=javascript}
const Sort = ({ sortKey, onSort, children }) =>
<Button onClick={() => onSort(sortKey)}>
{children}
</Button>
๋ณด์๋ค์ํผ Sort ์ปดํฌ๋ํธ๋ ๊ณตํต์ธ Button ์ปดํฌ๋ํธ๋ฅผ ๋ค์ ์ฌ์ฉํ์ต๋๋ค. onSort()
๋ฉ์๋์์ ๋ฒํผ ํด๋ฆญ์ ์ ๋ฌ๋ ๊ฐ๊ฐ์ sortKey
๊ฐ ์ค์ ๋ฉ๋๋ค. ์ด ํค๋๋ฅผ ํด๋ฆญํ ๋ ๋ฆฌ์คํธ๊ฐ ์ ๋ ฌ๋๋์ง ํ์ธํด๋ด
์๋ค.
์ข๋ ์์๊ฒ ๊พธ๋ฉฐ์ผํ ๋ถ๋ถ์ด ์์ต๋๋ค. ์ง๊ธ์๋ ํค๋ ๋ฒํผ์ด ๋ณด๊ธฐ ์ข์ง ์์ต๋๋ค. Sort ์ปดํฌ๋ํธ ์์ className
์ ์ง์ ํ๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
const Sort = ({ sortKey, onSort, children }) =>
# leanpub-start-insert
<Button
onClick={() => onSort(sortKey)}
className="button-inline"
>
# leanpub-end-insert
{children}
</Button>
์ด์ ๋ฉ์ง๊ฒ ๋ณด์ด๋ค์. ๋ค์์ผ๋ก ๋ด๋ฆผ์ฐจ์ ์ ๋ ฌ์ ๊ตฌํํ๊ฒ ์ต๋๋ค. Sort ์ปดํฌ๋ํธ๋ฅผ ๋ ๋ฒ ํด๋ฆญํ๋ฉด ๋ฆฌ์คํธ๊ฐ ๋ด๋ฆผ์ฐจ์์ผ๋ก ์ ๋ ฌ๋ฉ๋๋ค. ๋จผ์ ๋ถ๋ฆฌ์ธ ๊ฐ์ ์ฌ์ฉํ๋ isSortReverse
์ํ๋ฅผ ๋ง๋ค๊ฒ ์ต๋๋ค. isSortReverse
๊ฐ true
์ผ ๊ฒฝ์ฐ ๋ด๋ฆผ์ฐจ์์ผ๋ก ์ ๋ ฌ๋๋ฉฐ, ๋ฐ๋๋ก false
์ผ ๊ฒฝ์ฐ ์ ๋ ฌ๋์ง ์์ต๋๋ค.
{title="src/App.js",lang=javascript}
this.state = {
results: null,
searchKey: '',
searchTerm: DEFAULT_QUERY,
error: null,
isLoading: false,
sortKey: 'NONE',
# leanpub-start-insert
isSortReverse: false,
# leanpub-end-insert
};
onSort()
๋ฉ์๋์์ ๋ฆฌ์คํธ๊ฐ ๋ด๋ฆผ์ฐจ์์ผ๋ก ์ ๋ ฌ๋์๋์ง ํ์ธํ๋๋ก ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค. ์กฐ๊ฑด๋ฌธ์ ๋ง๋ค์ด sortKey
์ํ๊ฐ์ด ์
๋ ฅ๋ sortKey
์ ๊ฐ์ผ๋ฉฐ isSortReverse
๊ฐ true
๋ก ์ค์ ๋์ง ์์ ๊ฒฝ์ฐ ์ ๋ ฌํ๊ฒ ๋ง๋ค๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
onSort(sortKey) {
# leanpub-start-insert
const isSortReverse = this.state.sortKey === sortKey && !this.state.isSortReverse;
this.setState({ sortKey, isSortReverse });
# leanpub-end-insert
}
Table ์ปดํฌ๋ํธ์ isSortReverse props๋ฅผ ์ ๋ฌํฉ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
render() {
const {
searchTerm,
results,
searchKey,
error,
isLoading,
sortKey,
# leanpub-start-insert
isSortReverse
# leanpub-end-insert
} = this.state;
...
return (
<div className="page">
...
<Table
list={list}
sortKey={sortKey}
# leanpub-start-insert
isSortReverse={isSortReverse}
# leanpub-end-insert
onSort={this.onSort}
onDismiss={this.onDismiss}
/>
...
</div>
);
}
}
๋ค์ Table ์ปดํฌ๋ํธ์ ๋ฐ์ดํฐ๋ฅผ ๊ณ์ฐํ๋๋ก ํ์ดํ ํจ์ ๋ธ๋ก์ ์ถ๊ฐํ๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
# leanpub-start-insert
const Table = ({
list,
sortKey,
isSortReverse,
onSort,
onDismiss
}) => {
const sortedList = SORTS[sortKey](list);
const reverseSortedList = isSortReverse
? sortedList.reverse()
: sortedList;
return(
# leanpub-end-insert
<div className="table">
<div className="table-header">
...
</div>
# leanpub-start-insert
{reverseSortedList.map(item =>
# leanpub-end-insert
...
)}
</div>
# leanpub-start-insert
);
}
# leanpub-end-insert
ํค๋๋ฅผ ํด๋ฆญํด ๋ฆฌ์คํธ๊ฐ ๋ด๋ฆผ์ฐจ์์ผ๋ก ์ ์ ๋ ฌ๋์๋์ง ํ์ธํด๋ณด์ธ์.
๋ง์ง๋ง์ผ๋ก ์ฌ์ฉ์ ๊ฒฝํ ํฅ์์ ์ํด ๊ฐ์ ํ ์ฌํญ์ด ๋จ์์ต๋๋ค. ์ง๊ธ ์ด๋ค ์ด์ ๊ธฐ์ค์ผ๋ก ์ ๋ ฌํ๋์ง ์ ์๊ฐ ์์ต๋๋ค. ์๊ฐ์ ์ธ ํจ๊ณผ๋ฅผ ์ถ๊ฐํด๋ณด๊ฒ ์ต๋๋ค.
๊ฐ Sort ์ปดํฌ๋ํธ๋ ํน์ sortKey
๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ์ด ํค๋ก ํ์ฌ ์ ํ๋ ์ ๋ ฌ์ ์๋ณํ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ปดํฌ๋ํธ ๋ด๋ถ ์ํ sortKey
๋ฅผ ์ ํ๋ ์ ๋ ฌ ํค๋ก Sort ์ปดํฌ๋ํธ๋ก ์ ๋ฌํ๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
const Table = ({
list,
sortKey,
isSortReverse,
onSort,
onDismiss
}) => {
const sortedList = SORTS[sortKey](list);
const reverseSortedList = isSortReverse
? sortedList.reverse()
: sortedList;
return(
<div className="table">
<div className="table-header">
<span style={{ width: '40%' }}>
<Sort
sortKey={'TITLE'}
onSort={onSort}
# leanpub-start-insert
activeSortKey={sortKey}
# leanpub-end-insert
>
Title
</Sort>
</span>
<span style={{ width: '30%' }}>
<Sort
sortKey={'AUTHOR'}
onSort={onSort}
# leanpub-start-insert
activeSortKey={sortKey}
# leanpub-end-insert
>
Author
</Sort>
</span>
<span style={{ width: '10%' }}>
<Sort
sortKey={'COMMENTS'}
onSort={onSort}
# leanpub-start-insert
activeSortKey={sortKey}
# leanpub-end-insert
>
Comments
</Sort>
</span>
<span style={{ width: '10%' }}>
<Sort
sortKey={'POINTS'}
onSort={onSort}
# leanpub-start-insert
activeSortKey={sortKey}
# leanpub-end-insert
>
Points
</Sort>
</span>
<span style={{ width: '10%' }}>
Archive
</span>
</div>
{reverseSortedList.map(item =>
...
)}
</div>
);
}
์ด์ Sort ์ปดํฌ๋ํธ์์ sortKey
์ activeSortKey
๋ฅผ ๊ทผ๊ฑฐ๋ก ์ ๋ ฌ์ด ํ์ฑํ๋์๋์ง ์ ์ ์์ต๋๋ค. ์๊ฐ์ ์ธ ํจ๊ณผ๋ฅผ ์ฃผ๊ธฐ ์ํด Sort ์ปดํฌ๋ํธ์ className
์์ฑ์ ์ง์ ํฉ๋๋ค.
{title="src/App.js",lang=javascript}
# leanpub-start-insert
const Sort = ({
sortKey,
activeSortKey,
onSort,
children
}) => {
const sortClass = ['button-inline'];
if (sortKey === activeSortKey) {
sortClass.push('button-active');
}
return (
<Button
onClick={() => onSort(sortKey)}
className={sortClass.join(' ')}
>
{children}
</Button>
);
}
# leanpub-end-insert
sortClass
๋ฅผ ์ ์ํ๋ ๋ฐฉ๋ฒ์ด ๊ฝค ์ข์๋ณด์ด์ง ์์ต๋๋ค. classnames ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์ด๋ฅผ ํด๊ฒฐํด๋ณด๊ฒ ์ต๋๋ค.
classnames ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํฉ๋๋ค.
{title="Command Line",lang="text"}
npm install classnames
๊ทธ๋ฆฌ๊ณ src/App.js ํ์ผ์์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
{title="src/App.js",lang=javascript}
import React, { Component } from 'react';
import axios from 'axios';
import { sortBy } from 'lodash';
# leanpub-start-insert
import classNames from 'classnames';
# leanpub-end-insert
import './App.css';
classname ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด className
์ปดํฌ๋ํธ๋ฅผ ์กฐ๊ฑด๋ถ ํด๋์ค๋ก ์ ์ํ๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
const Sort = ({
sortKey,
activeSortKey,
onSort,
children
}) => {
# leanpub-start-insert
const sortClass = classNames(
'button-inline',
{ 'button-active': sortKey === activeSortKey }
);
# leanpub-end-insert
return (
# leanpub-start-insert
<Button
onClick={() => onSort(sortKey)}
className={sortClass}
>
# leanpub-end-insert
{children}
</Button>
);
}
๋ค์ ํ
์คํธ๋ฅผ ์คํํด๋ด
์๋ค. ์ค๋
์ท ํ
์คํธ ๋ฟ๋ง ์๋๋ผ Table ์ปดํฌ๋ํธ ๋จ์ ํ
์คํธ๊ฐ ์คํจํ๋์ง๋ฅผ ํ์ธํฉ์๋ค. ์ปดํฌ๋ํธ๋ฅผ ๋ค์ ๋ณ๊ฒฝํ๊ธฐ ๋๋ฌธ์, ์ค๋
์ท ํ
์คํธ๋ฅผ ์๋ฝํ ์ ์์ต๋๋ค๋ง, ๋จ์ ํ
์คํธ๋ ์์ ํด์ผ ํฉ๋๋ค. src/App.test.js ํ์ผ์์ ์๋์ ๊ฐ์ด Table ์ปดํฌ๋ํธ ๋ถ๋ถ์ sortKey
๊ณผ isSortReverse
๋ถ๋ฆฌ์ธ์ ์ถ๊ฐํฉ๋๋ค.
{title="src/App.test.js",lang=javascript}
...
describe('Table', () => {
const props = {
list: [
{ title: '1', author: '1', num_comments: 1, points: 2, objectID: 'y' },
{ title: '2', author: '2', num_comments: 1, points: 2, objectID: 'z' },
],
# leanpub-start-insert
sortKey: 'TITLE',
isSortReverse: false,
# leanpub-end-insert
};
...
});
Table ์ปดํฌ๋ํธ์ ํ์ฅ๋ prop๊ฐ ์๊ธฐ ๋๋ฌธ์ ํ๋ฒ ๋ Table ์ปดํฌ๋ํธ ์ค๋ ์ท ํ ์คํธ ์คํจ๋ฅผ ์น์ธํด์ผํ ์๋ ์์ต๋๋ค.
๋๋์ด ์ด๋ ค์ด ์ ๋ ฌ ๊ธฐ๋ฅ์ ๋ชจ๋ ์์ฑํ์ต๋๋ค.
- Font Awesome ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด ์ ๋ ฌ ์์ด์ฝ์ ์ถ๊ฐํ๊ธฐ
- ํ์ดํ ์ ๋๋ ํ์ดํ ์๋ ์์ด์ฝ์ ์ ๋ ฌ ๋ ์ด๋ธ ์ ๋ชฉ ์์ ์ถ๊ฐ
{pagebreak}
5์ฅ์์๋ ๋ฆฌ์กํธ์์ ์ปดํฌ๋ํธ ๊ณ ๊ธ ํ ํฌ๋์ ๋ฐฐ์ ์ต๋๋ค. ์ง๊ธ๊น์ง ํ์ตํ ๋ด์ฉ์ ์ ๋ฆฌํด๋ด ์๋ค.
- ๋ฆฌ์กํธ
- ref ์์ฑ์ผ๋ก DOM ๋ ธ๋๋ฅผ ์ ๊ทผํ ์ ์์ต๋๋ค.
- ๊ณ ์ฐจ ์ปดํฌ๋ํธ๋ ๊ณ ๊ธ ์ปดํฌ๋ํธ์ ์ํ ์ผ๋ฐ์ ์ธ ํ ํฌ๋์ ๋๋ค.
- ๋ฆฌ์กํธ์์ ์ด๋ ค์ด ์ธํฐ๋ ์ ์ ๊ตฌํํ์ต๋๋ค.
- ์กฐ๊ฑด๋ถ ํด๋์ค๋ช
์ ์ํด
classnames
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
- ES6
- ๊ตฌ์กฐ ํด์ฒด๋ก ๊ฐ์ฒด์ ๋ฐฐ์ด์ ๋ถ๋ฆฌํ์ต๋๋ค.
์ค์ต ์ฝ๋๋ ๊ณต์ ๊นํ๋ธ ๋ฆฌํผ์งํ ๋ฆฌ์์ ํ์ธํ ์ ์์ต๋๋ค.