環境與套件
"next-mdx-remote": "^4.1.0",
"@stitches/react": "^1.2.8",
"next": 12.3.0,
"react": 18.2.0
也適用於另一款 MDX 套件:
"mdx-bundler": 9.0.1
MDX 的好處是把 Markdown 加上各種想要的元件,達到容易維護又能高度客製化。
之前已經在試用 Remix 時體驗過,而最近要用到 Next.js + TypeScript 的時候,遇到許多型別錯誤的問題。翻閱 Google 和 Stackoverflow 許久都沒什麼幫助,只好向社群求助,記錄在這篇文章。
接下來的內容
- 以 MDX 的元件取代 Markdown 產生的標籤
- 只有
children
的 TypeScript 設定 - 圖片與超連結標籤的 TypeScript 設定
以 MDX 的元件取代 Markdown 產生的標籤
MDX 可以把 Markdown 的語法以 React 元件取代(MDX 說明文件),next-mdx-remote 和 mdx-bundler 也有。
const mdxComponents = {
h2: Heading2,
h3: Heading3,
p: ArticleP,
ul: ArticleUL,
ol: ArticleOL,
a: ArticleLink,
img: ArticleImage,
blockquote: ArticleBlockquote,
hr: () => <Divider position="article" />
};
// ...
<MDXRemote {...source} components={mdxComponents} />
還有一種方法是直接加入元件,並放在 .mdx
內文即可使用,更有彈性。
const mdxComponents = {
LyricSection
};
只有 children
的 TypeScript 設定
從 TypeScript 語法提示得知,傳入的 props 型別只要依照 HTML 的標準即可。標籤的 children
大多是選填,因此要加上問號:
interface ChildrenProps {
children?: React.ReactNode;
}
const Paragraph = styled('p', {
'&:not(blockquote p)': {
fontSize: '$20',
lineHeight: '$32'
}
});
export default function ArticleP({ children }: ChildrenProps) {
return (<Paragraph>{children}</Paragraph>);
};
圖片與超連結標籤的 TypeScript 設定
圖片與超連結就不是單純的只有 children
,因此遇到了 TypeScript 型別錯誤。
圖片 <img>
Next.js 有非常方便的 next/image
,但是,文章內的圖片寬高都不是固定的。而每個圖片都另外做 React 元件,都失去了 Markdown 的便利性。因此就把 Markdown 產生的 <img>
取代為 <img loading="lazy">
。
圖片語法可以傳入 3 個屬性:src
、alt
和 title
:
![alt 取代文字](src "title")
它們都是選填的,因此設定型別時得:
interface ImageProps {
src?: string;
alt?: string;
title?: string;
}
next/link 超連結
使用 Next.js 時,通常是先用 next/link
包住 <a>
,並以 passHref
傳遞 href
。
然而,href
在 next/link
是必填,但原生 HTML 語法是選填,使用以下的型別設定
interface ArticleLinkProps {
children?: React.ReactNode;
href: string;
}
就會出現型別錯誤:
Type '{ a: ({ children, href }: ArticleLinkProps) => JSX.Element; }' is not assignable to type 'MDXComponents'.
Type '{ a: ({ children, href }: ArticleLinkProps) => JSX.Element; }' is not assignable to type '{ a?: keyof IntrinsicElements | Component<DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>> | undefined; ... 173 more ...; view?: keyof IntrinsicElements | ... 1 more ... | undefined; }'
# ...還有一大串
在前端社群問到的方法:
// ArticleLink.tsx
import type { ComponentProps } from 'react';
import Link from 'next/link';
import { StyledLink } from './styled';
export default function ArticleLink({ children, href }: ComponentProps<'a'>) {
if (!href) {
throw new Error('should pass href as props')
}
return (
<Link href={href} passHref>
<StyledLink>{children}</StyledLink>
</Link>
);
};
Props 的型別設定為 ComponentProps<'a'>
,並在沒有 href
的情況丟出錯誤即可。
點選 Codesandbox 試試看。