React에는 다음과 같은 한계가 존재
Recoli은 직교하지만 본질적인 방향 그래프를 정의하고 React 트리에 붙인다. 상태 변화는 이 그래프의 뿌리(aotms라고 부르는)로부터 순수함수를 거쳐 컴포넌트로 흐르는 다음과 같은 접근 방식을 따른다.
npm install recoil
우선 cra에 있는 App.js에서 RecoliRoot
를 감싸주어야함
import { RecoilRoot } from 'recoil';
function App() {
return (
<div className="App">
<RecoilRoot>
</RecoilRoot>
</div>
);
}
export default App;
store.js
//atom 선언
import { atom } from 'recoil';
export const fontSizeState = atom({
key: "fontSzieState",
default: 14,
});
FontButton.jsx
import React from 'react'
import { useRecoilState } from 'recoil'
import { fontSizeState } from './store'
export default function FontButton() {
const [fontSize,setFontSize] = useRecoilState(fontSizeState);
return (
<button onClick={() => setFontSize((size)=> size+1)} style={{fontSize}}>
Click to Enlarge
</button>
)
}
Text.jsx
import React from 'react'
import { useRecoilState } from 'recoil'
import { fontSizeState } from './store'
export default function Text() {
const [fontSize,setFontSize] = useRecoilState(fontSizeState);
return (
<p style={{fontSize}}>This text will increase in size too.</p>
)
}
import { RecoilRoot } from "recoil";
import FontButton from "./components/RecoilExeample/FontButton";
import Text from "./components/RecoilExeample/Text";
function App() {
return (
<div className="App">
<RecoilRoot>
<FontButton />
<Text />
</RecoilRoot>
</div>
);
}
export default App;
클릭 시 버튼 사이즈와 text 사이즈가 커지게 된다.
RecoilRoot
로 인해 provider
처럼 최상위 루트에서 자식들끼리 다른 component임에도 불구하고 똑같이 상태 변하는 것을 알 수 있다.
store.js
import { atom, selector } from "recoil";
export const fontSizeState = atom({
key: "fontSzieState",
default: 14,
});
export const fontSizeLabelState = selector({
key: "fontSizeLabelState",
get: ({ get }) => {
const fontSize = get(fontSizeState);
const unit = "px";
return `${fontSize}${unit}`;
},
});
FontButton.jsx
import React from 'react'
import { useRecoilState } from 'recoil'
import { fontSizeLabelState, fontSizeState } from './store'
export default function FontButton() {
const [fontSize,setFontSize] = useRecoilState(fontSizeState);
const [fontSizeLabel,setfontSizeLabel] = useRecoilState(fontSizeLabelState);
return (
<>
<div> Current font size:{fontSizeLabel}</div>
<button onClick={() => setFontSize((size)=> size+1)} style={{fontSize}}>
Click to Enlarge
</button>
</>
)
}
selector는 atom이나 다른 값들이 바뀌면 자기도 같이 바뀌는데 무엇인가 더하거나 그 값을 변경을 취해서 보여준다.
CounterStore.js
import { atom, selector } from "recoil";
export const textState = atom({
key: "textState", //이 아이디는 유니크해야함
default: "",
});
export const charCountState = selector({
key: "charCountState",
get: ({ get }) => {
const text = get(textState);
return text.length;
},
});
CharacterCounter.jsx
import React from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { charCountState, textState } from './CounterStore'
export default function CharacterCounter() {
return (
<div>
<TextInput/>
<CharacterCount/>
</div>
)
}
function TextInput(){
const [text,setText] = useRecoilState(textState);
const onChange = (event) =>{
setText(event.target.value);
}
return(
<div>
<input type="text" value={text} onChange={onChange}/>
<br/>
Echo:{text}
</div>
)
}
function CharacterCount(){
const count = useRecoilValue(charCountState);
return <>Character Count:{count}</>
}
useRecoliState는 get/set를 같이 가지고 있지만 useRecoilValue는 값만가져와서 사용한다.
msw를 이용하여 비동기 api 연결하기
handlers.js
import { rest } from "msw";
export const handlers = [
rest.put("http://localhost:3000/api/user-name", async (req, res, ctx) => {
const id = req.url.searchParams.get("id");
return res(
ctx.json({
name: id === "1" ? "The one" : "The others",
})
);
}),
];
CureentUserInfo.jsx
import axios from 'axios';
import React from 'react'
import { atom, selector, useRecoilValue } from 'recoil'
import ErrorBoundary from './ErrorBoundary';
const currentUserIdState = atom({
key:'CurrentUserID',
default:1,
})
const tableOfUser=[{name:'park'},{name:'min'}];
// const currentUserNameState = selector({
// key:'CurrentUserName',
// get:({get})=>{
// return tableOfUser[get(cureentUserIdState)].name;
// },
// })
const currentUserNameQuery = selector({
key:'CurrentUserName',
get: async({get})=>{
const response = await axios.get(
`/api/user-name?id=${get(currentUserIdState)}`
)
return response.data.name;
}
})
function CurrentUser() {
const userName = useRecoilValue(currentUserNameQuery);
return (
<div>
{userName}
</div>
)
}
export default function CureentUserInfo(){
return (
<React.Suspense fallback={<div>Loading...</div>}>
<CurrentUser/>
</React.Suspense>
)
}
React.Suspense는 currentUserNameQuery
의 값으로 내려오게 되면 fallback으로 promise 요청중인 상태라면 그 외의 fallback의 상태를 보여준다. username
이라는 값이 결정되기 전에 fallback으로 대신하여 상태를 전달하여 보여줌
errorboundary와 유사하다.
Errorboundary로 error 처리
ErrorBoundary.jsx
import React from 'react';
export default class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 다음 렌더링에서 폴백 UI가 보이도록 상태를 업데이트 합니다.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 에러 리포팅 서비스에 에러를 기록할 수도 있습니다.
// logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 폴백 UI를 커스텀하여 렌더링할 수 있습니다.
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
CureentUserInfo.jsx
import axios from 'axios';
import React from 'react'
import { atom, selector, useRecoilValue } from 'recoil'
import ErrorBoundary from './ErrorBoundary';
const currentUserIdState = atom({
key:'CurrentUserID',
default:2,
})
const tableOfUser=[{name:'park'},{name:'min'}];
// const currentUserNameState = selector({
// key:'CurrentUserName',
// get:({get})=>{
// return tableOfUser[get(cureentUserIdState)].name;
// },
// })
const currentUserNameQuery = selector({
key:'CurrentUserName',
get: async({get})=>{
const response = await axios.get(
`/api/user-name?id=${get(currentUserIdState)}`
)
return response.data.name;
}
})
function CurrentUser() {
const userName = useRecoilValue(currentUserNameQuery);
return (
<div>
{userName}
</div>
)
}
export default function CureentUserInfo(){
return (
<ErrorBoundary>
<React.Suspense fallback={<div>Loading...</div>}>
<CurrentUser/>
</React.Suspense>
</ErrorBoundary>
)
}
error시에는 아래 문구가 나온다.