LoginSignup
8
5

More than 5 years have passed since last update.

Reactビギナーズガイドをtypescriptで勉強し直してわかったこと④【optionalなpropsをdefaultPropsで上書きしてるのにコンパイルエラー】

Posted at

いよいよTypescriptで「Reactビギナーズガイド」を勉強し直すこの取り組みも終盤に近づいて参りました。
whinepadの作成に取り掛かっているのですが、やはり一筋縄ではなかなか行かないです。
今回ハマったのはこちら

defaultPropsでoptionalなpropsを上書きしているにも関わらずエラー

Dialogtest.tsx
import * as React from 'react';

interface DialogProps {
    message: string;    
    onAction?(str: string): void;
}
class Dialogtest extends React.Component<DialogProps, {}> {
    public static defaultProps = {
        onAction: (str: string) => {window.console.log(str + 'default'); },
    };
    render() {
        return (
            <div className="DialogFooter">{
                <span
                    className="DialogDismiss"
                    onClick={this.props.onAction.bind(this, 'hello')}
                >{this.props.message}
                </span>
            }
            </div>
        );
    }
}
export default Dialogtest;

この
onClick={this.props.onAction.bind(this, 'dismiss')}

[ts] オブジェクトは 'undefined' である可能性があります。
というエラーが表示されてしまいコンパイル通りませんでした。
プロパティで渡されるonAction関数オブジェクトがinterfaceでオプショナル指定があるため、エラーしちゃってました。

ちなみに呼び出し側はこちら

index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
import './index.css';
import Dialogtest from './whinepad/Dialogtest';

ReactDOM.render(
  <div>
    <Dialogtest message="hello" onAction={(str) => { window.console.log(str); }} />
  </div>,  
  document.getElementById('root') as HTMLElement
);
registerServiceWorker();

カスタムコンポーネントに対してカスタムイベントをコールバックでアタッチしています。
このonActionを渡す時と渡さない時がある時になかなかうまく処理できませんでした。

type assertionが必要

ちゃんとdefaultPropsでoverwriteしているのに、おっかしいなあ、と悩んで色々調べたところ
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/11640
こちらに正解?を上げてくれている方がおりました。

どうもdefaultPropsはランタイムで認識されるため
tsconfig.jsonで
"strictNullChecks": true
となっている場合は現状はコンパイルエラーしてしまう模様。
type assertionが必要でした。

正解から書くと、

Dialogtest.tsx
import * as React from 'react';

interface DialogProps {
    message: string;
    onAction?(str: string): void;
}
interface DefaultDialogProps {
    onAction(str: string): void;
}
type PropsWithDefault = DialogProps & DefaultDialogProps;

class Dialogtest extends React.Component<DialogProps, {}> {
    public static defaultProps = {
        onAction: (str: string) => {window.console.log(str + 'default');  },
    };
    render() {
        const {message, onAction} = this.props as PropsWithDefault;
        return (
            <div className="DialogFooter">{
                <span
                    className="DialogDismiss"
                    onClick={onAction.bind(this, 'hello')}
                >{message}
                </span>
            }
            </div>
        );
    }
}
export default Dialogtest;

型エイリアスを使ってasでassertion(cast)する方法があるような感じでした。
確かにこれでエラーも表示されなくなりました。

交差型なのでinterfaceの継承でも同じことができます。

Dialogtest.tsx
import * as React from 'react';

interface DialogProps {
    message: string;
    onAction?(str: string): void;
}
interface DefaultDialogProps extends DialogProps {
    onAction(str: string): void;
}

class Dialogtest extends React.Component<DialogProps, {}> {
    static defaultProps = {
        onAction: (str: string) => {window.console.log(str + 'default'); },
    };
    render() {
        const {message, onAction} = this.props as DefaultDialogProps;
        return (
            <div className="DialogFooter">{
                <span
                    className="DialogDismiss"
                    onClick={onAction.bind(this, 'hello')}
                >{message}
                </span>
            }
            </div>
        );
    }
}
export default Dialogtest;

いずれにしても結構面倒ですがtypescriptだんだんわかってきました。
もう少しいい方法がないか模索中。

8
5
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5