: Recoil은 React 전용 State Management Library이다.
• Redux는 flux pattern의 등장으로 단방향으로 처리 되던 리액트의 단점을 변화시킴
• 외부 저장공간을 사용 → 그러다 보니 강한제약이 생김 → 장점은 많지만 번거로움
• Recoil을 사용해서 state를 전역으로 관리해주면 코드가 굉장히 깔끔해짐
• React 내부 상태만 이용
• 작은 Atom 단위로 관리
• 순수함수 Selector
• Re-Render 최소화(상태를 변경하면 Atom을 참조하는 component만 re-render함)
• 데이터의 흐름을 따라서
(Recoil의 상태들은 상호 의존성을 가지고 있는데, 데이터 흐름에 따라서 여러 상태들의 연관된 컴포넌트를 유기적으로 관리 가능)
• 읽기전용, 쓰기전용 별도로 제공(설명은 하단에)
→ 이런 구조가 있다는 가정하에!
import {atom} from 'recoil'
export const modalState = atom({ // atom이라는 함수에 key와 기본값 설정해줌
key: 'modalState',
default: 'false',
});
// 해당 atom 호출하는 방법(React의 hook과 유사)
// ModalState
import {useRecoilState} from 'recoil';
import {modalState} from '../store/atoms'
function List({data}) { // hooks처럼 호출해서 사용
const [modlaOpen, setModalOpen] = useRecoilState(modalState);
const handleRegister = () => {
setModalOpen(true);
};
return (
<button onClick = {handleRegister}>상품 추가</article>
{modalOpen && (<RegisterModal />)}
)
};
finction RegisterModal() {
const [modalOpen, setModalOpen] = useRecoilState(modalState);
const handleClick = () => {
setModalOpen(false);
};
...
};
// 해당 atom 호출하는 방법
// List State
import {atom} from 'recoil'
export const productState = atom({
key:'productState',
default: [],
});
function ProductList() {
const list = useRecoilValue(productsState); // 읽기 전용
return list && (
<div className="product-list">
{list.map((row) => (<Peoduct key={row.idx} data={row />}
))}
</div>
);
};
function Product({data}) {
return (
data && (
<article className = "product">
<img src={data.img} alt={data.name} />
<dl>
<dt>[{data.brand}] {data.name}</dt>
<dt>{data.category}</dt>
<dt>{data.price}원</dt>
</dl>
)
)
};
function RegisterModal() {
const [formData, serFormData] = useState(); // (formData는 특히 외부로 공유될 필요가 없어서 굳이 recoil 사용 안함)
const setList = useSetRecoilState(productsState); // 쓰기 전용
const setIsOpen = useStateRecoilState(modalState); //쓰기전용
const handleChange = (e) => { // Form 요소의 변경된 내부 state에 담아서
setFormData({
...formData,
[e.target.name]: e.target,value,
});
};
const handleSubmit = async(e) => { // List형 atom에 insert하는 로직
e.preventDefault();
setList((prev) => [...prev, formData]);
setIsOpen(false);
}; // → 쓰기전용
};
export const productState = atom({ // 상품을 담을 수 있는 atom 정의
key: 'productState',
default:{
idx: 0,
name:'',
category:'',
brand:'',
price: 0,
desc:'',
},
});
function Product({data}) {
const list = useRecoilValue(productsState);
const setProduct = useSetRecoilState(productState);
...
const handleDetail = (idx) => { // list component에서 각 행을 클릭하면 idx 값을 내려 받아서, list에 있는 data중 idx와 일치하는 값을 찾아서
setProduct(list.filter((row) => row.idx === idx)[0]); // product atom에 담음
setRegisterOpen(true);
};
return(
data && (
<article
className = "product"
onClick ={()=> handleDetail(data.idx)}>
...
</article>
)
);
}
function RegisterModal() {
const [list, setList] = useRecoilState(productsState);
const product = useRecoilValue(productState); // 앞에 담아온 product state를 불러와서 input의 초기값으로 설정
const resetProduct = useRecoilState(productState);
...
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target,value,
});
};
const handleSubmit = async(e) => {
e.preventDefault();
if(product){ // 데이터가 있는지 여부에 따라서 modal팝업이 등록 팝업인지, 수정팝업인지 분기처리를 해줌
let newList = list.map((row) =>{
if(row.idx === product.idx){
return product;
} else {
return row;
}
)};
setList(newList);
}
setIsOpen(false);
};
};
// 컴포넌트가 사라질때 해당 값을 초기화 해줘야함, recoil은 전역상태라서 특정값으로 변경되지 않는 이상, 이전 값을 유지함
// 그래서 recoil에서는 reset함수를 추가로 제공
useEffect(() => { // useEffect를 통해서 컴포넌트가 unmount되는 시점에서 값을 초기화
return () => {
restProduct();
};
}, []);
...
<input
type="text"
name="name"
defaultValue={product? .name} // 초기화 하지 않으면 향후 신규 등록시 기존 데이터가 그대로 불러와지는 오류 발생
onChange={handleChange}
/>
출처: FEConf Korea YouTube