Provider로 데이터를 공급해주면 Consumer로 데이터를 사용한다.
PropsDrilling을 피하려고 나온 상태 공유 API. 관리 API가 아니다
ToDO를 만들면서 실습해보자!
강의도중 처음 배운 내용들 위주로 보았다.
&:not(:first-child)
not
선택자. 위처럼 사용한다면 first-child
를 제외한 태그들에 적용한다! 몰랐는데 굉장히 유용해보인다.
const 내가지을이름Context = createContext();
const 내가지을이름Provider = ({children}) => {
...
return
(
<내가지을이름Context.Provider value={{프로바이더 내부에서 선언한 상태, 변수, 함수들을 전달한다.}}>
{children}
</내가지을이름Context.Provider>
)
}
이렇게 사용하면된다!
더 구체적인 예시는ㅇㅏ래에..
import { createContext, useContext, useState } from "react";
import { v4 } from "uuid";
const TaskContext = createContext();
export const useTasks = () => useContext(TaskContext);
const TaskProvider = ({ children }) => {
const [tasks, setTasks] = useState([]);
const addTask = (content) => {
setTasks([
...tasks,
{
id: v4(),
content,
isCompleted: false,
},
]);
};
const updateTask = (id, isCompleted) => {
setTasks(
tasks.map((item) => (item.id === id ? { ...item, isCompleted } : item))
);
};
const removeTask = (id) => {
setTasks(tasks.filter((item) => item.id !== id));
};
return (
<TaskContext.Provider value={{ tasks, addTask, updateTask, removeTask }}>
{children}
</TaskContext.Provider>
);
};
export default TaskProvider;
이렇게 contextApi로 만든 상태를 사용하고싶다면
<TaskProvider>
<div>
<NewTaskForm />
<TaskList css={{ marginTop: 16 }} />
</div>
</TaskProvider>
컴포넌트 최상단에서 이렇게 감싸주면 된다. children프로퍼티로 들어가야하니까 ㅎㅎ
이후 사용할 컴포넌트에서
const { updateTask, removeTask } = useTasks();
이렇게 가져와서 쓰면 동기화가 잘 된다.
컴포넌트니까 결국 props를 받아서 사용할수있다.
text-decoration: ${({prop}) => prop && 'line-through'}
템플릿 리터럴처럼 ${}
로 감싼뒤 사용한다. 괄호랑 브라켓이 좀 엮여있어서 살짝 헷갈리는구먼
그런데 문제가생겼다...조건부 스타일이 안들어감.
아래 해결방법.
input type=checkbox
로 사용할때 value가 true,false
처럼 불리언이 아니라 on
으로만 나온다 ㅋㅋㅋ심지어 off
도 안나옴.
그래서 e.target.checked
를 가져와서 사용해야한다.
멍충~
자 시작해보자이
드래그&드롭을 이용하여 파일을 업로드하는 컴포넌트다. 유용하겠는걸?
참고로 파일업로드도 원래 <input type='file'/>
을 사용하지만, 스타일링이 힘들어서 화면에서 안보이게하고 label
이용함. 마치 토글처럼
또한 파일이 업로드되면 value
에 파일이 들어온다.
파일을 드래그해서 놓을때, 브라우저 기본동작으로인해 파일이 새 창에 열린다. 따라서 전파와 기본 이벤트를 막아주어야함.
const handleDragEnter = (e) => {
if (!droppable) {
return;
}
e.preventDefault();
e.stopPropagation();
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
setDragging(true);
}
};
const handleDragLeave = (e) => {
if (!droppable) {
return;
}
e.preventDefault();
e.stopPropagation();
setDragging(false);
};
const handleDragOver = (e) => {
if (!droppable) {
return;
}
e.preventDefault();
e.stopPropagation();
};
const handleFileDrop = (e) => {
if (!droppable) {
return;
}
e.preventDefault();
e.stopPropagation();
const { files } = e.dataTransfer;
const changedFile = files[0];
setFile(changedFile);
onChange && onChange(changedFile);
setDragging(false);
};
자세히보면 각 이벤트 핸들러마다 전파와 기본이벤트를 막는는 로직이 보인다.
그리고 children
을 함수로 던져줄수도 있다는걸 첨 알았다.
//Upload.js 로직 일부
{typeof children === "function" ? children(file, dragging) : children}
//사용할때
<Upload droppable>
{(file, dragging) => (
<div
style={{
width: 300,
height: 100,
border: "4px dashed #aaa",
borderColor: dragging ? "black" : "#aaa",
}}
>
{file ? file.name : "클릭하거나 드래그하시오"}
</div>
)}
</Upload>
children
에게 prop을 전달할때 이렇게도 전달할수 있구나?
알림이 쌓인 수를 보여주는 UI를 Badge라고함!
이렇게 생긴 UI임.
스타일은 제거하고 코드 올려보면..
const Badge = ({
children,
count,
maxCount,
backgroundColor,
textColor,
dot = false,
showZero,
...props
}) => {
let badge = null;
const colorStyle = {
backgroundColor,
color: textColor,
};
if (count) {
badge = (
<Super style={colorStyle}>
{maxCount && count > maxCount ? `${maxCount}+` : count}
</Super>
);
} else {
if (count !== undefined) {
badge = showZero ? <Super style={colorStyle}>0</Super> : null;
} else if (dot) {
badge = (
<Super className="dot" style={colorStyle}>
0
</Super>
);
}
}
return (
<BadgeContainer {...props}>
{children}
{count > 0 || (count === 0 && showZero) ? badge : null}
</BadgeContainer>
);
};
이렇게 되어있다.
이정도 조건문은 괜찮은 걸까? 항상 볼때마다 고민이 많다. 아무래도 모르는게 많기 때문이겠지...
주말에 멘토님이 추천해주신 SOLID원칙을 다시 정독해봐야겠다.
https://feathericons.com/ 에서 제공해주는 무료 svg아이콘을 한번 래핑해서 사용해보겠다. Vue강의시간에서 fonts.google을 래핑한것과 유사하구만
노드의 Buffer
모듈을 통하여 svg아이콘을 img에 넣으려했으나,
이렇게 오류가 남. 찾아보니 Webpack5버전부터 기존에 제공하던 node.js의 자동 폴리필을 제공하지 않는다. 번들사이즈를 줄이려는 노력이겠지?
아무튼 없다는 소리니까 일단 설치한뒤...필요한곳에서 import
해서 사용하니 해결됐다.
import { Buffer } from "buffer";
const Icon = ({
name,
size = 16,
strokeWidth = 2,
color = "#222",
rotate,
...props
}) => {
const iconStyle = {
"stroke-width": strokeWidth,
storke: color,
width: size,
height: size,
};
const icon = require("feather-icons").icons[name];
const svg = icon ? icon.toSvg(iconStyle) : "";
const base64 = Buffer.from(svg, "utf8").toString("base64");
return (
<span {...props}>
<img
alt={name}
style={{ transform: rotate && `rotate(${rotate}deg)` }}
src={`data:image/svg+xml;base64,${base64}`}
/>
</span>
);
};
export default Icon;
그런데, 아이콘 태그를 <i>
태그로 감싸는 부분이 의아했다. 글씨를 기울이는 태그였는데, 아이콘을 나타내는 태그기도 한것인가?
검색해봄
=> 다른 용도지만 잘 안쓰여서 찾아서 수정하기 편하고 icon의 첫글자와 같아서 사용한다.
시맨틱 태그로 그냥 나와주면 안되나?ㅋㅋㅋ
사용자의 프로필 사진을 나타내는데 쓰임. 특별히 모르는 부분과 이해 안가는 부분이 아예 없고 어제 작성했던 Image태그를 활용하는 부분이라 넘어간다.
다만 리액트에서 제공하는 React.cloneElement
같은 메서드들의 쓰임새를 잘 기억해둘 필요가 있겠다.
자식을 렌더링해줄때 이 메서드를 사용한다. 그런데, 이게 왜 필요할까? 어차피 개발자는 필요한 컴포넌트만 자식으로 넣어서 사용할텐데 말이다.
공식문서에 나와있는 용법이다
아하 그러니까 React.cloneElement를 사용할때 예외사항(정말 의도치 않은 jSX외 요소)가 들어올때 예외처리를 하기위함이구나!
잘못하면 앱이 뻗거나 렌더링이 중단될테니 말이다.
안전이 최고!
이런 컴포넌트를 만든다.
const Slider = ({
min = 0,
max = 100,
step = 0.1,
defaultValue,
onChange,
...props
}) => {
const [dragging, setDragging] = useState(false);
const [value, setValue] = useState(defaultValue ? defaultValue : min);
const sliderRef = useRef(null);
const handleMouseDown = useCallback(() => setDragging(true), []);
const handleMouseUp = useCallback(() => setDragging(false), []);
useEffect(() => {
const handleMouseMove = (e) => {
if (!dragging) {
return;
}
const handleOffset = e.pageX - sliderRef.current.offsetLeft;
const { offsetWidth: sliderWidth } = sliderRef.current;
const track = handleOffset / sliderWidth;
let newValue;
if (track < 0) {
newValue = min;
} else if (track > 1) {
newValue = max;
} else {
newValue = Math.round((min + (max - min) * track) / step) * step;
}
setValue(newValue);
onChange && onChange(newValue);
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [value, min, max, dragging, sliderRef, handleMouseUp, onChange, step]);
const percentage = ((value - min) / (max - min)) * 100;
return (
<SliderContainer {...props} ref={sliderRef}>
<Rail />
<Track style={{ width: `${percentage}%` }} />
<Handle
onMouseDown={handleMouseDown}
style={{ left: `${percentage}%` }}
/>
</SliderContainer>
);
};
특이한 부분은 없지만, 값을 잘 활용해서 핸들을 움직이는게 인상적이었다.
사용자 친화적 경험을 위해 최소-최대값을 설정하여 마우스가 넘어가면 최소-최대값을 넘겨준것도 좋았다.
이를 위해 useEffect에서 도큐먼트에 이벤트를 걸어주었다. 이후 return으로 청소해준다.
타자가 일단 빨라야한다...ㅋㅋㅋ강사님들 보면 영타가 정말 빠르시다. 의식적으로 오타 없이 빨리치려고 노력해봐야겠다.
그리고 계속 컴포넌트를 배우는데...다 외우기엔 무리가 있다. 이해만 하고 넘어가자! 블랙박스학습법이다!
그렇게 어려운건 없어서 재밌었다 ㅎㅎ