TypeScript & Context APIのdefaultValueの書き方(use***がうまく機能しない時)
TypeScriptを書くようになって約6ヶ月経ちました。
初投稿&走り書きとなるので今後新たな知見を得たらカイゼンしていこうと思います。
背景
ReactのContext APIを使ってProviderコンポーネントを作成し、その後useContext
を各コンポーネントで使用する代わりにカスタムHookを作成しそれぞれのコンポーネントで使用する、というケースです。
エラーの内容
CounterProvider
の中で作成したuseCount
というHookを使おうとした時、下記のエラーが発生しました。
与えられた型にcount
プロパティが存在しません!
※8/11追記: こちらのエラーが表示されるようになったのはyarn create next-app app_name --typescript
をした際にtsconfig.json
のstrict
がtrue
になるからでした。
as
をつかって型を与える方法
import React, { createContext, useState, useContext, ReactNode } from 'react';
interface ContextInterface {
count: number;
}
const CountContext = createContext({} as ContextInterface);
// Type Assertionで型を与える
// Providerなど省略
export const useCount = () => useContext(CountContext);
function Test() {
const { count } = useCount();
// エラーが発生しない
return (
<div>
<h1>{count}</h1>
</div>
);
}
まずはType AssertionでdefaultValueに対し型を与える方法です。この場合、useContext
(今回はカスタムフックであるuseCount
)をエラーを生じさせずに使用することができます。
ただし、この書き方だと下記の問題が発生します
<>
{/*useCountを使用しているコンポーネントがCounterProviderの外にある*/}
<Test />
<CountProvider>
{/*useCountを使用しないコンポーネント*/}
<SomeComponent />
</CountProvider>
</>
もしなんらかの理由でuseCount
を使いたいコンポーネントがCountProvider
の外にあっても何のエラーも表示されません。 上の場合<Test />
コンポーネント内部でuseCount
を使っていますが、CountProvider
の外に位置するため正しくバリューを取得できず、且つエラーも表示されません。
undefinedをdefaultValueにする
👆の解決策としては下記のコードを書きます。
const CountContext = createContext<ContextInterface | undefined>(undefined);
このままだと同じくProperty 'count' does not exist on type 'ContextInterface | undefined'.ts(2339)
という警告が出てしまうので、undefined
のチェックをカスタムフック内に書きます。
export const useCount = () => {
const context = useContext(CountContext);
if (context === undefined) {
throw new Error('useCount must be used within a CountProvider');
}
return context;
};
こちらの書き方だと警告が消え、さらにCountProvider
の外でuseCont
(useContext(CountContext)
)を使用するとエラーを表示させることができます。
サンプルコード全文
import React, { createContext, useState, useContext, ReactNode } from 'react';
interface ContextInterface {
readonly count: number;
}
interface Props {
children: ReactNode;
}
const CountContext = createContext<ContextInterface | undefined>(undefined);
const CountProvider = (props: Props) => {
const [count, setCount] = useState(0);
return (
<CountContext.Provider value={{ count }}>
{props.children}
</CountContext.Provider>
);
};
const useCount = () => {
const context = useContext(CountContext);
if (context === undefined) {
throw new Error('useCount must be used within a CountProvider');
}
return context;
};
const HomePage = () => {
return (
<section>
<CountProvider>
<Test />
</CountProvider>
</section>
);
};
function Test() {
const { count } = useCount();
return (
<div>
<h1>{count}</h1>
</div>
);
}
export default HomePage;
Zennでの初投稿となります!間違いや「もっとこうした方が良いよ!」という点がありましたらぜひコメントを頂けると学びになります!
参考記事:
Discussion