오랜만에 리액트를 해서 그런지... 재밌어서 매일 밤을 새가며 개발을 하고 있다.
아직은 백엔드에서 기술 구현이 진행되는 중이라 프론트에서 처리할 데이터가 없다. 따라서 퍼블리싱, 이벤트 처리, 화면 전환 효과 등과 같은 부가적인 것들을 먼저 처리해보기로 했다.
이번에 도전하는 가장 큰 이벤트 효과는 바로 ..!
요즘 왠만한 사이트들은 다 있다는 다크모드 , 우리 오랑우톡도 질 수 없기 때문에 진작에 UI 짤 때부터 다크모드 버전도 짜놓았었다.
<다크모드 UI>
사실 다크모드는 내 욕심이기도 했다. '자바스크립트 코드레시피'
라는 책을 풀다가 다크모드와 관련된 챕터가 나왔는데, 그 챕터를 풀면서 너무 재밌었어서 꼭 한 번 구현해보고 싶었다.
말을 한 사람이 책임을 져야하는 법.. 다크모드는 내가 책임지고 구현을 하기로 했다.
삽질만 약 3시간 넘게 했다.
열심히 삽질하면서 내가 이걸 왜 말을 뱉었을까.. 후회도 하긴 했다ㅋ
그래서 결론부터 말하자면, 구현이 운좋게 잘 되었다.
이전까지 나는 리액트도 겨우겨우 쓰던 사람이라 redux 쪽은 프론트 오빠가 주로 맡아서 상태관리를 했고, 나는 그냥 값을 받아서 state 로 페이지 내에서 관리, 사용하는 정도만 했었다.
그러나 이번엔 redux 가 아닌 recoil 을 도입해서 사용하는건 어떻겠냐는 제안을 받았고,
이번엔 나도 도전해보기로 했다.
대강 찾아보고 공부해보니, state와 매우 유사해 state 쓰는 것처럼 쓰면 되고 props 로 아래로만 전달되는 것이 아니라 전역적으로 불러서 쓸 수 있는데, redux보다는 훨씬 쉬웠다 !
그래서 나는 recoil 을 사용해서 다크버전을 구현해보았다.
구글링을 해보니, recoil 을 사용한 다크버전 예시 포스팅이 딱 하나가 있었는데, 그건 타입스크립트를 사용한거라 참고하기가 좀 어려웠다.
그래서 그냥 골머리 싸매면서 하루종일 recoil로 다크모드만 잡고 있던 끝에 결국 구현했다.
1. 사용자가 서비스를 켠다. (사용자가 처음 서비스를 켤시에는 무조건 light 모드)
2. 사용자가 다크버전 버튼을 누른다.
3. 색상이 반전되면서 다크버전으로 화면구성이 바뀐다.
4. 다시 버튼을 누르면 라이트 모드가 된다. (버튼 누를 때마다 현재 모드의 반대 모드로 가야한다.)
처음 생각을 했을 때, 그냥 순수 css/js 로도 되겠는데? 하고 생각을 했지만. . . styled-component 로 이미 퍼블리싱이 완료된 상태라 무조건 styled-component 에 적용시킬 생각을 하면서 흐름을 짰다.
1. 버튼 클릭시 toggle 이벤트가 실행된다.
2. toggle 이벤트는 recoil 에서 가져온 현재 state 상태를 변환시킨다. (현재 state 상태의 반대로.)
3. globalcss.js 에 현재 state 값을 가져온다.
4. props 로 styled-component 에 현재 state 상태를 적용.
5. toggle 될 때마다 상태가 바뀌면서 다크-화이트 모드를 번갈아가며 적용되게 한다.
import { atom } from 'recoil';
// 라이트 모드 속성 저장
export const LightState = atom({
key: 'light',
default: {
mode: 'light',
bgColor: 'white',
textColor: '#4B5364',
// 채팅 리스트 배경색
bgColor2: '#F4F4F4',
// 채팅 글씨색
textColor2: '#4B5364',
},
});
// 다크 모드 속성 저장
export const DarkState = atom({
key: 'dark',
default: {
mode: 'dark',
bgColor: '#383737',
bgColor2: 'white',
// 채팅 리스트 배경색
textColor: 'white',
// 채팅 글씨색
textColor2: '#4B5364',
},
});
// 현재 모드 속성 저장
export const modeState = atom({
key: 'isMode',
// 디폴트는 라이트 모드 속성.
default: LightState,
});
처음에는 theme.js 를 따로 만들어 props 로 최상위에 전달해서 globalcss 에서 사용하려고 했는데, recoil 에 배열 값으로 세팅하고 불러서 쓰는 게 훨씬 간단할 것 같아 recoil 에 배열 형태로 컬러까지 모두 세팅해주었다.
LightState 와 DarkState 는 각각 Light 모드일 때와 Dark 모드일 때의 mode 이름과 색상값들을 저장해놓았다.
modeState 는 현재 모드를 저장한다. default 값을 LightState 로 저장해두어 사용자가 버튼을 누르지 않는 처음 들어올 때의 경우에는 무조건 Light 모드가 되도록 했다.
const Btn = styled.button`
width: 80px;
height: 40px;
font-size: 1.5rem;
font-family: 'Kakao-Regular';
border-radius: 20px;
border-style: none;
background-color: ${(props) => props.textColor};
color: ${(props) => props.bgColor};
position: absolute;
top: 30px;
right: 30px;
`;
function ToggleBtn() {
// 버튼에 들어갈 텍스트 값
const [BtnName, setBtnName] = useState('DARK');
// 현재 mode 판별 state
const [Theme, setTheme] = useRecoilState(modeState);
// lightmode 배열 값 불러오기
const lightmode = useRecoilValue(LightState);
// darkmode 배열 값 불러오기
const darkmode = useRecoilValue(DarkState);
// 버튼 누를 시 이벤트 처리 현재 테마의 상태를 변경해준다.
const toggle = () => {
// light 면 dark 로
if (Theme === lightmode) {
setTheme(darkmode);
setBtnName('LIGHT');
// dark 면 light로
} else {
setTheme(lightmode);
setBtnName('DARK');
}
};
const current = useRecoilValue(modeState);
const bgColor = current.bgColor;
const textColor = current.textColor;
// 현재 어떤 mode 인지 버튼 이름에 출력
return (
<Btn onClick={toggle} bgColor={bgColor} textColor={textColor}>
{BtnName}
</Btn>
);
}
export default ToggleBtn;
버튼을 누를 때마다 lightmode 면 darkmode로, darkmode면 light 모드로 setTheme 을 통해 state 값을 계속 변경하게 했다.
현재 state 값을 current 에 저장하고, current 에는 배열이 들어가므로 bgColor와 textColor 에 필요한 값들을 저장해주었다.
누를 때마다 버튼의 색깔이 바뀌어야 하기 때문에 props 형태로 Btn 컴포넌트에 전달해주었다.
추가로 Light 모드일 때는 Dark 글씨가, Dark 모드일 때는 Light 글씨가 뜨게 하기 위해 따로 state 를 설정해 처리해주었다.
이렇게 해서 state 관리는 끝났다. 이제 다양한 컴포넌트에서 useRecoilValue 를 사용해 recoil 에서 state 값을 불러 사용하기만 하면 된다.
html{
/* 폰트 기본값 설정 */
font-size: 12px;
font-family: "Kakao-Regular";
// props 로 받은 색 적용
color : ${(props) => props.textColor};
background-color : ${(props) => props.bgColor};
}
`;
const GlobalCss = () => {
// 현재 모드 가져오기
const current = useRecoilValue(modeState);
// 현재 모드의 배경색 저장
const bgColor = current.bgColor;
// 현재 모드의 텍스트 색 저장
const textColor = current.textColor;
// props 형태로 색깔 전달
return <GlobalStyle bgColor={bgColor} textColor={textColor} />;
};
export default GlobalCss;
이번에 전역적으로 상태관리하는 것도 처음해봤고, recoil 도 처음 써보았고, 다크모드를 react 에서 심지어 styled-component 로 해보는 것은 처음이라 코드 구성이 정말 헷갈렸다. 그래서 그런지 코드가 그렇게 효율적이지는 못한 것 같다. 허허
삽질만 몇 시간을 하면서 만들어냈지만 아직은 조금 조잡한 감이 없지 않아 있다. (새로고침하면 모드 풀림, 무조건 Light-Dark 세트로 recoil 에 color 값을 등록해줘야함. 매우 귀찮음.)
시간이 나면 더 공부해서 조잡한 점들을 고쳐보아야겠다.
앞으로의 무쓸모톤도 파이팅 !