{"id":1085,"date":"2022-05-09T08:12:05","date_gmt":"2022-05-09T07:12:05","guid":{"rendered":"https:\/\/greladesign.co\/blog\/?p=1085"},"modified":"2023-03-24T07:34:58","modified_gmt":"2023-03-24T06:34:58","slug":"typescript-custom-type-guards","status":"publish","type":"post","link":"https:\/\/greladesign.co\/blog\/2022\/05\/09\/typescript-custom-type-guards\/","title":{"rendered":"TypeScript: custom type guards"},"content":{"rendered":"\n<p>Type guards are tools for <a href=\"https:\/\/www.typescriptlang.org\/docs\/handbook\/2\/narrowing.html\">type narrowing<\/a> in TypeScript. You may have used some of those features e.g. <code>typeof<\/code>, <code>instanceof<\/code>, <code>in<\/code> etc. already but I want to describe writing guards for types created by you.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2>Type Predicate<\/h2>\n\n\n\n<p>To create a custom guard we need a function that returns a <em>type predicate<\/em>, it is in the form of <code>param is Type<\/code>. There is no naming convention, but I use the <code>guard<\/code> prefix which explicitely indicates the purpose of the function (as opposed to <code>is<\/code> prefix).<\/p>\n\n\n\n<p>In the guard function you need to make all the required assessments on incoming object and return that result. <\/p>\n\n\n\n<p>Below is the example implementation:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">interface<\/span> INavigationAction {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">type<\/span>: <span class=\"hljs-built_in\">string<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    path: <span class=\"hljs-built_in\">string<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> guardINavigationAction = (test: unknown): test is INavigationAction =&gt; {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> has = <span class=\"hljs-function\">(<span class=\"hljs-params\"><span class=\"hljs-params\">prop<\/span>: <span class=\"hljs-params\">keyof<\/span> <span class=\"hljs-params\">INavigationAction<\/span><\/span>) =&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-built_in\">Object<\/span>.prototype.hasOwnProperty.call(test, prop);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">typeof<\/span> test === <span class=\"hljs-string\">\"object\"<\/span> \n<\/span><\/span><span class='shcb-loc'><span>        &amp;&amp; !!test \n<\/span><\/span><span class='shcb-loc'><span>        &amp;&amp; has(<span class=\"hljs-string\">'type'<\/span>) \n<\/span><\/span><span class='shcb-loc'><span>        &amp;&amp; has(<span class=\"hljs-string\">'path'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-built_in\">console<\/span>.log(guardINavigationAction({<span class=\"hljs-keyword\">type<\/span>:<span class=\"hljs-string\">\"Something\"<\/span>})); <span class=\"hljs-comment\">\/\/ false<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-built_in\">console<\/span>.log(guardINavigationAction({path:<span class=\"hljs-string\">\"a\/path\"<\/span>})); <span class=\"hljs-comment\">\/\/ false<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-built_in\">console<\/span>.log(guardINavigationAction({<span class=\"hljs-keyword\">type<\/span>:<span class=\"hljs-string\">\"Something\"<\/span>, path:<span class=\"hljs-string\">\"a\/path\"<\/span>})); <span class=\"hljs-comment\">\/\/ true<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-built_in\">console<\/span>.log(guardINavigationAction({<span class=\"hljs-keyword\">type<\/span>:<span class=\"hljs-string\">\"Something\"<\/span>, path:<span class=\"hljs-string\">\"a\/path\"<\/span>, extraParam: <span class=\"hljs-literal\">true<\/span>})); <span class=\"hljs-comment\">\/\/ true<\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2>Usage<\/h2>\n\n\n\n<p>It is handy in all those places where the type narrowing is needed, e.g when union type is used:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">interface<\/span> IPrintAction {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">type<\/span>: <span class=\"hljs-built_in\">string<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    orientation: <span class=\"hljs-string\">'PORTRAIT'<\/span> | <span class=\"hljs-string\">'LANDSCAPE'<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> action:IPrintAction | INavigationAction;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">if<\/span>(guardINavigationAction(action)) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ navigate somewhere<\/span>\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">else<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ print it<\/span>\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>but also in places where you dont know exactly what data is coming e.g. from the API.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">interface<\/span> IResponseData {\n<\/span><\/span><span class='shcb-loc'><span>    prop1: <span class=\"hljs-built_in\">number<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    prop2: <span class=\"hljs-built_in\">string<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> guardIResponseData = (test: unknown): test is IResponseData =&gt; {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> has = <span class=\"hljs-function\">(<span class=\"hljs-params\"><span class=\"hljs-params\">prop<\/span>: <span class=\"hljs-params\">keyof<\/span> <span class=\"hljs-params\">IResponseData<\/span><\/span>) =&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-built_in\">Object<\/span>.prototype.hasOwnProperty.call(test, prop);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">typeof<\/span> test === <span class=\"hljs-string\">\"object\"<\/span> &amp;&amp; !!test\n<\/span><\/span><span class='shcb-loc'><span>           &amp;&amp; has(<span class=\"hljs-string\">'prop1'<\/span>) &amp;&amp; has(<span class=\"hljs-string\">'prop2'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> response: Response = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">'api\/path'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> response.json(); <span class=\"hljs-comment\">\/\/ data has `any` type<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">if<\/span>(guardIResponseData(data)) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ it is IResponseData<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.log(data.prop1);\n<\/span><\/span><span class='shcb-loc'><span>} <span class=\"hljs-keyword\">else<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ some other type<\/span>\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As shown above type guards are simple yet very useful tools in the programmers arsenal.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Type guards are tools for type narrowing in TypeScript. You may have used some of those features e.g. typeof, instanceof, in etc. already but I want to describe writing guards for types created by you.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_import_markdown_pro_load_document_selector":0,"_import_markdown_pro_submit_text_textarea":""},"categories":[356],"tags":[358,357],"_links":{"self":[{"href":"https:\/\/greladesign.co\/blog\/wp-json\/wp\/v2\/posts\/1085"}],"collection":[{"href":"https:\/\/greladesign.co\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/greladesign.co\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/greladesign.co\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/greladesign.co\/blog\/wp-json\/wp\/v2\/comments?post=1085"}],"version-history":[{"count":14,"href":"https:\/\/greladesign.co\/blog\/wp-json\/wp\/v2\/posts\/1085\/revisions"}],"predecessor-version":[{"id":1142,"href":"https:\/\/greladesign.co\/blog\/wp-json\/wp\/v2\/posts\/1085\/revisions\/1142"}],"wp:attachment":[{"href":"https:\/\/greladesign.co\/blog\/wp-json\/wp\/v2\/media?parent=1085"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/greladesign.co\/blog\/wp-json\/wp\/v2\/categories?post=1085"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/greladesign.co\/blog\/wp-json\/wp\/v2\/tags?post=1085"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}