협업에서의 문제를 다뤄봄 꼼꼼하게 정리를 해보길
참고 사이트:
https://www.w3schools.com/jsref/event_onmousemove.asp
https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_onmousemove_dom
import React, { useState } from 'react';
import './AppXY.css';
export default function AppXY() {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const handleMove = (e) => {
setX(e.clientX);
setY(e.clientY);
};
return (
<div
className='container'
onMouseMove={handleMove}
>
<div
className='pointer'
style={{position: "absolute", left: x, top: y}}
/>
</div>
);
}
body, html, div {
height: 100%;
width: 100%;
}
.container {
height: 100%;
width: 100%;
background-color: orange;
position: relative;
}
.pointer {
height: 1rem;
width: 1rem;
background-color: red;
border-radius: 100%;
}
와~~~ 해냄
더 찾아보면서 코드를 줄이는 방법을 알게되었다.
import React, { useState } from 'react';
import './AppXY.css';
export default function AppXY() {
const [xy, setXY] = useState({x: 0, y: 0});
const handleMove = (e) => {
setXY({x: e.clientX, y: e.clientY});
};
return (
<div
className='container'
onMouseMove={handleMove}
>
<div
className='pointer'
style={{position: "absolute", left: xy.x, top: xy.y}}
/>
</div>
);
}
.container {
height: 100vh;
width: 100vh;
background-color: orange;
position: relative;
}
.pointer {
height: 1rem;
width: 1rem;
background-color: red;
border-radius: 100%;
}
vh는 창사이즈에 맞는 퍼센트
리액트보단 브라우저 이벤트에 대한 지식이 필요했던 예제다.
검색 방법:
react pointer event 로 검색하기
import React, { useState } from 'react';
import './AppXY.css';
export default function AppXY() {
const [xy, setXY] = useState({x: 0, y: 0});
return (
<div className='container' onPointerMove={(e) => {
setXY({x: e.clientX, y: e.clientY});
}}>
<div
className='pointer'
style={{position: "absolute", left: xy.x, top: xy.y}}
/>
</div>
);
}
오잉 마우스 무브가 아니라 포인터 무브를 사용하네
import React, { useState } from 'react';
import './AppXY.css';
export default function AppXY() {
const [position, setPosition] = useState({x: 0, y: 0});
return (
<div className='container' onPointerMove={(e) => {
setPosition({x: e.clientX, y: e.clientY});
}}>
<div
className='pointer'
style={{position: "absolute", left: position.x, top: position.y}}
/>
</div>
);
}
import React, { useState } from 'react';
import './AppXY.css';
export default function AppXY() {
const [position, setPosition] = useState({x: 0, y: 0});
return (
<div className='container' onPointerMove={(e) => {
// setPosition({x: e.clientX, y: e.clientY});
// 만약 수평으로만 이동이 가능하다면?
setPosition(prev => ({x: e.clientX, y: prev.y}));
}}>
<div
className='pointer'
style={{position: "absolute", left: position.x, top: position.y}}
/>
</div>
);
}
setPosition(prev => ({ ...prev, x: e.clientX}));
… 모든 것들을 그대로 돌리고 x만 바꾼다
setState는 중첩된 객체의 업데이트를 지원하지 않는다. 그래서 그 중첩된 리스트자체를 수정해서 리스트를 넣어주는 것.
Object.assign({}, person.mentor);
열거 가능한 자체 속성을 복사해 대상 객체에 붙여넣는다.
import React, { useState } from 'react';
export default function AppMentor(props) {
const [person, setPerson] = useState({
name: '엘리',
title: '개발자',
mentor: { // 중첩된 객체
name: '밥',
title: '시니어개발자',
},
});
return (
<div>
<h1>
{person.name}는 {person.title}
</h1>
<p>
{person.name}의 멘토는 {person.mentor.name} ({person.mentor.title})
</p>
<button
onClick={() => {
const name = prompt(`what's your mentor's name?`);
let tempList = Object.assign({}, person.mentor);
tempList.name = name;
setPerson(prev => ({...prev, mentor: tempList}));
}}
>
멘토 이름 바꾸기
</button>
<button
onClick={() => {
const title = prompt(`what's your mentor's title?`);
let tempList = Object.assign({}, person.title);
tempList.title = title;
setPerson(prev => ({...prev, mentor: tempList}));
}}
>
멘토 타이틀 바꾸기
</button>
</div>
);
}
setPerson(person => ({...person, mentor: {...person.mentor, name}}));
아하!! 그냥 이렇게 넣어도 되는거구나!!
import React, { useState } from 'react';
export default function AppMentor(props) {
const [person, setPerson] = useState({
name: '엘리',
title: '개발자',
mentor: { // 중첩된 객체
name: '밥',
title: '시니어개발자',
},
});
return (
<div>
<h1>
{person.name}는 {person.title}
</h1>
<p>
{person.name}의 멘토는 {person.mentor.name} ({person.mentor.title})
</p>
<button
onClick={() => {
const name = prompt(`what's your mentor's name?`);
setPerson(person => ({...person, mentor: {...person.mentor, name}}));
}}
>
멘토 이름 바꾸기
</button>
<button
onClick={() => {
const title = prompt(`what's your mentor's title?`);
setPerson(person => ({...person, mentor: {...person.mentor, title}}));
}}
>
멘토 타이틀 바꾸기
</button>
</div>
);
}
배열의 인덱스는 키값으로 쓰길 추천하지 않음
import React, { useState } from 'react';
export default function AppMentor() {
const [person, setPerson] = useState({
name: '엘리',
title: '개발자',
mentors: [
{
name: '밥',
title: '시니어개발자',
index: '0'
},
{
name: '제임스',
title: '시니어개발자',
index: '1'
},
],
});
return (
<div>
<h1>
{person.name}는 {person.title}
</h1>
<p>{person.name}의 멘토는:</p>
<ul>
{person.mentors.map((mentor) => (
<li key={mentor.index}>
{mentor.name} ({mentor.title})
</li>
))}
</ul>
<button
onClick={() => {
let prev = prompt(`누구의 이름을 바꾸고 싶은가요?`);
while(true) {
if(person.mentors.find(mentor => mentor.name === prev) === undefined) { // 없는 이름일 때
alert('존재하지 않는 이름입니다. 다시 입력해주세요.');
} else { break; }
prev = prompt(`누구의 이름을 바꾸고 싶은가요?`);
}
const current = prompt(`이름을 무엇으로 바꾸고 싶은가요?`);
// 따로 mentors 부분만 복사해와서 고친 다음에 setPerson으로 넣음
let tempPerson = Object.assign(person.mentors);
tempPerson.map((mentor, index) => { // 돌면서 해당 부분의 이름을 바꿔줌
if(mentor.name === prev) {
tempPerson[index].name = current;
}
});
setPerson({...person, mentors: tempPerson});
}}
>
멘토의 이름을 바꾸기
</button>
</div>
);
}
헤헤 해결
import React, { useState } from 'react';
export default function AppMentor() {
const [person, setPerson] = useState({
name: '엘리',
title: '개발자',
mentors: [
{
name: '밥',
title: '시니어개발자',
index: '0'
},
{
name: '제임스',
title: '시니어개발자',
index: '1'
},
],
});
return (
<div>
<h1>
{person.name}는 {person.title}
</h1>
<p>{person.name}의 멘토는:</p>
<ul>
{person.mentors.map((mentor) => (
<li key={mentor.index}>
{mentor.name} ({mentor.title})
</li>
))}
</ul>
<button
onClick={() => {
const prev = prompt(`누구의 이름을 바꾸고 싶은가요?`);
const current = prompt(`이름을 무엇으로 바꾸고 싶은가요?`);
setPerson(person => ({
...person,
mentors: person.mentors.map((mentor) => {
if(mentor.name === prev) {
return {...mentor, name: current };
}
return mentor;
})
}));
}}
>
멘토의 이름을 바꾸기
</button>
</div>
);
}
읭 근데 이건 없는 경우를 해결해주지 않네
왜이렇게 빙글빙글 돌아가면서 해야하는가?
리액트에서 가지고 있는 상태는 불변성을 유지해야한다. 한 번 만들어지면 변경하면 안되게 만들어야한다. 변경해야한다면 새로운 값 새로운 배열 새로운 객체로 만들어주어야한다.
ㅇㅎㅇㅎ
person.mentors[0].name = current; 이렇게 하면 안되는 이유는?
ㄴ 이렇게 해도 업데이트 되지 않기 때문 setPerson으로 해야함
똑같은 객체에서 (참조값이 같은 상태에서) 값을 아무리 변경해봤자 같은 걸로 인지해서 바꿔주지 않음
<button
onClick={() => {
const deleteName = prompt(`삭제할 멘토의 이름을 입력해주세요.`);
setPerson(person => ({
...person,
mentors: person.mentors.filter((mentor) => mentor.name != deleteName)
}));
}}
>멘토 삭제하기</button>
추가는…… 모르게써
<button
onClick={() => {
const newName = prompt(`추가할 멘토의 이름을 입력해주세요.`);
const newTitle = prompt(`추가할 멘토의 역할을 입력해주세요.`);
const tempList = {
name: newName,
title: newTitle
};
console.log(tempList);
setPerson(person => ({
...person, mentors: Object.assign(person.mentors.map(mentor => mentor), tempList)
}));
}}
>멘토 추가하기</button>
안 됨 ㅠㅠ
<button
onClick={() => {
const name = prompt(`누구를 삭제하고 싶은가요?`);
setPerson(person => ({
...person,
mentors: person.mentors.filter((m) => m.name !== name)
}));
}}
>멘토 삭제하기</button>
왕 삭제하기는 똑같애
<button
onClick={() => {
const name = prompt(`멘토의 이름은?`);
const title = prompt(`멘토의 직함은?`);
setPerson(person => ({
...person,
mentors: [...person.mentors, {name, title}],
}));
}}
>멘토 추가하기</button>
멘토는 배열 스프레드 연산자를 배열에 사용하면 요소가 하나씩 풀어짐
ㄴ 그렇구나… 이거 때문에 그렇게 고생했는데 그냥 … 이면 모든 것이 해결 되는 것이었음
import React, { useState } from 'react';
export default function AppMentor() {
const [person, setPerson] = useState(initialPerson);
const handleUpdate = () => {
const prev = prompt(`누구의 이름을 바꾸고 싶은가요?`);
const current = prompt(`이름을 무엇으로 바꾸고 싶은가요?`);
setPerson(person => ({
...person,
mentors: person.mentors.map((mentor) => {
if(mentor.name === prev) {
return {...mentor, name: current };
}
return mentor;
})
}))
};
const handleAdd = () => {
const name = prompt(`멘토의 이름은?`);
const title = prompt(`멘토의 직함은?`);
setPerson(person => ({
...person,
mentors: [...person.mentors, {name, title}],
}));
};
const handleDelete = () => {
const name = prompt(`누구를 삭제하고 싶은가요?`);
setPerson(person => ({
...person,
mentors: person.mentors.filter((m) => m.name !== name)
}));
};
return (
<div>
<h1>
{person.name}는 {person.title}
</h1>
<p>{person.name}의 멘토는:</p>
<ul>
{person.mentors.map((mentor, index) => (
<li key={index}>
{mentor.name} ({mentor.title})
</li>
))}
</ul>
<button onClick={handleUpdate}>멘토의 이름을 바꾸기</button>
<button onClick={handleAdd}>멘토 추가하기</button>
<button onClick={handleDelete}>멘토 삭제하기</button>
</div>
);
}
const initialPerson = {
name: '엘리',
title: '개발자',
mentors: [
{
name: '밥',
title: '시니어개발자',
},
{
name: '제임스',
title: '시니어개발자',
},
],
};
정적인 데이터는 아래에 함수는 위에
이러한 상태들은 컴포넌트 안에서만 사용할 수 있음
최신 버전에서는 contextAPi를 사용하면 공통적으로 상태관리를 할 수 있다.
peson의 action에 따라서 하는 함수를 만드는 것
const [person, dispatch] = useReducer(personReducer, initialPerson);
초기값은 이니셜펄슨
dispatch를 이용해서 원하는 명령을 이용할 수 있음
person은 객체를 받고 action은 넘겨준걸 받아옴
export default function personReducer(person, action) {
switch(action.type) {
case 'updated': {
const {prev, current} = action;
return {
...person,
mentors: person.mentors.map((mentor) => {
if(mentor.name === prev) {
return {...mentor, name: current };
}
return mentor;
}),
};
}
case 'added': {
const {name, title} = action;
return {
...person,
mentors: [...person.mentors, {name, title}],
};
}
case 'deleted': {
return {
...person,
mentors: person.mentors.filter((mentor) => mentor.name !== action.name),
};
}
default: {
throw Error(`알 수 없는 액션 타입니다: ${action.type}`);
}
}
}
import React, { useReducer } from 'react';
import personReducer from './reducer/person-reducer';
export default function AppMentor() {
// const [person, setPerson] = useState(initialPerson);
const [person, dispatch] = useReducer(personReducer, initialPerson);
const handleUpdate = () => {
const prev = prompt(`누구의 이름을 바꾸고 싶은가요?`);
const current = prompt(`이름을 무엇으로 바꾸고 싶은가요?`);
dispatch({type: 'updated', prev, current});
};
const handleAdd = () => {
const name = prompt(`멘토의 이름은?`);
const title = prompt(`멘토의 직함은?`);
dispatch({type: 'added', name, title});
};
const handleDelete = () => {
const name = prompt(`누구를 삭제하고 싶은가요?`);
dispatch({type: 'deleted', name});
};
return (
<div>
<h1>
{person.name}는 {person.title}
</h1>
<p>{person.name}의 멘토는:</p>
<ul>
{person.mentors.map((mentor, index) => (
<li key={index}>
{mentor.name} ({mentor.title})
</li>
))}
</ul>
<button onClick={handleUpdate}>멘토의 이름을 바꾸기</button>
<button onClick={handleAdd}>멘토 추가하기</button>
<button onClick={handleDelete}>멘토 삭제하기</button>
</div>
);
}
const initialPerson = {
name: '엘리',
title: '개발자',
mentors: [
{
name: '밥',
title: '시니어개발자',
},
{
name: '제임스',
title: '시니어개발자',
},
],
};
아무리 리듀서를 사용해도 중첩된 객체가 많으면 많을 수록 …pesron으로 빙글빙글 돌면서 해야하는데
이걸 좀더 직관적으로하게 해주는게 immer
use-immer를 설치
usestate를 내부적으로 사용하고 있음
import React, { useState } from 'react';
import { useImmer } from 'use-immer';
export default function AppMentorsImmer() {
const [person, updatePerson] = useImmer(initialPerson);
const handleUpdate = () => {
const prev = prompt(`누구의 이름을 바꾸고 싶은가요?`);
const current = prompt(`이름을 무엇으로 바꾸고 싶은가요?`);
updatePerson((person) => {
const mentor = person.mentors.find((m) => m.name === prev); // 이름이 맞는지 확인
mentor.name = current;
});
};
const handleAdd = () => {
const name = prompt(`멘토의 이름은?`);
const title = prompt(`멘토의 직함은?`);
updatePerson((person) => {
person.mentors.push({ name, title });
});
};
const handleDelete = () => {
const name = prompt(`누구를 삭제하고 싶은가요?`);
updatePerson((person) => {
const index = person.mentors.findIndex((m) => m.name === name);
person.mentors.splice(index, 1); // 해당 인덱스의 아이템을 삭제
});
};
return (
<div>
<h1>
{person.name}는 {person.title}
</h1>
<p>{person.name}의 멘토는:</p>
<ul>
{person.mentors.map((mentor, index) => (
<li key={index}>
{mentor.name} ({mentor.title})
</li>
))}
</ul>
<button onClick={handleUpdate}>멘토의 이름을 바꾸기</button>
<button onClick={handleAdd}>멘토 추가하기</button>
<button onClick={handleDelete}>멘토 삭제하기</button>
</div>
);
}
const initialPerson = {
name: '엘리',
title: '개발자',
mentors: [
{
name: '밥',
title: '시니어개발자',
},
{
name: '제임스',
title: '시니어개발자',
},
],
};
import React, { useState } from "react";
export default function AppForm() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const handleSubmit = (e) => {
e.preventDefault(); // 이게 없으면 submit 버튼 누르면 페이지가 리프레쉬 됨
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">이름:</label>
<input
type="text"
id="name"
name={name}
onChange={(e) => {
setName(e.target.value);
}}
/>
<label htmlFor="email">이메일:</label>
<input
type="email"
id="email"
name={email}
onChange={(e) => {
setEmail(e.target.value);
}}
/>
<button>Submit</button>
</form>
);
}
이렇게 보다 하나로, 폼으로 관리하는 방법
import React, { useState } from "react";
export default function AppForm() {
const [form, setForm] = useState({});
const handleSubmit = (e) => {
e.preventDefault(); // 이게 없으면 submit 버튼 누르면 페이지가 리프레쉬 됨
console.log(form);
};
const handleChange = (e) => {
// 이벤트에서 발생하는 타겟을 받아오기
const { name, value } = e.target;
setForm({ ...form, [name]: value });
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">이름:</label>
<input type="text" id="name" name={form.name} onChange={handleChange} />
<label htmlFor="email">이메일:</label>
<input
type="email"
id="email"
name={form.email}
onChange={handleChange}
/>
<button>Submit</button>
</form>
);
}
폼에있는 인풋데이터는 사용자가 바로 수정가능하고 바로 확인가능하기 때문에 언컨트롤 컴포넌트 - 리액트의 추구 원칙과 어긋난다 항상 상태로부터 발생되어야한다
그렇기에 항상 이 상태를 이용해서 변경 될 때마다 업데이트 해줘야한다 폼은 객체를 이용해서 사용할 수 있다
import React, { useState } from "react";
export default function AppForm() {
const [form, setForm] = useState({ name: "", email: "" });
const handleSubmit = (e) => {
e.preventDefault(); // 이게 없으면 submit 버튼 누르면 페이지가 리프레쉬 됨
console.log(form);
};
const handleChange = (e) => {
// 이벤트에서 발생하는 타겟을 받아오기
const { name, value } = e.target; // e.target.name 과 e.target.value로 바인딩 됨
setForm({ ...form, [name]: value });
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">이름:</label>
<input
type="text"
id="name"
name="name"
value={form.name}
onChange={handleChange}
/>
<label htmlFor="email">이메일:</label>
<input
type="email"
id="email"
name="email"
value={form.email}
onChange={handleChange}
/>
<button>Submit</button>
</form>
);
}
import React from "react";
export default function AppWrap() {
return (
<div>
<Navbar />
</div>
);
}
function Navbar() {
return (
<header style={{ backgroundColor: "yellow" }}>
<Avatar
image="https://images.unsplash.com/photo-1574158622682-e40e69881006?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=580&q=80"
name="han"
size={200}
/>
</header>
);
}
function Avatar({ image, name, size }) {
return (
<div>
<img
src={image}
alt={`${name}`}
width={size}
height={size}
style={{ borderRadius: "50%" }}
/>
</div>
);
}
여기서 냅바는 재사용성이 좀 떨어진다 안에 글씨를 넣고 싶기도 빼고 싶기도 한데….
이럴 때 유용한게 웹 컴포넌트
import React from "react";
export default function AppWrap() {
return (
<div>
<Navbar>
<Avatar
image="https://images.unsplash.com/photo-1574158622682-e40e69881006?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=580&q=80"
name="han"
size={200}
/>
</Navbar>
</div>
);
}
function Navbar({ children }) {
return <header style={{ backgroundColor: "yellow" }}>{children}</header>;
}
function Avatar({ image, name, size }) {
return (
<div>
<img
src={image}
alt={`${name}`}
width={size}
height={size}
style={{ borderRadius: "50%" }}
/>
</div>
);
}
냅바 안의 내용이 children으로 전달이 됨
이렇게 했을 때의 장점 냅바를 내가 원하는 컨텐츠를 넣어ㅓㅅ 만들 수 있음
import React from "react";
export default function AppCard() {
return (
<>
<Card>
<p>Card1</p>
</Card>
<Card>
<h1>Card2</h1>
<p>설명</p>
</Card>
<Card>
<article></article>
</Card>
</>
);
}
function Card({ children }) {
return (
<div
style={{
backgroundColor: "black",
borderRadius: "20px",
color: "white",
minHeight: "200px",
maxWidth: "200px",
margin: "1rem",
padding: "1rem",
textAlign: "center",
}}
>
{children}
</div>
);
}