color palette을 만들면서 배웠던 것을 정리해보았다.
이렇게나 많이 배웠다니.. 역시 한 프로젝트만 패는(?)것이 나에게 맞는 공부법인것 같다는 생각이 든다.
깃헙 코드
stopPropagation을 드림코딩에서는 부모 이벤트까지 모두 차단하기 때문에 예상치 못한 버그가 일어나서 지양해야한다고 했지만 , 콜트는 사용하였다.
근데 more버튼을 클릭했을때 copy가 안일어나게 하려고 CopyClipBoard에서 e.taget, e.currentTarget
을 이용해 이벤트를 차단하려고 했는데, onCopy를 통해 전달되는 인자는 copy결과였다 이벤트가 아니라.
onClick으로 이벤트객체를 받아오려 했으니 CopyClipBoard 컴포넌트는 onClick을 지원하지 않는것 같다. 이벤트가 undefined가 뜬다.
아마 이벤트 객체는 은닉화 되어있는 것 같은 느낌이 든다.
그리고 stackoverflow에서 보니깐 stopPropagation을 사용할 때는 사용해도 아무 문제 없다고 답변을 달았다. 무조건 지양하는것은 근시안적인 사고방식이라면서 말이다.
음 일단, 근데 여기서 처럼 stopPropagation을 사용할 수 밖에 없는 경우가 있으니 드림코딩의 논리는 설득력이 떨어진다.
// ColorBox.js
<CopyToClipboard text={background} onCopy={this.changeCopyState}>
{!isSingleColor && (
<Link to={`${moreUrl}`} onClick={e => e.stopPropagation()}>
<span className={`see-more ${moreButton}`}>MORE</span>
</Link>
)}
<CopyToClipboard/>
이렇게 하면 CopyToClipboard에 걸려있는 onCopy이벤트가 작동되지 않는다. 이벤트가 더이상 bubbling하지 않기 때문
MiniPalette에서 삭제버튼 구현시 부모 anchor에 걸린 default 이벤트까지 실행되는 이슈
2번처럼 stopPropagation을 해보았으나 a태그가 이미 실행이 되고 난 뒤에 작동하는 거 같다.
pointer-events:none
도 시도해보았으나 실패다. 왜냐면 이건 클릭 target본인이나 자식 요소만 적용가능하기 때문.
근데, preventDefault를 해주니 깔끔하게 해결이 되었다!
음... 삭제버튼은 svg인데 그게 버블링 되어서 anchor까지 도달한다. 즉, 삭제에서의 e.preventDefault
는 부모 anchor에 까지 도달하여 적용되는 것인가?
코드를 보자.
// PaletteList.js
const palettes = paletteList.map(palette => (
<Link key={uuid()} to={`palette/${palette.id}`}>
<MiniPalette removePalette={removePalette} key={uuid()} {...palette} />
</Link>
));
// MiniPalette.js
const handleDelete = evt => {
evt.preventDefault();
removePalette(id);
};
return (
<div className={root}>
<Delete className={deleteIcon} onClick={handleDelete} />
<div className={colorsClass}>{miniColorBoxes}</div>
<h5 className={title}>
{paletteName} <span className={emojiClass}>{emoji}</span>
</h5>
</div>
);
아래의 사진을 보면 좀 더 자세하게 알 수 있을 것이다.
instantValidate
라는 prop이 있다는 것을 발견했고 false값을 준 결과 해결하였다!그럼 일단 코드부터 보자
// createNewPaltte.style.js
const AppBar = styled(MuiAppBar, {
shouldForwardProp: prop => prop !== 'open',
})(({ theme, open }) => {
return {
transition: theme.transitions.create(['margin', 'width'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
// 아래의 코드는 무엇을 의미할까??
...(open && {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: `${drawerWidth}px`,
transition: theme.transitions.create(['margin', 'width'], {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
}),
};
});
AppBar의 모습이다.
근데 open뒤에 코드가 조금 생소해보인다.
무슨뜻이냐면, createNewPalette에서 open state를 받아와서 true일 경우 open뒤에 있는 {...}를 모두 풀어서 펼치라는 뜻이다.
classNames
라고 잘 입력해주자! 그리고 거기에 해당하는 클래스이름도 오타없이 잘 적어주고exit
이 붙고 새로운 컴포넌트에는 enter
가 붙는다.근데 addRandomColor를 호출할 때 마다 colors가 바뀌므로 removeColor도 새롭게 호출되어 draggableColorBox에 전달된다.
두 함수를 철저하게 분리시키는 방법은 dependency를 분리하는 방법인데 어쩔 수 없이 colors를 참조할 수 밖에 없으니... 그래서 결국 class 컴포넌트로 다 바꾸고 두 함수는 메소드와 시켜서 constructor에 bind시켜주었더니 removeColor의 reference를 그대로 유지할 수 있게 되었다. 고로, draggableColorBox를 삭제할 때 위치가 바뀌지 않은 colorbox는 리랜더링 되지 않는 것을 확인 할 수 있었다.
왜냐면 state를 고립시켜야할때도 있고, 확장시켜야 할 때도 있다.
예를들어, Palette.js에서 level이나 format같은 state는 부모 컴포넌트 , 즉 Palette.js에서 관리하는게 맞다. 왜냐면 level이나 format은 그 밑에 컴포넌트까지 공유하는 state이기 때문. 그러나 Colorbox안에 있는 copied state는 Colorbox안에서 관리한다. ColorBox안에서만 통용되기 때문. 아니면 state의 사용 범위가 꽤나 넓을 것 같다고 판단되면 context를 쓰는 것도 괜찮은 방법이다.
state를 변형시키는 방법이 굉장히 다양하다고 한다면(todo App같은 경우) useReduce를 활용해도 괜찮다.
자식과 부모의 setState가 차례대로 실행될 경우 자식의 setState가 실행되지 않았다. NavBar에서 Hook을 사용하여 snackBar와 Slider(color format을 선택할 수 있는 dropdown)를 구현하려고 했다.
Slider에서 format을 선택하면 onSelectFormat
가 실행된다. setFormat,setOpen이 실행되면서 slider의 value가 바뀌고 snackBar가 open된다. 그리고 부모 컴포넌트, 즉 Palette.js에서 format이 바뀌고 리랜더링 된다. 즉 3번 리랜더링 된다는 의미이다.
// NavBar.js
const { changeLevel, changeFormat, isSingleColor } = props;
const [open, setOpen] = useState(false);
const [formatState, setFormat] = useState('hex');
const onSelectFormat = e => {
setFormat(e.target.value); //
setOpen(true); //
changeFormat(e.target.value); // 부모로 부터 내려온 메소드
};
//Palette.js
changeFormat(format) {
this.setState({ format });
}
근데, setFormat,setOpen이 실행되지 않는다... 혹시나 해서, changeFormat을 지워버리니 잘 작동한다. 역시 부모에서 리랜더링 될때 나머지 두개의 setState가 덮어씌어진 느낌이 든다. 아! 이거 이전 포스팅에서 설명한적이 있다.
//NavBar.js
const onSelectFormat = e => {
setOpen(true);
setFormat(e.target.value);
setTimeout(() => {
changeFormat(e.target.value);
}, 500);
};
아아아아!! 근데 setTimeOut안쓰고도 state가 유지되는 법을 알았다. 바로 NavBar에 key를 없애주면 된다.... 아니 정확히 말하면 key에 uuid값을 매번 새로 보내주고 있었다. 그러니깐 랜더링 될때 uuid값이 매번 변경되므로 다른 컴포넌트로 인식해서 NavBar도 완전 새로고침 되지 않을까하는 추측을 해본다.(key='NavBar'라고 하니깐 state값이 그대로 유지된다!)
그럼 NavBar안에 있는 format state가 새로 랜더링 되어 원래대로 돌아가지 않고 그대로 유지 된다. CreateColorPicker에서 submit을 하고 나서도 colorName state가 유지되는것을 발견하였고 차이점이 뭔지 살펴보다가 알아냈다...
이에 대한 자세한 내용은 아티클로 따로 뽑았으니 참고!
// CreateColorPicker.js
const {
addColor,
isPaletteFull,
colors,
isColorBoxEditing,
editingBoxInfo,
editColorBoxEnd,
} = props;
const [currentColor, setCurrentColor] = useState(
isColorBoxEditing ? editingBoxInfo.name : 'purple'
);
따라서 useEffect를 이용하려고 했다. dependency로는 editingBoxInfo값을 이용하려고 했으나 obj이기 때문에 매번 colorPicker가 변경되었다.
class 컴포넌트인 createNewPalette안에 있는 editingBoxInfo obj를 memoization하려고 찾아보았다. 그러나 마땅한 방법이 없었다
그래서 editingBoxInfo값을 JSON.stringify 해서 넘겨주거나 editingBoxInfo안에 있는 name, color를 dependency로 사용하기로 했다!
const palettes = paletteList.map(palette => (
<CSSTransition key={palette.id} timeout={500} classNames="PaletteItem">
<Link to={`palette/${palette.id}`}>
<MiniPalette
key={palette.id}
{...palette} // colors:[..blahblah]
openDeleteDialog={this.handleDialogOpen}
/>
</Link>
</CSSTransition>
));
miniPalette은 memo로 저장되어있다.
그래서 MiniPalette
으로 넘어가는 props의 reference는 똑같이 유지가 되어야 한다. 그러나 map안에서 cb function의 parameter는 reference가 똑같이 유지되는건가? palette안에 colors라는 key가 있는데 value는 array이다. 그럼 Minipalette으로 넘어갈때마다 다른 ref로 된 array가 넘어가서 새로 랜더링 될 수 있는데 리랜더링 되지 않는다.
paletteList가 useState안에 있어서 그런가? useState는 ref까지 자동으로 고정시켜주나? 알아봐야겠다.
display: inline-block;
이라고 하면 wrap이랑 똑같은 효과가 나타난다. 왜냐면 span처럼 inline처리가 되기 때문이다.
.copy-overlay
에서 width,height 다 100% 정해놓고 .copy-overlay.show
일때 position을 absolute로 바꿈으로써 다른 color-box를 변경시키지 않고 전 화면을 깔끔하게 다 덮게 할 수 있는것이다.
.copy-overlay {
opacity: 0;
z-index: 0;
width: 100%;
height: 100%;
transition: transform 0.6s ease-in-out;
transform: scale(0.1);
}
.copy-overlay.show {
opacity: 1;
transform: scale(50);
z-index: 10;
position: absolute;
}
border-radius에 의해서 코너가 삐죽 튀어나왔을때 overflow:hidden
이라고 해주면 깔끔하게 정리된다. nth-child로 코너에 있는 dom만 선별해서 border-radius를 일일이 설정해주지 않아도 된다.
{height:100%}
라고 설정했는데도 마지막 줄에 있는 box가 짤리는 버그가 있었다.강의보고 따라하지 말고 일단 전체적인 숲을 먼저 머릿속에 그려놓자.
그리고 커밋 보면서 코드를 쭉 훑어보자.
그리고 이해한 맥락을 토대로 내가 코드를 짜본다.
그리고 pseudo code를 적고 깃헙에 올리면 더더욱 좋다.
핵심은 내가 직접 생각해서 나만의 방식대로 말해보는거다.
seedColor를 보면 colt가 JSON 데이터를 설계한 흔적을 엿볼 수 있다. Palette,PaletteList,ColorBox와 같은 컴포넌트를 만들때 어떠한 데이터가 필요하고 그 데이터가 어떻게 정리되어야할지 분명 고민했을것이고 그러한 고민의 결과가 바로 seedColor이다.
나도 앱을 만들때 아래와 같은 흐름으로 사고해보자(top-down방식)