[React] 중요 내용 정리
자식 컴포넌트가 부모한테 값 전달하기
배열 데이터 삽입하기
자식 컴포넌트가 부모한테 값 전달하기 위해서는 App 컴포넌트 내부에서 handleCreate라는 메소드를 만든다. 만든 메소드를 자식컴포넌트에게 <PhoneForm onCreate={this.handleCreate} />
props로 전달해주고, props로 전달한 함수 onCreate={...}
를 호출시켜서 data 값이 App.js 로 들어간다.
1// App.js
2import React, { Component } from "react";
3import "./App.css";
4import PhoneForm from "./components/PhoneForm";
5
6class App extends Component {
7 id = 0; // id는 렌더링 되는 값이 아니기 때문에 state 값 안에 넣어줄 필요 없음
8
9 state = {
10 information: []
11 };
12 handleCreate = data => {
13 const { information } = this.state; // 비구조 할당
14
15 // 불변성 유지. 기존것 기반으로 값 주입. concat
16 this.setState({
17 information: information.concat({
18 ...data,
19 id: this.id++
20
21 /* 또는 다른 방법1:
22 name: data.name,
23 phone: data.phone,
24 id: this.id++
25
26 방법2:
27 .concat(Object.assign( {}, data, {
28 id: this.id++
29 }))
30
31 */
32 })
33 // 비구조할당 안쓰면 this.state.information.concat(data)가 된다.
34 });
35 };
36
37 render() {
38 return (
39 <div>
40 <PhoneForm onCreate={this.handleCreate} />
41 {JSON.stringify(this.state.information)}
42 </div>
43 );
44 }
45}
46
47export default App;
1// PhoneForm.js
2import React, { Component } from "react";
3
4class PhoneForm extends Component {
5 state = {
6 name: "",
7 phone: ""
8 };
9
10 handleChange = e => {
11 this.setState({
12 [e.target.name]: e.target.value
13 });
14 };
15
16 handleSumbit = e => {
17 e.preventDefault();
18 this.props.onCreate({
19 name: this.state.name,
20 phone: this.state.phone
21 });
22 this.setState({
23 name: "",
24 phone: ""
25 });
26 };
27
28 render() {
29 return (
30 <form onSubmit={this.handleSumbit}>
31 <input
32 name="name"
33 placeholder="name"
34 onChange={this.handleChange}
35 value={this.state.name}
36 />
37 <input
38 name="phone"
39 placeholder="phone number"
40 onChange={this.handleChange}
41 value={this.state.phone}
42 />
43 <button type="submit">submit</button>
44 </form>
45 );
46 }
47}
48
49export default PhoneForm;
베열 렌더링하기
JavaScript 배열 내장함수 map()
map()
1const numbers = [1, 2, 3, 4, 5];
2const squared = numbers.map(n => n * n);
3console.log(squared); // [1, 4, 9, 16, 25]
PhoneInfoList 에 적용
1// App.js
2import React, { Component } from "react";
3import "./App.css";
4import PhoneForm from "./components/PhoneForm";
5import PhoneInfo from "./components/PhoneInfo";
6import PhoneInfoList from "./components/PhoneInfoList";
7
8class App extends Component {
9 id = 0;
10
11 state = {
12 information: []
13 };
14 handleCreate = data => {
15 const { information } = this.state; // 비구조 할당
16
17 // 불변성 유지. 기존것 기반으로 값 주입. concat
18 this.setState({
19 information: information.concat(
20 Object.assign({}, data, {
21 id: this.id++
22 })
23 )
24 });
25 };
26
27 render() {
28 return (
29 <div>
30 <PhoneForm onCreate={this.handleCreate} />
31 <PhoneInfoList data={this.state.information} />
32 </div>
33 );
34 }
35}
36
37export default App;
1// PhoneForm.js
2import React, { Component } from "react";
3
4class PhoneForm extends Component {
5 state = {
6 name: "",
7 phone: ""
8 };
9
10 handleChange = e => {
11 this.setState({
12 [e.target.name]: e.target.value
13 });
14 };
15
16 handleSumbit = e => {
17 e.preventDefault();
18 this.props.onCreate({
19 name: this.state.name,
20 phone: this.state.phone
21 });
22 this.setState({
23 name: "",
24 phone: ""
25 });
26 };
27
28 render() {
29 return (
30 <form onSubmit={this.handleSumbit}>
31 <input
32 name="name"
33 placeholder="name"
34 onChange={this.handleChange}
35 value={this.state.name}
36 />
37 <input
38 name="phone"
39 placeholder="phone number"
40 onChange={this.handleChange}
41 value={this.state.phone}
42 />
43 <button type="submit">submit</button>
44 </form>
45 );
46 }
47}
48
49export default PhoneForm;
1// PhoneInfo.js
2import React, { Component } from "react";
3
4class PhoneInfo extends Component {
5 render() {
6 const { name, phone, id } = this.props.info;
7
8 const style = {
9 border: "1px solid black",
10 padding: "8px",
11 margin: "8px"
12 };
13
14 return (
15 <div style={style}>
16 <div>
17 <b>{name}</b>
18 </div>
19 <div>{phone}</div>
20 </div>
21 );
22 }
23}
24
25export default PhoneInfo;
1// PhoneInfoList.js
2import React, { Component } from "react";
3import PhoneInfo from "./PhoneInfo";
4
5class PhoneInfoList extends Component {
6 static defaultProps = {
7 data: []
8 };
9 render() {
10 const { data } = this.props;
11
12 const list = data.map(info => <PhoneInfo info={info} key={info.id} />);
13 return <div>{list}</div>;
14 }
15}
16
17export default PhoneInfoList;
배열 렌더링, key
key가 없다면?
key가 없으면 순차적으로 모든 값이 바뀌게 된다. key가 있으면 해당 값만 사이에 끼어들거나 삭제되게 된다.
배열 데이터 삭제하기
불변성 유지하면서 작업 처리 필요. .slice
, .filter
사용.
.slice
기존의 배열 건드리지 않음( 불변성 지킴 )
1const numbers = [1, 2, 3, 4, 5];
2numbers.slice(0, 2);
3// [1, 2] 기존의 numbers 배열은 건드리지 않음
4
5numbers.slice(0, 2).concat(numbers.slice(3, 5))
6// [1, 2, 4, 5] 3을 제외한 값 출력 가능
7
8[
9 ...numbers.slice(0, 2),
10 10,
11 ...numbers.slice(3, 5)
12]
13// [1, 2, 10, 4, 5]
.filter
기존의 배열 건드리지 않음( 불변성 지킴 )
1const numbers = [1, 2, 3, 4, 5];
2numbers.filter(n => n > 3);
3// [4, 5]
4
5numbers.filter(n => n !== 3);
6// [1, 2, 4, 5]
적용 : 데이터 삭제
1// App.js
2import React, { Component } from "react";
3import "./App.css";
4import PhoneForm from "./components/PhoneForm";
5import PhoneInfo from "./components/PhoneInfo";
6import PhoneInfoList from "./components/PhoneInfoList";
7
8class App extends Component {
9 id = 0;
10
11 state = {
12 information: []
13 };
14 handleCreate = data => {
15 const { information } = this.state; // 비구조 할당
16
17 // 불변성 유지. 기존것 기반으로 값 주입. concat
18 this.setState({
19 information: information.concat(
20 Object.assign({}, data, {
21 id: this.id++
22 })
23 )
24 });
25 };
26
27 handleRemove = (id) => {
28 const { information } = this.state;
29 this.setState({
30 information: information.filter(info => info.id !== id)
31 });
32 }
33
34 render() {
35 return (
36 <div>
37 <PhoneForm onCreate={this.handleCreate} />
38 <PhoneInfoList
39 data={this.state.information}
40 onRemove={this.handleRemove}
41 />
42
43 </div>
44 );
45 }
46}
47
48export default App;
1// PhoneForm.js
2import React, { Component } from "react";
3
4class PhoneForm extends Component {
5 state = {
6 name: "",
7 phone: ""
8 };
9
10 handleChange = e => {
11 this.setState({
12 [e.target.name]: e.target.value
13 });
14 };
15
16 handleSumbit = e => {
17 e.preventDefault();
18 this.props.onCreate({
19 name: this.state.name,
20 phone: this.state.phone
21 });
22 this.setState({
23 name: "",
24 phone: ""
25 });
26 };
27
28 render() {
29 return (
30 <form onSubmit={this.handleSumbit}>
31 <input
32 name="name"
33 placeholder="name"
34 onChange={this.handleChange}
35 value={this.state.name}
36 />
37 <input
38 name="phone"
39 placeholder="phone number"
40 onChange={this.handleChange}
41 value={this.state.phone}
42 />
43 <button type="submit">submit</button>
44 </form>
45 );
46 }
47}
48
49export default PhoneForm;
1// PhoneInfo.js
2import React, { Component } from "react";
3
4class PhoneInfo extends Component {
5
6 handleRemove = () => {
7 const { info, onRemove } = this.props;
8 onRemove(info.id);
9 }
10 render() {
11 const { name, phone } = this.props.info;
12
13 const style = {
14 border: "1px solid black",
15 padding: "8px",
16 margin: "8px"
17 };
18
19 return (
20 <div style={style}>
21 <div><b>{name}</b></div>
22 <div>{phone}</div>
23 <button onClick={this.handleRemove}>삭제</button>
24 </div>
25 );
26 }
27}
28
29export default PhoneInfo;
1// PhoneInfoList.js
2import React, { Component } from "react";
3import PhoneInfo from "./PhoneInfo";
4
5class PhoneInfoList extends Component {
6 static defaultProps = {
7 data: []
8 };
9 render() {
10 const { data, onRemove } = this.props;
11
12 const list = data.map(info => (
13 <PhoneInfo
14 onRemove={onRemove}
15 info={info}
16 key={info.id}
17 />
18 ));
19 return <div>{list}</div>;
20 }
21}
22
23export default PhoneInfoList;
배열 안의 데이터 수정하기
배열 안의 데이터 수정할 때는 .slice
, .map
을 사용할 수 있다.
.slice
1const numbers = [1, 2, 3, 4, 5];
2[
3 ...numbers.slice(0, 2),
4 9,
5 ...numbers.slice(3, 5)
6]
.map
1const numbers = [1, 2, 3, 4, 5];
2numbers.map(n => {
3 if(n === 3) {
4 return 9;
5 } return n;
6});
적용
1// App.js
2import React, { Component } from "react";
3import "./App.css";
4import PhoneForm from "./components/PhoneForm";
5import PhoneInfo from "./components/PhoneInfo";
6import PhoneInfoList from "./components/PhoneInfoList";
7
8class App extends Component {
9 id = 0;
10
11 state = {
12 information: []
13 };
14 handleCreate = data => {
15 const { information } = this.state; // 비구조 할당
16
17 // 불변성 유지. 기존것 기반으로 값 주입. concat
18 this.setState({
19 information: information.concat(
20 Object.assign({}, data, {
21 id: this.id++
22 })
23 )
24 });
25 };
26
27 handleRemove = (id) => {
28 const { information } = this.state;
29 this.setState({
30 information: information.filter(info => info.id !== id)
31 });
32 }
33
34 handleUpdate = (id, data) => {
35 const { information } = this.state; // 비구조화 할당 통해서 information에 대한 레퍼런스 가져옴
36 this.setState({
37 information: information.map(
38 info => {
39 if(info.id === id) {
40 return {
41 id,
42 ...data,
43 };
44 }
45 return info;
46 }
47 )
48 })
49 }
50
51 render() {
52 return (
53 <div>
54 <PhoneForm onCreate={this.handleCreate} />
55 <PhoneInfoList
56 data={this.state.information}
57 onRemove={this.handleRemove}
58 onUpdate={this.handleUpdate}
59 />
60
61 </div>
62 );
63 }
64}
65
66export default App;
1// PhoneForm.js
2import React, { Component } from "react";
3
4class PhoneForm extends Component {
5 state = {
6 name: "",
7 phone: ""
8 };
9
10 handleChange = e => {
11 this.setState({
12 [e.target.name]: e.target.value
13 });
14 };
15
16 handleSumbit = e => {
17 e.preventDefault();
18 this.props.onCreate({
19 name: this.state.name,
20 phone: this.state.phone
21 });
22 this.setState({
23 name: "",
24 phone: ""
25 });
26 };
27
28 render() {
29 return (
30 <form onSubmit={this.handleSumbit}>
31 <input
32 name="name"
33 placeholder="name"
34 onChange={this.handleChange}
35 value={this.state.name}
36 />
37 <input
38 name="phone"
39 placeholder="phone number"
40 onChange={this.handleChange}
41 value={this.state.phone}
42 />
43 <button type="submit">submit</button>
44 </form>
45 );
46 }
47}
48
49export default PhoneForm;
1// PhoneInfo.js
2import React, { Component, Fragment } from "react";
3
4class PhoneInfo extends Component {
5
6 state = {
7 editing: false,
8 name: '',
9 phone: '',
10 }
11
12 handleRemove = () => {
13 const { info, onRemove } = this.props;
14 onRemove(info.id);
15 }
16
17 handleToggleEdit = () => { // editing 값을 반전시킴. f -> t, t -> f
18 // true -> false
19 // onUpdate
20 // false -> true
21 // state에 info 값들 넣어주기
22 const { info, onUpdate } = this.props;
23 if (this.state.editing) {
24 onUpdate(info.id, {
25 name: this.state.name,
26 phone: this.state.phone
27 });
28 } else {
29 this.setState({
30 name: info.name,
31 phone: info.phone,
32 });
33 }
34
35 this.setState({
36 editing: !this.state.editing,
37 })
38 }
39
40 handleChange = (e) => {
41 this.setState({
42 [e.target.name]: e.target.value
43 })
44 }
45
46 render() {
47 const { name, phone } = this.props.info;
48 const { editing } = this.state;
49
50 const style = {
51 border: "1px solid black",
52 padding: "8px",
53 margin: "8px"
54 };
55
56 return (
57 <div style={style}>
58 {
59 editing ? (
60 <Fragment>
61 <div>
62 <input
63 name="name"
64 onChange={this.handleChange}
65 value={this.state.name}
66 />
67 </div>
68 <div>
69 <input
70 name="phone"
71 onChange={this.handleChange}
72 value={this.state.phone}
73 />
74 </div>
75 </Fragment>
76 ) : (
77 <Fragment>
78 <div><b>{name}</b></div>
79 <div>{phone}</div>
80 </Fragment>
81 )
82 }
83 <button onClick={this.handleRemove}>삭제</button>
84 <button onClick={this.handleToggleEdit}>
85 { editing ? '적용' : '수정' }
86 </button>
87 </div>
88
89 );
90 }
91}
92
93export default PhoneInfo;
1// PhoneInfoList.js
2import React, { Component } from "react";
3import PhoneInfo from "./PhoneInfo";
4
5class PhoneInfoList extends Component {
6 static defaultProps = {
7 data: []
8 };
9 render() {
10 const { data, onRemove, onUpdate } = this.props;
11
12 const list = data.map(info => (
13 <PhoneInfo
14 onRemove={onRemove}
15 onUpdate={onUpdate}
16 info={info}
17 key={info.id}
18 />
19 ));
20 return <div>{list}</div>;
21 }
22}
23
24export default PhoneInfoList;
최적화, 활용, Ref
shouldComponentUpdate를 통한 최적화. 불변성을 왜 유지하는가
불변성을 왜 유지하는가?
불필요한 렌더링을 막아서 최적화 시킬 수 있다. shouldComponentUpdate
를 통해서 업데이트가 불필요할 때는 업데이트를 하지 않도록 해줘야 한다. 이 경우 PhoneInfo.js
에 shouldComponentUpdate
를 구현한다.
컴포넌트 업데이트 성능 최적화
현재의 props와 nextProps의 값을 비교해서 다를 때 업데이트 하게 만든다.
1// PhoneInfo.js
2(...)
3
4shouldComponentUpdate(nextProps, nextState) {
5 if (this.state !== nextState) { // 현재의 props와 nextProps의 값을 비교
6 return true;
7 }
8 return this.props.info !== nextProps.info; // 동일하면 렌더함수 다시 호출 안함
9}
10
11(...)
이름으로 전화번호 찾기
1// App.js
2state = {
3 (...)
4 keyword: '', // keyword 생성
5};
6
7handleChange = (e) => { // hadleChange 함수 구현
8 this.setState({
9 keyword: e.target.value,
10 })
11}
12(...)
13render() {
14 return (
15 <div>
16 (...)
17 <input // Serach 만들고 연결
18 value={this.state.keyword}
19 onChange={this.handleChange}
20 placeholder="Search"
21 />
22 <PhoneInfoList
23 data={this.state.information.filter( // filter 구현
24 info => info.name.indexOf(this.state.keyword) > -1
25 )}
26 (...)
27 />
28
29 </div>
30 );
31}
Ref를 통하여 DOM에 직접 접근하기
입력후 focus를 name에 있게 변경하기. ref를 통해서 DOM에 직접 접근해야 한다.
아래 코드에 직접 접근해야 한다. 이때 ref를 사용.
1// PhoneForm.js
2 (...)
3<input
4 name="name"
5 placeholder="name"
6 onChange={this.handleChange}
7 value={this.state.name}
8/>
9 (...)
ref 사용 방법 두가지
1. 함수 사용
ref={ function }
라는 값에 함수를 작성한다. 이 함수는 ref
를 parameter
로 받아서 이 컴포넌트의 멤버 변수로 ref
값을 넣어주는 작업을 한다.
1// PhoneForm.js
2(...)
3
4class PhoneForm extends Component {
5 input = null;
6
7 (...)
8
9 render() {
10 return (
11 <form onSubmit={this.handleSumbit}>
12 <input
13 name="name"
14 placeholder="name"
15 onChange={this.handleChange}
16 value={this.state.name}
17 ref={ref => this.input = ref} // ref 함수
18 (...)
this.input
은 상단의 input
인데 rendering 되면 input = ref
에 의해서 값이 ref
가 된다.
1// PhoneForm.js
2 handleSubmit = e => {
3 (...)
4 this.input.focus();
5 };
HandleSubmit 이 발생했을 때 input
DOM에 직접적으로 접근 해서 focus()
를 찾는다.
2. React.createRef()
사용
1// PhoneForm.js
2(...)
3
4class PhoneForm extends Component {
5 input = React.createRef(); // React.createRef();
6
7 (...)
8
9 handleSubmit = e => {
10 (...)
11 this.input.current.focus(); // current.focus()
12 };
13
14 (...)
15
16 render() {
17 return (
18 (...)
19 ref={this.input} // ref 함수
20 (...)
React.createRef()
를 이용하면 current
를 통해서 해당 DOM에 접근 가능하다. 그러므로 this.input.current.focus()
로 작성한다.
전체코드 정리
1// App.js
2import React, { Component } from "react";
3import "./App.css";
4import PhoneForm from "./components/PhoneForm";
5import PhoneInfo from "./components/PhoneInfo";
6import PhoneInfoList from "./components/PhoneInfoList";
7
8class App extends Component {
9 id = 3;
10
11 state = {
12 information: [
13 {
14 id: 0,
15 name: 'Gouda',
16 phone: '010-0000-0001'
17 },
18 {
19 id: 1,
20 name: '자두',
21 phone: '010-0000-0002'
22 },
23 {
24 id: 2,
25 name: '돌돌이',
26 phone: '010-0000-0003'
27 }
28 ],
29 keyword: '',
30 };
31
32 handleChange = (e) => {
33 this.setState({
34 keyword: e.target.value,
35 })
36 }
37
38 handleCreate = data => {
39 const { information } = this.state; // 비구조 할당
40
41 // 불변성 유지. 기존것 기반으로 값 주입. concat
42 this.setState({
43 information: information.concat(
44 Object.assign({}, data, {
45 id: this.id++
46 })
47 )
48 });
49 };
50
51 handleRemove = (id) => {
52 const { information } = this.state;
53 this.setState({
54 information: information.filter(info => info.id !== id)
55 });
56 }
57
58 handleUpdate = (id, data) => {
59 const { information } = this.state; // 비구조화 할당 통해서 information에 대한 레퍼런스 가져옴
60 this.setState({
61 information: information.map(
62 info => {
63 if(info.id === id) {
64 return {
65 id,
66 ...data,
67 };
68 }
69 return info;
70 }
71 )
72 })
73 }
74
75 render() {
76 return (
77 <div>
78 <PhoneForm onCreate={this.handleCreate} />
79 <input
80 value={this.state.keyword}
81 onChange={this.handleChange}
82 placeholder="Search"
83 />
84 <PhoneInfoList
85 data={this.state.information.filter(
86 info => info.name.indexOf(this.state.keyword) > -1
87 )}
88 onRemove={this.handleRemove}
89 onUpdate={this.handleUpdate}
90 />
91
92 </div>
93 );
94 }
95}
96
97export default App;
1// PhoneForm.js
2import React, { Component } from "react";
3
4class PhoneForm extends Component {
5 input = React.createRef();
6 state = {
7 name: "",
8 phone: ""
9 };
10
11 handleChange = e => {
12 this.setState({
13 [e.target.name]: e.target.value
14 });
15 };
16
17 handleSubmit = e => {
18 e.preventDefault();
19 this.props.onCreate({
20 name: this.state.name,
21 phone: this.state.phone
22 });
23 this.setState({
24 name: "",
25 phone: ""
26 });
27 this.input.current.focus();
28 };
29
30 render() {
31 return (
32 <form onSubmit={this.handleSubmit}>
33 <input
34 name="name"
35 placeholder="name"
36 onChange={this.handleChange}
37 value={this.state.name}
38 ref={this.input}
39 />
40 <input
41 name="phone"
42 placeholder="phone number"
43 onChange={this.handleChange}
44 value={this.state.phone}
45 />
46 <button type="submit">submit</button>
47 </form>
48 );
49 }
50}
51
52export default PhoneForm;
1// PhoneInfo.js
2import React, { Component, Fragment } from "react";
3
4class PhoneInfo extends Component {
5
6 state = {
7 editing: false,
8 name: '',
9 phone: '',
10 }
11
12 shouldComponentUpdate(nextProps, nextState) {
13 if (this.state !== nextState) {
14 return true;
15 }
16 return this.props.info !== nextProps.info;
17 // 동일하면 렌더함수 다시 호출 안함
18 }
19
20 handleRemove = () => {
21 const { info, onRemove } = this.props;
22 onRemove(info.id);
23 }
24
25 handleToggleEdit = () => { // editing 값을 반전시킴. f -> t, t -> f
26 // true -> false
27 // onUpdate
28 // false -> true
29 // state에 info 값들 넣어주기
30 const { info, onUpdate } = this.props;
31 if (this.state.editing) {
32 onUpdate(info.id, {
33 name: this.state.name,
34 phone: this.state.phone
35 });
36 } else {
37 this.setState({
38 name: info.name,
39 phone: info.phone,
40 });
41 }
42
43 this.setState({
44 editing: !this.state.editing,
45 })
46 }
47
48 handleChange = (e) => {
49 this.setState({
50 [e.target.name]: e.target.value
51 })
52 }
53
54 render() {
55 const { name, phone } = this.props.info;
56 const { editing } = this.state;
57
58 const style = {
59 border: "1px solid black",
60 padding: "8px",
61 margin: "8px"
62 };
63
64 console.log(name);
65
66 return (
67 <div style={style}>
68 {
69 editing ? (
70 <Fragment>
71 <div>
72 <input
73 name="name"
74 onChange={this.handleChange}
75 value={this.state.name}
76 />
77 </div>
78 <div>
79 <input
80 name="phone"
81 onChange={this.handleChange}
82 value={this.state.phone}
83 />
84 </div>
85 </Fragment>
86 ) : (
87 <Fragment>
88 <div><b>{name}</b></div>
89 <div>{phone}</div>
90 </Fragment>
91 )
92 }
93 <button onClick={this.handleRemove}>삭제</button>
94 <button onClick={this.handleToggleEdit}>
95 { editing ? '적용' : '수정' }
96 </button>
97 </div>
98
99 );
100 }
101}
102
103export default PhoneInfo;
1// PhoneInfoList.js
2import React, { Component } from "react";
3import PhoneInfo from "./PhoneInfo";
4
5class PhoneInfoList extends Component {
6 static defaultProps = {
7 data: []
8 };
9 render() {
10 const { data, onRemove, onUpdate } = this.props;
11
12 console.log('rendering list');
13
14 const list = data.map(info => (
15 <PhoneInfo
16 onRemove={onRemove}
17 onUpdate={onUpdate}
18 info={info}
19 key={info.id}
20 />
21 ));
22 return <div>{list}</div>;
23 }
24}
25
26export default PhoneInfoList;