From 0375deaa442c9e2b0b97d1e315f5818061e3ae7a Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sun, 8 Dec 2019 04:44:08 -0800 Subject: [PATCH 01/57] CompletedTodo --- snippets/snippets/todo/oone.ts | 6 ++++ snippets/snippets/todo/rlya.ts | 11 ++++++ src/lib/snippets.ts | 19 ++++++++++ src/pages/todo.tsx | 66 ++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 snippets/snippets/todo/oone.ts create mode 100644 snippets/snippets/todo/rlya.ts diff --git a/snippets/snippets/todo/oone.ts b/snippets/snippets/todo/oone.ts new file mode 100644 index 0000000..dcd9a2e --- /dev/null +++ b/snippets/snippets/todo/oone.ts @@ -0,0 +1,6 @@ +// Returns an array where "done" is all true +function completeAll( + todos: readonly Todo[] +): CompletedTodo[] { + // ... +} diff --git a/snippets/snippets/todo/rlya.ts b/snippets/snippets/todo/rlya.ts new file mode 100644 index 0000000..be88630 --- /dev/null +++ b/snippets/snippets/todo/rlya.ts @@ -0,0 +1,11 @@ +type Todo = Readonly<{ + id: number + text: string + done: boolean +}> + +type CompletedTodo = Readonly<{ + id: number + text: string + done: true +}> diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 93deb01..f13c033 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -614,6 +614,13 @@ type Todo = Readonly<{ done: boolean }>` +export const oone = `// Returns an array where "done" is all true +function completeAll( + todos: readonly Todo[] +): CompletedTodo[] { + // ... +}` + export const qaqa = `type Foo = { bar: number } @@ -636,6 +643,18 @@ export const reel = `function toggleTodo(todo) { } }` +export const rlya = `type Todo = Readonly<{ + id: number + text: string + done: boolean +}> + +type CompletedTodo = Readonly<{ + id: number + text: string + done: true +}>` + export const szan = `// Make input todos as readonly array function completeAll( todos: readonly Todo[] diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 0ad9e6d..aa60a16 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1068,6 +1068,72 @@ const Page = () => ( ) }, + { + title: ( + <> + The CompletedTodo type + + ), + content: ( + <> +

+ Take a look at the following code. In addition to the{' '} + Todo type, we’ve defined a new type called{' '} + CompletedTodo. +

+ + (lineIndex === 9 && tokenIndex >= 0 && tokenIndex <= 4) || + (lineIndex === 6 && tokenIndex >= 0 && tokenIndex <= 3) + } + /> +

+ The new CompletedTodo is almost identical to{' '} + Todo, except it has done: true instead + of done: boolean.{' '} + + In TypeScript, you can use exact values (like{' '} + true or false) when specifying a type. + +

+

+ We can now specify the return type of completeAll(){' '} + to be an array of CompletedTodo’s: +

+ + lineIndex === 3 && tokenIndex >= 2 && tokenIndex <= 6 + } + /> +

+ By doing this, TypeScript will force you to return an array of + todo items where done is true—if not, it + will result in a compile error. +

+ + ) + }, + { + title: <>Intersection types, + content: ( + <> + +

Question - there seems to be some duplicate code

+ + ) + } + ]} + /> + + ) + }, underConstructionCard ]} /> From 9308118087d32ebadb9db49f6c6910a5f5f19ec8 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Mon, 9 Dec 2019 00:41:18 -0800 Subject: [PATCH 02/57] Intersection types --- snippets/snippets/todo/qnwc.ts | 11 ++++ snippets/snippets/todo/rmuo.ts | 10 +++ snippets/snippets/todo/wdjp.ts | 11 ++++ snippets/snippets/todo/xrwn.ts | 13 ++++ src/components/Emoji/A.tsx | 16 +++++ src/components/Emoji/Ampersand.tsx | 15 +++++ src/components/Emoji/B.tsx | 16 +++++ src/components/Emoji/index.tsx | 8 ++- src/lib/snippets.ts | 49 ++++++++++++++ src/pages/todo.tsx | 101 +++++++++++++++++++++++++++-- 10 files changed, 245 insertions(+), 5 deletions(-) create mode 100644 snippets/snippets/todo/qnwc.ts create mode 100644 snippets/snippets/todo/rmuo.ts create mode 100644 snippets/snippets/todo/wdjp.ts create mode 100644 snippets/snippets/todo/xrwn.ts create mode 100644 src/components/Emoji/A.tsx create mode 100644 src/components/Emoji/Ampersand.tsx create mode 100644 src/components/Emoji/B.tsx diff --git a/snippets/snippets/todo/qnwc.ts b/snippets/snippets/todo/qnwc.ts new file mode 100644 index 0000000..dd316e0 --- /dev/null +++ b/snippets/snippets/todo/qnwc.ts @@ -0,0 +1,11 @@ +// They booth have property x, +// but B’s x (true) is +// more specific than A’s x (boolean) +type A = { x: boolean } +type B = { x: true } + +// This intersection type… +type AandB = A & B + +// …is equivalent to: +type AandB = { x: true } diff --git a/snippets/snippets/todo/rmuo.ts b/snippets/snippets/todo/rmuo.ts new file mode 100644 index 0000000..ac11604 --- /dev/null +++ b/snippets/snippets/todo/rmuo.ts @@ -0,0 +1,10 @@ +type Todo = Readonly<{ + id: number + text: string + done: boolean +}> + +// Override the done property of Todo +type CompletedTodo = Todo & { + readonly done: true +} diff --git a/snippets/snippets/todo/wdjp.ts b/snippets/snippets/todo/wdjp.ts new file mode 100644 index 0000000..26ddee3 --- /dev/null +++ b/snippets/snippets/todo/wdjp.ts @@ -0,0 +1,11 @@ +type A = { a: number } +type B = { b: string } + +// This intersection type… +type AandB = A & B + +// …is equivalent to: +type AandB = { + a: number + b: string +} diff --git a/snippets/snippets/todo/xrwn.ts b/snippets/snippets/todo/xrwn.ts new file mode 100644 index 0000000..96ef30b --- /dev/null +++ b/snippets/snippets/todo/xrwn.ts @@ -0,0 +1,13 @@ +type Todo = Readonly<{ + // id and text are the same as CompletedTodo + id: number + text: string + done: boolean +}> + +type CompletedTodo = Readonly<{ + // id and text are the same as Todo + id: number + text: string + done: true +}> diff --git a/src/components/Emoji/A.tsx b/src/components/Emoji/A.tsx new file mode 100644 index 0000000..e666576 --- /dev/null +++ b/src/components/Emoji/A.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +const SvgA = (props: React.SVGProps) => ( + + + + +) + +export default SvgA diff --git a/src/components/Emoji/Ampersand.tsx b/src/components/Emoji/Ampersand.tsx new file mode 100644 index 0000000..18829ea --- /dev/null +++ b/src/components/Emoji/Ampersand.tsx @@ -0,0 +1,15 @@ +import React from 'react' + +const SvgAmpersand = (props: React.SVGProps) => ( + + + + + +) + +export default SvgAmpersand diff --git a/src/components/Emoji/B.tsx b/src/components/Emoji/B.tsx new file mode 100644 index 0000000..704481d --- /dev/null +++ b/src/components/Emoji/B.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +const SvgB = (props: React.SVGProps) => ( + + + + +) + +export default SvgB diff --git a/src/components/Emoji/index.tsx b/src/components/Emoji/index.tsx index 48b3c66..44b29e2 100644 --- a/src/components/Emoji/index.tsx +++ b/src/components/Emoji/index.tsx @@ -17,6 +17,9 @@ import UpdatedUI from 'src/components/Emoji/UpdatedUi' import Sweat from 'src/components/Emoji/Sweat' import TransformTypechecked from 'src/components/Emoji/TransformTypechecked' import Readonly from 'src/components/Emoji/Readonly' +import A from 'src/components/Emoji/A' +import B from 'src/components/Emoji/B' +import Ampersand from 'src/components/Emoji/Ampersand' export const emojiToComponent = { bird: Bird, @@ -35,7 +38,10 @@ export const emojiToComponent = { updatedData: UpdatedData, sweat: Sweat, transformTypechecked: TransformTypechecked, - readonly: Readonly + readonly: Readonly, + a: A, + b: B, + ampersand: Ampersand } export const EmojiWrapper = ({ diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index f13c033..0df0341 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -636,6 +636,18 @@ function toggleTodo(todo) { // ... }` +export const qnwc = `// They booth have property x, +// but B’s x (true) is +// more specific than A’s x (boolean) +type A = { x: boolean } +type B = { x: true } + +// This intersection type… +type AandB = A & B + +// …is equivalent to: +type AandB = { x: true }` + export const reel = `function toggleTodo(todo) { return { text: todo.text, @@ -655,6 +667,17 @@ type CompletedTodo = Readonly<{ done: true }>` +export const rmuo = `type Todo = Readonly<{ + id: number + text: string + done: boolean +}> + +// Override the done property of Todo +type CompletedTodo = Todo & { + readonly done: true +}` + export const szan = `// Make input todos as readonly array function completeAll( todos: readonly Todo[] @@ -701,6 +724,18 @@ function toggleTodo(todo) { // { id: …, text: '…', done: true } }` +export const wdjp = `type A = { a: number } +type B = { b: string } + +// This intersection type… +type AandB = A & B + +// …is equivalent to: +type AandB = { + a: number + b: string +}` + export const wymp = `const originalTodo = { id: 1, text: '…', @@ -718,6 +753,20 @@ console.log(originalTodo) console.log('New Todo:') console.log(newTodo)` +export const xrwn = `type Todo = Readonly<{ + // id and text are the same as CompletedTodo + id: number + text: string + done: boolean +}> + +type CompletedTodo = Readonly<{ + // id and text are the same as Todo + id: number + text: string + done: true +}>` + export const yhto = `type Todo = { readonly id: number readonly text: string diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index aa60a16..f132d7c 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1108,9 +1108,12 @@ const Page = () => ( } />

- By doing this, TypeScript will force you to return an array of - todo items where done is true—if not, it - will result in a compile error. + By doing this,{' '} + + TypeScript will force you to return an array of todo items where{' '} + done is true + + —if not, it will result in a compile error.

) @@ -1125,12 +1128,102 @@ const Page = () => ( type: 'chickEgg', children: ( <> -

Question - there seems to be some duplicate code

+

+ Question: There seems to be some + duplicate code between Todo and{' '} + CompletedTodo. Can we refactor this? +

) } ]} /> +

+ Good question, Little Duckling! If you look closely,{' '} + Todo and CompletedTodo have identical{' '} + id and text types. +

+ + lineIndex === 2 || + lineIndex === 3 || + lineIndex === 9 || + lineIndex === 10 + } + /> +

+ We can de-duplicate the code by using a TypeScript feature called{' '} + intersection types. +

+

+ In TypeScript, you can use the & sign to create + an intersection type of two types. +

+ + & creates an{' '} + intersection type of two types. + + } + /> +

+ + The intersection type A & B is a type that has{' '} + all of the properties of A and{' '} + B. + {' '} + Here’s an example: +

+ + lineIndex === 4 && tokenIndex >= 7 + } + /> +

+ Furthermore,{' '} + + if the second type is more specific than the first type, the + second type overrides the first. + {' '} + Here’s an example: +

+ + lineIndex === 10 && tokenIndex >= 7 && tokenIndex <= 13 + } + /> +

+ We can apply this idea to update the definition of{' '} + CompletedTodo. We’ll define{' '} + CompletedTodo using Todo like this: +

+ + (lineIndex === 7 && tokenIndex >= 7) || + lineIndex === 8 || + lineIndex === 9 + } + /> +

+ By doing the above, you can define CompleteTodo to + have the same properties as Todo except for{' '} + done—without duplicating code. +

+

