react-quill
quill은 유명한 위지위그 에디터로 많은 사람들이 애용하고 있는 오픈소스다.
react-quill 라이브러리를 사용하면 리액트로도 손쉽게 quill 에디터를 설정하여 사용할 수 있다.
next.js에서 react-quill을 설정했을 때 문제가 되었던 점
next.js은 ssr과 csr을 아주 훌륭하게 지원하지만, 양 측 환경 또한 신경써주어야 한다.
단순 react로 모든 컴포넌트를 짠다고 가정할 경우, 대부분의 상황에서는 CSR만 고려해도되므로 DOM에서 동작하는 라이브러리를 사용하더라도 손쉽게 사용할 수 있지만, next.js에서 DOM을 활용하는 라이브러리를 사용할 때는 SSR 단계에서 컴포넌트의 렌더링을 막거나 라이브러리 자체의 import를 막아야하는 경우가 생긴다.
react-quill을 next.js에서 에러없이 사용하려면 ssr단계에서는 import를 막아야한다.
react-quill 라이브러리 자체가 DOM의 document 필요로 하기 때문인데,
ssr 환경에서는 document 객체가 없기 때문에 서버사이드에서 document is not defined 에러를 내뿜는다.
이를 방지하기 위한 기능으로 next.js에서는 dynamic 함수를 제공한다.
dynamic import는 Lazy Loading과 관련있다.
Lazy Loading은 로드하는데 꽤나 많은 시간을 잡아먹는 라이브러리들을 비동기적으로 호출해서 사용자로 하여금 기다리는 시간을 지루하지 않게 해주는 효과가 있다.
https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading
Dynamic
const ComponentC = dynamic(() => import('../components/C'), { ssr: false })
dynamic는 import할 컴포넌트 또는 라이브러리와 기타 옵션들을 인수로 받는다.
몇몇 옵션들이 있지만 내가 사용할 옵션은 { ssr: false } 였다.
ssr : false 옵션을 사용하면 ssr 단계에서 해당 라이브러리를 import 하는 것을 막아준다.
ssr로 인한 에러가 없도록 아래와 같이 라이브러리를 import 했다.
interface ForwardedQuillComponent extends ReactQuillProps {
forwardedRef: React.Ref<ReactQuill>
}
const QuillNoSSRWrapper = dynamic(
async () => {
const { default: QuillComponent } = await import('react-quill')
const Quill = ({ forwardedRef, ...props }: ForwardedQuillComponent) => (
<QuillComponent ref={forwardedRef} {...props} />
)
return Quill
},
{ loading: () => <div>...loading</div>, ssr: false },
)
dynamic 함수의 2번째 인수인 옵션 객체에서 loading 옵션과 ssr 옵션을 추가했다.
그리고 Quill 자체에 ref type이 정의되어 있지않아 ref 타입을 덮어씌워서 리턴했다.
// ...
const quillRef = useRef<ReactQuill>(null)
const imageHandler = () => {
let input = document.createElement('input')
input.setAttribute('type', 'file')
input.setAttribute('accept', 'image/*')
input.click()
input.addEventListener('change', async () => {
if (quillRef.current) {
const editor = quillRef.current.getEditor()
const cursorPosition = editor.getSelection()!
editor.insertEmbed(
cursorPosition.index,
'image',
'https://cdn.pixabay.com/photo/2023/09/14/19/14/landscape-8253576_1280.jpg',
)
editor.setSelection(cursorPosition.index + 1, 0)
}
})
}
const modules = useMemo(
() => ({
toolbar: {
container: toolbarOptions,
handlers: { image: imageHandler },
},
}),
[],
)
// ...
return (
<div className={cn(className)} {...props}>
{isMounted ? (
<QuillNoSSRWrapper
forwardedRef={quillRef}
className="h-[500px]"
theme="snow"
modules={modules}
formats={formats}
onChange={onChangeContent}
/>
) : (
<div>...loading</div>
)}
</div>
)
// ...
위는 react-quill의 구현에 관련있는 코드를 가져온 것이다.
이미지를 첨부할 때 이벤트를 가로챈 뒤, 첨부된 이미지를 백엔드와 연동해서 url로 바꾸기 위해 imageHandler를 커스터마이징 했다.
modules 객체는 useMemo를 통해 값이 메모이제이션될 수 있도록 했는데,
에디터에 타이핑을 할 경우, quill-editor 컴포넌트가 리렌더링 되기 때문에 imageHandler 함수도 다시 계산된다.
그러므로 에디터와 modules의 연결이 타이핑을 할때마다 끊기게 된다.
이는 전혀 의도한 바가 아니므로 useMemo를 이용해 modules 객체를 메모이제이션해서 연결이 끊기지 않도록 조치했다.
'개발 일지' 카테고리의 다른 글
컴포넌트 사이즈 설정 (0) | 2023.11.16 |
---|---|
젠장할 소프트 네비게이션 (1) | 2023.11.07 |
(nextjs) shadcn/ui를 활용한 signin 컴포넌트 개발 (1) | 2023.10.20 |
(express, front) 회원가입 로직 개발 (이메일 인증, 중복 체크 등) (0) | 2023.08.08 |
Context와 Redux의 리렌더링에 관한 고찰 (0) | 2023.08.04 |