+ Summary:{' '} + + Just like JavaScript has boolean operators like{' '} + &&, TypeScript has{' '} + type operators like & which + lets you combine two types. + +

) }, From e92038f6149120e7e7a660a62a7e5a9d7b2d2056 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Mon, 9 Dec 2019 13:45:38 -0800 Subject: [PATCH 03/57] Implement completeAll() --- snippets/snippets/todo/hszk.ts | 8 ++++ snippets/snippets/todo/okva.ts | 6 +++ snippets/snippets/todo/whae.ts | 9 ++++ src/components/ResultHighlight.tsx | 1 + src/lib/snippets.ts | 26 +++++++++++ src/pages/todo.tsx | 71 +++++++++++++++++++++++++++++- 6 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 snippets/snippets/todo/hszk.ts create mode 100644 snippets/snippets/todo/okva.ts create mode 100644 snippets/snippets/todo/whae.ts diff --git a/snippets/snippets/todo/hszk.ts b/snippets/snippets/todo/hszk.ts new file mode 100644 index 0000000..4933986 --- /dev/null +++ b/snippets/snippets/todo/hszk.ts @@ -0,0 +1,8 @@ +function completeAll( + todos: readonly Todo[] +): CompletedTodo[] { + return todos.map(todo => ({ + ...todo, + done: true + })) +} diff --git a/snippets/snippets/todo/okva.ts b/snippets/snippets/todo/okva.ts new file mode 100644 index 0000000..77bf9e7 --- /dev/null +++ b/snippets/snippets/todo/okva.ts @@ -0,0 +1,6 @@ +console.log( + completeAll([ + { id: 1, text: '…', done: false }, + { id: 2, text: '…', done: true } + ]) +) diff --git a/snippets/snippets/todo/whae.ts b/snippets/snippets/todo/whae.ts new file mode 100644 index 0000000..c99e1c1 --- /dev/null +++ b/snippets/snippets/todo/whae.ts @@ -0,0 +1,9 @@ +function completeAll( + todos: readonly Todo[] +): CompletedTodo[] { + return todos.map(todo => ({ + ...todo, + // What if we set done to false? + done: false + })) +} diff --git a/src/components/ResultHighlight.tsx b/src/components/ResultHighlight.tsx index 8044988..ca125d7 100644 --- a/src/components/ResultHighlight.tsx +++ b/src/components/ResultHighlight.tsx @@ -35,6 +35,7 @@ const ResultHighlight = ({ children }: { children: string }) => { {...tokenProps} css={css` font-style: normal !important; + white-space: pre-wrap; `} > {children} diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 0df0341..1989109 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -514,6 +514,15 @@ function toggleTodo(todo: Todo): Todo { // ... }` +export const hszk = `function completeAll( + todos: readonly Todo[] +): CompletedTodo[] { + return todos.map(todo => ({ + ...todo, + done: true + })) +}` + export const jkjo = `// By default, the properties of Todo are // NOT read-only type Todo = { @@ -614,6 +623,13 @@ type Todo = Readonly<{ done: boolean }>` +export const okva = `console.log( + completeAll([ + { id: 1, text: '…', done: false }, + { id: 2, text: '…', done: true } + ]) +)` + export const oone = `// Returns an array where "done" is all true function completeAll( todos: readonly Todo[] @@ -736,6 +752,16 @@ type AandB = { b: string }` +export const whae = `function completeAll( + todos: readonly Todo[] +): CompletedTodo[] { + return todos.map(todo => ({ + ...todo, + // What if we set done to false? + done: false + })) +}` + export const wymp = `const originalTodo = { id: 1, text: '…', diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index f132d7c..0ed1373 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -559,7 +559,7 @@ const Page = () => ( />

- Try pressing to see if it compiles! + Try pressing to see if it compiles!

(

Before toggleTodo()… @@ -1227,6 +1226,74 @@ const Page = () => ( ) }, + { + title: ( + <> + Finally implementing completeAll() + + ), + content: ( + <> +

+ We’re finally ready to implement completeAll(). + Here’s the code— + + try pressing + + ! +

+ lineNumber >= 3 && lineNumber <= 6} + /> +

+ It compiled! Let’s run this function on an example todo list.{' '} + + Press : + +

+ + {`[ + { id: 1, text: '…', done: true }, + { id: 2, text: '…', done: true } +]`} + + } + /> +

+ done all became true, as expected! +

+

+ Now, let’s see what happens if we make a mistake and set{' '} + done to false: +

+ + Types of property 'done' are incompatible. +
+ Type 'false' is not assignable to type 'true'. + + } + resultError + shouldHighlight={lineNumber => lineNumber === 6} + shouldHighlightResult={lineNumber => lineNumber === 6} + /> +

+ It failed because CompletedTodo must have{' '} + done: true. Once again, TypeScript caught an error + early. +

+ + ) + }, underConstructionCard ]} /> From af3948b6dd038298b602d1cbd0bbe6cf3b24cd01 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Mon, 9 Dec 2019 15:58:40 -0800 Subject: [PATCH 04/57] Summary and section headers --- src/components/Card.tsx | 66 ++++++++++++++++++++++++---- src/components/CardHeadingText.tsx | 33 ++++++++++++++ src/components/CardSubtitleText.tsx | 24 ++++++++++ src/components/CardTitleText.tsx | 24 ++++++++++ src/components/ContentTags/H3.tsx | 26 ----------- src/components/ContentTags/index.tsx | 1 - src/components/Emoji/DoneTrue.tsx | 21 +++++++++ src/components/Emoji/index.tsx | 4 +- src/components/PostPage.tsx | 32 ++++++++------ src/lib/theme/colors.ts | 1 + src/pages/todo.tsx | 47 +++++++++++++++++++- 11 files changed, 228 insertions(+), 51 deletions(-) create mode 100644 src/components/CardHeadingText.tsx create mode 100644 src/components/CardSubtitleText.tsx create mode 100644 src/components/CardTitleText.tsx delete mode 100644 src/components/ContentTags/H3.tsx create mode 100644 src/components/Emoji/DoneTrue.tsx diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 20531ae..813882b 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -3,15 +3,19 @@ import { css, jsx } from '@emotion/core' import React from 'react' import { allColors } from 'src/lib/theme/colors' import useTheme from 'src/hooks/useTheme' -import { H3 } from 'src/components/ContentTags' +import CardTitleText from 'src/components/CardTitleText' +import CardSubtitleText from 'src/components/CardSubtitleText' +import CardHeadingText from 'src/components/CardHeadingText' export interface CardProps { children: React.ReactNode - color?: 'default' | 'pink' | 'green' + color?: 'default' | 'pink' | 'green' | 'darkGreen' slideNumber?: number slideCount?: number isLast?: boolean title?: React.ReactNode + heading?: React.ReactNode + subtitle?: React.ReactNode footer?: { content: React.ReactNode color?: CardProps['color'] @@ -28,7 +32,8 @@ export const backgroundColor = ( ({ default: 'lightYellow1' as const, pink: 'lightPink2' as const, - green: 'lightGreen' as const + green: 'lightGreen' as const, + darkGreen: 'darkGreen' as const }[color]) const Card = ({ @@ -38,9 +43,11 @@ const Card = ({ slideNumber, slideCount, isLast, - footer + footer, + heading, + subtitle }: CardProps) => { - const { ns, colors, fontSizes, spaces, radii } = useTheme() + const { ns, colors, fontSizes, spaces, radii, lineHeights } = useTheme() return ( <>
+ {subtitle && ( + + {subtitle} + + )} {title && ( -

{title} -

+ + )} + {heading && ( + + {heading} + )} {children}
diff --git a/src/components/CardHeadingText.tsx b/src/components/CardHeadingText.tsx new file mode 100644 index 0000000..2150f5e --- /dev/null +++ b/src/components/CardHeadingText.tsx @@ -0,0 +1,33 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core' +import useTheme from 'src/hooks/useTheme' + +const CardHeadingText = (props: JSX.IntrinsicElements['h4']) => { + const { fontSizes, maxWidths, lineHeights, ns, spaces } = useTheme() + return ( +
+

+

+ ) +} + +export default CardHeadingText diff --git a/src/components/CardSubtitleText.tsx b/src/components/CardSubtitleText.tsx new file mode 100644 index 0000000..f5c7e15 --- /dev/null +++ b/src/components/CardSubtitleText.tsx @@ -0,0 +1,24 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core' +import useTheme from 'src/hooks/useTheme' + +const CardSubtitleText = (props: JSX.IntrinsicElements['h4']) => { + const { fontSizes, lineHeights, ns, spaces } = useTheme() + return ( +

+ ) +} + +export default CardSubtitleText diff --git a/src/components/CardTitleText.tsx b/src/components/CardTitleText.tsx new file mode 100644 index 0000000..342aa56 --- /dev/null +++ b/src/components/CardTitleText.tsx @@ -0,0 +1,24 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core' +import useTheme from 'src/hooks/useTheme' + +const CardTitleText = (props: JSX.IntrinsicElements['h3']) => { + const { fontSizes, lineHeights, ns, spaces } = useTheme() + return ( +

+ ) +} + +export default CardTitleText diff --git a/src/components/ContentTags/H3.tsx b/src/components/ContentTags/H3.tsx deleted file mode 100644 index 410d2d4..0000000 --- a/src/components/ContentTags/H3.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/** @jsx jsx */ -import { css, jsx } from '@emotion/core' -import useTheme from 'src/hooks/useTheme' - -const H3 = (props: JSX.IntrinsicElements['h3']) => { - const { fontSizes, lineHeights, ns, spaces } = useTheme() - return ( -

- ) -} - -export default H3 diff --git a/src/components/ContentTags/index.tsx b/src/components/ContentTags/index.tsx index 4d0b9c3..70a8b9f 100644 --- a/src/components/ContentTags/index.tsx +++ b/src/components/ContentTags/index.tsx @@ -1,5 +1,4 @@ export { default as P } from 'src/components/ContentTags/P' -export { default as H3 } from 'src/components/ContentTags/H3' export { default as Code } from 'src/components/ContentTags/Code' export { default as Highlight } from 'src/components/ContentTags/Highlight' export { default as Hr } from 'src/components/ContentTags/Hr' diff --git a/src/components/Emoji/DoneTrue.tsx b/src/components/Emoji/DoneTrue.tsx new file mode 100644 index 0000000..eba2668 --- /dev/null +++ b/src/components/Emoji/DoneTrue.tsx @@ -0,0 +1,21 @@ +import React from 'react' + +const SvgDoneTrue = (props: React.SVGProps) => ( + + + + + + + + + +) + +export default SvgDoneTrue diff --git a/src/components/Emoji/index.tsx b/src/components/Emoji/index.tsx index 44b29e2..413a66d 100644 --- a/src/components/Emoji/index.tsx +++ b/src/components/Emoji/index.tsx @@ -20,6 +20,7 @@ import Readonly from 'src/components/Emoji/Readonly' import A from 'src/components/Emoji/A' import B from 'src/components/Emoji/B' import Ampersand from 'src/components/Emoji/Ampersand' +import DoneTrue from 'src/components/Emoji/DoneTrue' export const emojiToComponent = { bird: Bird, @@ -41,7 +42,8 @@ export const emojiToComponent = { readonly: Readonly, a: A, b: B, - ampersand: Ampersand + ampersand: Ampersand, + doneTrue: DoneTrue } export const EmojiWrapper = ({ diff --git a/src/components/PostPage.tsx b/src/components/PostPage.tsx index 69a96f2..b7509c4 100644 --- a/src/components/PostPage.tsx +++ b/src/components/PostPage.tsx @@ -16,6 +16,8 @@ export interface EpisodeCardType { content?: React.ReactNode footer?: CardProps['footer'] color?: CardProps['color'] + heading?: React.ReactNode + subtitle?: React.ReactNode } const PostPage = ({ @@ -103,19 +105,23 @@ const PostPage = ({ margin-bottom: ${spaces(6)}; `} > - {cards.map(({ title, content, footer, color }, index) => ( - - {content} - - ))} + {cards.map( + ({ title, content, footer, color, heading, subtitle }, index) => ( + + {content} + + ) + )} diff --git a/src/lib/theme/colors.ts b/src/lib/theme/colors.ts index 326e64e..0bc9daa 100644 --- a/src/lib/theme/colors.ts +++ b/src/lib/theme/colors.ts @@ -14,6 +14,7 @@ export const allColors = { white: '#FFFFFF', white85: 'rgba(255, 255, 255, 0.85)', paleGreen: '#C8DCC7', + darkGreen: '#009795', red: '#DB4003', yellowHighlight: '#FFFF00', yellowHighlightTransparent: 'rgba(255, 255, 0, 0)' diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 0ed1373..ec8ce6d 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -132,10 +132,33 @@ const Page = () => ( goal is to make you want to learn more. +

+ There are 4 sections total in this article. Here + are the topics covered in each section: +

+
    + + Section 1: Types, Read-only Properties, and + Mapped Types + + + Section 2: Array Types, Literal Types, and + Intersection Types + +

Let’s get started!

) }, + { + title: ( + <> + Section 1 of 4 + + ), + heading: <>Types, Read-only Properties, and Mapped Types, + color: 'darkGreen' + }, { title: <>Transform data into UI, content: ( @@ -819,6 +842,7 @@ const Page = () => ( }, { color: 'green', + subtitle: <>Section 1 Summary:, title: <>Types are like lightweight, automatic unit tests, content: ( <> @@ -895,6 +919,15 @@ const Page = () => ( ) }, + { + title: ( + <> + Section 2 of 4 + + ), + heading: <>Array Types, Literal Types, and Intersection Types, + color: 'darkGreen' + }, { title: <>Mark all as completed, content: ( @@ -1092,10 +1125,20 @@ const Page = () => ( Todo, except it has done: true instead of done: boolean.{' '} - In TypeScript, you can use exact values (like{' '} + In TypeScript, you can use exact values (like{' '} true or false) when specifying a type. - + {' '} + This is called literal types.

+ + You can use exact values when specifying a type. This is + called literal types. + + } + />

We can now specify the return type of completeAll(){' '} to be an array of CompletedTodo’s: From 440a60c195ca383511ed889ca04a595956c345e6 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 10 Dec 2019 17:26:26 -0800 Subject: [PATCH 05/57] Add example to slide 14 --- snippets/snippets/todo/ruga.ts | 5 ++ snippets/snippets/todo/zswn.ts | 6 ++ src/lib/snippets.ts | 13 +++++ src/pages/todo.tsx | 104 ++++++++++++++++++++++++++++----- 4 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 snippets/snippets/todo/ruga.ts create mode 100644 snippets/snippets/todo/zswn.ts diff --git a/snippets/snippets/todo/ruga.ts b/snippets/snippets/todo/ruga.ts new file mode 100644 index 0000000..3ccde1e --- /dev/null +++ b/snippets/snippets/todo/ruga.ts @@ -0,0 +1,5 @@ +function completeAll( + todos: readonly Todo[] +): Todo[] { + // ... +} diff --git a/snippets/snippets/todo/zswn.ts b/snippets/snippets/todo/zswn.ts new file mode 100644 index 0000000..2b458a8 --- /dev/null +++ b/snippets/snippets/todo/zswn.ts @@ -0,0 +1,6 @@ +// Will this compile? +const testTodo: CompletedTodo = { + id: 1, + text: '…', + done: false +} diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 1989109..dceb2cb 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -694,6 +694,12 @@ type CompletedTodo = Todo & { readonly done: true }` +export const ruga = `function completeAll( + todos: readonly Todo[] +): Todo[] { + // ... +}` + export const szan = `// Make input todos as readonly array function completeAll( todos: readonly Todo[] @@ -812,3 +818,10 @@ export const yxjg = `function toggleTodo(todo) { done: !todo.done } }` + +export const zswn = `// Will this compile? +const testTodo: CompletedTodo = { + id: 1, + text: '…', + done: false +}` diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index ec8ce6d..f92432d 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -107,9 +107,9 @@ const Page = () => ( This tutorial doesn’t rely on any specific frontend library , so it doesn’t matter whether you know React, Vue, or some - other libraries. As long as you know how to build a todo list - using a JS library, you should be able to understand this - tutorial. No prior TypeScript knowledge is necessary. + other libraries. As long as you have basic JS knowledge, you + should be able to understand this tutorial. No prior TypeScript + knowledge is necessary. To save time,{' '} @@ -127,23 +127,47 @@ const Page = () => ( I’m not going to cover everything about TypeScript. {' '} - I’m only going to cover some of the cool concepts in - TypeScript—mostly basic, some intermediate-level concepts. My - goal is to make you want to learn more. + I’m only going to cover some of the coolest concepts in + TypeScript—mostly basic. My goal is to make you want to learn + more. + + I’m only going to cover TypeScript basics. My goal is to make + you want to learn more. + + } + />

- There are 4 sections total in this article. Here - are the topics covered in each section: + There are 3 sections total in this article, plus + “one more thing” at the end. Here are the topics covered in each + section—there are a total of 10 topics covered:

    - Section 1: Types, Read-only Properties, and - Mapped Types + Section 1:{' '} + + Types, Read-only Properties, and Mapped Types + - Section 2: Array Types, Literal Types, and - Intersection Types + Section 2:{' '} + + Array Types, Literal Types, and Intersection Types + + + + Section 3:{' '} + + Union Types, Discriminated Unions, and Optional Properties + + + + One more thing:{' '} + Indexed Access Operator

Let’s get started!

@@ -153,7 +177,7 @@ const Page = () => ( { title: ( <> - Section 1 of 4 + Section 1 of 3 ), heading: <>Types, Read-only Properties, and Mapped Types, @@ -922,7 +946,7 @@ const Page = () => ( { title: ( <> - Section 2 of 4 + Section 2 of 3 ), heading: <>Array Types, Literal Types, and Intersection Types, @@ -1140,8 +1164,30 @@ const Page = () => ( } />

- We can now specify the return type of completeAll(){' '} - to be an array of CompletedTodo’s: + Let’s take a look at an example. In the following code, we added a{' '} + CompletedTodo to a todo item that has{' '} + done: false. Let’s see what happens{' '} + + when you it + + : +

+ Type 'false' is not assignable to type 'true'.} + shouldHighlightResult={lineNumber => lineNumber === 4} + /> +

+ It failed to compile because done is not{' '} + true. By using literal types like the above, you can + specify exactly what value is allowed for each property. +

+

+ Coming back to completeAll(), we can now specify the + return type of completeAll() to be an array of{' '} + CompletedTodo’s:

( ) }, + { + color: 'green', + subtitle: <>Section 2 Summary:, + title: <>TypeScript can handle arrays and exact values, + content: ( + <> +

In this section, we’ve learned the following:

+

+ + 1. We can specify an array type by adding []. We + can also set an array as readonly. + +

+ + (lineNumber === 2 && tokenNumber >= 2 && tokenNumber <= 6) || + (lineNumber === 1 && tokenNumber >= 2) + } + /> +

+ 2. +

+ + ) + }, underConstructionCard ]} /> From 8897eaa8d7ea578451d4e49569ad92ed867c8679 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 10 Dec 2019 17:36:30 -0800 Subject: [PATCH 06/57] Done with section 2 --- snippets/snippets/todo/bpmz.ts | 5 +++++ src/lib/articles.ts | 2 +- src/lib/snippets.ts | 6 ++++++ src/pages/todo.tsx | 35 +++++++++++++++++++++++++++++++--- 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 snippets/snippets/todo/bpmz.ts diff --git a/snippets/snippets/todo/bpmz.ts b/snippets/snippets/todo/bpmz.ts new file mode 100644 index 0000000..38a0d93 --- /dev/null +++ b/snippets/snippets/todo/bpmz.ts @@ -0,0 +1,5 @@ +type CompletedTodo = Readonly<{ + id: number + text: string + done: true +}> diff --git a/src/lib/articles.ts b/src/lib/articles.ts index 78b0502..a65db70 100644 --- a/src/lib/articles.ts +++ b/src/lib/articles.ts @@ -8,7 +8,7 @@ export const articlesData = { todo: { title: 'TypeScript Tutorial for JS Programmers Who Know How to Build a Todo App', - date: DateTime.fromISO('2019-12-01T12:00:00Z'), + date: DateTime.fromISO('2019-12-13T12:00:00Z'), description: 'Learn TypeScript by Building a Todo App', ogImage: 'todo' }, diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index dceb2cb..96533f8 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -484,6 +484,12 @@ export const bnli = `const foo: Todo = { done: true }` +export const bpmz = `type CompletedTodo = Readonly<{ + id: number + text: string + done: true +}>` + export const csum = `// todo must match the Todo type function toggleTodo(todo: Todo) { // ... diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index f92432d..4dd1ece 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1182,7 +1182,7 @@ const Page = () => (

It failed to compile because done is not{' '} true. By using literal types like the above, you can - specify exactly what value is allowed for each property. + specify exactly what value is allowed for a property.

Coming back to completeAll(), we can now specify the @@ -1389,7 +1389,10 @@ const Page = () => ( title: <>TypeScript can handle arrays and exact values, content: ( <> -

In this section, we’ve learned the following:

+

+ In this section, we’ve learned that TypeScript can handle arrays + and exact values: +

1. We can specify an array type by adding []. We @@ -1404,7 +1407,33 @@ const Page = () => ( } />

- 2. + + 2. We can use literal types to specify exactly which value is + allowed for a property. + +

+ lineNumber === 3} + /> +

+ Finally, we learned that{' '} + + we can use intersection types to override some properties and + remove code duplication. + +

+ + (lineIndex === 7 && tokenIndex >= 7) || + lineIndex === 8 || + lineIndex === 9 + } + /> +

+ In the next (and final) section, we’ll take a look at one of the + most powerful features of TypeScript: Unions.

) From 927b8ae2138c889dab361fd8380c70120338ca3a Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 10 Dec 2019 20:39:55 -0800 Subject: [PATCH 07/57] Start separator --- src/components/SeparatorItem.tsx | 22 ++++++++++++ src/components/TodoItem.tsx | 4 +-- src/components/TodoList.tsx | 47 ++++++++++++++++--------- src/components/TodoWithData.tsx | 54 +++++++++++++++++----------- src/pages/todo.tsx | 60 +++++++++++++++++++++++--------- 5 files changed, 133 insertions(+), 54 deletions(-) create mode 100644 src/components/SeparatorItem.tsx diff --git a/src/components/SeparatorItem.tsx b/src/components/SeparatorItem.tsx new file mode 100644 index 0000000..9d77aaa --- /dev/null +++ b/src/components/SeparatorItem.tsx @@ -0,0 +1,22 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core' +import useTheme from 'src/hooks/useTheme' + +const SeparatorItem = ({ text }: { text?: string }) => { + const { spaces, colors } = useTheme() + return ( +
+ {text} +
+ ) +} + +export default SeparatorItem diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx index fdf9861..d949baa 100644 --- a/src/components/TodoItem.tsx +++ b/src/components/TodoItem.tsx @@ -6,7 +6,7 @@ import useTheme from 'src/hooks/useTheme' import { useState, useContext } from 'react' import TodoWithDataContext from 'src/components/TodoWithDataContext' -const Todo = ({ +const TodoItem = ({ index, done, text @@ -85,4 +85,4 @@ const Todo = ({ ) } -export default Todo +export default TodoItem diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx index d2ac335..cbb1120 100644 --- a/src/components/TodoList.tsx +++ b/src/components/TodoList.tsx @@ -2,34 +2,49 @@ import { css, jsx } from '@emotion/core' import useTheme from 'src/hooks/useTheme' import TodoItem from 'src/components/TodoItem' -import { TodoType } from 'src/components/TodoWithData' +import { ItemType } from 'src/components/TodoWithData' import { useContext } from 'react' import TodoWithDataContext from 'src/components/TodoWithDataContext' +import SeparatorItem from 'src/components/SeparatorItem' const TodoList = ({ - todos, + todos: items, showMarkAllAsCompleted }: { - todos: TodoType[] + todos: ItemType[] showMarkAllAsCompleted?: boolean }) => { const { fontSizes, spaces, colors } = useTheme() const { dispatch, disabled } = useContext(TodoWithDataContext) return ( <> - {todos.map((todo, index) => ( -
- -
- ))} + {items.map((item, index) => + item.type !== 'separator' ? ( +
+ +
+ ) : ( +
+ +
+ ) + )} {showMarkAllAsCompleted && !disabled && (
+const prettierFormat = (state: ItemType[]) => format(JSON.stringify(state), { semi: false, singleQuote: true, @@ -22,11 +22,17 @@ const prettierFormat = (state: TodoType[]) => .trim() .substring(1) -export type TodoType = { - id: number - text: string - done: boolean -} +export type ItemType = + | { + id: number + text: string + done: boolean + type?: 'todo' + } + | { + type: 'separator' + text?: string + } export type TodoAction = | { @@ -38,27 +44,35 @@ export type TodoAction = } export type TodoState = { - todos: TodoType[] + todos: ItemType[] lastChangedIndices: number[] } const reducer = (state: TodoState, action: TodoAction) => { switch (action.type) { - case 'toggle': - return { - todos: [ - ...state.todos.slice(0, action.index), - { - ...state.todos[action.index], - done: !state.todos[action.index].done - }, - ...state.todos.slice(action.index + 1) - ], - lastChangedIndices: [action.index] + case 'toggle': { + const item = state.todos[action.index] + if (item.type !== 'separator') { + return { + todos: [ + ...state.todos.slice(0, action.index), + { + ...state.todos[action.index], + done: !item.done + }, + ...state.todos.slice(action.index + 1) + ], + lastChangedIndices: [action.index] + } + } else { + return state } + } case 'markAllAsCompleted': return { - todos: state.todos.map(item => ({ ...item, done: true })), + todos: state.todos.map(item => + item.type !== 'separator' ? { ...item, done: true } : item + ), lastChangedIndices: state.todos.map((_, index) => index) } default: @@ -77,7 +91,7 @@ const TodoWithData = ({ highlightLineIndexOffset, shouldHighlight }: { - defaultData: TodoType[] + defaultData: ItemType[] caption?: React.ReactNode promptArrowText?: React.ReactNode showData?: boolean diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 4dd1ece..862a81c 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -20,6 +20,9 @@ import BubbleQuotes from 'src/components/BubbleQuotes' import ResultHighlight from 'src/components/ResultHighlight' const compileSuccess = 'Compiled successfully!' +const section1 = 'Types, Read-only Properties, and Mapped Types' +const section2 = 'Array Types, Literal Types, and Intersection Types' +const section3 = 'Union Types, Discriminated Unions, and Optional Properties' const Page = () => ( (

    - Section 1:{' '} - - Types, Read-only Properties, and Mapped Types - + Section 1: {section1} - Section 2:{' '} - - Array Types, Literal Types, and Intersection Types - + Section 2: {section2} - Section 3:{' '} - - Union Types, Discriminated Unions, and Optional Properties - + Section 3: {section3} One more thing:{' '} @@ -180,7 +174,7 @@ const Page = () => ( Section 1 of 3 ), - heading: <>Types, Read-only Properties, and Mapped Types, + heading: section1, color: 'darkGreen' }, { @@ -949,7 +943,7 @@ const Page = () => ( Section 2 of 3 ), - heading: <>Array Types, Literal Types, and Intersection Types, + heading: section2, color: 'darkGreen' }, { @@ -958,8 +952,8 @@ const Page = () => ( <>

    Some todo apps allow you to{' '} - mark all items as completed. On the - following todo app,{' '} + mark all items as completed. On the following + todo app,{' '} try pressing “Mark all as completed”:

    ( ) }, + { + title: ( + <> + Section 3 of 3 + + ), + heading: section3, + color: 'darkGreen' + }, + { + title: <>Separators, + content: ( + <> +

    + + Sometimes, you might want to separate a group of todo items from + others. + {' '} + On the following todo app, the third item is a{' '} + separator which visually separates the first two + todo items from the last. +

    + There’s a separator after the second todo item} + defaultData={[ + { id: 1, text: 'First todo', done: false }, + { id: 2, text: 'Second todo', done: false }, + { type: 'separator' }, + { id: 3, text: 'Third todo', done: false } + ]} + /> + + ) + }, underConstructionCard ]} /> From 254bd741a2ef74933515d3f6e2a4de3da9dfe8f5 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Thu, 12 Dec 2019 05:04:20 +0900 Subject: [PATCH 08/57] Finish slide 20 --- snippets/snippets/todo/jnuw.ts | 11 ++++ snippets/snippets/todo/wmgl.ts | 9 +++ snippets/snippets/todo/yvum.ts | 11 ++++ src/components/Emoji/Type.tsx | 16 ++++++ src/components/Emoji/index.tsx | 4 +- src/components/TodoList.tsx | 4 +- src/components/TodoWithData.tsx | 9 +-- src/lib/snippets.ts | 34 +++++++++++ src/pages/todo.tsx | 99 +++++++++++++++++++++++++++++++-- 9 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 snippets/snippets/todo/jnuw.ts create mode 100644 snippets/snippets/todo/wmgl.ts create mode 100644 snippets/snippets/todo/yvum.ts create mode 100644 src/components/Emoji/Type.tsx diff --git a/snippets/snippets/todo/jnuw.ts b/snippets/snippets/todo/jnuw.ts new file mode 100644 index 0000000..9444e8d --- /dev/null +++ b/snippets/snippets/todo/jnuw.ts @@ -0,0 +1,11 @@ +type Todo = Readonly<{ + id: number + text: string + done: boolean + kind: 'todo' +}> + +type Separator = Readonly<{ + id: number + kind: 'separator' +}> diff --git a/snippets/snippets/todo/wmgl.ts b/snippets/snippets/todo/wmgl.ts new file mode 100644 index 0000000..a01be87 --- /dev/null +++ b/snippets/snippets/todo/wmgl.ts @@ -0,0 +1,9 @@ +type Todo = Readonly<{ + id: number + text: string + done: boolean +}> + +type Separator = Readonly<{ + id: number +}> diff --git a/snippets/snippets/todo/yvum.ts b/snippets/snippets/todo/yvum.ts new file mode 100644 index 0000000..b90f533 --- /dev/null +++ b/snippets/snippets/todo/yvum.ts @@ -0,0 +1,11 @@ +// In addition to the Todo type… +type Todo = Readonly<{ + id: number + text: string + done: boolean +}> + +// We need to create the new Separator type? +type Separator = Readonly<{ + // ??? +}> diff --git a/src/components/Emoji/Type.tsx b/src/components/Emoji/Type.tsx new file mode 100644 index 0000000..796f3c0 --- /dev/null +++ b/src/components/Emoji/Type.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +const SvgType = (props: React.SVGProps) => ( + + + + + + +) + +export default SvgType diff --git a/src/components/Emoji/index.tsx b/src/components/Emoji/index.tsx index 413a66d..143766a 100644 --- a/src/components/Emoji/index.tsx +++ b/src/components/Emoji/index.tsx @@ -21,6 +21,7 @@ import A from 'src/components/Emoji/A' import B from 'src/components/Emoji/B' import Ampersand from 'src/components/Emoji/Ampersand' import DoneTrue from 'src/components/Emoji/DoneTrue' +import Type from 'src/components/Emoji/Type' export const emojiToComponent = { bird: Bird, @@ -43,7 +44,8 @@ export const emojiToComponent = { a: A, b: B, ampersand: Ampersand, - doneTrue: DoneTrue + doneTrue: DoneTrue, + type: Type } export const EmojiWrapper = ({ diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx index cbb1120..f589724 100644 --- a/src/components/TodoList.tsx +++ b/src/components/TodoList.tsx @@ -19,7 +19,7 @@ const TodoList = ({ return ( <> {items.map((item, index) => - item.type !== 'separator' ? ( + item.kind !== 'separator' ? (
    ) : (
    { switch (action.type) { case 'toggle': { const item = state.todos[action.index] - if (item.type !== 'separator') { + if (item.kind !== 'separator') { return { todos: [ ...state.todos.slice(0, action.index), @@ -71,7 +72,7 @@ const reducer = (state: TodoState, action: TodoAction) => { case 'markAllAsCompleted': return { todos: state.todos.map(item => - item.type !== 'separator' ? { ...item, done: true } : item + item.kind !== 'separator' ? { ...item, done: true } : item ), lastChangedIndices: state.todos.map((_, index) => index) } diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 96533f8..efca7d2 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -545,6 +545,18 @@ function toggleTodo( // ... }` +export const jnuw = `type Todo = Readonly<{ + id: number + text: string + done: boolean + kind: 'todo' +}> + +type Separator = Readonly<{ + id: number + kind: 'separator' +}>` + export const kuzw = `function completeAll(todos: Todo[]): Todo[] { // We want it to return a new array // instead of modifying the original array @@ -774,6 +786,16 @@ export const whae = `function completeAll( })) }` +export const wmgl = `type Todo = Readonly<{ + id: number + text: string + done: boolean +}> + +type Separator = Readonly<{ + id: number +}>` + export const wymp = `const originalTodo = { id: 1, text: '…', @@ -811,6 +833,18 @@ export const yhto = `type Todo = { readonly done: boolean }` +export const yvum = `// In addition to the Todo type… +type Todo = Readonly<{ + id: number + text: string + done: boolean +}> + +// We need to create the new Separator type? +type Separator = Readonly<{ + // ??? +}>` + export const ywiv = `// The return value must match the Todo type function toggleTodo(todo: Todo): Todo { // ... diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 862a81c..9957fbe 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1445,24 +1445,115 @@ const Page = () => ( title: <>Separators, content: ( <> +

    + Let me introduce the new feature of our todo app called{' '} + separators.{' '} +

    Sometimes, you might want to separate a group of todo items from others. {' '} - On the following todo app, the third item is a{' '} - separator which visually separates the first two - todo items from the last. + On the following todo app, the third item is a separator which + visually separates the first two todo items from the last.

    There’s a separator after the second todo item} defaultData={[ { id: 1, text: 'First todo', done: false }, { id: 2, text: 'Second todo', done: false }, - { type: 'separator' }, + { id: 1, kind: 'separator' }, { id: 3, text: 'Third todo', done: false } ]} /> +

    This one has two separators:

    + +

    + Now, here’s a question:{' '} + + What type should we create to support these + separator items? + +

    + + What type should we create to support separator + items? + + } + /> + + ) + }, + { + title: ( + <> + The Separator type + + ), + content: ( + <> + +

    + Let me guess: In addition to the + existing Todo type, we need to create the + new Separator type to describe a separator + item, right? Something like this? +

    + + ) + } + ]} + /> + + lineIndex === 8 && tokenIndex <= 3 + } + /> +

    + Exactly, Little Duckling! Now, let’s think about + what needs to go into the Separator type.{' '} +

    +

    + First,{' '} + + we’ll add id just like todos + {' '} + because we’ll probably need to store the separator data into a + backend database using this id. +

    + lineIndex === 7} + /> +

    + Second,{' '} + + let’s add a kind property to BOTH Todo{' '} + and Separator to differentiate them. + +

    + lineIndex === 4 || lineIndex === 9} + /> ) }, From a9cb54f4c42fe737c19e311c843acff15cb8c392 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Thu, 12 Dec 2019 06:05:17 +0900 Subject: [PATCH 09/57] Addition to slide 20 --- snippets/snippets/todo/jnuw.ts | 2 +- src/components/TodoList.tsx | 4 ++-- src/components/TodoWithData.tsx | 31 ++++++++++++++++++++----------- src/lib/snippets.ts | 2 +- src/lib/theme/letterSpacings.ts | 3 ++- src/pages/todo.tsx | 29 +++++++++++++++++++++++++++-- 6 files changed, 53 insertions(+), 18 deletions(-) diff --git a/snippets/snippets/todo/jnuw.ts b/snippets/snippets/todo/jnuw.ts index 9444e8d..2a7dbc3 100644 --- a/snippets/snippets/todo/jnuw.ts +++ b/snippets/snippets/todo/jnuw.ts @@ -1,8 +1,8 @@ type Todo = Readonly<{ id: number + kind: 'todo' text: string done: boolean - kind: 'todo' }> type Separator = Readonly<{ diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx index f589724..4b90bca 100644 --- a/src/components/TodoList.tsx +++ b/src/components/TodoList.tsx @@ -21,7 +21,7 @@ const TodoList = ({ {items.map((item, index) => item.kind !== 'separator' ? (
    ) : (
    +const prettierFormat = (state: ItemType[], smallText = false) => format(JSON.stringify(state), { semi: false, singleQuote: true, - printWidth: 48, + printWidth: smallText ? 54 : 48, plugins: [parser], parser: 'babel' }) @@ -90,7 +90,8 @@ const TodoWithData = ({ showMarkAllAsCompleted, disabled, highlightLineIndexOffset, - shouldHighlight + shouldHighlight, + smallText }: { defaultData: ItemType[] caption?: React.ReactNode @@ -101,8 +102,9 @@ const TodoWithData = ({ disabled?: boolean highlightLineIndexOffset?: number shouldHighlight?: (tokenIndex: number) => boolean + smallText?: boolean }) => { - const { spaces, ns, maxWidths, radii, colors } = useTheme() + const { spaces, ns, maxWidths, radii, colors, letterSpacings } = useTheme() const [state, dispatch] = useReducer(reducer, { todos: defaultData, lastChangedIndices: [] @@ -138,15 +140,22 @@ const TodoWithData = ({ {showData && ( shouldHighlight && state.lastChangedIndices diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index efca7d2..7f426a6 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -547,9 +547,9 @@ function toggleTodo( export const jnuw = `type Todo = Readonly<{ id: number + kind: 'todo' text: string done: boolean - kind: 'todo' }> type Separator = Readonly<{ diff --git a/src/lib/theme/letterSpacings.ts b/src/lib/theme/letterSpacings.ts index 1fd5750..a73d8c7 100644 --- a/src/lib/theme/letterSpacings.ts +++ b/src/lib/theme/letterSpacings.ts @@ -1,6 +1,7 @@ export const allLetterSpacings = { title: '-0.02em', - wide: '0.15em' + wide: '0.15em', + smallCode: '-0.025em' } const letterSpacings = (x: keyof typeof allLetterSpacings) => diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 9957fbe..b2a5402 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1548,11 +1548,36 @@ const Page = () => ( let’s add a kind property to BOTH Todo{' '} and Separator to differentiate them. - + {' '} +

    +

    + For each type, we’ll specify exactly what value is allowed for the{' '} + kind property (reminder—this is called{' '} + literal types). The Todo item must + have kind: 'todo', and the Separator{' '} + item must have kind: 'separator'.

    lineIndex === 4 || lineIndex === 9} + shouldHighlight={lineIndex => lineIndex === 2 || lineIndex === 9} + /> +

    + That’s it! Let’s now take a look at an example. Here’s a todo list + containing some separators, displayed with its associated data: +

    + tokenIndex === 20} /> ) From a23cd33d39898633a1ca7d752b09bc1931d06a09 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Thu, 12 Dec 2019 07:17:07 +0900 Subject: [PATCH 10/57] Update showMarkAllAsCompleted --- src/components/TodoList.tsx | 46 +++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx index 4b90bca..3c64d8b 100644 --- a/src/components/TodoList.tsx +++ b/src/components/TodoList.tsx @@ -45,31 +45,33 @@ const TodoList = ({
    ) )} - {showMarkAllAsCompleted && !disabled && ( -
    - item.kind === 'todo' && !item.done).length > 0 && ( +
    dispatch({ type: 'markAllAsCompleted' })} > - Mark all as completed - -
    - )} + dispatch({ type: 'markAllAsCompleted' })} + > + Mark all as completed + +
    + )} ) } From ba9a47e5f475f046377c812cdd032b0a393ad8da Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Thu, 12 Dec 2019 07:33:38 +0900 Subject: [PATCH 11/57] Start slide 21 --- src/pages/todo.tsx | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index b2a5402..26deefc 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1293,7 +1293,7 @@ const Page = () => ( } />

    - By doing the above, you can define CompleteTodo to + By doing the above, you can define CompletedTodo to have the same properties as Todo except for{' '} done—without duplicating code.

    @@ -1582,6 +1582,45 @@ const Page = () => ( ) }, + { + title: ( + <> + How does completeAll() change? + + ), + content: ( + <> +

    + Now, let’s revisit the mark all as completed{' '} + feature again. If you{' '} + press “Mark all as completed” below, it + ignores the separators and only toggles the todo items. +

    + + ↓ Try pressing “Mark all as completed” + + } + showMarkAllAsCompleted + smallText + defaultData={[ + { id: 1, kind: 'todo', text: 'A', done: false }, + { id: 1, kind: 'separator' }, + { id: 2, kind: 'todo', text: 'B', done: false } + ]} + highlightLineIndexOffset={1} + shouldHighlight={tokenIndex => tokenIndex === 20} + /> +

    + To get this to work, we need to modify our{' '} + completeAll() function to support the{' '} + Separator type. +

    + + ) + }, underConstructionCard ]} /> From 81ec40f6216891813d6cdd9bcba1deae85c191bb Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Thu, 12 Dec 2019 10:51:25 +0900 Subject: [PATCH 12/57] Finish slide 21 --- .prettierrc | 6 ++ snippets/snippets/todo/afeb.ts | 7 ++ snippets/snippets/todo/bepv.ts | 5 ++ snippets/snippets/todo/czgn.ts | 3 + snippets/snippets/todo/vgnb.ts | 4 ++ snippets/snippets/todo/wrcf.ts | 9 +++ snippets/snippets/todo/xtkd.ts | 3 + snippets/snippets/todo/ybsb.ts | 8 +++ src/components/CodeBlock.tsx | 18 ++++- src/components/Emoji/VerticalBar.tsx | 11 +++ src/components/Emoji/index.tsx | 4 +- src/components/TodoWithData.tsx | 12 ++-- src/lib/snippets.ts | 46 ++++++++++++ src/pages/todo.tsx | 101 +++++++++++++++++++++++++-- 14 files changed, 221 insertions(+), 16 deletions(-) create mode 100644 snippets/snippets/todo/afeb.ts create mode 100644 snippets/snippets/todo/bepv.ts create mode 100644 snippets/snippets/todo/czgn.ts create mode 100644 snippets/snippets/todo/vgnb.ts create mode 100644 snippets/snippets/todo/wrcf.ts create mode 100644 snippets/snippets/todo/xtkd.ts create mode 100644 snippets/snippets/todo/ybsb.ts create mode 100644 src/components/Emoji/VerticalBar.tsx diff --git a/.prettierrc b/.prettierrc index 7ce38e9..5b94cd9 100644 --- a/.prettierrc +++ b/.prettierrc @@ -7,6 +7,12 @@ "options": { "printWidth": 48 } + }, + { + "files": "snippets/snippets/**/ybsb.ts", + "options": { + "printWidth": 54 + } } ] } diff --git a/snippets/snippets/todo/afeb.ts b/snippets/snippets/todo/afeb.ts new file mode 100644 index 0000000..2a76f70 --- /dev/null +++ b/snippets/snippets/todo/afeb.ts @@ -0,0 +1,7 @@ +type CompletedTodo = Todo & { + readonly done: true +} + +function completeAll( + items: readonly (Todo | Separator)[] +): CompletedTodo[] // <- Before diff --git a/snippets/snippets/todo/bepv.ts b/snippets/snippets/todo/bepv.ts new file mode 100644 index 0000000..b8e6746 --- /dev/null +++ b/snippets/snippets/todo/bepv.ts @@ -0,0 +1,5 @@ +function completeAll( + // After: An array of items, each of which + // can be either Todo or Separator + items: readonly (Todo | Separator)[] +) diff --git a/snippets/snippets/todo/czgn.ts b/snippets/snippets/todo/czgn.ts new file mode 100644 index 0000000..5513e1d --- /dev/null +++ b/snippets/snippets/todo/czgn.ts @@ -0,0 +1,3 @@ +function completeAll( + items: readonly (Todo | Separator)[] +): (CompletedTodo | Separator)[] // <- After diff --git a/snippets/snippets/todo/vgnb.ts b/snippets/snippets/todo/vgnb.ts new file mode 100644 index 0000000..bd73fa7 --- /dev/null +++ b/snippets/snippets/todo/vgnb.ts @@ -0,0 +1,4 @@ +function completeAll( + // Before: An array of Todo’s + todos: readonly Todo[] +) diff --git a/snippets/snippets/todo/wrcf.ts b/snippets/snippets/todo/wrcf.ts new file mode 100644 index 0000000..1660625 --- /dev/null +++ b/snippets/snippets/todo/wrcf.ts @@ -0,0 +1,9 @@ +// TODO: Need to update to support separators +function completeAll( + todos: readonly Todo[] +): CompletedTodo[] { + return todos.map(todo => ({ + ...todo, + done: true + })) +} diff --git a/snippets/snippets/todo/xtkd.ts b/snippets/snippets/todo/xtkd.ts new file mode 100644 index 0000000..02a703a --- /dev/null +++ b/snippets/snippets/todo/xtkd.ts @@ -0,0 +1,3 @@ +// A union type of Todo and Separator. +// This means: "either Todo OR Separator" +Todo | Separator diff --git a/snippets/snippets/todo/ybsb.ts b/snippets/snippets/todo/ybsb.ts new file mode 100644 index 0000000..decd23c --- /dev/null +++ b/snippets/snippets/todo/ybsb.ts @@ -0,0 +1,8 @@ +;[ + // Todo + { id: 1, kind: 'todo', text: 'A', done: false }, + // Separator + { id: 1, kind: 'separator' }, + // Todo + { id: 2, kind: 'todo', text: 'B', done: false } +] diff --git a/src/components/CodeBlock.tsx b/src/components/CodeBlock.tsx index 2c9ab5f..d13bc4d 100644 --- a/src/components/CodeBlock.tsx +++ b/src/components/CodeBlock.tsx @@ -21,7 +21,8 @@ const CodeBlock = ({ shouldHighlightResult, resultError, tokenIndexIndentWorkaround, - defaultErrorHighlight + defaultErrorHighlight, + narrowText }: { snippet: string shouldHighlight?: (lineIndex: number, tokenIndex: number) => boolean @@ -35,9 +36,18 @@ const CodeBlock = ({ resultError?: boolean tokenIndexIndentWorkaround?: number defaultErrorHighlight?: boolean + narrowText?: boolean }) => { const [resultVisible, setResultVisible] = useState(defaultResultVisible) - const { radii, colors, ns, maxWidths, spaces, fontSizes } = useTheme() + const { + radii, + colors, + ns, + maxWidths, + spaces, + fontSizes, + letterSpacings + } = useTheme() const buttonOnClick = () => setResultVisible(true) return ( @@ -79,6 +89,10 @@ const CodeBlock = ({ css` border-bottom-right-radius: ${radii(0.5)}; border-bottom-left-radius: ${radii(0.5)}; + `, + narrowText && + css` + letter-spacing: ${letterSpacings('smallCode')}; ` ]} lineCssOverrides={(lineIndex, tokenIndex) => diff --git a/src/components/Emoji/VerticalBar.tsx b/src/components/Emoji/VerticalBar.tsx new file mode 100644 index 0000000..d33776c --- /dev/null +++ b/src/components/Emoji/VerticalBar.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +const SvgVerticalBar = (props: React.SVGProps) => ( + + + + + +) + +export default SvgVerticalBar diff --git a/src/components/Emoji/index.tsx b/src/components/Emoji/index.tsx index 143766a..95c099e 100644 --- a/src/components/Emoji/index.tsx +++ b/src/components/Emoji/index.tsx @@ -22,6 +22,7 @@ import B from 'src/components/Emoji/B' import Ampersand from 'src/components/Emoji/Ampersand' import DoneTrue from 'src/components/Emoji/DoneTrue' import Type from 'src/components/Emoji/Type' +import VerticalBar from 'src/components/Emoji/VerticalBar' export const emojiToComponent = { bird: Bird, @@ -45,7 +46,8 @@ export const emojiToComponent = { b: B, ampersand: Ampersand, doneTrue: DoneTrue, - type: Type + type: Type, + verticalBar: VerticalBar } export const EmojiWrapper = ({ diff --git a/src/components/TodoWithData.tsx b/src/components/TodoWithData.tsx index bb17066..9836c34 100644 --- a/src/components/TodoWithData.tsx +++ b/src/components/TodoWithData.tsx @@ -11,11 +11,11 @@ import CodeBlockHighlight from 'src/components/CodeBlockHighlight' import { format } from 'prettier/standalone' import parser from 'prettier/parser-babylon' -const prettierFormat = (state: ItemType[], smallText = false) => +const prettierFormat = (state: ItemType[], narrowText = false) => format(JSON.stringify(state), { semi: false, singleQuote: true, - printWidth: smallText ? 54 : 48, + printWidth: narrowText ? 54 : 48, plugins: [parser], parser: 'babel' }) @@ -91,7 +91,7 @@ const TodoWithData = ({ disabled, highlightLineIndexOffset, shouldHighlight, - smallText + narrowText }: { defaultData: ItemType[] caption?: React.ReactNode @@ -102,7 +102,7 @@ const TodoWithData = ({ disabled?: boolean highlightLineIndexOffset?: number shouldHighlight?: (tokenIndex: number) => boolean - smallText?: boolean + narrowText?: boolean }) => { const { spaces, ns, maxWidths, radii, colors, letterSpacings } = useTheme() const [state, dispatch] = useReducer(reducer, { @@ -141,7 +141,7 @@ const TodoWithData = ({ ` +export const wrcf = `// TODO: Need to update to support separators +function completeAll( + todos: readonly Todo[] +): CompletedTodo[] { + return todos.map(todo => ({ + ...todo, + done: true + })) +}` + export const wymp = `const originalTodo = { id: 1, text: '…', @@ -827,6 +860,19 @@ type CompletedTodo = Readonly<{ done: true }>` +export const xtkd = `// A union type of Todo and Separator. +// This means: "either Todo OR Separator" +Todo | Separator` + +export const ybsb = `[ + // Todo + { id: 1, kind: 'todo', text: 'A', done: false }, + // Separator + { id: 1, kind: 'separator' }, + // Todo + { id: 2, kind: 'todo', text: 'B', done: false } +]` + export const yhto = `type Todo = { readonly id: number readonly text: string diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 26deefc..165b4e6 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1567,7 +1567,7 @@ const Page = () => (

    ( ) }, { - title: ( - <> - How does completeAll() change? - - ), + title: <>Union types, content: ( <>

    @@ -1604,7 +1600,7 @@ const Page = () => ( } showMarkAllAsCompleted - smallText + narrowText defaultData={[ { id: 1, kind: 'todo', text: 'A', done: false }, { id: 1, kind: 'separator' }, @@ -1618,6 +1614,97 @@ const Page = () => ( completeAll() function to support the{' '} Separator type.

    + lineNumber === 0} + /> +

    + + First, we need to change the input (parameter) type. + {' '} + Previously, the input was just an array of Todos. +

    + + lineNumber === 2 && tokenNumber >= 3 + } + /> +

    + However, now the array can contain both{' '} + Todos and Separators. +

    + +

    + To represent this, we need to use a TypeScript feature called{' '} + union types. In TypeScript, you can use the + syntax A | B to create a union type, which represents + a type that’s{' '} + + either A or B + + . +

    + + A | B is an union type, which + means{' '} + + either A or B. + + + } + /> + +

    + In this case, we need to create a union of Todo and{' '} + Separator like this: +

    +

    + + Because the input to completeAll() is an array of{' '} + Todo | Separator’s, we can update the parameter + type like below. + {' '} + (Also: I’ve updated the parameter name from todos to{' '} + items.) +

    + lineNumber === 3} + /> +

    + How about the ouptut (return) type? Before, we + set it as an array of CompletedTodo’s: +

    + + lineNumber === 6 && tokenNumber >= 4 && tokenNumber <= 6 + } + /> +

    + We can use the union type here again.{' '} + + completeAll() should return an array where each + item is either CompletedTodo or{' '} + Separator + + , so we’ll specify the output type like this: +

    + + lineNumber === 2 && tokenNumber >= 4 && tokenNumber <= 12 + } + /> +

    + That’s it for the input and output types of{' '} + completeAll(). Next, we’ll update its{' '} + implementation. +

    ) }, From 165e413f699d067403de327933df7122456a6e38 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Thu, 12 Dec 2019 11:01:09 +0900 Subject: [PATCH 13/57] Start slide 22 --- snippets/snippets/todo/sgdc.ts | 9 +++++++++ snippets/snippets/todo/zbii.ts | 5 +++++ src/lib/snippets.ts | 16 ++++++++++++++++ src/pages/todo.tsx | 19 +++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 snippets/snippets/todo/sgdc.ts create mode 100644 snippets/snippets/todo/zbii.ts diff --git a/snippets/snippets/todo/sgdc.ts b/snippets/snippets/todo/sgdc.ts new file mode 100644 index 0000000..861ec6f --- /dev/null +++ b/snippets/snippets/todo/sgdc.ts @@ -0,0 +1,9 @@ +// Previous version of completeAll() +function completeAll( + todos: readonly Todo[] +): CompletedTodo[] { + return todos.map(todo => ({ + ...todo, + done: true + })) +} diff --git a/snippets/snippets/todo/zbii.ts b/snippets/snippets/todo/zbii.ts new file mode 100644 index 0000000..d7b89d9 --- /dev/null +++ b/snippets/snippets/todo/zbii.ts @@ -0,0 +1,5 @@ +function completeAll( + items: readonly (Todo | Separator)[] +): (CompletedTodo | Separator)[] { + // What would the updated version look like? +} diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 5dcfa56..9fe6f35 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -736,6 +736,16 @@ export const ruga = `function completeAll( // ... }` +export const sgdc = `// Previous version of completeAll() +function completeAll( + todos: readonly Todo[] +): CompletedTodo[] { + return todos.map(todo => ({ + ...todo, + done: true + })) +}` + export const szan = `// Make input todos as readonly array function completeAll( todos: readonly Todo[] @@ -905,6 +915,12 @@ export const yxjg = `function toggleTodo(todo) { } }` +export const zbii = `function completeAll( + items: readonly (Todo | Separator)[] +): (CompletedTodo | Separator)[] { + // What would the updated version look like? +}` + export const zswn = `// Will this compile? const testTodo: CompletedTodo = { id: 1, diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 165b4e6..cb00ab1 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1708,6 +1708,25 @@ const Page = () => ( ) }, + { + title: <>Discriminated unions, + content: ( + <> +

    + Again, here’s the previous version of completeAll(): +

    + lineNumber === 0} + /> +

    + Let’s now think about what the updated version (with updated + types) would look like: +

    + + + ) + }, underConstructionCard ]} /> From 00732571c9ab98b84e66da941f64061b4f94e0c8 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Fri, 13 Dec 2019 08:13:10 +0900 Subject: [PATCH 14/57] Continue with todo --- snippets/snippets/todo/rymr.ts | 5 ++ snippets/snippets/todo/wptf.ts | 27 +++++++++++ snippets/snippets/todo/ykpe.ts | 5 ++ src/lib/snippets.ts | 40 ++++++++++++++++ src/pages/todo.tsx | 87 ++++++++++++++++++++++++++-------- 5 files changed, 145 insertions(+), 19 deletions(-) create mode 100644 snippets/snippets/todo/rymr.ts create mode 100644 snippets/snippets/todo/wptf.ts create mode 100644 snippets/snippets/todo/ykpe.ts diff --git a/snippets/snippets/todo/rymr.ts b/snippets/snippets/todo/rymr.ts new file mode 100644 index 0000000..7b83026 --- /dev/null +++ b/snippets/snippets/todo/rymr.ts @@ -0,0 +1,5 @@ +function completeAll( + items: readonly (Todo | Separator)[] +): (CompletedTodo | Separator)[] { + return items.map(item => /* ? */) +} diff --git a/snippets/snippets/todo/wptf.ts b/snippets/snippets/todo/wptf.ts new file mode 100644 index 0000000..ed1c60a --- /dev/null +++ b/snippets/snippets/todo/wptf.ts @@ -0,0 +1,27 @@ +type Todo = Readonly<{ + id: number + text: string + done: boolean + kind: 'todo' +}> + +type Separator = Readonly<{ + id: number + kind: 'separator' +}> + +type CompletedTodo = Todo & { + readonly done: true +} + +function completeAll( + items: readonly (Todo | Separator)[] +): (CompletedTodo | Separator)[] { + return items.map(item => { + if (item.kind === 'todo') { + return { ...item, done: true } + } else { + return item + } + }) +} diff --git a/snippets/snippets/todo/ykpe.ts b/snippets/snippets/todo/ykpe.ts new file mode 100644 index 0000000..7b83026 --- /dev/null +++ b/snippets/snippets/todo/ykpe.ts @@ -0,0 +1,5 @@ +function completeAll( + items: readonly (Todo | Separator)[] +): (CompletedTodo | Separator)[] { + return items.map(item => /* ? */) +} diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 9fe6f35..5f9b060 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -736,6 +736,12 @@ export const ruga = `function completeAll( // ... }` +export const rymr = `function completeAll( + items: readonly (Todo | Separator)[] +): (CompletedTodo | Separator)[] { + return items.map(item => /* ? */) +}` + export const sgdc = `// Previous version of completeAll() function completeAll( todos: readonly Todo[] @@ -829,6 +835,34 @@ type Separator = Readonly<{ id: number }>` +export const wptf = `type Todo = Readonly<{ + id: number + text: string + done: boolean + kind: 'todo' +}> + +type Separator = Readonly<{ + id: number + kind: 'separator' +}> + +type CompletedTodo = Todo & { + readonly done: true +} + +function completeAll( + items: readonly (Todo | Separator)[] +): (CompletedTodo | Separator)[] { + return items.map(item => { + if (item.kind === 'todo') { + return { ...item, done: true } + } else { + return item + } + }) +}` + export const wrcf = `// TODO: Need to update to support separators function completeAll( todos: readonly Todo[] @@ -889,6 +923,12 @@ export const yhto = `type Todo = { readonly done: boolean }` +export const ykpe = `function completeAll( + items: readonly (Todo | Separator)[] +): (CompletedTodo | Separator)[] { + return items.map(item => /* ? */) +}` + export const yvum = `// In addition to the Todo type… type Todo = Readonly<{ id: number diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index cb00ab1..3176aef 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -24,6 +24,26 @@ const section1 = 'Types, Read-only Properties, and Mapped Types' const section2 = 'Array Types, Literal Types, and Intersection Types' const section3 = 'Union Types, Discriminated Unions, and Optional Properties' +const TodoWithSeparatorAndMarkAllAsCompleted = () => ( + + ↓ Try pressing “Mark all as completed” + + } + showMarkAllAsCompleted + narrowText + defaultData={[ + { id: 1, kind: 'todo', text: 'A', done: false }, + { id: 1, kind: 'separator' }, + { id: 2, kind: 'todo', text: 'B', done: false } + ]} + highlightLineIndexOffset={1} + shouldHighlight={tokenIndex => tokenIndex === 20} + /> +) + const Page = () => ( ( press “Mark all as completed” below, it ignores the separators and only toggles the todo items.

    - - ↓ Try pressing “Mark all as completed” - - } - showMarkAllAsCompleted - narrowText - defaultData={[ - { id: 1, kind: 'todo', text: 'A', done: false }, - { id: 1, kind: 'separator' }, - { id: 2, kind: 'todo', text: 'B', done: false } - ]} - highlightLineIndexOffset={1} - shouldHighlight={tokenIndex => tokenIndex === 20} - /> +

    To get this to work, we need to modify our{' '} completeAll() function to support the{' '} @@ -1709,7 +1713,11 @@ const Page = () => ( ) }, { - title: <>Discriminated unions, + title: ( + <> + Implementing completeAll() + + ), content: ( <>

    @@ -1723,7 +1731,48 @@ const Page = () => ( Let’s now think about what the updated version (with updated types) would look like:

    - + lineNumber === 3} + /> +

    + First, the .map() part would be the same as before—we + just need to change what goes inside .map(). +

    + + lineNumber === 3 && tokenNumber === 10 + } + /> +

    + Now, let’s take a look at the todo app again. If you press{' '} + “Mark all as completed”, here’s what + happens to each item: +

    +
      + + If it’s{' '} + + a Todo, change done to{' '} + true. + + + + If it’s{' '} + + a Separator, do nothing. + + +
    + +

    + + We can check the kind property to differentiate + between a Todo and a Separator + + . So let’s do that inside .map(): +

    ) }, From bacbf5ee90e31b7b405fbade6859468da93a3fec Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sat, 14 Dec 2019 12:00:44 +0900 Subject: [PATCH 15/57] Optional properties --- snippets/snippets/todo/bxzu.ts | 12 ++++++ snippets/snippets/todo/kquk.ts | 6 +++ src/lib/snippets.ts | 20 ++++++++++ src/pages/todo.tsx | 70 +++++++++++++++++++++++++++++++--- 4 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 snippets/snippets/todo/bxzu.ts create mode 100644 snippets/snippets/todo/kquk.ts diff --git a/snippets/snippets/todo/bxzu.ts b/snippets/snippets/todo/bxzu.ts new file mode 100644 index 0000000..b97fef3 --- /dev/null +++ b/snippets/snippets/todo/bxzu.ts @@ -0,0 +1,12 @@ +type Todo = { + // Make id optional + id?: number + text: string + done: boolean +} + +// This will now compile! +const bar: Todo = { + text: '…', + done: true +} diff --git a/snippets/snippets/todo/kquk.ts b/snippets/snippets/todo/kquk.ts new file mode 100644 index 0000000..840dc8d --- /dev/null +++ b/snippets/snippets/todo/kquk.ts @@ -0,0 +1,6 @@ +type Todo = { + // Make id optional + id?: number + text: string + done: boolean +} diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 5f9b060..9b7a25d 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -504,6 +504,19 @@ export const bpmz = `type CompletedTodo = Readonly<{ done: true }>` +export const bxzu = `type Todo = { + // Make id optional + id?: number + text: string + done: boolean +} + +// This will now compile! +const bar: Todo = { + text: '…', + done: true +}` + export const csum = `// todo must match the Todo type function toggleTodo(todo: Todo) { // ... @@ -575,6 +588,13 @@ type Separator = Readonly<{ kind: 'separator' }>` +export const kquk = `type Todo = { + // Make id optional + id?: number + text: string + done: boolean +}` + export const kuzw = `function completeAll(todos: Todo[]): Todo[] { // We want it to return a new array // instead of modifying the original array diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 3176aef..87108cb 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -20,9 +20,10 @@ import BubbleQuotes from 'src/components/BubbleQuotes' import ResultHighlight from 'src/components/ResultHighlight' const compileSuccess = 'Compiled successfully!' -const section1 = 'Types, Read-only Properties, and Mapped Types' +const section1 = + 'Types, Optional Properties, Read-only Properties, and Mapped Types' const section2 = 'Array Types, Literal Types, and Intersection Types' -const section3 = 'Union Types, Discriminated Unions, and Optional Properties' +const section3 = 'Union Types and Discriminated Unions' const TodoWithSeparatorAndMarkAllAsCompleted = () => ( (

    There are 3 sections total in this article, plus “one more thing” at the end. Here are the topics covered in each - section—there are a total of 10 topics covered: + section—there are a total of 9 topics covered:

      @@ -501,6 +502,49 @@ const Page = () => ( ) }, + { + color: 'pink', + subtitle: <>Side Note, + title: ( + <> + Optional id? + + ), + content: ( + <> +

      + + What if we actually want to make the{' '} + id property of a todo item + optional? + {' '} + For example, a new todo item that’s not yet saved in a database + may not have an id yet. +

      +

      + To make a property optional in TypeScript,{' '} + + you can add a question mark ( + ?) + {' '} + after the property name: +

      + + lineIndex === 2 && tokenIndex <= 1 + } + /> +

      + But for the purposes of our discussion,{' '} + + we’ll keep id as required + {' '} + to make things simpler. +

      + + ) + }, { title: ( <> @@ -512,7 +556,11 @@ const Page = () => (

      Now, let’s use TypeScript to prevent the mistake Little Duckling made earlier. To recap, here’s the Todo type we - created earlier: + created earlier ( + + id is required + + ):

      @@ -901,7 +949,19 @@ const Page = () => ( />

      - 2. We can use the readonly keyword to make sure + 2. We can make some properties optional by adding ?{' '} + after the property name. + +

      + + lineIndex === 2 && tokenIndex <= 1 + } + /> +

      + + 3. We can use the readonly keyword to make sure that an object’s properties are not modified.

      From 0e063697b825e47a56ee4d2ef0dd8c650960fa4b Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sat, 14 Dec 2019 12:27:03 +0900 Subject: [PATCH 16/57] Remove completeAll() rewrite --- snippets/snippets/todo/afeb.ts | 7 -- snippets/snippets/todo/bepv.ts | 5 - snippets/snippets/todo/czgn.ts | 3 - snippets/snippets/todo/sgdc.ts | 9 -- snippets/snippets/todo/vgnb.ts | 4 - snippets/snippets/todo/wrcf.ts | 9 -- snippets/snippets/todo/ybsb.ts | 8 -- snippets/snippets/todo/ykpe.ts | 5 - snippets/snippets/todo/zbii.ts | 5 - src/lib/snippets.ts | 64 ------------- src/pages/todo.tsx | 170 +-------------------------------- 11 files changed, 5 insertions(+), 284 deletions(-) delete mode 100644 snippets/snippets/todo/afeb.ts delete mode 100644 snippets/snippets/todo/bepv.ts delete mode 100644 snippets/snippets/todo/czgn.ts delete mode 100644 snippets/snippets/todo/sgdc.ts delete mode 100644 snippets/snippets/todo/vgnb.ts delete mode 100644 snippets/snippets/todo/wrcf.ts delete mode 100644 snippets/snippets/todo/ybsb.ts delete mode 100644 snippets/snippets/todo/ykpe.ts delete mode 100644 snippets/snippets/todo/zbii.ts diff --git a/snippets/snippets/todo/afeb.ts b/snippets/snippets/todo/afeb.ts deleted file mode 100644 index 2a76f70..0000000 --- a/snippets/snippets/todo/afeb.ts +++ /dev/null @@ -1,7 +0,0 @@ -type CompletedTodo = Todo & { - readonly done: true -} - -function completeAll( - items: readonly (Todo | Separator)[] -): CompletedTodo[] // <- Before diff --git a/snippets/snippets/todo/bepv.ts b/snippets/snippets/todo/bepv.ts deleted file mode 100644 index b8e6746..0000000 --- a/snippets/snippets/todo/bepv.ts +++ /dev/null @@ -1,5 +0,0 @@ -function completeAll( - // After: An array of items, each of which - // can be either Todo or Separator - items: readonly (Todo | Separator)[] -) diff --git a/snippets/snippets/todo/czgn.ts b/snippets/snippets/todo/czgn.ts deleted file mode 100644 index 5513e1d..0000000 --- a/snippets/snippets/todo/czgn.ts +++ /dev/null @@ -1,3 +0,0 @@ -function completeAll( - items: readonly (Todo | Separator)[] -): (CompletedTodo | Separator)[] // <- After diff --git a/snippets/snippets/todo/sgdc.ts b/snippets/snippets/todo/sgdc.ts deleted file mode 100644 index 861ec6f..0000000 --- a/snippets/snippets/todo/sgdc.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Previous version of completeAll() -function completeAll( - todos: readonly Todo[] -): CompletedTodo[] { - return todos.map(todo => ({ - ...todo, - done: true - })) -} diff --git a/snippets/snippets/todo/vgnb.ts b/snippets/snippets/todo/vgnb.ts deleted file mode 100644 index bd73fa7..0000000 --- a/snippets/snippets/todo/vgnb.ts +++ /dev/null @@ -1,4 +0,0 @@ -function completeAll( - // Before: An array of Todo’s - todos: readonly Todo[] -) diff --git a/snippets/snippets/todo/wrcf.ts b/snippets/snippets/todo/wrcf.ts deleted file mode 100644 index 1660625..0000000 --- a/snippets/snippets/todo/wrcf.ts +++ /dev/null @@ -1,9 +0,0 @@ -// TODO: Need to update to support separators -function completeAll( - todos: readonly Todo[] -): CompletedTodo[] { - return todos.map(todo => ({ - ...todo, - done: true - })) -} diff --git a/snippets/snippets/todo/ybsb.ts b/snippets/snippets/todo/ybsb.ts deleted file mode 100644 index decd23c..0000000 --- a/snippets/snippets/todo/ybsb.ts +++ /dev/null @@ -1,8 +0,0 @@ -;[ - // Todo - { id: 1, kind: 'todo', text: 'A', done: false }, - // Separator - { id: 1, kind: 'separator' }, - // Todo - { id: 2, kind: 'todo', text: 'B', done: false } -] diff --git a/snippets/snippets/todo/ykpe.ts b/snippets/snippets/todo/ykpe.ts deleted file mode 100644 index 7b83026..0000000 --- a/snippets/snippets/todo/ykpe.ts +++ /dev/null @@ -1,5 +0,0 @@ -function completeAll( - items: readonly (Todo | Separator)[] -): (CompletedTodo | Separator)[] { - return items.map(item => /* ? */) -} diff --git a/snippets/snippets/todo/zbii.ts b/snippets/snippets/todo/zbii.ts deleted file mode 100644 index d7b89d9..0000000 --- a/snippets/snippets/todo/zbii.ts +++ /dev/null @@ -1,5 +0,0 @@ -function completeAll( - items: readonly (Todo | Separator)[] -): (CompletedTodo | Separator)[] { - // What would the updated version look like? -} diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 9b7a25d..e8267ef 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -469,14 +469,6 @@ const { getState, setState } = makeState() setState('foo') console.log(getState())` -export const afeb = `type CompletedTodo = Todo & { - readonly done: true -} - -function completeAll( - items: readonly (Todo | Separator)[] -): CompletedTodo[] // <- Before` - export const ampt = `function toggleTodo(todo: Todo): Todo { return { // This line was missing @@ -486,12 +478,6 @@ export const ampt = `function toggleTodo(todo: Todo): Todo { } }` -export const bepv = `function completeAll( - // After: An array of items, each of which - // can be either Todo or Separator - items: readonly (Todo | Separator)[] -)` - export const bnli = `const foo: Todo = { id: 1, text: '…', @@ -522,10 +508,6 @@ function toggleTodo(todo: Todo) { // ... }` -export const czgn = `function completeAll( - items: readonly (Todo | Separator)[] -): (CompletedTodo | Separator)[] // <- After` - export const dqwb = `function toggleTodo(todo: Todo): Todo { // Little Duckling’s refactoring todo.done = !todo.done @@ -762,16 +744,6 @@ export const rymr = `function completeAll( return items.map(item => /* ? */) }` -export const sgdc = `// Previous version of completeAll() -function completeAll( - todos: readonly Todo[] -): CompletedTodo[] { - return todos.map(todo => ({ - ...todo, - done: true - })) -}` - export const szan = `// Make input todos as readonly array function completeAll( todos: readonly Todo[] @@ -796,11 +768,6 @@ export const uxlb = `function toggleTodo(todo: Todo): Todo { return todo }` -export const vgnb = `function completeAll( - // Before: An array of Todo’s - todos: readonly Todo[] -)` - export const vgnq = `// This will continue to work because // the input todo is not modified function toggleTodo(todo: Todo): Todo { @@ -883,16 +850,6 @@ function completeAll( }) }` -export const wrcf = `// TODO: Need to update to support separators -function completeAll( - todos: readonly Todo[] -): CompletedTodo[] { - return todos.map(todo => ({ - ...todo, - done: true - })) -}` - export const wymp = `const originalTodo = { id: 1, text: '…', @@ -928,27 +885,12 @@ export const xtkd = `// A union type of Todo and Separator. // This means: "either Todo OR Separator" Todo | Separator` -export const ybsb = `[ - // Todo - { id: 1, kind: 'todo', text: 'A', done: false }, - // Separator - { id: 1, kind: 'separator' }, - // Todo - { id: 2, kind: 'todo', text: 'B', done: false } -]` - export const yhto = `type Todo = { readonly id: number readonly text: string readonly done: boolean }` -export const ykpe = `function completeAll( - items: readonly (Todo | Separator)[] -): (CompletedTodo | Separator)[] { - return items.map(item => /* ? */) -}` - export const yvum = `// In addition to the Todo type… type Todo = Readonly<{ id: number @@ -975,12 +917,6 @@ export const yxjg = `function toggleTodo(todo) { } }` -export const zbii = `function completeAll( - items: readonly (Todo | Separator)[] -): (CompletedTodo | Separator)[] { - // What would the updated version look like? -}` - export const zswn = `// Will this compile? const testTodo: CompletedTodo = { id: 1, diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 87108cb..1935c08 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -25,26 +25,6 @@ const section1 = const section2 = 'Array Types, Literal Types, and Intersection Types' const section3 = 'Union Types and Discriminated Unions' -const TodoWithSeparatorAndMarkAllAsCompleted = () => ( - - ↓ Try pressing “Mark all as completed” - - } - showMarkAllAsCompleted - narrowText - defaultData={[ - { id: 1, kind: 'todo', text: 'A', done: false }, - { id: 1, kind: 'separator' }, - { id: 2, kind: 'todo', text: 'B', done: false } - ]} - highlightLineIndexOffset={1} - shouldHighlight={tokenIndex => tokenIndex === 20} - /> -) - const Page = () => ( ( color: 'darkGreen' }, { - title: <>Separators, + title: <>Nea Feature: Separators, content: ( <>

      @@ -1633,8 +1613,8 @@ const Page = () => (

      For each type, we’ll specify exactly what value is allowed for the{' '} kind property (reminder—this is called{' '} - literal types). The Todo item must - have kind: 'todo', and the Separator{' '} + literal types). Each Todo item must + have kind: 'todo', and each Separator{' '} item must have kind: 'separator'.

      ( ) }, { - title: <>Union types, + title: <>, content: ( <> -

      - Now, let’s revisit the mark all as completed{' '} - feature again. If you{' '} - press “Mark all as completed” below, it - ignores the separators and only toggles the todo items. -

      - -

      - To get this to work, we need to modify our{' '} - completeAll() function to support the{' '} - Separator type. -

      - lineNumber === 0} - /> -

      - - First, we need to change the input (parameter) type. - {' '} - Previously, the input was just an array of Todos. -

      - - lineNumber === 2 && tokenNumber >= 3 - } - /> -

      - However, now the array can contain both{' '} - Todos and Separators. -

      -

      To represent this, we need to use a TypeScript feature called{' '} union types. In TypeScript, you can use the @@ -1721,118 +1668,11 @@ const Page = () => ( } /> -

      In this case, we need to create a union of Todo and{' '} Separator like this:

      -

      - - Because the input to completeAll() is an array of{' '} - Todo | Separator’s, we can update the parameter - type like below. - {' '} - (Also: I’ve updated the parameter name from todos to{' '} - items.) -

      - lineNumber === 3} - /> -

      - How about the ouptut (return) type? Before, we - set it as an array of CompletedTodo’s: -

      - - lineNumber === 6 && tokenNumber >= 4 && tokenNumber <= 6 - } - /> -

      - We can use the union type here again.{' '} - - completeAll() should return an array where each - item is either CompletedTodo or{' '} - Separator - - , so we’ll specify the output type like this: -

      - - lineNumber === 2 && tokenNumber >= 4 && tokenNumber <= 12 - } - /> -

      - That’s it for the input and output types of{' '} - completeAll(). Next, we’ll update its{' '} - implementation. -

      - - ) - }, - { - title: ( - <> - Implementing completeAll() - - ), - content: ( - <> -

      - Again, here’s the previous version of completeAll(): -

      - lineNumber === 0} - /> -

      - Let’s now think about what the updated version (with updated - types) would look like: -

      - lineNumber === 3} - /> -

      - First, the .map() part would be the same as before—we - just need to change what goes inside .map(). -

      - - lineNumber === 3 && tokenNumber === 10 - } - /> -

      - Now, let’s take a look at the todo app again. If you press{' '} - “Mark all as completed”, here’s what - happens to each item: -

      -
        - - If it’s{' '} - - a Todo, change done to{' '} - true. - - - - If it’s{' '} - - a Separator, do nothing. - - -
      - -

      - - We can check the kind property to differentiate - between a Todo and a Separator - - . So let’s do that inside .map(): -

      + ) }, From dc42ee404acdaf84e210f4658962eaa548a8a80f Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sat, 14 Dec 2019 15:25:56 +0900 Subject: [PATCH 17/57] SImplify --- src/pages/todo.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 1935c08..0dea701 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -23,7 +23,7 @@ const compileSuccess = 'Compiled successfully!' const section1 = 'Types, Optional Properties, Read-only Properties, and Mapped Types' const section2 = 'Array Types, Literal Types, and Intersection Types' -const section3 = 'Union Types and Discriminated Unions' +const section3 = 'Union Types' const Page = () => ( ( } />

      - There are 3 sections total in this article, plus - “one more thing” at the end. Here are the topics covered in each - section—there are a total of 9 topics covered: + There are 3 sections total in this article. Here + are the topics covered in each section.

        @@ -160,10 +159,6 @@ const Page = () => ( Section 3: {section3} - - One more thing:{' '} - Indexed Access Operator -

      Let’s get started!

      @@ -1502,7 +1497,7 @@ const Page = () => ( color: 'darkGreen' }, { - title: <>Nea Feature: Separators, + title: <>New feature: Separators, content: ( <>

      From f31124a9c59ce48edc45e1ef5a809a93380cc362 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sat, 14 Dec 2019 16:07:32 +0900 Subject: [PATCH 18/57] Remove optional properties from section 1 --- snippets/snippets/todo/bxzu.ts | 12 ------- snippets/snippets/todo/kquk.ts | 6 ---- src/lib/snippets.ts | 20 ----------- src/pages/todo.tsx | 62 ++-------------------------------- 4 files changed, 3 insertions(+), 97 deletions(-) delete mode 100644 snippets/snippets/todo/bxzu.ts delete mode 100644 snippets/snippets/todo/kquk.ts diff --git a/snippets/snippets/todo/bxzu.ts b/snippets/snippets/todo/bxzu.ts deleted file mode 100644 index b97fef3..0000000 --- a/snippets/snippets/todo/bxzu.ts +++ /dev/null @@ -1,12 +0,0 @@ -type Todo = { - // Make id optional - id?: number - text: string - done: boolean -} - -// This will now compile! -const bar: Todo = { - text: '…', - done: true -} diff --git a/snippets/snippets/todo/kquk.ts b/snippets/snippets/todo/kquk.ts deleted file mode 100644 index 840dc8d..0000000 --- a/snippets/snippets/todo/kquk.ts +++ /dev/null @@ -1,6 +0,0 @@ -type Todo = { - // Make id optional - id?: number - text: string - done: boolean -} diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index e8267ef..b3a38f1 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -490,19 +490,6 @@ export const bpmz = `type CompletedTodo = Readonly<{ done: true }>` -export const bxzu = `type Todo = { - // Make id optional - id?: number - text: string - done: boolean -} - -// This will now compile! -const bar: Todo = { - text: '…', - done: true -}` - export const csum = `// todo must match the Todo type function toggleTodo(todo: Todo) { // ... @@ -570,13 +557,6 @@ type Separator = Readonly<{ kind: 'separator' }>` -export const kquk = `type Todo = { - // Make id optional - id?: number - text: string - done: boolean -}` - export const kuzw = `function completeAll(todos: Todo[]): Todo[] { // We want it to return a new array // instead of modifying the original array diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 0dea701..e278cfb 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -20,10 +20,9 @@ import BubbleQuotes from 'src/components/BubbleQuotes' import ResultHighlight from 'src/components/ResultHighlight' const compileSuccess = 'Compiled successfully!' -const section1 = - 'Types, Optional Properties, Read-only Properties, and Mapped Types' +const section1 = 'Types, Read-only Properties, and Mapped Types' const section2 = 'Array Types, Literal Types, and Intersection Types' -const section3 = 'Union Types' +const section3 = 'Union Types and Optional Properties' const Page = () => ( ( ) }, - { - color: 'pink', - subtitle: <>Side Note, - title: ( - <> - Optional id? - - ), - content: ( - <> -

      - - What if we actually want to make the{' '} - id property of a todo item - optional? - {' '} - For example, a new todo item that’s not yet saved in a database - may not have an id yet. -

      -

      - To make a property optional in TypeScript,{' '} - - you can add a question mark ( - ?) - {' '} - after the property name: -

      - - lineIndex === 2 && tokenIndex <= 1 - } - /> -

      - But for the purposes of our discussion,{' '} - - we’ll keep id as required - {' '} - to make things simpler. -

      - - ) - }, { title: ( <> @@ -924,19 +880,7 @@ const Page = () => ( />

      - 2. We can make some properties optional by adding ?{' '} - after the property name. - -

      - - lineIndex === 2 && tokenIndex <= 1 - } - /> -

      - - 3. We can use the readonly keyword to make sure + 2. We can use the readonly keyword to make sure that an object’s properties are not modified.

      From 984e61dd2980213e45e7d1736e5f11445f0154f9 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sat, 14 Dec 2019 20:13:35 +0900 Subject: [PATCH 19/57] Start location labels --- snippets/snippets/todo/jnuw.ts | 11 --- snippets/snippets/todo/rymr.ts | 5 -- snippets/snippets/todo/wmgl.ts | 9 --- snippets/snippets/todo/wptf.ts | 27 ------- snippets/snippets/todo/xtkd.ts | 3 - snippets/snippets/todo/yvum.ts | 11 --- src/components/Emoji/Home.tsx | 28 +++++++ src/components/Emoji/Work.tsx | 24 ++++++ src/components/Emoji/index.tsx | 6 +- src/components/PlaceLabel.tsx | 32 ++++++++ src/lib/snippets.ts | 72 ----------------- src/pages/todo.tsx | 140 ++------------------------------- 12 files changed, 97 insertions(+), 271 deletions(-) delete mode 100644 snippets/snippets/todo/jnuw.ts delete mode 100644 snippets/snippets/todo/rymr.ts delete mode 100644 snippets/snippets/todo/wmgl.ts delete mode 100644 snippets/snippets/todo/wptf.ts delete mode 100644 snippets/snippets/todo/xtkd.ts delete mode 100644 snippets/snippets/todo/yvum.ts create mode 100644 src/components/Emoji/Home.tsx create mode 100644 src/components/Emoji/Work.tsx create mode 100644 src/components/PlaceLabel.tsx diff --git a/snippets/snippets/todo/jnuw.ts b/snippets/snippets/todo/jnuw.ts deleted file mode 100644 index 2a7dbc3..0000000 --- a/snippets/snippets/todo/jnuw.ts +++ /dev/null @@ -1,11 +0,0 @@ -type Todo = Readonly<{ - id: number - kind: 'todo' - text: string - done: boolean -}> - -type Separator = Readonly<{ - id: number - kind: 'separator' -}> diff --git a/snippets/snippets/todo/rymr.ts b/snippets/snippets/todo/rymr.ts deleted file mode 100644 index 7b83026..0000000 --- a/snippets/snippets/todo/rymr.ts +++ /dev/null @@ -1,5 +0,0 @@ -function completeAll( - items: readonly (Todo | Separator)[] -): (CompletedTodo | Separator)[] { - return items.map(item => /* ? */) -} diff --git a/snippets/snippets/todo/wmgl.ts b/snippets/snippets/todo/wmgl.ts deleted file mode 100644 index a01be87..0000000 --- a/snippets/snippets/todo/wmgl.ts +++ /dev/null @@ -1,9 +0,0 @@ -type Todo = Readonly<{ - id: number - text: string - done: boolean -}> - -type Separator = Readonly<{ - id: number -}> diff --git a/snippets/snippets/todo/wptf.ts b/snippets/snippets/todo/wptf.ts deleted file mode 100644 index ed1c60a..0000000 --- a/snippets/snippets/todo/wptf.ts +++ /dev/null @@ -1,27 +0,0 @@ -type Todo = Readonly<{ - id: number - text: string - done: boolean - kind: 'todo' -}> - -type Separator = Readonly<{ - id: number - kind: 'separator' -}> - -type CompletedTodo = Todo & { - readonly done: true -} - -function completeAll( - items: readonly (Todo | Separator)[] -): (CompletedTodo | Separator)[] { - return items.map(item => { - if (item.kind === 'todo') { - return { ...item, done: true } - } else { - return item - } - }) -} diff --git a/snippets/snippets/todo/xtkd.ts b/snippets/snippets/todo/xtkd.ts deleted file mode 100644 index 02a703a..0000000 --- a/snippets/snippets/todo/xtkd.ts +++ /dev/null @@ -1,3 +0,0 @@ -// A union type of Todo and Separator. -// This means: "either Todo OR Separator" -Todo | Separator diff --git a/snippets/snippets/todo/yvum.ts b/snippets/snippets/todo/yvum.ts deleted file mode 100644 index b90f533..0000000 --- a/snippets/snippets/todo/yvum.ts +++ /dev/null @@ -1,11 +0,0 @@ -// In addition to the Todo type… -type Todo = Readonly<{ - id: number - text: string - done: boolean -}> - -// We need to create the new Separator type? -type Separator = Readonly<{ - // ??? -}> diff --git a/src/components/Emoji/Home.tsx b/src/components/Emoji/Home.tsx new file mode 100644 index 0000000..758fc34 --- /dev/null +++ b/src/components/Emoji/Home.tsx @@ -0,0 +1,28 @@ +import React from 'react' + +const SvgHome = (props: React.SVGProps) => ( + + + + + + + + + + +) + +export default SvgHome diff --git a/src/components/Emoji/Work.tsx b/src/components/Emoji/Work.tsx new file mode 100644 index 0000000..0d76e60 --- /dev/null +++ b/src/components/Emoji/Work.tsx @@ -0,0 +1,24 @@ +import React from 'react' + +const SvgWork = (props: React.SVGProps) => ( + + + + + + +) + +export default SvgWork diff --git a/src/components/Emoji/index.tsx b/src/components/Emoji/index.tsx index 95c099e..a678bdb 100644 --- a/src/components/Emoji/index.tsx +++ b/src/components/Emoji/index.tsx @@ -23,6 +23,8 @@ import Ampersand from 'src/components/Emoji/Ampersand' import DoneTrue from 'src/components/Emoji/DoneTrue' import Type from 'src/components/Emoji/Type' import VerticalBar from 'src/components/Emoji/VerticalBar' +import Work from 'src/components/Emoji/Work' +import Home from 'src/components/Emoji/Home' export const emojiToComponent = { bird: Bird, @@ -47,7 +49,9 @@ export const emojiToComponent = { ampersand: Ampersand, doneTrue: DoneTrue, type: Type, - verticalBar: VerticalBar + verticalBar: VerticalBar, + work: Work, + home: Home } export const EmojiWrapper = ({ diff --git a/src/components/PlaceLabel.tsx b/src/components/PlaceLabel.tsx new file mode 100644 index 0000000..1d688e5 --- /dev/null +++ b/src/components/PlaceLabel.tsx @@ -0,0 +1,32 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core' +import Emoji from 'src/components/Emoji' +import useTheme from 'src/hooks/useTheme' + +const PlaceLabel = ({ place }: { place: 'home' | 'work' }) => { + const { colors, spaces, fontSizes, radii } = useTheme() + return ( + + + + {' '} + {place === 'home' ? 'Home' : 'Work'} + + ) +} + +export default PlaceLabel diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index b3a38f1..96533f8 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -545,18 +545,6 @@ function toggleTodo( // ... }` -export const jnuw = `type Todo = Readonly<{ - id: number - kind: 'todo' - text: string - done: boolean -}> - -type Separator = Readonly<{ - id: number - kind: 'separator' -}>` - export const kuzw = `function completeAll(todos: Todo[]): Todo[] { // We want it to return a new array // instead of modifying the original array @@ -718,12 +706,6 @@ export const ruga = `function completeAll( // ... }` -export const rymr = `function completeAll( - items: readonly (Todo | Separator)[] -): (CompletedTodo | Separator)[] { - return items.map(item => /* ? */) -}` - export const szan = `// Make input todos as readonly array function completeAll( todos: readonly Todo[] @@ -792,44 +774,6 @@ export const whae = `function completeAll( })) }` -export const wmgl = `type Todo = Readonly<{ - id: number - text: string - done: boolean -}> - -type Separator = Readonly<{ - id: number -}>` - -export const wptf = `type Todo = Readonly<{ - id: number - text: string - done: boolean - kind: 'todo' -}> - -type Separator = Readonly<{ - id: number - kind: 'separator' -}> - -type CompletedTodo = Todo & { - readonly done: true -} - -function completeAll( - items: readonly (Todo | Separator)[] -): (CompletedTodo | Separator)[] { - return items.map(item => { - if (item.kind === 'todo') { - return { ...item, done: true } - } else { - return item - } - }) -}` - export const wymp = `const originalTodo = { id: 1, text: '…', @@ -861,28 +805,12 @@ type CompletedTodo = Readonly<{ done: true }>` -export const xtkd = `// A union type of Todo and Separator. -// This means: "either Todo OR Separator" -Todo | Separator` - export const yhto = `type Todo = { readonly id: number readonly text: string readonly done: boolean }` -export const yvum = `// In addition to the Todo type… -type Todo = Readonly<{ - id: number - text: string - done: boolean -}> - -// We need to create the new Separator type? -type Separator = Readonly<{ - // ??? -}>` - export const ywiv = `// The return value must match the Todo type function toggleTodo(todo: Todo): Todo { // ... diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index e278cfb..9614bb1 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -18,6 +18,7 @@ import RunButtonText from 'src/components/RunButtonText' import CodeBlock from 'src/components/CodeBlock' import BubbleQuotes from 'src/components/BubbleQuotes' import ResultHighlight from 'src/components/ResultHighlight' +import PlaceLabel from 'src/components/PlaceLabel' const compileSuccess = 'Compiled successfully!' const section1 = 'Types, Read-only Properties, and Mapped Types' @@ -1441,143 +1442,18 @@ const Page = () => ( color: 'darkGreen' }, { - title: <>New feature: Separators, + title: <>New Feature: Location labels, content: ( <>

      - Let me introduce the new feature of our todo app called{' '} - separators.{' '} + Let’s add a new feature to our todo app:{' '} + Location labels.

      - - Sometimes, you might want to separate a group of todo items from - others. - {' '} - On the following todo app, the third item is a separator which - visually separates the first two todo items from the last. -

      - There’s a separator after the second todo item} - defaultData={[ - { id: 1, text: 'First todo', done: false }, - { id: 2, text: 'Second todo', done: false }, - { id: 1, kind: 'separator' }, - { id: 3, text: 'Third todo', done: false } - ]} - /> -

      This one has two separators:

      - -

      - Now, here’s a question:{' '} - - What type should we create to support these - separator items? - -

      - - What type should we create to support separator - items? - - } - /> - - ) - }, - { - title: ( - <> - The Separator type - - ), - content: ( - <> - -

      - Let me guess: In addition to the - existing Todo type, we need to create the - new Separator type to describe a separator - item, right? Something like this? -

      - - ) - } - ]} - /> - - lineIndex === 8 && tokenIndex <= 3 - } - /> -

      - Exactly, Little Duckling! Now, let’s think about - what needs to go into the Separator type.{' '} -

      -

      - First,{' '} - - we’ll add id just like todos - {' '} - because we’ll probably need to store the separator data into a - backend database using this id. + Each todo item can now optionally be labeled as{' '} + or , like + this:

      - lineIndex === 7} - /> -

      - Second,{' '} - - let’s add a kind property to BOTH Todo{' '} - and Separator to differentiate them. - {' '} -

      -

      - For each type, we’ll specify exactly what value is allowed for the{' '} - kind property (reminder—this is called{' '} - literal types). Each Todo item must - have kind: 'todo', and each Separator{' '} - item must have kind: 'separator'. -

      - lineIndex === 2 || lineIndex === 9} - /> -

      - That’s it! Let’s now take a look at an example. Here’s a todo list - containing some separators, displayed with its associated data: -

      - tokenIndex === 20} - /> ) }, @@ -1585,6 +1461,7 @@ const Page = () => ( title: <>, content: ( <> +

      To represent this, we need to use a TypeScript feature called{' '} union types. In TypeScript, you can use the @@ -1611,7 +1488,6 @@ const Page = () => ( In this case, we need to create a union of Todo and{' '} Separator like this:

      - ) }, From 99927d531973bc6ecfcfec2b308ad2b71d1c0714 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sun, 15 Dec 2019 08:33:58 +0900 Subject: [PATCH 20/57] Completely remove separator --- src/components/SeparatorItem.tsx | 22 --------------- src/components/TodoList.tsx | 41 +++++++++------------------- src/components/TodoWithData.tsx | 46 ++++++++++++-------------------- 3 files changed, 30 insertions(+), 79 deletions(-) delete mode 100644 src/components/SeparatorItem.tsx diff --git a/src/components/SeparatorItem.tsx b/src/components/SeparatorItem.tsx deleted file mode 100644 index 9d77aaa..0000000 --- a/src/components/SeparatorItem.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/** @jsx jsx */ -import { css, jsx } from '@emotion/core' -import useTheme from 'src/hooks/useTheme' - -const SeparatorItem = ({ text }: { text?: string }) => { - const { spaces, colors } = useTheme() - return ( -
      - {text} -
      - ) -} - -export default SeparatorItem diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx index 3c64d8b..f8d823d 100644 --- a/src/components/TodoList.tsx +++ b/src/components/TodoList.tsx @@ -5,7 +5,6 @@ import TodoItem from 'src/components/TodoItem' import { ItemType } from 'src/components/TodoWithData' import { useContext } from 'react' import TodoWithDataContext from 'src/components/TodoWithDataContext' -import SeparatorItem from 'src/components/SeparatorItem' const TodoList = ({ todos: items, @@ -18,33 +17,19 @@ const TodoList = ({ const { dispatch, disabled } = useContext(TodoWithDataContext) return ( <> - {items.map((item, index) => - item.kind !== 'separator' ? ( -
      - -
      - ) : ( -
      - -
      - ) - )} + {items.map((item, index) => ( +
      + +
      + ))} {showMarkAllAsCompleted && !disabled && items.filter(item => item.kind === 'todo' && !item.done).length > 0 && ( diff --git a/src/components/TodoWithData.tsx b/src/components/TodoWithData.tsx index 9836c34..3804cb5 100644 --- a/src/components/TodoWithData.tsx +++ b/src/components/TodoWithData.tsx @@ -22,18 +22,12 @@ const prettierFormat = (state: ItemType[], narrowText = false) => .trim() .substring(1) -export type ItemType = - | { - id: number - text: string - done: boolean - kind?: 'todo' - } - | { - id: number - kind: 'separator' - text?: string - } +export type ItemType = { + id: number + text: string + done: boolean + kind?: 'todo' +} export type TodoAction = | { @@ -53,27 +47,21 @@ const reducer = (state: TodoState, action: TodoAction) => { switch (action.type) { case 'toggle': { const item = state.todos[action.index] - if (item.kind !== 'separator') { - return { - todos: [ - ...state.todos.slice(0, action.index), - { - ...state.todos[action.index], - done: !item.done - }, - ...state.todos.slice(action.index + 1) - ], - lastChangedIndices: [action.index] - } - } else { - return state + return { + todos: [ + ...state.todos.slice(0, action.index), + { + ...state.todos[action.index], + done: !item.done + }, + ...state.todos.slice(action.index + 1) + ], + lastChangedIndices: [action.index] } } case 'markAllAsCompleted': return { - todos: state.todos.map(item => - item.kind !== 'separator' ? { ...item, done: true } : item - ), + todos: state.todos.map(item => ({ ...item, done: true })), lastChangedIndices: state.todos.map((_, index) => index) } default: From d762c30000b2b783900e7fdede3269633f7b883e Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sun, 15 Dec 2019 10:50:07 +0900 Subject: [PATCH 21/57] Location labels --- .../{PlaceLabel.tsx => LocationLabel.tsx} | 11 ++-- src/components/TodoItem.tsx | 60 +++++++++++-------- src/components/TodoList.tsx | 13 ++-- src/components/TodoWithData.tsx | 10 ++-- src/pages/todo.tsx | 20 ++++++- 5 files changed, 73 insertions(+), 41 deletions(-) rename src/components/{PlaceLabel.tsx => LocationLabel.tsx} (69%) diff --git a/src/components/PlaceLabel.tsx b/src/components/LocationLabel.tsx similarity index 69% rename from src/components/PlaceLabel.tsx rename to src/components/LocationLabel.tsx index 1d688e5..275753f 100644 --- a/src/components/PlaceLabel.tsx +++ b/src/components/LocationLabel.tsx @@ -3,7 +3,7 @@ import { css, jsx } from '@emotion/core' import Emoji from 'src/components/Emoji' import useTheme from 'src/hooks/useTheme' -const PlaceLabel = ({ place }: { place: 'home' | 'work' }) => { +const LocationLabel = ({ location }: { location: 'home' | 'work' }) => { const { colors, spaces, fontSizes, radii } = useTheme() return ( { display: inline-block; background: ${colors('white')}; padding: ${spaces(0.125)} ${spaces(0.5)}; - font-size: ${fontSizes(0.85, true)}; + font-size: ${fontSizes(0.8, true)}; border: 2px solid ${colors('paleGreen')}; border-radius: ${radii(0.25)}; + color: ${colors('darkGreen')}; `} > { margin-right: ${spaces(0.125)}; `} > - + {' '} - {place === 'home' ? 'Home' : 'Work'} + {location === 'home' ? 'Home' : 'Work'} ) } -export default PlaceLabel +export default LocationLabel diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx index d949baa..8744228 100644 --- a/src/components/TodoItem.tsx +++ b/src/components/TodoItem.tsx @@ -5,15 +5,19 @@ import Emoji from 'src/components/Emoji' import useTheme from 'src/hooks/useTheme' import { useState, useContext } from 'react' import TodoWithDataContext from 'src/components/TodoWithDataContext' +import { Todo } from 'src/components/TodoWithData' +import LocationLabel from 'src/components/LocationLabel' const TodoItem = ({ index, done, - text + text, + location }: { index: number - text: string - done: boolean + text: Todo['text'] + done: Todo['done'] + location: Todo['location'] }) => { const { spaces, colors } = useTheme() const { dispatch, disabled } = useContext(TodoWithDataContext) @@ -25,6 +29,7 @@ const TodoItem = ({ css={css` padding: ${spaces(0.25)} ${spaces(0.5)}; display: flex; + align-items: center; `} > : } - disabled ? undefined : dispatch({ type: 'toggle', index }) - } - onMouseOver={hoverOn} - onMouseOut={hoverOff} - onTouchStart={hoverOn} - onTouchEnd={hoverOff} - onTouchCancel={hoverOff} - onFocus={hoverOn} - onBlur={hoverOff} + css={css` + flex: 1; + `} > - {text} + + disabled ? undefined : dispatch({ type: 'toggle', index }) + } + onMouseOver={hoverOn} + onMouseOut={hoverOff} + onTouchStart={hoverOn} + onTouchEnd={hoverOff} + onTouchCancel={hoverOff} + onFocus={hoverOn} + onBlur={hoverOff} + > + {text} + + {location && }
    ) } diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx index f8d823d..ef6f946 100644 --- a/src/components/TodoList.tsx +++ b/src/components/TodoList.tsx @@ -2,7 +2,7 @@ import { css, jsx } from '@emotion/core' import useTheme from 'src/hooks/useTheme' import TodoItem from 'src/components/TodoItem' -import { ItemType } from 'src/components/TodoWithData' +import { Todo } from 'src/components/TodoWithData' import { useContext } from 'react' import TodoWithDataContext from 'src/components/TodoWithDataContext' @@ -10,7 +10,7 @@ const TodoList = ({ todos: items, showMarkAllAsCompleted }: { - todos: ItemType[] + todos: Todo[] showMarkAllAsCompleted?: boolean }) => { const { fontSizes, spaces, colors } = useTheme() @@ -27,12 +27,17 @@ const TodoList = ({ ` } > - +
    ))} {showMarkAllAsCompleted && !disabled && - items.filter(item => item.kind === 'todo' && !item.done).length > 0 && ( + items.filter(item => !item.done).length > 0 && (
    +const prettierFormat = (state: Todo[], narrowText = false) => format(JSON.stringify(state), { semi: false, singleQuote: true, @@ -22,11 +22,11 @@ const prettierFormat = (state: ItemType[], narrowText = false) => .trim() .substring(1) -export type ItemType = { +export type Todo = { id: number text: string done: boolean - kind?: 'todo' + location?: 'home' | 'work' } export type TodoAction = @@ -39,7 +39,7 @@ export type TodoAction = } export type TodoState = { - todos: ItemType[] + todos: Todo[] lastChangedIndices: number[] } @@ -81,7 +81,7 @@ const TodoWithData = ({ shouldHighlight, narrowText }: { - defaultData: ItemType[] + defaultData: Todo[] caption?: React.ReactNode promptArrowText?: React.ReactNode showData?: boolean diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 9614bb1..3fbb1bc 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -18,7 +18,7 @@ import RunButtonText from 'src/components/RunButtonText' import CodeBlock from 'src/components/CodeBlock' import BubbleQuotes from 'src/components/BubbleQuotes' import ResultHighlight from 'src/components/ResultHighlight' -import PlaceLabel from 'src/components/PlaceLabel' +import LocationLabel from 'src/components/LocationLabel' const compileSuccess = 'Compiled successfully!' const section1 = 'Types, Read-only Properties, and Mapped Types' @@ -1451,9 +1451,23 @@ const Page = () => (

    Each todo item can now optionally be labeled as{' '} - or , like - this: + or{' '} + , like this:

    + + Each todo item can now optionally be labeled as{' '} + or{' '} + + + } + defaultData={[ + { id: 1, text: 'Do laundry', done: false, location: 'home' }, + { id: 2, text: 'Email boss', done: false, location: 'work' }, + { id: 3, text: 'Go to gym', done: false } + ]} + /> ) }, From 78b82e41cde5af17aa6d4e256ba3006b650d3159 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sun, 15 Dec 2019 13:14:21 +0900 Subject: [PATCH 22/57] Done with slide 21 --- snippets/snippets/todo/rvyq.ts | 7 ++ snippets/snippets/todo/umjt.ts | 7 ++ snippets/snippets/todo/yztr.ts | 6 + .../{LocationLabel.tsx => PlaceLabel.tsx} | 8 +- src/components/TodoItem.tsx | 8 +- src/components/TodoList.tsx | 2 +- src/components/TodoWithData.tsx | 13 +- src/lib/snippets.ts | 23 ++++ src/pages/todo.tsx | 116 +++++++++++++++--- 9 files changed, 163 insertions(+), 27 deletions(-) create mode 100644 snippets/snippets/todo/rvyq.ts create mode 100644 snippets/snippets/todo/umjt.ts create mode 100644 snippets/snippets/todo/yztr.ts rename src/components/{LocationLabel.tsx => PlaceLabel.tsx} (79%) diff --git a/snippets/snippets/todo/rvyq.ts b/snippets/snippets/todo/rvyq.ts new file mode 100644 index 0000000..440721b --- /dev/null +++ b/snippets/snippets/todo/rvyq.ts @@ -0,0 +1,7 @@ +type Todo = Readonly<{ + id: number + text: string + done: boolean + // place is optional + place?: 'home' | 'work' +}> diff --git a/snippets/snippets/todo/umjt.ts b/snippets/snippets/todo/umjt.ts new file mode 100644 index 0000000..9c0e87f --- /dev/null +++ b/snippets/snippets/todo/umjt.ts @@ -0,0 +1,7 @@ +type Todo = Readonly<{ + id: number + text: string + done: boolean + // Union Type: Can be either 'home' or 'work' + place: 'home' | 'work' +}> diff --git a/snippets/snippets/todo/yztr.ts b/snippets/snippets/todo/yztr.ts new file mode 100644 index 0000000..4a742ec --- /dev/null +++ b/snippets/snippets/todo/yztr.ts @@ -0,0 +1,6 @@ +// How to update this to support place labels? +type Todo = Readonly<{ + id: number + text: string + done: boolean +}> diff --git a/src/components/LocationLabel.tsx b/src/components/PlaceLabel.tsx similarity index 79% rename from src/components/LocationLabel.tsx rename to src/components/PlaceLabel.tsx index 275753f..2a2baad 100644 --- a/src/components/LocationLabel.tsx +++ b/src/components/PlaceLabel.tsx @@ -3,7 +3,7 @@ import { css, jsx } from '@emotion/core' import Emoji from 'src/components/Emoji' import useTheme from 'src/hooks/useTheme' -const LocationLabel = ({ location }: { location: 'home' | 'work' }) => { +const PlaceLabel = ({ place }: { place: 'home' | 'work' }) => { const { colors, spaces, fontSizes, radii } = useTheme() return ( { margin-right: ${spaces(0.125)}; `} > - + {' '} - {location === 'home' ? 'Home' : 'Work'} + {place === 'home' ? 'Home' : 'Work'} ) } -export default LocationLabel +export default PlaceLabel diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx index 8744228..8ffbabe 100644 --- a/src/components/TodoItem.tsx +++ b/src/components/TodoItem.tsx @@ -6,18 +6,18 @@ import useTheme from 'src/hooks/useTheme' import { useState, useContext } from 'react' import TodoWithDataContext from 'src/components/TodoWithDataContext' import { Todo } from 'src/components/TodoWithData' -import LocationLabel from 'src/components/LocationLabel' +import PlaceLabel from 'src/components/PlaceLabel' const TodoItem = ({ index, done, text, - location + place }: { index: number text: Todo['text'] done: Todo['done'] - location: Todo['location'] + place: Todo['place'] }) => { const { spaces, colors } = useTheme() const { dispatch, disabled } = useContext(TodoWithDataContext) @@ -92,7 +92,7 @@ const TodoItem = ({ {text} - {location && } + {place && }
    ) } diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx index ef6f946..c936a55 100644 --- a/src/components/TodoList.tsx +++ b/src/components/TodoList.tsx @@ -31,7 +31,7 @@ const TodoList = ({ index={index} done={item.done} text={item.text} - location={item.location} + place={item.place} />
    ))} diff --git a/src/components/TodoWithData.tsx b/src/components/TodoWithData.tsx index 5348a82..380cc73 100644 --- a/src/components/TodoWithData.tsx +++ b/src/components/TodoWithData.tsx @@ -26,7 +26,7 @@ export type Todo = { id: number text: string done: boolean - location?: 'home' | 'work' + place?: 'home' | 'work' } export type TodoAction = @@ -79,6 +79,7 @@ const TodoWithData = ({ disabled, highlightLineIndexOffset, shouldHighlight, + shouldAlwaysHighlight, narrowText }: { defaultData: Todo[] @@ -90,6 +91,7 @@ const TodoWithData = ({ disabled?: boolean highlightLineIndexOffset?: number shouldHighlight?: (tokenIndex: number) => boolean + shouldAlwaysHighlight?: (lineIndex: number, tokenIndex: number) => boolean narrowText?: boolean }) => { const { spaces, ns, maxWidths, radii, colors, letterSpacings } = useTheme() @@ -144,6 +146,15 @@ const TodoWithData = ({ letter-spacing: ${letterSpacings('smallCode')}; ` ]} + lineCssOverrides={(lineIndex, tokenIndex) => + shouldAlwaysHighlight && + shouldAlwaysHighlight(lineIndex, tokenIndex) && + css` + background: ${colors('yellowHighlight')}; + border-bottom: 2px solid ${colors('darkOrange')}; + font-weight: bold; + ` + } lineCssOverridesAnimation={(lineIndex, tokenIndex) => shouldHighlight && state.lastChangedIndices diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 96533f8..d0466a0 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -706,6 +706,14 @@ export const ruga = `function completeAll( // ... }` +export const rvyq = `type Todo = Readonly<{ + id: number + text: string + done: boolean + // place is optional + place?: 'home' | 'work' +}>` + export const szan = `// Make input todos as readonly array function completeAll( todos: readonly Todo[] @@ -724,6 +732,14 @@ export const tgvw = `const bar: Todo = { done: true }` +export const umjt = `type Todo = Readonly<{ + id: number + text: string + done: boolean + // Union Type: Can be either 'home' or 'work' + place: 'home' | 'work' +}>` + export const uxlb = `function toggleTodo(todo: Todo): Todo { // Little Duckling’s refactoring todo.done = !todo.done @@ -825,6 +841,13 @@ export const yxjg = `function toggleTodo(todo) { } }` +export const yztr = `// How to update this to support place labels? +type Todo = Readonly<{ + id: number + text: string + done: boolean +}>` + export const zswn = `// Will this compile? const testTodo: CompletedTodo = { id: 1, diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 3fbb1bc..1c9a88a 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -18,7 +18,7 @@ import RunButtonText from 'src/components/RunButtonText' import CodeBlock from 'src/components/CodeBlock' import BubbleQuotes from 'src/components/BubbleQuotes' import ResultHighlight from 'src/components/ResultHighlight' -import LocationLabel from 'src/components/LocationLabel' +import PlaceLabel from 'src/components/PlaceLabel' const compileSuccess = 'Compiled successfully!' const section1 = 'Types, Read-only Properties, and Mapped Types' @@ -1442,45 +1442,73 @@ const Page = () => ( color: 'darkGreen' }, { - title: <>New Feature: Location labels, + title: <>New Feature: Place labels, content: ( <>

    Let’s add a new feature to our todo app:{' '} - Location labels. + Place labels.

    - Each todo item can now optionally be labeled as{' '} - or{' '} - , like this: + + Each todo item can now optionally be labeled as{' '} + or + {' '} + as shown below. People can use this feature to identify which + tasks need to be done at home, at work, or elsewhere.

    Each todo item can now optionally be labeled as{' '} - or{' '} - + or } defaultData={[ - { id: 1, text: 'Do laundry', done: false, location: 'home' }, - { id: 2, text: 'Email boss', done: false, location: 'work' }, + { id: 1, text: 'Do laundry', done: false, place: 'home' }, + { id: 2, text: 'Email boss', done: false, place: 'work' }, { id: 3, text: 'Go to gym', done: false } ]} /> +

    + Let’s take a look at the associated data.{' '} + + Each todo now can have an optional place property, + which can be either 'home' or 'work'. + +

    + + lineIndex === 5 || lineIndex === 11 + } + /> +

    + To implement this in TypeScript, we first need to update our + definition of the Todo type. Let’s take a look at how + we can do this. +

    + lineIndex === 0} + /> ) }, { - title: <>, + title: <>Union types, content: ( <> -

    - To represent this, we need to use a TypeScript feature called{' '} + To represent place labels, we can use a TypeScript feature called{' '} union types. In TypeScript, you can use the - syntax A | B to create a union type, which represents - a type that’s{' '} + syntax A | B to create a union type, + which represents a type that’s{' '} either A or B @@ -1499,8 +1527,62 @@ const Page = () => ( } />

    - In this case, we need to create a union of Todo and{' '} - Separator like this: + In this case, because the place property can be + either 'home' or 'work', we can create a + union 'home' | 'work': +

    + + lineIndex === 5 && tokenIndex >= 3 + } + /> + + ) + }, + { + title: <>Optional properties, + content: ( + <> +

    + We briefly mentioned that place labels like{' '} + or are{' '} + optional—we can have todo items without a place + label. In our previous example, “Go to Gym”{' '} + didn’t have any place label: +

    + + Place labels are optional: “Go to Gym”{' '} + didn’t have any place label. + + } + defaultData={[ + { id: 1, text: 'Do laundry', done: false, place: 'home' }, + { id: 2, text: 'Email boss', done: false, place: 'work' }, + { id: 3, text: 'Go to gym', done: false } + ]} + shouldAlwaysHighlight={lineIndex => lineIndex === 13} + /> +

    + In TypeScript,{' '} + + you can add a question mark (?) after a property + name to make the property optional: + +

    + + lineIndex === 5 && tokenIndex <= 1 + } + /> +

    + That’s it! The above Todo type will allow the{' '} + place property to be 'home',{' '} + 'work', or missing from the object.

    ) From a37580757df6934b3c54e39081a4c08b1d222798 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sun, 15 Dec 2019 14:28:11 +0900 Subject: [PATCH 23/57] Start slide 22 --- src/components/Emoji/Pin.tsx | 14 ++++++ src/components/Emoji/index.tsx | 4 +- src/components/PlaceLabel.tsx | 7 +-- src/components/TodoWithData.tsx | 2 +- src/pages/todo.tsx | 77 ++++++++++++++++++++++++++++++--- 5 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 src/components/Emoji/Pin.tsx diff --git a/src/components/Emoji/Pin.tsx b/src/components/Emoji/Pin.tsx new file mode 100644 index 0000000..6d70204 --- /dev/null +++ b/src/components/Emoji/Pin.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +const SvgPin = (props: React.SVGProps) => ( + + + + + +) + +export default SvgPin diff --git a/src/components/Emoji/index.tsx b/src/components/Emoji/index.tsx index a678bdb..ed4aceb 100644 --- a/src/components/Emoji/index.tsx +++ b/src/components/Emoji/index.tsx @@ -25,6 +25,7 @@ import Type from 'src/components/Emoji/Type' import VerticalBar from 'src/components/Emoji/VerticalBar' import Work from 'src/components/Emoji/Work' import Home from 'src/components/Emoji/Home' +import Pin from 'src/components/Emoji/Pin' export const emojiToComponent = { bird: Bird, @@ -51,7 +52,8 @@ export const emojiToComponent = { type: Type, verticalBar: VerticalBar, work: Work, - home: Home + home: Home, + pin: Pin } export const EmojiWrapper = ({ diff --git a/src/components/PlaceLabel.tsx b/src/components/PlaceLabel.tsx index 2a2baad..6e1cd3a 100644 --- a/src/components/PlaceLabel.tsx +++ b/src/components/PlaceLabel.tsx @@ -2,8 +2,9 @@ import { css, jsx } from '@emotion/core' import Emoji from 'src/components/Emoji' import useTheme from 'src/hooks/useTheme' +import { Todo } from 'src/components/TodoWithData' -const PlaceLabel = ({ place }: { place: 'home' | 'work' }) => { +const PlaceLabel = ({ place }: { place: NonNullable }) => { const { colors, spaces, fontSizes, radii } = useTheme() return ( { margin-right: ${spaces(0.125)}; `} > - + {' '} - {place === 'home' ? 'Home' : 'Work'} + {place === 'home' ? 'Home' : place === 'work' ? 'Work' : place.custom} ) } diff --git a/src/components/TodoWithData.tsx b/src/components/TodoWithData.tsx index 380cc73..756b089 100644 --- a/src/components/TodoWithData.tsx +++ b/src/components/TodoWithData.tsx @@ -26,7 +26,7 @@ export type Todo = { id: number text: string done: boolean - place?: 'home' | 'work' + place?: 'home' | 'work' | { custom: string } } export type TodoAction = diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 1c9a88a..6f2137d 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1467,7 +1467,7 @@ const Page = () => ( defaultData={[ { id: 1, text: 'Do laundry', done: false, place: 'home' }, { id: 2, text: 'Email boss', done: false, place: 'work' }, - { id: 3, text: 'Go to gym', done: false } + { id: 3, text: 'Buy milk', done: false } ]} />

    @@ -1482,7 +1482,7 @@ const Page = () => ( defaultData={[ { id: 1, text: 'Do laundry', done: false, place: 'home' }, { id: 2, text: 'Email boss', done: false, place: 'work' }, - { id: 3, text: 'Go to gym', done: false } + { id: 3, text: 'Buy milk', done: false } ]} shouldAlwaysHighlight={lineIndex => lineIndex === 5 || lineIndex === 11 @@ -1548,21 +1548,21 @@ const Page = () => ( We briefly mentioned that place labels like{' '} or are{' '} optional—we can have todo items without a place - label. In our previous example, “Go to Gym”{' '} + label. In our previous example, “Buy milk”{' '} didn’t have any place label:

    - Place labels are optional: “Go to Gym”{' '} + Place labels are optional: “Buy milk”{' '} didn’t have any place label. } defaultData={[ { id: 1, text: 'Do laundry', done: false, place: 'home' }, { id: 2, text: 'Email boss', done: false, place: 'work' }, - { id: 3, text: 'Go to gym', done: false } + { id: 3, text: 'Buy milk', done: false } ]} shouldAlwaysHighlight={lineIndex => lineIndex === 13} /> @@ -1587,6 +1587,73 @@ const Page = () => ( ) }, + { + title: <>Custom places, + content: ( + <> +

    + Now, let’s improve the place labels feature by allowing users to + assign a custom place label for places other than{' '} + or . +

    +

    + In the following example, we’ve created new{' '} + custom place labels called{' '} + and{' '} + : +

    + + Added custom places {' '} + and + + } + defaultData={[ + { + id: 3, + text: 'Go to gym', + done: false, + place: { custom: 'Gym' } + }, + { + id: 4, + text: 'Read a book', + done: false, + place: { custom: 'Cafe' } + } + ]} + /> +

    + Here’s what the data looks like.{' '} + + For custom places, we’ll set the place property as + an object like this: {`{ custom: 'Gym' }`}. + +

    + + (lineIndex === 5 || lineIndex === 11) && tokenIndex >= 3 + } + /> + + ) + }, underConstructionCard ]} /> From 1ee3cd60d7d9dd583d473b514544de5b9643c9b2 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sun, 15 Dec 2019 16:24:04 +0900 Subject: [PATCH 24/57] Done with slide 21 --- .prettierrc | 2 +- snippets/snippets/todo/hquv.ts | 8 + snippets/snippets/todo/mzyn.ts | 7 + snippets/snippets/todo/npgx.ts | 6 + snippets/snippets/todo/rvyq.ts | 4 +- snippets/snippets/todo/umjt.ts | 8 +- snippets/snippets/todo/yvpp.ts | 9 + src/components/CodeBlockHighlight.tsx | 2 +- src/components/CodeResult.tsx | 2 +- src/components/PlaceLabel.tsx | 7 +- src/components/TodoBlank.tsx | 2 +- src/components/TodoWithData.tsx | 15 +- src/lib/snippets.ts | 48 ++++- src/pages/todo.tsx | 281 +++++++++++++++----------- 14 files changed, 251 insertions(+), 150 deletions(-) create mode 100644 snippets/snippets/todo/hquv.ts create mode 100644 snippets/snippets/todo/mzyn.ts create mode 100644 snippets/snippets/todo/npgx.ts create mode 100644 snippets/snippets/todo/yvpp.ts diff --git a/.prettierrc b/.prettierrc index 5b94cd9..4765af0 100644 --- a/.prettierrc +++ b/.prettierrc @@ -9,7 +9,7 @@ } }, { - "files": "snippets/snippets/**/ybsb.ts", + "files": ["snippets/snippets/**/umjt.ts", "snippets/snippets/**/rvyq.ts"], "options": { "printWidth": 54 } diff --git a/snippets/snippets/todo/hquv.ts b/snippets/snippets/todo/hquv.ts new file mode 100644 index 0000000..267782b --- /dev/null +++ b/snippets/snippets/todo/hquv.ts @@ -0,0 +1,8 @@ +;[ + // ... + // ... + // ... + // ... + // No place property + { id: 5, text: 'Read a book', done: false } +] diff --git a/snippets/snippets/todo/mzyn.ts b/snippets/snippets/todo/mzyn.ts new file mode 100644 index 0000000..df1fced --- /dev/null +++ b/snippets/snippets/todo/mzyn.ts @@ -0,0 +1,7 @@ +// Creates a union type of number and string +type Foo = number | string + +// You can assign either a number or a string +// variable to Foo. So these will both compile: +const a: Foo = 1 +const b: Foo = 'hello' diff --git a/snippets/snippets/todo/npgx.ts b/snippets/snippets/todo/npgx.ts new file mode 100644 index 0000000..5f1c6bf --- /dev/null +++ b/snippets/snippets/todo/npgx.ts @@ -0,0 +1,6 @@ +type Todo = Readonly<{ + id: number + text: string + done: boolean + place: Place +}> diff --git a/snippets/snippets/todo/rvyq.ts b/snippets/snippets/todo/rvyq.ts index 440721b..b26bdc6 100644 --- a/snippets/snippets/todo/rvyq.ts +++ b/snippets/snippets/todo/rvyq.ts @@ -1,7 +1,9 @@ +type Place = 'home' | 'work' | { custom: string } + type Todo = Readonly<{ id: number text: string done: boolean // place is optional - place?: 'home' | 'work' + place?: Place }> diff --git a/snippets/snippets/todo/umjt.ts b/snippets/snippets/todo/umjt.ts index 9c0e87f..72c7600 100644 --- a/snippets/snippets/todo/umjt.ts +++ b/snippets/snippets/todo/umjt.ts @@ -1,7 +1 @@ -type Todo = Readonly<{ - id: number - text: string - done: boolean - // Union Type: Can be either 'home' or 'work' - place: 'home' | 'work' -}> +type Place = 'home' | 'work' | { custom: string } diff --git a/snippets/snippets/todo/yvpp.ts b/snippets/snippets/todo/yvpp.ts new file mode 100644 index 0000000..c752152 --- /dev/null +++ b/snippets/snippets/todo/yvpp.ts @@ -0,0 +1,9 @@ +type Foo = { + // bar is an optional property because of "?" + bar?: number +} + +// These will both compile: +// bar can be present or missing +const a: Foo = {} +const b: Foo = { bar: 1 } diff --git a/src/components/CodeBlockHighlight.tsx b/src/components/CodeBlockHighlight.tsx index fc24c9d..a270746 100644 --- a/src/components/CodeBlockHighlight.tsx +++ b/src/components/CodeBlockHighlight.tsx @@ -37,7 +37,7 @@ const CodeBlockHighlight = ({ css={[ css` padding: ${spaces(0.75)} ${spaces(0.5)}; - line-height: 1.45; + line-height: 1.4; border: 2px solid ${colors('lightBrown')}; background-color: ${colors('lightPink1')}; font-size: ${fontSizes(0.8)}; diff --git a/src/components/CodeResult.tsx b/src/components/CodeResult.tsx index eb211ff..6acd1f7 100644 --- a/src/components/CodeResult.tsx +++ b/src/components/CodeResult.tsx @@ -43,7 +43,7 @@ const CodeResult = ({ css` font-size: ${fontSizes(0.8)}; word-break: break-all; - line-height: 1.45; + line-height: 1.4; ${nt} { font-size: ${fontSizes(0.85)}; } diff --git a/src/components/PlaceLabel.tsx b/src/components/PlaceLabel.tsx index 6e1cd3a..0455b03 100644 --- a/src/components/PlaceLabel.tsx +++ b/src/components/PlaceLabel.tsx @@ -12,7 +12,8 @@ const PlaceLabel = ({ place }: { place: NonNullable }) => { font-weight: bold; display: inline-block; background: ${colors('white')}; - padding: ${spaces(0.125)} ${spaces(0.5)}; + padding: ${spaces(0.125)} ${spaces(0.375)} ${spaces(0.125)} + ${spaces(place === 'home' || place === 'work' ? 0.375 : 0.25)}; font-size: ${fontSizes(0.8, true)}; border: 2px solid ${colors('paleGreen')}; border-radius: ${radii(0.25)}; @@ -21,7 +22,9 @@ const PlaceLabel = ({ place }: { place: NonNullable }) => { > diff --git a/src/components/TodoBlank.tsx b/src/components/TodoBlank.tsx index cf601db..d8b8cad 100644 --- a/src/components/TodoBlank.tsx +++ b/src/components/TodoBlank.tsx @@ -23,7 +23,7 @@ const TodoBlank = ({ y={2} width={32} height={32} - rx={4} + rx={2.25} /> diff --git a/src/components/TodoWithData.tsx b/src/components/TodoWithData.tsx index 756b089..2fe03b2 100644 --- a/src/components/TodoWithData.tsx +++ b/src/components/TodoWithData.tsx @@ -80,7 +80,8 @@ const TodoWithData = ({ highlightLineIndexOffset, shouldHighlight, shouldAlwaysHighlight, - narrowText + narrowText, + customSnippet }: { defaultData: Todo[] caption?: React.ReactNode @@ -93,6 +94,7 @@ const TodoWithData = ({ shouldHighlight?: (tokenIndex: number) => boolean shouldAlwaysHighlight?: (lineIndex: number, tokenIndex: number) => boolean narrowText?: boolean + customSnippet?: string }) => { const { spaces, ns, maxWidths, radii, colors, letterSpacings } = useTheme() const [state, dispatch] = useReducer(reducer, { @@ -129,10 +131,13 @@ const TodoWithData = ({ /> {showData && ( ` + export const ntau = `function toggleTodo(todo: Todo): Todo { // Little Duckling’s code from earlier: // Missing the "id" property @@ -706,12 +730,14 @@ export const ruga = `function completeAll( // ... }` -export const rvyq = `type Todo = Readonly<{ +export const rvyq = `type Place = 'home' | 'work' | { custom: string } + +type Todo = Readonly<{ id: number text: string done: boolean // place is optional - place?: 'home' | 'work' + place?: Place }>` export const szan = `// Make input todos as readonly array @@ -732,13 +758,7 @@ export const tgvw = `const bar: Todo = { done: true }` -export const umjt = `type Todo = Readonly<{ - id: number - text: string - done: boolean - // Union Type: Can be either 'home' or 'work' - place: 'home' | 'work' -}>` +export const umjt = `type Place = 'home' | 'work' | { custom: string }` export const uxlb = `function toggleTodo(todo: Todo): Todo { // Little Duckling’s refactoring @@ -827,6 +847,16 @@ export const yhto = `type Todo = { readonly done: boolean }` +export const yvpp = `type Foo = { + // bar is an optional property because of "?" + bar?: number +} + +// These will both compile: +// bar can be present or missing +const a: Foo = {} +const b: Foo = { bar: 1 }` + export const ywiv = `// The return value must match the Todo type function toggleTodo(todo: Todo): Todo { // ... diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 6f2137d..6950bf0 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -13,7 +13,7 @@ import { } from 'src/components/ContentTags' import * as snippets from 'src/lib/snippets' import underConstructionCard from 'src/lib/underConstructionCard' -import TodoWithData from 'src/components/TodoWithData' +import TodoWithData, { Todo } from 'src/components/TodoWithData' import RunButtonText from 'src/components/RunButtonText' import CodeBlock from 'src/components/CodeBlock' import BubbleQuotes from 'src/components/BubbleQuotes' @@ -24,6 +24,27 @@ const compileSuccess = 'Compiled successfully!' const section1 = 'Types, Read-only Properties, and Mapped Types' const section2 = 'Array Types, Literal Types, and Intersection Types' const section3 = 'Union Types and Optional Properties' +const placesExample: Todo[] = [ + { id: 1, text: 'Do laundry', done: false, place: 'home' as const }, + { id: 2, text: 'Email boss', done: false, place: 'work' as const }, + { + id: 3, + text: 'Go to gym', + done: false, + place: { custom: 'Gym' } + }, + { + id: 4, + text: 'Buy milk', + done: false, + place: { custom: 'Supermarket' } + }, + { + id: 5, + text: 'Read a book', + done: false + } +] const Page = () => ( ( compile resultError result={<>Type 'false' is not assignable to type 'true'.} - shouldHighlightResult={lineNumber => lineNumber === 4} + shouldHighlightResult={lineIndex => lineIndex === 4} />

    It failed to compile because done is not{' '} @@ -1329,7 +1350,7 @@ const Page = () => ( snippet={snippets.hszk} compile result={compileSuccess} - shouldHighlight={lineNumber => lineNumber >= 3 && lineNumber <= 6} + shouldHighlight={lineIndex => lineIndex >= 3 && lineIndex <= 6} />

    It compiled! Let’s run this function on an example todo list.{' '} @@ -1366,8 +1387,8 @@ const Page = () => ( } resultError - shouldHighlight={lineNumber => lineNumber === 6} - shouldHighlightResult={lineNumber => lineNumber === 6} + shouldHighlight={lineIndex => lineIndex === 6} + shouldHighlightResult={lineIndex => lineIndex === 6} />

    It failed because CompletedTodo must have{' '} @@ -1395,9 +1416,9 @@ const Page = () => (

    - (lineNumber === 2 && tokenNumber >= 2 && tokenNumber <= 6) || - (lineNumber === 1 && tokenNumber >= 2) + shouldHighlight={(lineIndex, tokenIndex) => + (lineIndex === 2 && tokenIndex >= 2 && tokenIndex <= 6) || + (lineIndex === 1 && tokenIndex >= 2) } />

    @@ -1408,7 +1429,7 @@ const Page = () => (

    lineNumber === 3} + shouldHighlight={lineIndex => lineIndex === 3} />

    Finally, we learned that{' '} @@ -1442,56 +1463,89 @@ const Page = () => ( color: 'darkGreen' }, { - title: <>New Feature: Place labels, + title: <>New Feature: Place tags, content: ( <>

    Let’s add a new feature to our todo app:{' '} - Place labels. + Place tags.{' '} + + Each todo item can now optionally be tagged with one of the + following tags: +

    +
      + + Home: + + + Work: + + + Custom place:{' '} + ,{' '} + , etc—the user + can create any custom place they want. + +

    + People can use this feature to identify which tasks need to be + done at home, at work, or elsewhere.{' '} - Each todo item can now optionally be labeled as{' '} - or - {' '} - as shown below. People can use this feature to identify which - tasks need to be done at home, at work, or elsewhere. + It’s optional, so there can be a todo item + without a place tag. +

    +

    Here’s an example:

    - Each todo item can now optionally be labeled as{' '} - or + Each todo item can now optionally be tagged with a{' '} + place tag } - defaultData={[ - { id: 1, text: 'Do laundry', done: false, place: 'home' }, - { id: 2, text: 'Email boss', done: false, place: 'work' }, - { id: 3, text: 'Buy milk', done: false } - ]} + defaultData={placesExample} />

    Let’s take a look at the associated data.{' '} Each todo now can have an optional place property, - which can be either 'home' or 'work'. + which can have the following value:

    +
      + + 'home' + + + 'work' + + + →{' '} + {`{ custom: 'Custom name' }`} + +
    +

    + So place can be 'home',{' '} + 'work', or an object containing a string{' '} + custom property. It can also be missing if there’s no + place tag. +

    +

    Here’s the associated data for our previous example:

    - lineIndex === 5 || lineIndex === 11 + lineIndex === 5 || + lineIndex === 11 || + lineIndex === 17 || + lineIndex === 23 } />

    To implement this in TypeScript, we first need to update our - definition of the Todo type. Let’s take a look at how - we can do this. + definition of the Todo type. Let’s take a look at + this next!

    ( content: ( <>

    - To represent place labels, we can use a TypeScript feature called{' '} - union types. In TypeScript, you can use the - syntax A | B to create a union type, - which represents a type that’s{' '} + To represent place tags, we can use a TypeScript feature called{' '} + union types. In TypeScript,{' '} + you can use the syntax A | B to create a{' '} + union type, which represents a type that’s{' '} either A or B . @@ -1527,130 +1581,113 @@ const Page = () => ( } />

    - In this case, because the place property can be - either 'home' or 'work', we can create a - union 'home' | 'work': + For example, if you create a type that’s equal to{' '} + number | string, it can be either number{' '} + OR string:

    - lineIndex === 5 && tokenIndex >= 3 + lineIndex === 1 && tokenIndex >= 7 } /> - - ) - }, - { - title: <>Optional properties, - content: ( - <>

    - We briefly mentioned that place labels like{' '} - or are{' '} - optional—we can have todo items without a place - label. In our previous example, “Buy milk”{' '} - didn’t have any place label: + In our todo app,{' '} + + we’ll first create a new Place type as a union type + {' '} + as follows:

    - - Place labels are optional: “Buy milk”{' '} - didn’t have any place label. + Place can be either 'home',{' '} + 'work', or an object containing a string{' '} + custom property } - defaultData={[ - { id: 1, text: 'Do laundry', done: false, place: 'home' }, - { id: 2, text: 'Email boss', done: false, place: 'work' }, - { id: 3, text: 'Buy milk', done: false } - ]} - shouldAlwaysHighlight={lineIndex => lineIndex === 13} + snippet={snippets.umjt} + shouldHighlight={(lineIndex, tokenIndex) => + lineIndex === 0 && tokenIndex >= 6 + } />

    - In TypeScript,{' '} - - you can add a question mark (?) after a property - name to make the property optional: - + Then we can assign the Place type to the{' '} + place property of Todo:

    + Assign Place to Todo’s{' '} + place property + + } + snippet={snippets.npgx} shouldHighlight={(lineIndex, tokenIndex) => - lineIndex === 5 && tokenIndex <= 1 + lineIndex === 4 && tokenIndex >= 3 } /> -

    - That’s it! The above Todo type will allow the{' '} - place property to be 'home',{' '} - 'work', or missing from the object. -

    ) }, { - title: <>Custom places, + title: <>Optional properties, content: ( <>

    - Now, let’s improve the place labels feature by allowing users to - assign a custom place label for places other than{' '} - or . -

    -

    - In the following example, we’ve created new{' '} - custom place labels called{' '} - and{' '} - : + We briefly mentioned that place tags like{' '} + or are{' '} + optional—we can have todo items without a place + tag. In our previous example, “Read a book”{' '} + didn’t have any place tag, so it didn’t have the{' '} + place property:

    - Added custom places {' '} - and + Place tags are optional: “Read a book”{' '} + didn’t have any place tag, so NO place property } - defaultData={[ - { - id: 3, - text: 'Go to gym', - done: false, - place: { custom: 'Gym' } - }, - { - id: 4, - text: 'Read a book', - done: false, - place: { custom: 'Cafe' } - } - ]} + defaultData={placesExample} + shouldAlwaysHighlight={lineIndex => lineIndex === 6} />

    - Here’s what the data looks like.{' '} + Can TypeScript describe these optional properties? Of + course it can. In TypeScript,{' '} - For custom places, we’ll set the place property as - an object like this: {`{ custom: 'Gym' }`}. + you can add a question mark (?) + after a property name to make the property optional:

    - - (lineIndex === 5 || lineIndex === 11) && tokenIndex >= 3 + + lineIndex === 2 && tokenIndex <= 1 + } + /> +

    + In our example, instead of place: Place, we can use{' '} + + place?: Place + {' '} + to make it optional: +

    + + lineIndex === 7 && tokenIndex <= 1 } /> +

    + That’s it! We’re now ready to use this new Todo type + in a function. +

    ) }, From f9c4f9f66fa6fc8753e69d9baad120bcb0fd13fc Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sun, 15 Dec 2019 23:03:31 +0900 Subject: [PATCH 25/57] Continue with slide 22-23 --- .prettierrc | 6 +- snippets/snippets/todo/awzp.ts | 8 + snippets/snippets/todo/qnrh.ts | 11 ++ snippets/snippets/todo/ybhj.ts | 3 + src/components/CodeBlockHighlight.tsx | 227 ++++++++++++++------------ src/components/CodeBlockPre.tsx | 48 ++++++ src/lib/articles.ts | 2 +- src/lib/snippets.ts | 25 +++ src/pages/todo.tsx | 75 ++++++++- 9 files changed, 300 insertions(+), 105 deletions(-) create mode 100644 snippets/snippets/todo/awzp.ts create mode 100644 snippets/snippets/todo/qnrh.ts create mode 100644 snippets/snippets/todo/ybhj.ts create mode 100644 src/components/CodeBlockPre.tsx diff --git a/.prettierrc b/.prettierrc index 4765af0..53eb7e7 100644 --- a/.prettierrc +++ b/.prettierrc @@ -9,7 +9,11 @@ } }, { - "files": ["snippets/snippets/**/umjt.ts", "snippets/snippets/**/rvyq.ts"], + "files": [ + "snippets/snippets/**/umjt.ts", + "snippets/snippets/**/rvyq.ts", + "snippets/snippets/**/awzp.ts" + ], "options": { "printWidth": 54 } diff --git a/snippets/snippets/todo/awzp.ts b/snippets/snippets/todo/awzp.ts new file mode 100644 index 0000000..ffb90dc --- /dev/null +++ b/snippets/snippets/todo/awzp.ts @@ -0,0 +1,8 @@ +// A React component to render a place label UI +function PlaceLabel({ place }: { place: Place }) { + return ( + + {placeToString(place)} + + ) +} diff --git a/snippets/snippets/todo/qnrh.ts b/snippets/snippets/todo/qnrh.ts new file mode 100644 index 0000000..d64a385 --- /dev/null +++ b/snippets/snippets/todo/qnrh.ts @@ -0,0 +1,11 @@ +placeToString('home') +// __home__ + +placeToString('work') +// __work__ + +placeToString({ custom: 'Gym' }) +// __gym__ + +placeToString({ custom: 'Supermarket' }) +// __supermarket__ diff --git a/snippets/snippets/todo/ybhj.ts b/snippets/snippets/todo/ybhj.ts new file mode 100644 index 0000000..0e131dd --- /dev/null +++ b/snippets/snippets/todo/ybhj.ts @@ -0,0 +1,3 @@ +function placeToString(place: Place): string { + // Figure out what code goes here! +} diff --git a/src/components/CodeBlockHighlight.tsx b/src/components/CodeBlockHighlight.tsx index a270746..508c68e 100644 --- a/src/components/CodeBlockHighlight.tsx +++ b/src/components/CodeBlockHighlight.tsx @@ -2,9 +2,24 @@ import { css, jsx, Interpolation } from '@emotion/core' import PrismHighlight, { defaultProps, Language } from 'prism-react-renderer' import theme from 'src/lib/prismTheme' -import useTheme from 'src/hooks/useTheme' import { useSpring } from 'react-spring' import AnimatedSpan from 'src/components/AnimatedSpan' +import CodeBlockPre from 'src/components/CodeBlockPre' +import Emoji from 'src/components/Emoji' + +const StringHighlight = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ) +} const CodeBlockHighlight = ({ snippet, @@ -24,7 +39,6 @@ const CodeBlockHighlight = ({ language: Language tokenIndexIndentWorkaround?: number }) => { - const { colors, ns, nt, spaces, fontSizes } = useTheme() return ( {({ tokens, getLineProps, getTokenProps }) => ( -
    -          
    - {tokens.map((line, i) => { - const { key: divKey, ...lineProps } = getLineProps({ - line, - key: i - }) - return ( -
    - {line.map((token, key) => { - const { - key: spanKey, - children, - ...tokenProps - } = getTokenProps({ - token, - key - }) - let updatedChildren: React.ReactNode = children - const hasIndent = - key === tokenIndexIndentWorkaround && - /^\s+/.exec(children) - if (hasIndent) { + + {tokens.map((line, i) => { + const { key: divKey, ...lineProps } = getLineProps({ + line, + key: i + }) + return ( +
    + {line.map((token, key) => { + const { + key: spanKey, + children, + ...tokenProps + } = getTokenProps({ + token, + key + }) + let updatedChildren: React.ReactNode = children + const hasIndent = + key === tokenIndexIndentWorkaround && /^\s+/.exec(children) + if (hasIndent) { + updatedChildren = ( + <> + + {(/^\s+/.exec(children) || [])[0]} + + + {children.split(/^\s+/)[1]} + + + ) + } + if (!hasIndent && lineCssOverridesAnimation) { + const springProps = lineCssOverridesAnimation(i, key) + if (springProps !== undefined) { updatedChildren = ( - <> - - {(/^\s+/.exec(children) || [])[0]} - - - {children.split(/^\s+/)[1]} - - + + {updatedChildren} + ) } - if (!hasIndent && lineCssOverridesAnimation) { - const springProps = lineCssOverridesAnimation(i, key) - if (springProps !== undefined) { - updatedChildren = ( - - {updatedChildren} - - ) - } - } - return ( - - {updatedChildren} - + } + if (children === '// __home__') { + updatedChildren = ( + <> + // -> returns{' '} + + ' Home' + + + ) + } + if (children === '// __work__') { + updatedChildren = ( + <> + // -> returns{' '} + + ' Work' + + ) - })} -
    - ) - })} -
    -
    + } + if (children === '// __gym__') { + updatedChildren = ( + <> + // -> returns{' '} + + ' Gym' + + + ) + } + if (children === '// __supermarket__') { + updatedChildren = ( + <> + // -> returns{' '} + + ' Supermarket' + + + ) + } + return ( + + {updatedChildren} + + ) + })} +
+ ) + })} + )} ) diff --git a/src/components/CodeBlockPre.tsx b/src/components/CodeBlockPre.tsx new file mode 100644 index 0000000..0c56039 --- /dev/null +++ b/src/components/CodeBlockPre.tsx @@ -0,0 +1,48 @@ +/** @jsx jsx */ +import { css, jsx, Interpolation } from '@emotion/core' +import useTheme from 'src/hooks/useTheme' + +const CodeBlockPre = ({ + cssOverrides, + children +}: { + cssOverrides?: Interpolation + children: React.ReactNode +}) => { + const { colors, ns, nt, spaces, fontSizes } = useTheme() + return ( +
+      
+ {children} +
+
+ ) +} + +export default CodeBlockPre diff --git a/src/lib/articles.ts b/src/lib/articles.ts index a65db70..f5ebd95 100644 --- a/src/lib/articles.ts +++ b/src/lib/articles.ts @@ -8,7 +8,7 @@ export const articlesData = { todo: { title: 'TypeScript Tutorial for JS Programmers Who Know How to Build a Todo App', - date: DateTime.fromISO('2019-12-13T12:00:00Z'), + date: DateTime.fromISO('2019-12-16T12:00:00Z'), description: 'Learn TypeScript by Building a Todo App', ogImage: 'todo' }, diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index b8df990..2dac3f3 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -478,6 +478,15 @@ export const ampt = `function toggleTodo(todo: Todo): Todo { } }` +export const awzp = `// A React component to render a place label UI +function PlaceLabel({ place }: { place: Place }) { + return ( + + {placeToString(place)} + + ) +}` + export const bnli = `const foo: Todo = { id: 1, text: '…', @@ -682,6 +691,18 @@ function toggleTodo(todo) { // ... }` +export const qnrh = `placeToString('home') +// __home__ + +placeToString('work') +// __work__ + +placeToString({ custom: 'Gym' }) +// __gym__ + +placeToString({ custom: 'Supermarket' }) +// __supermarket__` + export const qnwc = `// They booth have property x, // but B’s x (true) is // more specific than A’s x (boolean) @@ -841,6 +862,10 @@ type CompletedTodo = Readonly<{ done: true }>` +export const ybhj = `function placeToString(place: Place): string { + // Figure out what code goes here! +}` + export const yhto = `type Todo = { readonly id: number readonly text: string diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 6950bf0..a900eb6 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1684,10 +1684,81 @@ const Page = () => ( lineIndex === 7 && tokenIndex <= 1 } /> +

That’s it! We’re now ready to use these types in a function.

+ + ) + }, + { + title: ( + <> + Introducing placeToString() + + ), + content: ( + <> +

+ We’d like to implement a function called{' '} + placeToString(), which has the following input and + output: +

+
    + + Parameter should be a Place.{' '} + + Example: 'work'. + + + + Return value should be a string (with an emoji) + that will be used for the label UI.{' '} + + Example:{' '} + + ' Work' + + . + + +
+

Here are more examples:

+ +

+ We can then use the return value of placeToString(){' '} + to render place label UIs: ,{' '} + ,{' '} + , etc. For example, if we + were to use React, we’d write a component like this: +

+ + lineIndex === 4 && tokenIndex >= 2 && tokenIndex <= 5 + } + /> +

+ If we were to use the above React component,{' '} + {``} will render{' '} + . (Don’t worry if you don’t know + React—the bottom line is that placeToString() is + useful for rendering the UI.) +

+ + ) + }, + { + title: ( + <> + Let’s implement placeToString() + + ), + content: ( + <>

- That’s it! We’re now ready to use this new Todo type - in a function. + Let’s now implement placeToString(). Here’s the + starter code—can you figure out what goes inside?

+ ) }, From 932b5cdd74c72733e13ae4006ec0c3355ff7bc2d Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sun, 15 Dec 2019 23:06:39 +0900 Subject: [PATCH 26/57] Remove react --- .prettierrc | 6 +----- snippets/snippets/todo/awzp.ts | 8 -------- src/lib/snippets.ts | 9 --------- src/pages/todo.tsx | 35 +++++----------------------------- 4 files changed, 6 insertions(+), 52 deletions(-) delete mode 100644 snippets/snippets/todo/awzp.ts diff --git a/.prettierrc b/.prettierrc index 53eb7e7..4765af0 100644 --- a/.prettierrc +++ b/.prettierrc @@ -9,11 +9,7 @@ } }, { - "files": [ - "snippets/snippets/**/umjt.ts", - "snippets/snippets/**/rvyq.ts", - "snippets/snippets/**/awzp.ts" - ], + "files": ["snippets/snippets/**/umjt.ts", "snippets/snippets/**/rvyq.ts"], "options": { "printWidth": 54 } diff --git a/snippets/snippets/todo/awzp.ts b/snippets/snippets/todo/awzp.ts deleted file mode 100644 index ffb90dc..0000000 --- a/snippets/snippets/todo/awzp.ts +++ /dev/null @@ -1,8 +0,0 @@ -// A React component to render a place label UI -function PlaceLabel({ place }: { place: Place }) { - return ( - - {placeToString(place)} - - ) -} diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 2dac3f3..488f098 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -478,15 +478,6 @@ export const ampt = `function toggleTodo(todo: Todo): Todo { } }` -export const awzp = `// A React component to render a place label UI -function PlaceLabel({ place }: { place: Place }) { - return ( - - {placeToString(place)} - - ) -}` - export const bnli = `const foo: Todo = { id: 1, text: '…', diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index a900eb6..62d88fd 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1691,7 +1691,7 @@ const Page = () => ( { title: ( <> - Introducing placeToString() + Implementing placeToString() ), content: ( @@ -1703,7 +1703,7 @@ const Page = () => (

    - Parameter should be a Place.{' '} + Input should be a Place.{' '} Example: 'work'. @@ -1720,40 +1720,15 @@ const Page = () => (
-

Here are more examples:

+

Here are the examples:

We can then use the return value of placeToString(){' '} to render place label UIs: ,{' '} ,{' '} - , etc. For example, if we - were to use React, we’d write a component like this: + , etc in any UI library + (e.g. React).

- - lineIndex === 4 && tokenIndex >= 2 && tokenIndex <= 5 - } - /> -

- If we were to use the above React component,{' '} - {``} will render{' '} - . (Don’t worry if you don’t know - React—the bottom line is that placeToString() is - useful for rendering the UI.) -

- - ) - }, - { - title: ( - <> - Let’s implement placeToString() - - ), - content: ( - <>

Let’s now implement placeToString(). Here’s the starter code—can you figure out what goes inside? From 970bcbfb1ad0aeaeeafbe3d73292fc8bbba13ec3 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Sun, 15 Dec 2019 23:07:49 +0900 Subject: [PATCH 27/57] Comment --- snippets/snippets/todo/ybhj.ts | 1 + src/lib/snippets.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/snippets/snippets/todo/ybhj.ts b/snippets/snippets/todo/ybhj.ts index 0e131dd..dee878e 100644 --- a/snippets/snippets/todo/ybhj.ts +++ b/snippets/snippets/todo/ybhj.ts @@ -1,3 +1,4 @@ +// Takes a Place and returns a string function placeToString(place: Place): string { // Figure out what code goes here! } diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 488f098..6593518 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -853,7 +853,8 @@ type CompletedTodo = Readonly<{ done: true }>` -export const ybhj = `function placeToString(place: Place): string { +export const ybhj = `// Takes a Place and returns a string +function placeToString(place: Place): string { // Figure out what code goes here! }` From 321ab46eeb8522b54aa30cc132345601661b299f Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Mon, 16 Dec 2019 08:05:16 +0900 Subject: [PATCH 28/57] Clarify slide 22 --- src/components/EmojiSeparator.tsx | 21 ++++--- src/pages/todo.tsx | 92 +++++++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 14 deletions(-) diff --git a/src/components/EmojiSeparator.tsx b/src/components/EmojiSeparator.tsx index 26accf3..80a8272 100644 --- a/src/components/EmojiSeparator.tsx +++ b/src/components/EmojiSeparator.tsx @@ -8,16 +8,18 @@ import useTheme from 'src/hooks/useTheme' import Caption from 'src/components/Caption' interface EmojiSeparatorProps { - emojis: ReadonlyArray - size?: 'md' | 'lg' + emojis?: ReadonlyArray + size?: 'sm' | 'md' | 'lg' cssOverrides?: SerializedStyles description?: React.ReactNode + customChildren?: React.ReactNode } const fontSize = ( size: NonNullable ): ReadonlyArray => ({ + sm: [1, 1.2] as const, md: [2, 2.5] as const, lg: [3, 4] as const }[size]) @@ -26,6 +28,7 @@ const margins = ( size: NonNullable ): ReadonlyArray => ({ + sm: [0.75, 1] as const, md: [1.25, 1.5] as const, lg: [0, 2] as const }[size]) @@ -45,7 +48,8 @@ const EmojiSeparator = ({ emojis, size = 'md', cssOverrides, - description + description, + customChildren }: EmojiSeparatorProps) => { const { spaces, ns, fontSizes } = useTheme() return ( @@ -70,11 +74,12 @@ const EmojiSeparator = ({ justify-content: center; `} > - {emojis.map((emoji, index) => ( - - - - ))} + {customChildren || + (emojis || []).map((emoji, index) => ( + + + + ))} {description && ( ( + + {children} + +) + const Page = () => ( ( content: ( <>

- We’d like to implement a function called{' '} - placeToString(), which has the following input and - output: + As mentioned before, UI libraries like React or Vue{' '} + transform data into UI. +

+ UI libraries transform data into UI} + /> +

+ For place labels, we need to transform each Place{' '} + data into a place label UI: +

+ + 'home'{' '} + + + {' '} + + + } + /> + + 'work'{' '} + + + {' '} + + + } + /> + + {`{ custom: 'Gym' }`}{' '} + + + {' '} + + + } + description={ + <> +
+ Transform Place into a place label UI +
+ + } + /> +

+ To do this, we’d like to implement a function called{' '} + + placeToString() + + , which has the following input and output:

    @@ -1709,8 +1788,9 @@ const Page = () => ( - Return value should be a string (with an emoji) - that will be used for the label UI.{' '} + Return value should be a{' '} + string (with an emoji) that will be used for + the label UI.{' '} Example:{' '} From 5e615f5e7b01692630188de8263355fa1ff8c6cc Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Mon, 16 Dec 2019 09:26:38 +0900 Subject: [PATCH 29/57] Start slide 23 --- .prettierrc | 6 ++- snippets/snippets/todo/vgja.ts | 10 +++++ snippets/snippets/todo/ybhj.ts | 4 +- src/components/CodeBlockHighlight.tsx | 30 +++++++++++++++ src/components/CodeBlockPre.tsx | 2 +- src/components/CodeResult.tsx | 2 +- src/lib/snippets.ts | 17 +++++++-- src/pages/todo.tsx | 53 +++++++++++++++++++++------ 8 files changed, 104 insertions(+), 20 deletions(-) create mode 100644 snippets/snippets/todo/vgja.ts diff --git a/.prettierrc b/.prettierrc index 4765af0..9a5ea47 100644 --- a/.prettierrc +++ b/.prettierrc @@ -9,7 +9,11 @@ } }, { - "files": ["snippets/snippets/**/umjt.ts", "snippets/snippets/**/rvyq.ts"], + "files": [ + "snippets/snippets/**/umjt.ts", + "snippets/snippets/**/rvyq.ts", + "snippets/snippets/**/vgja.ts" + ], "options": { "printWidth": 54 } diff --git a/snippets/snippets/todo/vgja.ts b/snippets/snippets/todo/vgja.ts new file mode 100644 index 0000000..5fd2340 --- /dev/null +++ b/snippets/snippets/todo/vgja.ts @@ -0,0 +1,10 @@ +type Place = 'home' | 'work' | { custom: string } + +// Little Duckling’s implementation +function placeToString(place: Place): string { + if (place === 'home') { + return 'homeEmoji Home' + } else { + return 'pinEmoji ' + place.custom + } +} diff --git a/snippets/snippets/todo/ybhj.ts b/snippets/snippets/todo/ybhj.ts index dee878e..2bf3049 100644 --- a/snippets/snippets/todo/ybhj.ts +++ b/snippets/snippets/todo/ybhj.ts @@ -1,4 +1,4 @@ -// Takes a Place and returns a string function placeToString(place: Place): string { - // Figure out what code goes here! + // Takes a Place and returns a string + // that can be used for the place label UI } diff --git a/src/components/CodeBlockHighlight.tsx b/src/components/CodeBlockHighlight.tsx index 508c68e..7d00123 100644 --- a/src/components/CodeBlockHighlight.tsx +++ b/src/components/CodeBlockHighlight.tsx @@ -136,6 +136,36 @@ const CodeBlockHighlight = ({ ) } + if (/homeEmoji/.exec(children)) { + const splitChildren = children.split('homeEmoji') + updatedChildren = ( + <> + {splitChildren[0]} + + {splitChildren[1]} + + ) + } + if (/workEmoji/.exec(children)) { + const splitChildren = children.split('workEmoji') + updatedChildren = ( + <> + {splitChildren[0]} + + {splitChildren[1]} + + ) + } + if (/pinEmoji/.exec(children)) { + const splitChildren = children.split('pinEmoji') + updatedChildren = ( + <> + {splitChildren[0]} + + {splitChildren[1]} + + ) + } return ( ` -export const ybhj = `// Takes a Place and returns a string -function placeToString(place: Place): string { - // Figure out what code goes here! +export const ybhj = `function placeToString(place: Place): string { + // Takes a Place and returns a string + // that can be used for the place label UI }` export const yhto = `type Todo = { diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index aa749fd..2dfdcee 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -317,11 +317,11 @@ const Page = () => (

    To implement the above functionality, we need to write code that toggles the done property of a todo item. Let’s name - this function toggleTodo.{' '} + this function toggleTodo().{' '} - When you call toggleTodo on a todo object, it needs - to return a new todo object with the opposite boolean value for - the done property. + When you call toggleTodo() on a todo object, it + needs to return a new todo object with the opposite boolean + value for the done property.

    @@ -342,7 +342,7 @@ const Page = () => ( />

    And it looks like Little Duckling has implemented{' '} - toggleTodo for us: + toggleTodo() for us:

    ( children: ( <>

    - I’ve implemented toggleTodo for you. Could - you take a look? + I’ve implemented toggleTodo() for you. + Could you take a look?

    ) @@ -364,7 +364,7 @@ const Page = () => ( caption={ <> Little Duckling’s{' '} - toggleTodo implementation + toggleTodo() implementation } /> @@ -533,7 +533,7 @@ const Page = () => (

    First,{' '} - we specify that the input to toggleTodo must be{' '} + we specify that the input to toggleTodo() must be{' '} Todo . We do this by adding : Todo next to the parameter{' '} @@ -548,8 +548,8 @@ const Page = () => (

    Next,{' '} - we specify that the return type of toggleTodo must - also be Todo + we specify that the return type of toggleTodo(){' '} + must also be Todo . We do this by adding : Todo after the parameter list. @@ -625,7 +625,7 @@ const Page = () => ( children: ( <>

    - I think I can refactor toggleTodo as + I think I can refactor toggleTodo() as follows. Could you take a look?

    @@ -1817,6 +1817,35 @@ const Page = () => ( ) }, + { + title: <>Little Duckling’s implementation, + content: ( + <> + +

    + I tried to implement placeToString(). Could + you take a look? +

    + + ) + } + ]} + /> +

    + Let’s see if Little Duckling’s implementation compiles.{' '} + + Press ! + +

    + + + ) + }, underConstructionCard ]} /> From 1de18e5a5b66a47ff86b47b8d1af8625477da9e6 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Mon, 16 Dec 2019 09:29:45 +0900 Subject: [PATCH 30/57] Compile failure --- src/pages/todo.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 2dfdcee..78d0753 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1842,7 +1842,16 @@ const Page = () => ( Press !

    - + + lineIndex === 7 && tokenIndex >= 7 + } + /> ) }, From 62fb06e140fa8c733c3c913f526accb9646b318c Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Mon, 16 Dec 2019 10:44:13 +0900 Subject: [PATCH 31/57] Finish slide 23 --- .prettierrc | 6 ++- snippets/snippets/todo/dhor.ts | 17 ++++++ snippets/snippets/todo/eega.ts | 5 ++ snippets/snippets/todo/szco.ts | 11 ++++ src/lib/snippets.ts | 36 +++++++++++++ src/lib/theme/letterSpacings.ts | 2 +- src/pages/todo.tsx | 94 ++++++++++++++++++++++++++++++++- 7 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 snippets/snippets/todo/dhor.ts create mode 100644 snippets/snippets/todo/eega.ts create mode 100644 snippets/snippets/todo/szco.ts diff --git a/.prettierrc b/.prettierrc index 9a5ea47..989a400 100644 --- a/.prettierrc +++ b/.prettierrc @@ -12,10 +12,12 @@ "files": [ "snippets/snippets/**/umjt.ts", "snippets/snippets/**/rvyq.ts", - "snippets/snippets/**/vgja.ts" + "snippets/snippets/**/vgja.ts", + "snippets/snippets/**/dhor.ts", + "snippets/snippets/**/szco.ts" ], "options": { - "printWidth": 54 + "printWidth": 53 } } ] diff --git a/snippets/snippets/todo/dhor.ts b/snippets/snippets/todo/dhor.ts new file mode 100644 index 0000000..a94cbf0 --- /dev/null +++ b/snippets/snippets/todo/dhor.ts @@ -0,0 +1,17 @@ +type Place = 'home' | 'work' | { custom: string } + +// TypeScript knows what the type of place would be +// at each point inside the function +function placeToString(place: Place): string { + // In here, place = 'home', 'work' or { custom:… } + + if (place === 'home') { + // In here, place = 'home' + + return 'homeEmoji Home' + } else { + // In here, place = 'work' or { custom: string } + + return 'pinEmoji ' + place.custom + } +} diff --git a/snippets/snippets/todo/eega.ts b/snippets/snippets/todo/eega.ts new file mode 100644 index 0000000..081a813 --- /dev/null +++ b/snippets/snippets/todo/eega.ts @@ -0,0 +1,5 @@ +else { + // place = 'work' or { custom: string }, and + // place.custom is invalid if place = 'work' + return 'pinEmoji ' + place.custom +} \ No newline at end of file diff --git a/snippets/snippets/todo/szco.ts b/snippets/snippets/todo/szco.ts new file mode 100644 index 0000000..e2e94f4 --- /dev/null +++ b/snippets/snippets/todo/szco.ts @@ -0,0 +1,11 @@ +// Correct implementation +function placeToString(place: Place): string { + if (place === 'home') { + return 'homeEmoji Home' + } else if (place === 'work') { + return 'workEmoji Work' + } else { + // place is guaranteed to be { custom: string } + return 'pinEmoji ' + place.custom + } +} diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 7f2f3a9..db975b2 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -495,6 +495,24 @@ function toggleTodo(todo: Todo) { // ... }` +export const dhor = `type Place = 'home' | 'work' | { custom: string } + +// TypeScript knows what the type of place would be +// at each point inside the function +function placeToString(place: Place): string { + // In here, place = 'home', 'work' or { custom:… } + + if (place === 'home') { + // In here, place = 'home' + + return 'homeEmoji Home' + } else { + // In here, place = 'work' or { custom: string } + + return 'pinEmoji ' + place.custom + } +}` + export const dqwb = `function toggleTodo(todo: Todo): Todo { // Little Duckling’s refactoring todo.done = !todo.done @@ -508,6 +526,12 @@ export const dxfc = `// Associated data. If we're using React, this { id: 2, text: 'Second todo', done: false } ]` +export const eega = `else { + // place = 'work' or { custom: string }, and + // place.custom is invalid if place = 'work' + return 'pinEmoji ' + place.custom +}` + export const frtm = `type Todo = { id: number text: string @@ -759,6 +783,18 @@ function completeAll( // ... }` +export const szco = `// Correct implementation +function placeToString(place: Place): string { + if (place === 'home') { + return 'homeEmoji Home' + } else if (place === 'work') { + return 'workEmoji Work' + } else { + // place is guaranteed to be { custom: string } + return 'pinEmoji ' + place.custom + } +}` + export const tdbp = `// Takes an array of todo items and returns // a new array where "done" is all true function completeAll(todos) { diff --git a/src/lib/theme/letterSpacings.ts b/src/lib/theme/letterSpacings.ts index a73d8c7..ec10a97 100644 --- a/src/lib/theme/letterSpacings.ts +++ b/src/lib/theme/letterSpacings.ts @@ -1,7 +1,7 @@ export const allLetterSpacings = { title: '-0.02em', wide: '0.15em', - smallCode: '-0.025em' + smallCode: '-0.035em' } const letterSpacings = (x: keyof typeof allLetterSpacings) => diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 78d0753..5df90c3 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -414,8 +414,8 @@ const Page = () => ( ]} />

    - Here’s the correct implementation. It preserves the{' '} - id property. + No worries, Little Duckling! Here’s the correct implementation. It + preserves the id property.

    ( lineIndex === 7 && tokenIndex >= 7 } /> +

    + It failed! TypeScript noticed that there’s a + logic error here.{' '} + + Specifically, inside else, TypeScript knows that{' '} + place is either 'work' or{' '} + {`{ custom: string }`} + + : +

    + + lineIndex === 5 || lineIndex === 8 || lineIndex === 12 + } + /> +

    + Because place is either 'work' or{' '} + {`{ custom: string }`} inside else, and{' '} + + you can’t do place.custom if place is{' '} + 'work', TypeScript gives you a compile error. + +

    + lineIndex === 2} + /> +

    + Of course, the fix is to add{' '} + else if (place === 'work').{' '} + + Press ! + +

    + + (lineIndex === 4 && tokenIndex >= 3) || + lineIndex === 5 || + (lineIndex === 6 && tokenIndex <= 1) + } + /> + +

    + Oops! I forgot to check for{' '} + place === 'work'! +

    + + ) + } + ]} + /> +

    + No worries, Little Duckling! TypeScript was able to catch the + error early. +

    +

    + As we just saw,{' '} + + union types are powerful when combined with conditional + statements (e.g. if/else) + + : +

    +
      + + If we have a variable that’s a union type (e.g.{' '} + place)… + + + And check for its value in if/else… + + + Then TypeScript is smart about what the variable’s possible + values are for each branch of if/else. + +
    +

    + That’s everything! Let’s quickly summarize what we’ve learned. +

    ) }, From 65b4865f04a01d60683eb79531732c4cd652d551 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 06:10:23 +0900 Subject: [PATCH 32/57] Update slide 23 --- .prettierrc | 8 +- snippets/bin/generateSnippetsBundle.ts | 1 + .../snippets/todo/{ => longerWidth}/dhor.ts | 4 +- snippets/snippets/todo/longerWidth/ntup.ts | 18 +++ .../snippets/todo/{ => longerWidth}/rvyq.ts | 0 .../snippets/todo/{ => longerWidth}/szco.ts | 0 .../snippets/todo/{ => longerWidth}/umjt.ts | 0 .../snippets/todo/{ => longerWidth}/vgja.ts | 0 src/lib/snippets.ts | 125 ++++++++++-------- src/pages/todo.tsx | 7 + 10 files changed, 101 insertions(+), 62 deletions(-) rename snippets/snippets/todo/{ => longerWidth}/dhor.ts (79%) create mode 100644 snippets/snippets/todo/longerWidth/ntup.ts rename snippets/snippets/todo/{ => longerWidth}/rvyq.ts (100%) rename snippets/snippets/todo/{ => longerWidth}/szco.ts (100%) rename snippets/snippets/todo/{ => longerWidth}/umjt.ts (100%) rename snippets/snippets/todo/{ => longerWidth}/vgja.ts (100%) diff --git a/.prettierrc b/.prettierrc index 989a400..3b59ede 100644 --- a/.prettierrc +++ b/.prettierrc @@ -9,13 +9,7 @@ } }, { - "files": [ - "snippets/snippets/**/umjt.ts", - "snippets/snippets/**/rvyq.ts", - "snippets/snippets/**/vgja.ts", - "snippets/snippets/**/dhor.ts", - "snippets/snippets/**/szco.ts" - ], + "files": ["snippets/snippets/**/longerWidth/*.ts"], "options": { "printWidth": 53 } diff --git a/snippets/bin/generateSnippetsBundle.ts b/snippets/bin/generateSnippetsBundle.ts index 1d9488b..149394a 100644 --- a/snippets/bin/generateSnippetsBundle.ts +++ b/snippets/bin/generateSnippetsBundle.ts @@ -9,6 +9,7 @@ const regenerate = () => { const contents = fs.readFileSync(file, 'utf8') return `export const ${file .replace(/\.\/snippets\/snippets\/\w+\//, '') + .replace(/longerWidth\//, '') .replace(/\.ts/, '')} = \`${contents .trim() .replace(/^;/m, '') diff --git a/snippets/snippets/todo/dhor.ts b/snippets/snippets/todo/longerWidth/dhor.ts similarity index 79% rename from snippets/snippets/todo/dhor.ts rename to snippets/snippets/todo/longerWidth/dhor.ts index a94cbf0..ad62032 100644 --- a/snippets/snippets/todo/dhor.ts +++ b/snippets/snippets/todo/longerWidth/dhor.ts @@ -1,7 +1,7 @@ type Place = 'home' | 'work' | { custom: string } -// TypeScript knows what the type of place would be -// at each point inside the function +// TypeScript knows what the type of "place" +// would be at each point inside the function function placeToString(place: Place): string { // In here, place = 'home', 'work' or { custom:… } diff --git a/snippets/snippets/todo/longerWidth/ntup.ts b/snippets/snippets/todo/longerWidth/ntup.ts new file mode 100644 index 0000000..ec0d6e6 --- /dev/null +++ b/snippets/snippets/todo/longerWidth/ntup.ts @@ -0,0 +1,18 @@ +// If we have a variable that’s a union type… +type Place = 'home' | 'work' | { custom: string } + +function placeToString(place: Place): string { + // TypeScript is smart about what the variable’s + // possible values are for each branch of if/else + + if (place === 'home') { + // TypeScript knows that place = 'home' here + // (So it won’t compile if you do place.custom) + } else if (place === 'work') { + // TypeScript knows that place = 'work' here + // (So it won’t compile if you do place.custom) + } else { + // TypeScript knows place = { custom: … } here + // (So you can do place.custom) + } +} diff --git a/snippets/snippets/todo/rvyq.ts b/snippets/snippets/todo/longerWidth/rvyq.ts similarity index 100% rename from snippets/snippets/todo/rvyq.ts rename to snippets/snippets/todo/longerWidth/rvyq.ts diff --git a/snippets/snippets/todo/szco.ts b/snippets/snippets/todo/longerWidth/szco.ts similarity index 100% rename from snippets/snippets/todo/szco.ts rename to snippets/snippets/todo/longerWidth/szco.ts diff --git a/snippets/snippets/todo/umjt.ts b/snippets/snippets/todo/longerWidth/umjt.ts similarity index 100% rename from snippets/snippets/todo/umjt.ts rename to snippets/snippets/todo/longerWidth/umjt.ts diff --git a/snippets/snippets/todo/vgja.ts b/snippets/snippets/todo/longerWidth/vgja.ts similarity index 100% rename from snippets/snippets/todo/vgja.ts rename to snippets/snippets/todo/longerWidth/vgja.ts diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index db975b2..b10e66b 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -495,24 +495,6 @@ function toggleTodo(todo: Todo) { // ... }` -export const dhor = `type Place = 'home' | 'work' | { custom: string } - -// TypeScript knows what the type of place would be -// at each point inside the function -function placeToString(place: Place): string { - // In here, place = 'home', 'work' or { custom:… } - - if (place === 'home') { - // In here, place = 'home' - - return 'homeEmoji Home' - } else { - // In here, place = 'work' or { custom: string } - - return 'pinEmoji ' + place.custom - } -}` - export const dqwb = `function toggleTodo(todo: Todo): Todo { // Little Duckling’s refactoring todo.done = !todo.done @@ -601,6 +583,78 @@ export const lieq = `type Todo = { done: boolean }` +export const dhor = `type Place = 'home' | 'work' | { custom: string } + +// TypeScript knows what the type of "place" +// would be at each point inside the function +function placeToString(place: Place): string { + // In here, place = 'home', 'work' or { custom:… } + + if (place === 'home') { + // In here, place = 'home' + + return 'homeEmoji Home' + } else { + // In here, place = 'work' or { custom: string } + + return 'pinEmoji ' + place.custom + } +}` + +export const ntup = `// If we have a variable that’s a union type… +type Place = 'home' | 'work' | { custom: string } + +function placeToString(place: Place): string { + // TypeScript is smart about what the variable’s + // possible values are for each branch of if/else + + if (place === 'home') { + // TypeScript knows that place = 'home' here + // (So it won’t compile if you do place.custom) + } else if (place === 'work') { + // TypeScript knows that place = 'work' here + // (So it won’t compile if you do place.custom) + } else { + // TypeScript knows place = { custom: … } here + // (So you can do place.custom) + } +}` + +export const rvyq = `type Place = 'home' | 'work' | { custom: string } + +type Todo = Readonly<{ + id: number + text: string + done: boolean + // place is optional + place?: Place +}>` + +export const szco = `// Correct implementation +function placeToString(place: Place): string { + if (place === 'home') { + return 'homeEmoji Home' + } else if (place === 'work') { + return 'workEmoji Work' + } else { + // place is guaranteed to be { custom: string } + return 'pinEmoji ' + place.custom + } +}` + +export const umjt = `type Place = 'home' | 'work' | { custom: string }` + +export const vgja = `type Place = 'home' | 'work' | { custom: string } + +// Little Duckling’s implementation +function placeToString(place: Place): string { + if (place === 'home') { + return 'homeEmoji Home' + } else { + return 'pinEmoji ' + place.custom + } +}` + export const lund = `const result = toggleTodo({ id: 1, text: '…', @@ -766,16 +820,6 @@ export const ruga = `function completeAll( // ... }` -export const rvyq = `type Place = 'home' | 'work' | { custom: string } - -type Todo = Readonly<{ - id: number - text: string - done: boolean - // place is optional - place?: Place -}>` - export const szan = `// Make input todos as readonly array function completeAll( todos: readonly Todo[] @@ -783,18 +827,6 @@ function completeAll( // ... }` -export const szco = `// Correct implementation -function placeToString(place: Place): string { - if (place === 'home') { - return 'homeEmoji Home' - } else if (place === 'work') { - return 'workEmoji Work' - } else { - // place is guaranteed to be { custom: string } - return 'pinEmoji ' + place.custom - } -}` - export const tdbp = `// Takes an array of todo items and returns // a new array where "done" is all true function completeAll(todos) { @@ -806,25 +838,12 @@ export const tgvw = `const bar: Todo = { done: true }` -export const umjt = `type Place = 'home' | 'work' | { custom: string }` - export const uxlb = `function toggleTodo(todo: Todo): Todo { // Little Duckling’s refactoring todo.done = !todo.done return todo }` -export const vgja = `type Place = 'home' | 'work' | { custom: string } - -// Little Duckling’s implementation -function placeToString(place: Place): string { - if (place === 'home') { - return 'homeEmoji Home' - } else { - return 'pinEmoji ' + place.custom - } -}` - export const vgnq = `// This will continue to work because // the input todo is not modified function toggleTodo(todo: Todo): Todo { diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 5df90c3..7e0f2d4 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1939,6 +1939,13 @@ const Page = () => ( values are for each branch of if/else.
+ + lineIndex === 8 || lineIndex === 11 || lineIndex === 14 + } + />

That’s everything! Let’s quickly summarize what we’ve learned.

From 1a6bbac314e22354c1c93bb269789cd2fdd099ea Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 07:28:45 +0900 Subject: [PATCH 33/57] Simplify --- snippets/snippets/todo/longerWidth/ntup.ts | 4 ++-- src/lib/snippets.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/snippets/snippets/todo/longerWidth/ntup.ts b/snippets/snippets/todo/longerWidth/ntup.ts index ec0d6e6..5df8eb0 100644 --- a/snippets/snippets/todo/longerWidth/ntup.ts +++ b/snippets/snippets/todo/longerWidth/ntup.ts @@ -6,10 +6,10 @@ function placeToString(place: Place): string { // possible values are for each branch of if/else if (place === 'home') { - // TypeScript knows that place = 'home' here + // TypeScript knows place = 'home' here // (So it won’t compile if you do place.custom) } else if (place === 'work') { - // TypeScript knows that place = 'work' here + // TypeScript knows place = 'work' here // (So it won’t compile if you do place.custom) } else { // TypeScript knows place = { custom: … } here diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index b10e66b..dd7d8bd 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -609,10 +609,10 @@ function placeToString(place: Place): string { // possible values are for each branch of if/else if (place === 'home') { - // TypeScript knows that place = 'home' here + // TypeScript knows place = 'home' here // (So it won’t compile if you do place.custom) } else if (place === 'work') { - // TypeScript knows that place = 'work' here + // TypeScript knows place = 'work' here // (So it won’t compile if you do place.custom) } else { // TypeScript knows place = { custom: … } here From ae5a4e551fcbcacbb502add61ebe03cc64650e67 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 07:52:56 +0900 Subject: [PATCH 34/57] Finish slide 24 --- snippets/snippets/todo/npog.ts | 7 +++ src/lib/snippets.ts | 8 +++ src/pages/todo.tsx | 100 +++++++++++++++++++++++++++------ 3 files changed, 98 insertions(+), 17 deletions(-) create mode 100644 snippets/snippets/todo/npog.ts diff --git a/snippets/snippets/todo/npog.ts b/snippets/snippets/todo/npog.ts new file mode 100644 index 0000000..a5a98e5 --- /dev/null +++ b/snippets/snippets/todo/npog.ts @@ -0,0 +1,7 @@ +type Todo = Readonly<{ + id: number + text: string + done: boolean + // place is optional + place?: Place +}> diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index dd7d8bd..d18956c 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -715,6 +715,14 @@ export const npgx = `type Todo = Readonly<{ place: Place }>` +export const npog = `type Todo = Readonly<{ + id: number + text: string + done: boolean + // place is optional + place?: Place +}>` + export const ntau = `function toggleTodo(todo: Todo): Todo { // Little Duckling’s code from earlier: // Missing the "id" property diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 7e0f2d4..1a05adb 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -47,6 +47,31 @@ const placesExample: Todo[] = [ } ] +const UnionTypesSummary = () => ( + <> +
    + + If we have a variable that’s a union type (e.g.{' '} + place)… + + + And check for its value in if/else… + + + Then TypeScript is smart about what the variable’s possible values are + for each branch of if/else. + +
+ + lineIndex === 8 || lineIndex === 11 || lineIndex === 14 + } + /> + +) + const ArrowWrapper = ({ children }: { children: React.ReactNode }) => ( ( :

-
    - - If we have a variable that’s a union type (e.g.{' '} - place)… - - - And check for its value in if/else… - - - Then TypeScript is smart about what the variable’s possible - values are for each branch of if/else. - -
+ +

+ That’s everything! Let’s quickly summarize what we’ve learned. +

+ + ) + }, + { + color: 'green', + subtitle: <>Section 3 Summary:, + title: <>Union types are powerful, + content: ( + <> +

+ In this section, we’ve learned about union types and optional + properties: +

+

+ + 1. We can use the syntax A | B to create a{' '} + union type, which represents a type that’s + either A or B. + +

- lineIndex === 8 || lineIndex === 11 || lineIndex === 14 + caption={ + <> + Place can be either 'home',{' '} + 'work', or an object containing a string{' '} + custom property + + } + snippet={snippets.umjt} + shouldHighlight={(lineIndex, tokenIndex) => + lineIndex === 0 && tokenIndex >= 6 } />

- That’s everything! Let’s quickly summarize what we’ve learned. + + 2. We can add a question mark (?) after a property + name to make the property optional. + +

+ + lineIndex === 5 && tokenIndex <= 1 + } + /> +

+ Finally,{' '} + + union types are powerful when combined with conditional + statements (e.g. if/else). + +

+ +

+ Union types are one of the best ideas of TypeScript. You should + use them widely. There are other powerful features of union types + (discriminated unions, usage with mapped types, etc) which I won’t + cover here.

) From 55c37f2dc8b741892345f35c92d9fccd82b033c6 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 09:33:05 +0900 Subject: [PATCH 35/57] Update intro --- src/components/AboutMe.tsx | 24 +++++++++ src/components/Footer.tsx | 8 +-- src/components/GitHubButton.tsx | 34 +++++++++++++ src/components/InternalLink.tsx | 2 +- src/components/PostPage.tsx | 2 +- src/lib/articles.ts | 2 +- src/pages/_error.tsx | 3 ++ src/pages/generics.tsx | 30 ++++------- src/pages/index.tsx | 42 ++++++---------- src/pages/todo.tsx | 89 ++++++++++++++++++++++++++++++--- 10 files changed, 175 insertions(+), 61 deletions(-) create mode 100644 src/components/AboutMe.tsx create mode 100644 src/components/GitHubButton.tsx diff --git a/src/components/AboutMe.tsx b/src/components/AboutMe.tsx new file mode 100644 index 0000000..60d6194 --- /dev/null +++ b/src/components/AboutMe.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import { A, P } from 'src/components/ContentTags' + +const AboutMe = () => ( + <> +

+ About the author: I’m Shu Uesugi, a software engineer. + The most recent TypeScript project I worked on is an{' '} + interactive computer science course called{' '} + + “Y Combinator for Non-programmers” + + . +

+

+ I’m currently looking for a full-time opportunity:{' '} + Looking for a remote (PST timezone) position in full-stack engineering, + product management, or DevRel. You can email me at{' '} + shu@chibicode.com. +

+ +) + +export default AboutMe diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index b0efa88..ad63458 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -3,6 +3,7 @@ import { css, jsx } from '@emotion/core' import Container from 'src/components/Container' import useTheme from 'src/hooks/useTheme' import { A } from 'src/components/ContentTags' +import GitHubButton from 'src/components/GitHubButton' const Footer = () => { const { fontSizes, ns, spaces, colors } = useTheme() @@ -11,7 +12,6 @@ const Footer = () => { { } `} > - The source code for this site is{' '} + Source available on{' '} - available on GitHub + GitHub - . + : ) } diff --git a/src/components/GitHubButton.tsx b/src/components/GitHubButton.tsx new file mode 100644 index 0000000..b4ec06f --- /dev/null +++ b/src/components/GitHubButton.tsx @@ -0,0 +1,34 @@ +/** @jsx jsx */ +import { css, jsx } from '@emotion/core' +import { A } from 'src/components/ContentTags' +import useTheme from 'src/hooks/useTheme' + +export const SourceAvailableText = () => ( + <> + The source code for this site is on{' '} + + GitHub + + : + +) + +const GitHubButton = () => { + const { spaces } = useTheme() + return ( + + ) +} + +export default GitHubButton diff --git a/src/components/InternalLink.tsx b/src/components/InternalLink.tsx index 0132621..657faae 100644 --- a/src/components/InternalLink.tsx +++ b/src/components/InternalLink.tsx @@ -10,7 +10,7 @@ const InternalLink = ({ ...props }: JSX.IntrinsicElements['a'] & { href: string - cssOverrides: Interpolation + cssOverrides?: Interpolation }) => { const { colors } = useTheme() return ( diff --git a/src/components/PostPage.tsx b/src/components/PostPage.tsx index b7509c4..3f1e619 100644 --- a/src/components/PostPage.tsx +++ b/src/components/PostPage.tsx @@ -94,7 +94,7 @@ const PostPage = ({ backgroundColor: 'pink', children: ( <> - + ) } diff --git a/src/lib/articles.ts b/src/lib/articles.ts index f5ebd95..8e6b1f2 100644 --- a/src/lib/articles.ts +++ b/src/lib/articles.ts @@ -8,7 +8,7 @@ export const articlesData = { todo: { title: 'TypeScript Tutorial for JS Programmers Who Know How to Build a Todo App', - date: DateTime.fromISO('2019-12-16T12:00:00Z'), + date: DateTime.fromISO('2019-12-17T12:00:00Z'), description: 'Learn TypeScript by Building a Todo App', ogImage: 'todo' }, diff --git a/src/pages/_error.tsx b/src/pages/_error.tsx index 09c9bc4..a5ee212 100644 --- a/src/pages/_error.tsx +++ b/src/pages/_error.tsx @@ -4,6 +4,7 @@ import { P } from 'src/components/ContentTags' import { siteTitle, siteOgImage } from 'src/lib/meta' import Head from 'next/head' import Page from 'src/components/Page' +import ArticleList from 'src/components/ArticleList' const PageNotFound = () => { return ( @@ -22,6 +23,7 @@ const PageNotFound = () => { quotes={[ { type: 'bird', + backgroundColor: 'lightYellow1', children: ( <>

@@ -33,6 +35,7 @@ const PageNotFound = () => { } ]} /> + ) } diff --git a/src/pages/generics.tsx b/src/pages/generics.tsx index 0a30127..904ba05 100644 --- a/src/pages/generics.tsx +++ b/src/pages/generics.tsx @@ -11,6 +11,8 @@ import Emoji from 'src/components/Emoji' import { articlesData } from 'src/lib/articles' import { baseUrl } from 'src/lib/meta' import ReadMore from 'src/components/ReadMore' +import AboutMe from 'src/components/AboutMe' +import { SourceAvailableText } from 'src/components/GitHubButton' const Page = () => ( ( you might know someone (maybe one of your Twitter followers) who’s struggling with generics - . If you do, I’d appreciate it if you could share this article - with them. You can{' '} + . I’d appreciate it if you could share this article with them. + You can{' '} ( click here to tweet this article.

+

+ +

) } @@ -835,26 +840,13 @@ const Page = () => ( .

+

+ +

), footer: { - content: ( - <> -

- About the author: I’m Shu Uesugi, a software - engineer. The most recent TypeScript project I worked on is an{' '} - interactive computer science course called{' '} - - “Y Combinator for Non-programmers” - - . -

-

- You can email me at{' '} - shu@chibicode.com. -

- - ) + content: } } ]} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 44bde0a..8e682bd 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -4,36 +4,22 @@ import { P } from 'src/components/ContentTags' import { siteTitle, baseUrl, siteDescription, siteOgImage } from 'src/lib/meta' import Head from 'next/head' import Page from 'src/components/Page' -import ReadMore from 'src/components/ReadMore' import ArticleList from 'src/components/ArticleList' -export const FirstParagraph = ({ - defaultVisible -}: { - defaultVisible?: boolean -}) => ( +export const FirstParagraph = () => ( <> - - Why target beginner programmers? As TypeScript is - becoming popular, I believe that more beginner programmers (people - with only a few months of coding experience) will be learning it,{' '} - possibly as one of their first languages. So I wanted to - create tutorials specifically targeting beginner programmers. -

- } - readMoreText="Read more…" - preview={readMore => ( -

- Hello! I write tutorials to help{' '} - beginner programmers learn TypeScript. My tutorials might NOT - be as useful for experienced programmers learning TypeScript. - {readMore} -

- )} - defaultVisible={defaultVisible} - /> +

+ Hello! I write tutorials to help{' '} + beginner programmers learn TypeScript. My tutorials might NOT be + as useful for experienced programmers learning TypeScript. +

+

+ Why target beginner programmers? As TypeScript is + becoming popular, I believe that more beginner programmers will be + learning it. However, I noticed that many existing TypeScript tutorials + are too challenging for beginner programmers. I wanted to offer a + beginner-friendly alternative here. +

) @@ -59,7 +45,7 @@ const Index = () => { backgroundColor: 'lightYellow1', children: ( <> - + ) } diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 1a05adb..897c691 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -13,13 +13,18 @@ import { UlLi } from 'src/components/ContentTags' import * as snippets from 'src/lib/snippets' -import underConstructionCard from 'src/lib/underConstructionCard' import TodoWithData, { Todo } from 'src/components/TodoWithData' import RunButtonText from 'src/components/RunButtonText' import CodeBlock from 'src/components/CodeBlock' import BubbleQuotes from 'src/components/BubbleQuotes' import ResultHighlight from 'src/components/ResultHighlight' import PlaceLabel from 'src/components/PlaceLabel' +import InternalLink from 'src/components/InternalLink' +import AboutMe from 'src/components/AboutMe' +import { SourceAvailableText } from 'src/components/GitHubButton' +import TwitterLink from 'src/components/TwitterLink' +import { articlesData } from 'src/lib/articles' +import { baseUrl } from 'src/lib/meta' const compileSuccess = 'Compiled successfully!' const section1 = 'Types, Read-only Properties, and Mapped Types' @@ -222,7 +227,32 @@ const Page = () => (

Let’s get started!

- ) + ), + footer: { + content: ( + <> +

+ Note: If you already know TypeScript basics, + you won’t find anything new in this tutorial. However,{' '} + + you might know someone (maybe one of your Twitter followers) + who’re interested in learning TypeScript + + . I’d appreciate it if you could share this article with them. + You can{' '} + + click here to tweet this article. + +

+

+ +

+ + ) + } }, { title: ( @@ -1947,7 +1977,7 @@ const Page = () => ( As we just saw,{' '} union types are powerful when combined with conditional - statements (e.g. if/else) + statements (e.g. if/else or switch) :

@@ -2005,20 +2035,65 @@ const Page = () => ( Finally,{' '} union types are powerful when combined with conditional - statements (e.g. if/else). + statements (e.g. if/else or switch).

Union types are one of the best ideas of TypeScript. You should use them widely. There are other powerful features of union types - (discriminated unions, usage with mapped types, etc) which I won’t - cover here. + (discriminated unions, combining them with mapped types, etc) + which I won’t cover here.

) }, - underConstructionCard + { + title: <>Conclusion and next steps, + content: ( + <> + +

+ Thanks for reading! You should now know enough TypeScript to get + started with a project. +

+
    + + If you’re using React,{' '} + + React+TypeScript Cheatsheets + {' '} + is a good reference. + + + Once you become more familiar with TypeScript, you should learn{' '} + generics next. I’ve written an article on it + called “ + + TypeScript Generics for People Who Gave Up on Understanding + Generics + + ”. + + + + + + I plan to write more TypeScript articles by building on the todo + app example I used here. To get notified when I publish a new + article, follow me on{' '} + + Twitter at @chibicode + + . + +
+ + ), + footer: { + content: + } + } ]} /> ) From 4681d5e6a31d605d1ff5024b3be0df1752cb2846 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 09:42:58 +0900 Subject: [PATCH 36/57] Wordsmith --- src/pages/todo.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 897c691..4b86baa 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -751,7 +751,7 @@ const Page = () => ( />

That’s why Little Duckling’s refactoring is a bad refactor—even - thought it compiles correctly. + though it compiles correctly.

( tests in TypeScript!)

- This especially useful{' '} + This is especially useful{' '} when you’re using a UI library and need to transform data @@ -1325,7 +1325,7 @@ const Page = () => ( } />

- We can de-duplicate the code by using a TypeScript feature called{' '} + We can deduplicate the code by using a TypeScript feature called{' '} intersection types.

@@ -1579,19 +1579,19 @@ const Page = () => ( Let’s take a look at the associated data.{' '} Each todo now can have an optional place property, - which can have the following value: + which can have one of the following values:

    - 'home' + place: 'home' - 'work' + place: 'work' - →{' '} - {`{ custom: 'Custom name' }`} + →{' '} + {`place: { custom: 'Foo' }`}

@@ -1641,7 +1641,7 @@ const Page = () => ( emojis={['a', 'verticalBar', 'b']} description={ <> - A | B is an union type, which + A | B is a union type, which means{' '} either A or B. @@ -2041,7 +2041,7 @@ const Page = () => (

Union types are one of the best ideas of TypeScript. You should - use them widely. There are other powerful features of union types + use them often. There are other powerful features of union types (discriminated unions, combining them with mapped types, etc) which I won’t cover here.

From ab31941cb2f5817bbe8dc1be433f749bc1d9a412 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 09:45:00 +0900 Subject: [PATCH 37/57] Simplify --- src/pages/index.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 8e682bd..97556f7 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -15,10 +15,9 @@ export const FirstParagraph = () => (

Why target beginner programmers? As TypeScript is - becoming popular, I believe that more beginner programmers will be - learning it. However, I noticed that many existing TypeScript tutorials - are too challenging for beginner programmers. I wanted to offer a - beginner-friendly alternative here. + becoming popular, more beginner programmers will be learning it. However, + I noticed that many existing TypeScript tutorials are too hard for + beginner programmers. I wanted to offer a friendlier alternative.

) From 1804124752ac213b9077db2950ef733bf15d318c Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 16:35:24 +0900 Subject: [PATCH 38/57] Wordsmith --- src/pages/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 97556f7..4733309 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -16,8 +16,8 @@ export const FirstParagraph = () => (

Why target beginner programmers? As TypeScript is becoming popular, more beginner programmers will be learning it. However, - I noticed that many existing TypeScript tutorials are too hard for - beginner programmers. I wanted to offer a friendlier alternative. + I noticed that many existing tutorials are not so friendly for beginner + programmers. This is my attempt to offer a friendlier alternative.

) From 91c1ce3c2dfcd1d52cf98e426253016c31bb4858 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 16:41:50 +0900 Subject: [PATCH 39/57] Wordsmith slide 1 --- src/pages/todo.tsx | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 4b86baa..c4a1401 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -140,9 +140,8 @@ const Page = () => ( {' '} I think there are missed opportunities here. Building a todo app is a great way to learn something in frontend engineering, and - many JS programmers already know how to build a todo app in their - library of choice. There should be more TypeScript tutorials - featuring a todo app. + many JS programmers already know how to build one. There should be + more TypeScript tutorials featuring a todo app.

( This tutorial doesn’t rely on any specific frontend library , so it doesn’t matter whether you know React, Vue, or some - other libraries. As long as you have basic JS knowledge, you - should be able to understand this tutorial. No prior TypeScript - knowledge is necessary. + other libraries. You’ll be able to follow as long as you have + basic JS knowledge. No prior TypeScript knowledge is necessary. To save time,{' '} - I’m not going to talk about setting up a TypeScript project + I’m not going to talk about how to set up a TypeScript project —you should read other tutorials for that. For React, check out{' '} @@ -194,10 +192,10 @@ const Page = () => ( Also to save time,{' '} - I’m not going to cover everything about TypeScript. + I’m only going to cover the most essential concepts in + TypeScript. {' '} - I’m only going to cover some of the coolest concepts in - TypeScript—mostly basic. My goal is to make you want to learn + My goal is not to be exhaustive but to make you want to learn more. @@ -205,14 +203,14 @@ const Page = () => ( emojis={['sparkles', 'bird', 'sparkles']} description={ <> - I’m only going to cover TypeScript basics. My goal is to make - you want to learn more. + I’m only going to cover essentials. My goal is to make you + want to learn more. } />

There are 3 sections total in this article. Here - are the topics covered in each section. + are the topics covered in each section:

    From f68ed4dda6b2b6b2b2012e8e1ad4f588d4df6452 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 16:46:59 +0900 Subject: [PATCH 40/57] Wordsmith slide 3 --- snippets/snippets/todo/dxfc.ts | 2 +- src/lib/snippets.ts | 2 +- src/pages/todo.tsx | 30 ++++++++++++++++++++---------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/snippets/snippets/todo/dxfc.ts b/snippets/snippets/todo/dxfc.ts index f9d6085..f7088ab 100644 --- a/snippets/snippets/todo/dxfc.ts +++ b/snippets/snippets/todo/dxfc.ts @@ -1,5 +1,5 @@ // Associated data. If we're using React, this -// would be the todo component’s props or state +// would be the component’s props or state ;[ { id: 1, text: 'First todo', done: false }, { id: 2, text: 'Second todo', done: false } diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index d18956c..e0d9ef4 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -502,7 +502,7 @@ export const dqwb = `function toggleTodo(todo: Todo): Todo { }` export const dxfc = `// Associated data. If we're using React, this -// would be the todo component’s props or state +// would be the component’s props or state [ { id: 1, text: 'First todo', done: false }, { id: 2, text: 'Second todo', done: false } diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index c4a1401..32a98ae 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -277,7 +277,7 @@ const Page = () => ( description={<>UI libraries transform data into UI} />

    - Now, let’s take a look at our todo app again.{' '} + Now, let’s take a look at the following todo app.{' '} Can you guess what data is associated with this UI? @@ -300,21 +300,31 @@ const Page = () => (

      - id is the ID of each todo item. This is usually - generated by a backend database. + + id + {' '} + is the ID of each todo item. This is usually generated by a + backend database. - text contains the text of each todo item. + + text + {' '} + contains the text of each todo item. - And most importantly, done is true for - completed items and is false otherwise. + And most importantly,{' '} + + done + {' '} + is true for completed items and is{' '} + false otherwise.

    Let’s display the app together with its associated data.{' '} - Try checking and unchecking the checkboxes, and take a look at + Try checking and unchecking each checkbox, and take a look at how done changes.

    @@ -322,7 +332,7 @@ const Page = () => ( showData caption={ <> - ↓ Check and uncheck the checkboxes, + ↓ Check and uncheck the checkboxes,
    and take a look at how done changes @@ -350,8 +360,8 @@ const Page = () => ( ]} description={ <> - When the user interacts with the UI, the data gets updated, - and in turn, the UI gets updated + When a user interacts with the UI, the data gets updated, and + in turn, the UI gets updated } /> From 5672a27885e7ce3a69026312e174150ce220de9c Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 16:54:02 +0900 Subject: [PATCH 41/57] Start rewriting slide 4 --- snippets/snippets/todo/vpco.ts | 14 +++++++------- src/lib/snippets.ts | 16 ++++++++-------- src/pages/todo.tsx | 30 ++++++++++++++++++++++-------- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/snippets/snippets/todo/vpco.ts b/snippets/snippets/todo/vpco.ts index b79816c..0c7981d 100644 --- a/snippets/snippets/todo/vpco.ts +++ b/snippets/snippets/todo/vpco.ts @@ -1,11 +1,11 @@ // Returns a new todo object with the opposite // boolean value for the "done" proprty. function toggleTodo(todo) { - // Case 1: If todo is - // { id: …, text: '…', done: true }, return - // { id: …, text: '…', done: false } - // - // Case 2: If todo is - // { id: …, text: '…', done: false }, return - // { id: …, text: '…', done: true } + // ... } + +toggleTodo({ id: …, text: '…', done: true }) +// -> returns { id: …, text: '…', done: false } + +toggleTodo({ id: …, text: '…', done: false }) +// -> returns { id: …, text: '…', done: true } \ No newline at end of file diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index e0d9ef4..7395e2f 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -865,14 +865,14 @@ function toggleTodo(todo: Todo): Todo { export const vpco = `// Returns a new todo object with the opposite // boolean value for the "done" proprty. function toggleTodo(todo) { - // Case 1: If todo is - // { id: …, text: '…', done: true }, return - // { id: …, text: '…', done: false } - // - // Case 2: If todo is - // { id: …, text: '…', done: false }, return - // { id: …, text: '…', done: true } -}` + // ... +} + +toggleTodo({ id: …, text: '…', done: true }) +// -> returns { id: …, text: '…', done: false } + +toggleTodo({ id: …, text: '…', done: false }) +// -> returns { id: …, text: '…', done: true }` export const wdjp = `type A = { a: number } type B = { b: string } diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 32a98ae..4d73f5e 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -378,15 +378,29 @@ const Page = () => ( content: ( <>

    - To implement the above functionality, we need to write code that - toggles the done property of a todo item. Let’s name - this function toggleTodo().{' '} - - When you call toggleTodo() on a todo object, it - needs to return a new todo object with the opposite boolean - value for the done property. - + To implement the check/uncheck functionality, we need to write + code that toggles the done property of a todo item. +

    +

    + Let’s name this function{' '} + + toggleTodo() + + . Here’s how it should work:

    +
      + + + When you call toggleTodo() on a todo object… + + + + + …it needs to return a new todo object with the opposite + boolean value for the done property. + + +

    Now, let me introduce our junior developer,{' '} From 90d7daf70667a5340c17ac7b9a503ff81b633373 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 19:51:04 +0900 Subject: [PATCH 42/57] Update about me --- src/components/AboutMe.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/AboutMe.tsx b/src/components/AboutMe.tsx index 60d6194..e7389d2 100644 --- a/src/components/AboutMe.tsx +++ b/src/components/AboutMe.tsx @@ -13,9 +13,10 @@ const AboutMe = () => ( .

    - I’m currently looking for a full-time opportunity:{' '} - Looking for a remote (PST timezone) position in full-stack engineering, - product management, or DevRel. You can email me at{' '} + I’m currently looking for a full-time opportunity. I’m + specifically looking for a remote (US timezone) position in full-stack + engineering, product management, or DevRel. You can learn more about me on{' '} + my personal website. My email is{' '} shu@chibicode.com.

    From 1c908f07e004a4bae1d62d75c84d5a37e7490144 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 20:09:47 +0900 Subject: [PATCH 43/57] Wordsmith slide 4 --- snippets/snippets/todo/vpco.ts | 5 ++++- src/lib/snippets.ts | 5 ++++- src/pages/todo.tsx | 33 ++++++++++++++++----------------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/snippets/snippets/todo/vpco.ts b/snippets/snippets/todo/vpco.ts index 0c7981d..0961ef9 100644 --- a/snippets/snippets/todo/vpco.ts +++ b/snippets/snippets/todo/vpco.ts @@ -1,9 +1,12 @@ -// Returns a new todo object with the opposite +// Takes a single todo object and returns +// a new todo object containing the opposite // boolean value for the "done" proprty. function toggleTodo(todo) { // ... } +// Example usage: + toggleTodo({ id: …, text: '…', done: true }) // -> returns { id: …, text: '…', done: false } diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 7395e2f..ff5f441 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -862,12 +862,15 @@ function toggleTodo(todo: Todo): Todo { } }` -export const vpco = `// Returns a new todo object with the opposite +export const vpco = `// Takes a single todo object and returns +// a new todo object containing the opposite // boolean value for the "done" proprty. function toggleTodo(todo) { // ... } +// Example usage: + toggleTodo({ id: …, text: '…', done: true }) // -> returns { id: …, text: '…', done: false } diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 4d73f5e..dece483 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -298,6 +298,7 @@ const Page = () => (

    +

    Here’s what’s inside each todo object:

      @@ -379,7 +380,8 @@ const Page = () => ( <>

      To implement the check/uncheck functionality, we need to write - code that toggles the done property of a todo item. + code that toggles the done property of a single todo + item.

      Let’s name this function{' '} @@ -391,7 +393,8 @@ const Page = () => (

        - When you call toggleTodo() on a todo object… + When you call toggleTodo() on a single todo + object… @@ -404,8 +407,8 @@ const Page = () => (

        Now, let me introduce our junior developer,{' '} - Little Duckling. We’re going to use some - characters like him to make this article a bit more entertaining. + Little Duckling. He’s going to implement{' '} + toggleTodo() for us.

        ( } /> -

        - And it looks like Little Duckling has implemented{' '} - toggleTodo() for us: -

        ( Let’s check if Little Duckling’s implementation is correct.{' '} Take a look at the following test case. What do you think the - output would be? Try to guess first and press Press{' '} - below. + output would be? Try to guess first and press {' '} + below.

        ( ]} />

        - No worries, Little Duckling! Here’s the correct implementation. It - preserves the id property. + No worries, Little Duckling! Here’s the correct implementation:

        ( />

        Now, here’s a question:{' '} - How can we prevent mistakes like this?{' '} - Little Duckling is a junior developer, but we want to make sure - that he succeeds at his job by helping him make fewer mistakes. + + How can we prevent Little Duckling from making mistakes like + this? + {' '} + We want to help Little Duckling succeed at his job!

        ( } />

        - This is where TypeScript comes in. Let’s take a - look! + This is where TypeScript comes in.

        ) From ebec8e88b74c227d3a97f34d1bdf534b460adc06 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 20:40:13 +0900 Subject: [PATCH 44/57] Finish slide 5 --- snippets/snippets/todo/irmt.ts | 5 +++ src/lib/snippets.ts | 6 +++ src/pages/todo.tsx | 72 +++++++++++++++++++++++++--------- 3 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 snippets/snippets/todo/irmt.ts diff --git a/snippets/snippets/todo/irmt.ts b/snippets/snippets/todo/irmt.ts new file mode 100644 index 0000000..491bf2c --- /dev/null +++ b/snippets/snippets/todo/irmt.ts @@ -0,0 +1,5 @@ +const baz: Todo = { + id: 'abc', + text: '…', + done: true +} diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index ff5f441..9cae353 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -544,6 +544,12 @@ export const hszk = `function completeAll( })) }` +export const irmt = `const baz: Todo = { + id: 'abc', + text: '…', + done: true +}` + export const jkjo = `// By default, the properties of Todo are // NOT read-only type Todo = { diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index dece483..1ec2a53 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -523,27 +523,34 @@ const Page = () => ( ) }, { - title: <>Using TypeScript to catch mistakes early, + title: <>Type checking, content: ( <>

        - By using TypeScript, we can prevent the mistake - Little Duckling made. + By using TypeScript, we can prevent the mistake Little Duckling + made by doing something called type checking.

        - First, we create a type for the data we - use. In this case, we need to create a type for a todo item. We’ll - call it Todo and define using the following - TypeScript syntax: + First,{' '} + + we create a type + {' '} + for the data we use. In our case, we need to create a type for a + todo item.{' '} + + We’ll call this type Todo and define it using the + following TypeScript syntax + + :

        - We can then use this type to check if an object is indeed a todo - item. The TypeScript syntax for that is:{' '} - variableName: TypeName (highlighted below). Let’s try - it— + We can then use this type to{' '} + check if a variable is indeed a todo item. + The TypeScript syntax to do this check is:{' '} + variableName: Todo. Here’s an example below— - press . + press

        (

        - lineIndex === 0 && tokenIndex >= 1 && tokenIndex <= 4 - } tokenIndexIndentWorkaround={1} compile resultError @@ -578,13 +582,43 @@ const Page = () => ( } />

        - This one failed to compile because the id property - was missing.{' '} + This one failed to compile{' '} + + because the id property was missing + + . +

        +

        + Finally, how about this one?{' '} + + Try pressing . + +

        + lineIndex === 1} + compile + resultError + result={ + <>{`Type 'string' is not assignable to type 'number'.`} + } + /> +

        + This one also failed to compile{' '} - TypeScript can catch these small mistakes quickly, well before - running the code—without having to write unit tests. + because the id property was string{' '} + instead of number, as defined on the{' '} + Todo type:

        + lineIndex === 1} + /> +

        + The bottom line is that TypeScript lets you type check a variable + against a specified type, which helps you catch mistakes early. +

        ) }, From 9a90a99aad395946fdf8152dc1cdb2f81e764f47 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 20:47:29 +0900 Subject: [PATCH 45/57] Wordsmith slide 6 --- snippets/snippets/todo/csum.ts | 2 +- snippets/snippets/todo/irmt.ts | 5 ---- src/lib/snippets.ts | 8 +----- src/pages/todo.tsx | 46 ++++++++-------------------------- 4 files changed, 12 insertions(+), 49 deletions(-) delete mode 100644 snippets/snippets/todo/irmt.ts diff --git a/snippets/snippets/todo/csum.ts b/snippets/snippets/todo/csum.ts index 95aae08..9106c82 100644 --- a/snippets/snippets/todo/csum.ts +++ b/snippets/snippets/todo/csum.ts @@ -1,4 +1,4 @@ -// todo must match the Todo type +// Parameter "todo" must match the Todo type function toggleTodo(todo: Todo) { // ... } diff --git a/snippets/snippets/todo/irmt.ts b/snippets/snippets/todo/irmt.ts deleted file mode 100644 index 491bf2c..0000000 --- a/snippets/snippets/todo/irmt.ts +++ /dev/null @@ -1,5 +0,0 @@ -const baz: Todo = { - id: 'abc', - text: '…', - done: true -} diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 9cae353..9672a68 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -490,7 +490,7 @@ export const bpmz = `type CompletedTodo = Readonly<{ done: true }>` -export const csum = `// todo must match the Todo type +export const csum = `// Parameter "todo" must match the Todo type function toggleTodo(todo: Todo) { // ... }` @@ -544,12 +544,6 @@ export const hszk = `function completeAll( })) }` -export const irmt = `const baz: Todo = { - id: 'abc', - text: '…', - done: true -}` - export const jkjo = `// By default, the properties of Todo are // NOT read-only type Todo = { diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 1ec2a53..481ff7b 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -589,35 +589,9 @@ const Page = () => ( .

        - Finally, how about this one?{' '} - - Try pressing . - -

        - lineIndex === 1} - compile - resultError - result={ - <>{`Type 'string' is not assignable to type 'number'.`} - } - /> -

        - This one also failed to compile{' '} - - because the id property was string{' '} - instead of number, as defined on the{' '} - Todo type: - -

        - lineIndex === 1} - /> -

        - The bottom line is that TypeScript lets you type check a variable - against a specified type, which helps you catch mistakes early. + The bottom line: TypeScript lets you type check a + variable against a specified type, which helps you catch mistakes + early.

        ) @@ -632,8 +606,8 @@ const Page = () => ( <>

        Now, let’s use TypeScript to prevent the mistake Little Duckling - made earlier. To recap, here’s the Todo type we - created earlier ( + made. To recap, here’s the Todo type we created + earlier ( id is required @@ -672,7 +646,7 @@ const Page = () => ( />

        Now, let’s copy and paste the code Little Duckling wrote—the one - without the id property and see what happens.{' '} + without the id property—see what happens.{' '} Press below. @@ -684,8 +658,8 @@ const Page = () => ( result={`Property 'id' is missing in type '{ text: string; done: boolean; }' but required in type 'Todo'.`} />

        - It fails with an error because the returned object is missing the{' '} - id property and therefore does not match the{' '} + It failed because the returned object is missing + the id property and therefore does not match the{' '} Todo type. So{' '} TypeScript can prevent the mistake Little Duckling made! @@ -714,8 +688,8 @@ const Page = () => ( />

        It compiled! As you can see, TypeScript is great at preventing - mistakes AND letting you know when everything is implemented - correctly. + mistakes AND letting you know when everything has the correct + type.

        ) From 90fb84cba6a516aa4c24feac6ac699d50d728386 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 22:16:27 +0900 Subject: [PATCH 46/57] Finish slide 7 --- snippets/snippets/todo/longerWidth/wymp.ts | 13 +++++++ snippets/snippets/todo/njgr.ts | 2 +- snippets/snippets/todo/qbgu.ts | 5 ++- snippets/snippets/todo/wymp.ts | 16 --------- src/lib/snippets.ts | 38 +++++++++----------- src/pages/todo.tsx | 41 +++++++--------------- 6 files changed, 46 insertions(+), 69 deletions(-) create mode 100644 snippets/snippets/todo/longerWidth/wymp.ts delete mode 100644 snippets/snippets/todo/wymp.ts diff --git a/snippets/snippets/todo/longerWidth/wymp.ts b/snippets/snippets/todo/longerWidth/wymp.ts new file mode 100644 index 0000000..140439a --- /dev/null +++ b/snippets/snippets/todo/longerWidth/wymp.ts @@ -0,0 +1,13 @@ +const argument = { + id: 1, + text: '…', + done: true +} + +console.log('Before toggleTodo(), argument is:') +console.log(argument) + +toggleTodo(argument) + +console.log('After toggleTodo(), argument is:') +console.log(argument) diff --git a/snippets/snippets/todo/njgr.ts b/snippets/snippets/todo/njgr.ts index 2284593..48d3734 100644 --- a/snippets/snippets/todo/njgr.ts +++ b/snippets/snippets/todo/njgr.ts @@ -1,7 +1,7 @@ function toggleTodo(todo: Todo): Todo { // Little Duckling’s refactoring is a // bad refactoring because it modifies - // the original todo object + // the argument (input) todo object todo.done = !todo.done return todo } diff --git a/snippets/snippets/todo/qbgu.ts b/snippets/snippets/todo/qbgu.ts index f11bff2..0baae9a 100644 --- a/snippets/snippets/todo/qbgu.ts +++ b/snippets/snippets/todo/qbgu.ts @@ -1,6 +1,5 @@ -// Returns a new todo object -// with the opposite boolean value -// for the "done" proprty. +// We said earlier that +// toggleTodo must return a new todo object. function toggleTodo(todo) { // ... } diff --git a/snippets/snippets/todo/wymp.ts b/snippets/snippets/todo/wymp.ts deleted file mode 100644 index 184f8c0..0000000 --- a/snippets/snippets/todo/wymp.ts +++ /dev/null @@ -1,16 +0,0 @@ -const originalTodo = { - id: 1, - text: '…', - done: true -} - -console.log('Before toggleTodo()…') -console.log(originalTodo) - -const newTodo = toggleTodo(originalTodo) - -console.log('After toggleTodo()…') -console.log('Original Todo:') -console.log(originalTodo) -console.log('New Todo:') -console.log(newTodo) diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 9672a68..7d1efbc 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -655,6 +655,20 @@ function placeToString(place: Place): string { } }` +export const wymp = `const argument = { + id: 1, + text: '…', + done: true +} + +console.log('Before toggleTodo(), argument is:') +console.log(argument) + +toggleTodo(argument) + +console.log('After toggleTodo(), argument is:') +console.log(argument)` + export const lund = `const result = toggleTodo({ id: 1, text: '…', @@ -691,7 +705,7 @@ const b: Foo = 'hello'` export const njgr = `function toggleTodo(todo: Todo): Todo { // Little Duckling’s refactoring is a // bad refactoring because it modifies - // the original todo object + // the argument (input) todo object todo.done = !todo.done return todo }` @@ -761,9 +775,8 @@ type ReadonlyFoo = Readonly // ReadonlyFoo is { readonly bar: number }` -export const qbgu = `// Returns a new todo object -// with the opposite boolean value -// for the "done" proprty. +export const qbgu = `// We said earlier that +// toggleTodo must return a new todo object. function toggleTodo(todo) { // ... }` @@ -899,23 +912,6 @@ export const whae = `function completeAll( })) }` -export const wymp = `const originalTodo = { - id: 1, - text: '…', - done: true -} - -console.log('Before toggleTodo()…') -console.log(originalTodo) - -const newTodo = toggleTodo(originalTodo) - -console.log('After toggleTodo()…') -console.log('Original Todo:') -console.log(originalTodo) -console.log('New Todo:') -console.log(newTodo)` - export const xrwn = `type Todo = Readonly<{ // id and text are the same as CompletedTodo id: number diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 481ff7b..012d93e 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -695,7 +695,7 @@ const Page = () => ( ) }, { - title: <>Bad refactor, + title: <>Bad refactoring, content: ( <>

        @@ -737,54 +737,39 @@ const Page = () => (

        - Before toggleTodo()… + Before toggleTodo(), argument is:
        {`{ id: 1, text: '…', done: true }`} - After toggleTodo()… -
        - Original Todo:
        - {`{ id: 1, text: '…', done: false }`} - New Todo: + After toggleTodo(), argument is:
        {`{ id: 1, text: '…', done: false }`} } /> -

        Here’s what happened:

        -
          - - originalTodo originally had done: true - . - - - After toggleTodo(), both originalTodo{' '} - and newTodo have done: false. - - - So originalTodo was modified! - -

        - However, we’ve said earlier on this page that{' '} + argument changed after running{' '} + toggleTodo() on it. This is NOT good because we’ve + said earlier that{' '} - toggleTodo() must return a new todo object. It - should NOT modify the original object. - + toggleTodo() must return a new todo object. + {' '} + It should NOT modify the argument (input) todo object.

        lineIndex === 0} + shouldHighlight={lineIndex => lineIndex === 1} />

        - That’s why Little Duckling’s refactoring is a bad refactor—even + That’s why Little Duckling’s refactoring is a bad refactoring—even though it compiles correctly.

        lineIndex === 2 || lineIndex === 3} + shouldHighlight={lineIndex => lineIndex === 4} /> Date: Tue, 17 Dec 2019 22:24:09 +0900 Subject: [PATCH 47/57] Wordsmith slide 8 --- snippets/snippets/todo/vgnq.ts | 10 ++++++++-- src/lib/articles.ts | 2 +- src/lib/snippets.ts | 10 ++++++++-- src/pages/todo.tsx | 12 ++++++------ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/snippets/snippets/todo/vgnq.ts b/snippets/snippets/todo/vgnq.ts index 900ff55..3eb227d 100644 --- a/snippets/snippets/todo/vgnq.ts +++ b/snippets/snippets/todo/vgnq.ts @@ -1,5 +1,11 @@ -// This will continue to work because -// the input todo is not modified +type Todo = { + readonly id: number + readonly text: string + readonly done: boolean +} + +// Earlier implementation: it will continue to +// work because the input todo is not modified function toggleTodo(todo: Todo): Todo { return { id: todo.id, diff --git a/src/lib/articles.ts b/src/lib/articles.ts index 8e6b1f2..4b914d3 100644 --- a/src/lib/articles.ts +++ b/src/lib/articles.ts @@ -8,7 +8,7 @@ export const articlesData = { todo: { title: 'TypeScript Tutorial for JS Programmers Who Know How to Build a Todo App', - date: DateTime.fromISO('2019-12-17T12:00:00Z'), + date: DateTime.fromISO('2019-12-18T12:00:00Z'), description: 'Learn TypeScript by Building a Todo App', ogImage: 'todo' }, diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 7d1efbc..812b39d 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -865,8 +865,14 @@ export const uxlb = `function toggleTodo(todo: Todo): Todo { return todo }` -export const vgnq = `// This will continue to work because -// the input todo is not modified +export const vgnq = `type Todo = { + readonly id: number + readonly text: string + readonly done: boolean +} + +// Earlier implementation: it will continue to +// work because the input todo is not modified function toggleTodo(todo: Todo): Todo { return { id: todo.id, diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 012d93e..9638ccf 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -807,8 +807,8 @@ const Page = () => ( you can use the readonly keyword in TypeScript. {' '} - In the following code, the readonly keyword is added - to all of the properties of Todo. + Here, the readonly keyword is added to all of the + properties of Todo.

        ( />

        Now, let’s try to compile Little Duckling’s code again using the - updated definition of Todo. What happens this time? + above definition of Todo. What happens this time?

        ( } />

        - By the way, the previous implementation (before refactoring) will - continue to work because it does NOT modify the input todo item. + By the way, the earlier implementation we used will continue to + work because it does NOT modify the input todo item.

        lineIndex <= 1} + shouldHighlight={lineIndex => lineIndex === 6 || lineIndex === 7} /> ) From bc740c7886f37aa1b4094a85c460baf1d93c3ccf Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 22:41:07 +0900 Subject: [PATCH 48/57] Wordsmith slide 10 --- snippets/snippets/todo/frtm.ts | 2 +- snippets/snippets/todo/jkjo.ts | 15 ----- src/lib/snippets.ts | 18 +----- src/pages/todo.tsx | 104 +++++++++++++-------------------- 4 files changed, 41 insertions(+), 98 deletions(-) delete mode 100644 snippets/snippets/todo/jkjo.ts diff --git a/snippets/snippets/todo/frtm.ts b/snippets/snippets/todo/frtm.ts index d18fb64..44f7de4 100644 --- a/snippets/snippets/todo/frtm.ts +++ b/snippets/snippets/todo/frtm.ts @@ -5,7 +5,7 @@ type Todo = { } // Make sure that the input and the output -// is of the correct type +// are of the correct type (both must be Todo) function toggleTodo(todo: Todo): Todo { // ... } diff --git a/snippets/snippets/todo/jkjo.ts b/snippets/snippets/todo/jkjo.ts deleted file mode 100644 index e45213d..0000000 --- a/snippets/snippets/todo/jkjo.ts +++ /dev/null @@ -1,15 +0,0 @@ -// By default, the properties of Todo are -// NOT read-only -type Todo = { - id: number - text: string - done: boolean -} - -// By using Readonly<> here, it makes the -// properties readonly only within toggleTodo() -function toggleTodo( - todo: Readonly -): Todo { - // ... -} diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 812b39d..26ba19a 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -521,7 +521,7 @@ export const frtm = `type Todo = { } // Make sure that the input and the output -// is of the correct type +// are of the correct type (both must be Todo) function toggleTodo(todo: Todo): Todo { // ... }` @@ -544,22 +544,6 @@ export const hszk = `function completeAll( })) }` -export const jkjo = `// By default, the properties of Todo are -// NOT read-only -type Todo = { - id: number - text: string - done: boolean -} - -// By using Readonly<> here, it makes the -// properties readonly only within toggleTodo() -function toggleTodo( - todo: Readonly -): Todo { - // ... -}` - export const kuzw = `function completeAll(todos: Todo[]): Todo[] { // We want it to return a new array // instead of modifying the original array diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 9638ccf..4977cdd 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -880,7 +880,10 @@ const Page = () => ( First, here’s our read-only version of Todo:

        -

        The above code is equivalent to the following version:

        +

        + The above code is equivalent to the following + version: +

        @@ -889,60 +892,28 @@ const Page = () => ( } />

        + In TypeScript,{' '} - If you use Readonly<...> on an object type, - it makes all of its properties readonly + if you use the{' '} + + Readonly<...> + {' '} + keyword on an object type, it makes all of its properties{' '} + readonly - . Here’s another example: + . This is often easier than manually adding readonly{' '} + to every property.

        +

        Here’s another example:

        - This can be useful for toggleTodo(). For example, we - might want to: -

        -
          - - - Make the properties of Todo to be NOT{' '} - readonly by default, and… - - - - - Make them readonly ONLY within{' '} - toggleTodo(). - - -
        -

        In that case, we can write the following code:

        - - lineIndex === 11 && tokenIndex >= 1 - } - /> - -

        - That’s so cool!{' '} - - So you can convert one type into another type in - TypeScript? - -

        - - ) - } - ]} - /> -

        - Yes! In TypeScript, you can use keywords like{' '} - Readonly<...> to convert one type into another - type—in this case, it creates a new type with{' '} + + In TypeScript, you can use keywords like{' '} + Readonly<...> to convert one type into + another type. + {' '} + In this case, Readonly<...> takes an object + type (like Todo) and creates a new object type with{' '} readonly properties.

        ( />

        And the keywords like Readonly<...> are called{' '} - Mapped Types. There are many built-in mapped - types (like Required<...>,{' '} - Partial<...>, etc). You can also create your - own mapped types. I won’t cover these topics here—you can google - them. + mapped types. Mapped types are kind of like + functions, except the input/output are TypeScript types. +

        +

        + There are many built-in mapped types (like{' '} + Required<...>, Partial<...>, + etc). You can also create your own mapped types. I won’t cover + these topics here—you can google them.

        ) @@ -996,15 +970,15 @@ const Page = () => ( } />

        - Before TypeScript, you needed to write unit tests to verify these - things. So in a sense,{' '} + In JavaScript, you needed to write unit tests to test these + things. But TypeScript can check them automatically. So in a + sense,{' '} - TypeScript’s types act as lightweight unit tests that run - automatically every time you save (compile) the code. + TypeScript’s types act as lightweight unit tests that run every + time you save (compile) the code. {' '} - It helps you write less buggy code with very little overhead. (Of - course, this analogy is a simplification. You should still write - tests in TypeScript!) + (Of course, this analogy is a simplification. You should still + write tests in TypeScript!)

        This is especially useful{' '} @@ -1013,13 +987,13 @@ const Page = () => ( . For example, if you’re using React, you’ll need to transform data in state updates. You might also need to transform data when - passing data from a parent component to its child component. - TypeScript reduces bugs arising from these situations. + passing data from a parent component to its children. TypeScript + can reduce bugs arising from these situations.

        TypeScript reduces bugs when transforming/passing data + <>TypeScript can reduce bugs when transforming/passing data } /> From 8115699e3c6910a631a6b7d0bfa3f8f699bc2994 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 22:52:59 +0900 Subject: [PATCH 49/57] Wordsmith 12 --- src/pages/todo.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 4977cdd..711b3ad 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1029,10 +1029,9 @@ const Page = () => ( content: ( <>

        - Some todo apps allow you to{' '} - mark all items as completed. On the following - todo app,{' '} - try pressing “Mark all as completed”: + Let’s talk about a new feature of our todo app: “ + Mark all as completed”.{' '} + Try pressing “Mark all as completed” below:

        ( shouldHighlight={tokenIndex => tokenIndex === 15} />

        - After pressing “Mark all as completed”, all items will have{' '} - done: true. + After pressing “Mark all as completed”, all + items end up with done: true.

        Let’s implement this functionality using TypeScript. We’ll write a function called completeAll() which takes an array of - todo items and returns a new array where done is all{' '} - true. + todo items and returns a new array of todos where{' '} + done is all true.

        Before implementing it,{' '} - let’s add some types for the input and output of this function + let’s specify the input/output types for this function {' '} to prevent mistakes!

        From 94eb9a6d485f98553af648b70c6fcf918d8a53d0 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Tue, 17 Dec 2019 23:03:23 +0900 Subject: [PATCH 50/57] Slide 13 wordsmith --- snippets/snippets/todo/lgci.ts | 7 ------- snippets/snippets/todo/mwrj.ts | 2 +- snippets/snippets/todo/mxqy.ts | 5 +++++ src/lib/snippets.ts | 17 ++++++++--------- src/pages/todo.tsx | 17 +++++++++++------ 5 files changed, 25 insertions(+), 23 deletions(-) create mode 100644 snippets/snippets/todo/mxqy.ts diff --git a/snippets/snippets/todo/lgci.ts b/snippets/snippets/todo/lgci.ts index 269eace..e0898ff 100644 --- a/snippets/snippets/todo/lgci.ts +++ b/snippets/snippets/todo/lgci.ts @@ -1,10 +1,3 @@ -// Same as before: Each property is readonly -type Todo = Readonly<{ - id: number - text: string - done: boolean -}> - // Input is an array of Todo items: Todo[] function completeAll(todos: Todo[]) { // ... diff --git a/snippets/snippets/todo/mwrj.ts b/snippets/snippets/todo/mwrj.ts index 5fbb963..e6d20df 100644 --- a/snippets/snippets/todo/mwrj.ts +++ b/snippets/snippets/todo/mwrj.ts @@ -1,4 +1,4 @@ -// After declaring todos as: readonly Todo[], +// After declaring todos as readonly Todo[], // the following code WILL NOT compile: // Compile error - modifies the array diff --git a/snippets/snippets/todo/mxqy.ts b/snippets/snippets/todo/mxqy.ts new file mode 100644 index 0000000..6daa488 --- /dev/null +++ b/snippets/snippets/todo/mxqy.ts @@ -0,0 +1,5 @@ +type Todo = Readonly<{ + id: number + text: string + done: boolean +}> diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 26ba19a..f91ef39 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -549,14 +549,7 @@ export const kuzw = `function completeAll(todos: Todo[]): Todo[] { // instead of modifying the original array }` -export const lgci = `// Same as before: Each property is readonly -type Todo = Readonly<{ - id: number - text: string - done: boolean -}> - -// Input is an array of Todo items: Todo[] +export const lgci = `// Input is an array of Todo items: Todo[] function completeAll(todos: Todo[]) { // ... }` @@ -669,7 +662,7 @@ function completeAll(todos: Todo[]): Todo[] { // ... }` -export const mwrj = `// After declaring todos as: readonly Todo[], +export const mwrj = `// After declaring todos as readonly Todo[], // the following code WILL NOT compile: // Compile error - modifies the array @@ -678,6 +671,12 @@ todos[0] = { id: 1, text: '…', done: true } // Compile error - push() modifies the array todos.push({ id: 1, text: '…', done: true })` +export const mxqy = `type Todo = Readonly<{ + id: number + text: string + done: boolean +}>` + export const mzyn = `// Creates a union type of number and string type Foo = number | string diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 711b3ad..051698f 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1078,9 +1078,14 @@ const Page = () => ( content: ( <>

        - First, we’ll specify the type for the input - parameter of completeAll(). It’s an array of{' '} - Todo items.{' '} + For completeAll(), we’ll use the{' '} + Readonly version of the Todo type: +

        + +

        + First, we’ll specify the parameter type of{' '} + completeAll(), which is an array of Todo{' '} + items.{' '} To specify an array type, we add [] next to the type @@ -1090,7 +1095,7 @@ const Page = () => ( - lineIndex === 8 && tokenIndex >= 5 && tokenIndex <= 9 + lineIndex === 1 && tokenIndex >= 5 && tokenIndex <= 9 } />

        @@ -1126,14 +1131,14 @@ const Page = () => ( To make the array itself readonly, {' '} - we’ll add the readonly keyword to{' '} + we need to add the readonly keyword to{' '} Todo[] like so:

        - lineIndex === 2 && tokenIndex >= 1 + lineIndex === 2 && tokenIndex >= 2 } /> Date: Wed, 18 Dec 2019 08:40:48 +0900 Subject: [PATCH 51/57] Wordsmith 14 --- src/pages/todo.tsx | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 051698f..b7c0d85 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1222,8 +1222,13 @@ const Page = () => ( />

        The new CompletedTodo is almost identical to{' '} - Todo, except it has done: true instead - of done: boolean.{' '} + Todo, except it has{' '} + + done: true + {' '} + instead of done: boolean. +

        +

        In TypeScript, you can use exact values (like{' '} true or false) when specifying a type. @@ -1240,8 +1245,8 @@ const Page = () => ( } />

        - Let’s take a look at an example. In the following code, we added a{' '} - CompletedTodo to a todo item that has{' '} + Let’s take a look at an example. In the following code, we’ve + added CompletedTodo to a todo item that has{' '} done: false. Let’s see what happens{' '} when you it @@ -1253,15 +1258,18 @@ const Page = () => ( compile resultError result={<>Type 'false' is not assignable to type 'true'.} + shouldHighlight={(lineIndex, tokenIndex) => + lineIndex === 1 && tokenIndex >= 3 && tokenIndex <= 5 + } shouldHighlightResult={lineIndex => lineIndex === 4} />

        It failed to compile because done is not{' '} - true. By using literal types like the above, you can - specify exactly what value is allowed for a property. + true. By using literal types, you can specify exactly + what value is allowed for a property.

        - Coming back to completeAll(), we can now specify the + Coming back to completeAll(), we can specify the return type of completeAll() to be an array of{' '} CompletedTodo’s:

        @@ -1275,7 +1283,7 @@ const Page = () => ( By doing this,{' '} TypeScript will force you to return an array of todo items where{' '} - done is true + done is all true —if not, it will result in a compile error.

        From 71dc2fc263274545882f90d478d9aea21d97784e Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Wed, 18 Dec 2019 09:12:15 +0900 Subject: [PATCH 52/57] Up to slide 16 --- snippets/snippets/todo/qnwc.ts | 12 ++++++------ src/lib/snippets.ts | 12 ++++++------ src/pages/todo.tsx | 22 ++++++++++++++++++++-- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/snippets/snippets/todo/qnwc.ts b/snippets/snippets/todo/qnwc.ts index dd316e0..e79f458 100644 --- a/snippets/snippets/todo/qnwc.ts +++ b/snippets/snippets/todo/qnwc.ts @@ -1,11 +1,11 @@ -// They booth have property x, -// but B’s x (true) is -// more specific than A’s x (boolean) -type A = { x: boolean } -type B = { x: true } +// They booth have a property foo, +// but B’s foo (true) is +// more specific than A’s foo (boolean) +type A = { foo: boolean } +type B = { foo: true } // This intersection type… type AandB = A & B // …is equivalent to: -type AandB = { x: true } +type AandB = { foo: true } diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index f91ef39..a47bc07 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -776,17 +776,17 @@ placeToString({ custom: 'Gym' }) placeToString({ custom: 'Supermarket' }) // __supermarket__` -export const qnwc = `// They booth have property x, -// but B’s x (true) is -// more specific than A’s x (boolean) -type A = { x: boolean } -type B = { x: true } +export const qnwc = `// They booth have a property foo, +// but B’s foo (true) is +// more specific than A’s foo (boolean) +type A = { foo: boolean } +type B = { foo: true } // This intersection type… type AandB = A & B // …is equivalent to: -type AandB = { x: true }` +type AandB = { foo: true }` export const reel = `function toggleTodo(todo) { return { diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index b7c0d85..ce13301 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1329,8 +1329,11 @@ const Page = () => ( intersection types.

        - In TypeScript, you can use the & sign to create - an intersection type of two types. + In TypeScript,{' '} + + you can use the & sign to create an{' '} + intersection type of two types. +

        ( done: true. Once again, TypeScript caught an error early.

        +

        + That’s all for this section! By using{' '} + completeAll() with a UI library like React, we can + build the “Mark all as completed” feature we saw earlier. +

        + tokenIndex === 15} + /> ) }, From 695050425ebb8cb76f2f0da75c4a937f70bfdeeb Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Wed, 18 Dec 2019 09:22:08 +0900 Subject: [PATCH 53/57] Wordsmith slide 17 --- snippets/snippets/todo/ruga.ts | 2 +- src/lib/snippets.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/snippets/snippets/todo/ruga.ts b/snippets/snippets/todo/ruga.ts index 3ccde1e..8341493 100644 --- a/snippets/snippets/todo/ruga.ts +++ b/snippets/snippets/todo/ruga.ts @@ -1,5 +1,5 @@ function completeAll( todos: readonly Todo[] -): Todo[] { +): CompletedTodo[] { // ... } diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index a47bc07..16b7ef1 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -820,7 +820,7 @@ type CompletedTodo = Todo & { export const ruga = `function completeAll( todos: readonly Todo[] -): Todo[] { +): CompletedTodo[] { // ... }` From 038c3b5df48221d667cff5c1f04a585a10c7a581 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Wed, 18 Dec 2019 09:40:12 +0900 Subject: [PATCH 54/57] Wordsmith slide 19 --- src/pages/todo.tsx | 64 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index ce13301..2f54846 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1555,29 +1555,38 @@ const Page = () => ( <>

        Let’s add a new feature to our todo app:{' '} - Place tags.{' '} + Place tags. +

        +

        - Each todo item can now optionally be tagged with one of the - following tags: + Each todo item can now optionally be tagged with one of + the following pre-defined tags:

          - Home: + - Work: + +
        +

        + + Each todo item can also be tagged with a custom,{' '} + user-defined tags: + +

        +
          - Custom place:{' '} ,{' '} , etc—the user can create any custom place they want.

        - People can use this feature to identify which tasks need to be - done at home, at work, or elsewhere.{' '} + Users can use this feature to identify which tasks need to be done + at home, at work, or elsewhere.{' '} It’s optional, so there can be a todo item without a place tag. @@ -1596,26 +1605,47 @@ const Page = () => (

        Let’s take a look at the associated data.{' '} - Each todo now can have an optional place property, - which can have one of the following values: + Each todo can now have an optional place property, + which determines the place tag:

          - place: 'home' + + place: 'home' + {' '} + → + + + + place: 'work' + {' '} + → +
        +

        + For custom places,{' '} + + the place property will be an object containing a + string custom property: + +

        +
          - place: 'work' + + place: {`{ custom: 'Gym' }`} + {' '} + → - →{' '} - {`place: { custom: 'Foo' }`} + + place: {`{ custom: 'Supermarket' }`} + {' '} + →

        - So place can be 'home',{' '} - 'work', or an object containing a string{' '} - custom property. It can also be missing if there’s no + The place property can also be missing if there’s no place tag.

        Here’s the associated data for our previous example:

        From 83b09f39bf4f992b8f0c14e4e7c26ab2f8510144 Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Wed, 18 Dec 2019 10:18:55 +0900 Subject: [PATCH 55/57] Wordsmith slide 21 --- snippets/snippets/todo/longerWidth/fawy.ts | 7 ++++++ src/lib/snippets.ts | 8 +++++++ src/pages/todo.tsx | 25 ++++++++++++++++------ 3 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 snippets/snippets/todo/longerWidth/fawy.ts diff --git a/snippets/snippets/todo/longerWidth/fawy.ts b/snippets/snippets/todo/longerWidth/fawy.ts new file mode 100644 index 0000000..c4a0d27 --- /dev/null +++ b/snippets/snippets/todo/longerWidth/fawy.ts @@ -0,0 +1,7 @@ +type Place = 'home' | 'work' | { custom: string } + +// They all compile +const place1: Place = 'home' +const place2: Place = 'work' +const place3: Place = { custom: 'Gym' } +const place4: Place = { custom: 'Supermarket' } diff --git a/src/lib/snippets.ts b/src/lib/snippets.ts index 16b7ef1..28235cf 100644 --- a/src/lib/snippets.ts +++ b/src/lib/snippets.ts @@ -578,6 +578,14 @@ function placeToString(place: Place): string { } }` +export const fawy = `type Place = 'home' | 'work' | { custom: string } + +// They all compile +const place1: Place = 'home' +const place2: Place = 'work' +const place3: Place = { custom: 'Gym' } +const place4: Place = { custom: 'Supermarket' }` + export const ntup = `// If we have a variable that’s a union type… type Place = 'home' | 'work' | { custom: string } diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 2f54846..6d18ced 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1676,8 +1676,11 @@ const Page = () => ( content: ( <>

        - To represent place tags, we can use a TypeScript feature called{' '} - union types. In TypeScript,{' '} + To implement place tags, we can use a TypeScript feature called{' '} + union types. +

        +

        + In TypeScript,{' '} you can use the syntax A | B to create a{' '} union type, which represents a type that’s{' '} @@ -1730,7 +1733,11 @@ const Page = () => ( } />

        - Then we can assign the Place type to the{' '} + Here’s an example usage of the Place type: +

        + +

        + We can now assign the Place type to the{' '} place property of Todo:

        ( lineIndex === 4 && tokenIndex >= 3 } /> +

        + That’s it! Next, we’ll take a look at{' '} + optional properties. +

        ) }, @@ -1756,8 +1767,11 @@ const Page = () => ( We briefly mentioned that place tags like{' '} or are{' '} optional—we can have todo items without a place - tag. In our previous example, “Read a book”{' '} - didn’t have any place tag, so it didn’t have the{' '} + tag. +

        +

        + In our previous example, “Read a book”{' '} + didn’t have any place tag, so it didn’t have any{' '} place property:

        (

        lineIndex === 2 && tokenIndex <= 1 From ec1027fdc6abf6e1a214f4d3f97cd0fd994229fa Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Wed, 18 Dec 2019 10:43:47 +0900 Subject: [PATCH 56/57] Done with wordsmith --- src/pages/todo.tsx | 63 +++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/src/pages/todo.tsx b/src/pages/todo.tsx index 6d18ced..48aa21c 100644 --- a/src/pages/todo.tsx +++ b/src/pages/todo.tsx @@ -1919,11 +1919,11 @@ const Page = () => (

        Here are the examples:

        - We can then use the return value of placeToString(){' '} - to render place label UIs: ,{' '} - ,{' '} - , etc in any UI library - (e.g. React). + We can then use its return value to render place label UIs:{' '} + , ,{' '} + , etc in any UI library. + For example, in React, you can define a functional component and + call placeToString() inside it.

        Let’s now implement placeToString(). Here’s the @@ -1985,14 +1985,21 @@ const Page = () => ( lineIndex === 5 || lineIndex === 8 || lineIndex === 12 } /> -

        - Because place is either 'work' or{' '} - {`{ custom: string }`} inside else, and{' '} - - you can’t do place.custom if place is{' '} - 'work', TypeScript gives you a compile error. - -

        +

        Here’s what happened:

        +
          + + place is either 'work' or{' '} + {`{ custom: string }`} inside else. + + + + And place.custom is invalid if place{' '} + is 'work' + + . + +
        +

        That’s why TypeScript gave you a compile error.

        lineIndex === 2} @@ -2035,10 +2042,10 @@ const Page = () => ( error early.

        - As we just saw,{' '} + Summary: As we just saw,{' '} union types are powerful when combined with conditional - statements (e.g. if/else or switch) + statements (e.g. if/else) :

        @@ -2096,7 +2103,7 @@ const Page = () => ( Finally,{' '} union types are powerful when combined with conditional - statements (e.g. if/else or switch). + statements (e.g. if/else).

        @@ -2120,14 +2127,15 @@ const Page = () => (

          - If you’re using React,{' '} + Using React? If you’re using React,{' '} React+TypeScript Cheatsheets {' '} is a good reference. - Once you become more familiar with TypeScript, you should learn{' '} + What to learn next: Once you become more + familiar with TypeScript, you should learn{' '} generics next. I’ve written an article on it called “ @@ -2137,18 +2145,21 @@ const Page = () => ( ”. - + Source code: - - I plan to write more TypeScript articles by building on the todo - app example I used here. To get notified when I publish a new - article, follow me on{' '} +
        {' '} +

        + I plan to write more TypeScript articles by continuing on the todo + app example I used here. To get notified when I publish a new + article,{' '} + + follow me on{' '} Twitter at @chibicode - . - -

      + + . +

      ), footer: { From 897ef419b1eb8fc04cddfed1dfacf156466c7c9f Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Wed, 18 Dec 2019 10:44:08 +0900 Subject: [PATCH 57/57] Add to articleKeys --- src/lib/articles.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/articles.ts b/src/lib/articles.ts index 4b914d3..acd90c8 100644 --- a/src/lib/articles.ts +++ b/src/lib/articles.ts @@ -1,6 +1,7 @@ import { DateTime } from 'luxon' export const articleKeys: ReadonlyArray = [ + 'todo', 'generics' ]