diff --git a/README.md b/README.md index 3366e38..9e8428e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,21 @@ -# TypeScript for Beginner Programmers +# [TypeScript for Beginner Programmers](https://ts.chibicode.com) + +### This is the repository for the TypeScript tutorial website called **[TypeScript for Beginner Programmers](https://ycombinator.chibicode.com/)**. + +
## License & Credits - For emojis, I’m using [Twemoji](https://github.com/twitter/twemoji) by Twitter (CC-BY 4.0 license). -- The text for this course is licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). -- Everything else is licensed under the [MIT](LICENSE-non-text.txt) license. \ No newline at end of file +- The text for this website is licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). +- Everything else is licensed under the [MIT](LICENSE-non-text.txt) license. + +## Author + +**Shu Uesugi** + +- Twitter: [@chibicode](https://twitter.com/chibicode) +- [Website](https://chibicode.com) +- Email: [shu@chibicode.com](mailto:shu@chibicode.com) \ No newline at end of file diff --git a/snippets/snippets/generics/bwyu.ts b/snippets/snippets/generics/bwyu.ts new file mode 100644 index 0000000..b02171e --- /dev/null +++ b/snippets/snippets/generics/bwyu.ts @@ -0,0 +1,5 @@ +// Confused by generics code like this? +function makePair< + F extends number | string, + S extends boolean | F +>() diff --git a/snippets/snippets/generics/cqrm.ts b/snippets/snippets/generics/cqrm.ts new file mode 100644 index 0000000..ecb38cd --- /dev/null +++ b/snippets/snippets/generics/cqrm.ts @@ -0,0 +1,6 @@ +function makePair {
css`
font-size: 0.9em;
word-break: break-word;
- background: ${colors('lightPink2')};
+ background: ${color ? colors(color) : colors('lightPink2')};
`
]}
/>
diff --git a/src/components/ContentTags/index.tsx b/src/components/ContentTags/index.tsx
index 1aa7480..01b1846 100644
--- a/src/components/ContentTags/index.tsx
+++ b/src/components/ContentTags/index.tsx
@@ -3,4 +3,5 @@ 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'
+export { default as A } from 'src/components/ContentTags/A'
export { Ul, Ol, UlLi, OlLi } from 'src/components/ContentTags/List'
diff --git a/src/components/Emoji/SmilingCat.tsx b/src/components/Emoji/SmilingCat.tsx
new file mode 100644
index 0000000..0159207
--- /dev/null
+++ b/src/components/Emoji/SmilingCat.tsx
@@ -0,0 +1,40 @@
+import React from 'react'
+
+const SvgSparkles = (props: React.SVGProps) => (
+
+)
+
+export default SvgSparkles
diff --git a/src/components/Emoji/Sparkles.tsx b/src/components/Emoji/Sparkles.tsx
new file mode 100644
index 0000000..f0bf560
--- /dev/null
+++ b/src/components/Emoji/Sparkles.tsx
@@ -0,0 +1,16 @@
+import React from 'react'
+
+const SvgSmilingCat = (props: React.SVGProps) => (
+
+)
+
+export default SvgSmilingCat
diff --git a/src/components/Emoji/index.tsx b/src/components/Emoji/index.tsx
index cba72f7..aabdb2f 100644
--- a/src/components/Emoji/index.tsx
+++ b/src/components/Emoji/index.tsx
@@ -6,6 +6,8 @@ import Question from 'src/components/Emoji/Question'
import Run from 'src/components/Emoji/Run'
import ChickEgg from 'src/components/Emoji/ChickEgg'
import Twitter from 'src/components/Emoji/Twitter'
+import Sparkles from 'src/components/Emoji/Sparkles'
+import SmilingCat from 'src/components/Emoji/SmilingCat'
export const emojiToComponent = {
bird: Bird,
@@ -13,7 +15,9 @@ export const emojiToComponent = {
question: Question,
run: Run,
chickEgg: ChickEgg,
- twitter: Twitter
+ twitter: Twitter,
+ sparkles: Sparkles,
+ smilingCat: SmilingCat
}
const Emoji = ({
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
new file mode 100644
index 0000000..b0efa88
--- /dev/null
+++ b/src/components/Footer.tsx
@@ -0,0 +1,32 @@
+/** @jsx jsx */
+import { css, jsx } from '@emotion/core'
+import Container from 'src/components/Container'
+import useTheme from 'src/hooks/useTheme'
+import { A } from 'src/components/ContentTags'
+
+const Footer = () => {
+ const { fontSizes, ns, spaces, colors } = useTheme()
+
+ return (
+
+ The source code for this site is{' '}
+
+ available on GitHub
+
+ .
+
+ )
+}
+
+export default Footer
diff --git a/src/components/IndexPage.tsx b/src/components/IndexPage.tsx
new file mode 100644
index 0000000..796bd24
--- /dev/null
+++ b/src/components/IndexPage.tsx
@@ -0,0 +1,113 @@
+/** @jsx jsx */
+import { css, jsx } from '@emotion/core'
+import Page from 'src/components/Page'
+import InternalLink from 'src/components/InternalLink'
+import useTheme from 'src/hooks/useTheme'
+import { articlesList, articlesData } from 'src/lib/articles'
+import { dateString } from 'src/lib/date'
+
+const ArticleLink = ({
+ title,
+ href,
+ date
+}: {
+ title: string
+ href: string
+ date: string
+}) => {
+ const {
+ colors,
+ ns,
+ fontSizes,
+ spaces,
+ lineHeights,
+ letterSpacings
+ } = useTheme()
+ return (
+
+
+
+ {title}
+
+
+
+ {date}
+
+
+ )
+}
+
+const IndexPage = ({ children }: { children: React.ReactNode }) => {
+ const { ns, spaces, fontSizes, letterSpacings, colors } = useTheme()
+ return (
+
+ {children}
+
+ Articles
+
+
+ {articlesList.map(articleKey => (
+
+ ))}
+
+
+ )
+}
+
+export default IndexPage
diff --git a/src/components/Page.tsx b/src/components/Page.tsx
index 5e5875e..4e3802f 100644
--- a/src/components/Page.tsx
+++ b/src/components/Page.tsx
@@ -4,6 +4,7 @@ import GlobalStyles from 'src/components/GlobalStyles'
import Head from 'next/head'
import Container from 'src/components/Container'
import Header from 'src/components/Header'
+import Footer from 'src/components/Footer'
import useTheme from 'src/hooks/useTheme'
const Page = ({
@@ -49,16 +50,18 @@ const Page = ({
+
{children}
+
>
)
diff --git a/src/components/TwitterEmbed.tsx b/src/components/TwitterEmbed.tsx
new file mode 100644
index 0000000..6052d6f
--- /dev/null
+++ b/src/components/TwitterEmbed.tsx
@@ -0,0 +1,56 @@
+/** @jsx jsx */
+import { css, jsx } from '@emotion/core'
+import { useRef, useState } from 'react'
+import useTheme from 'src/hooks/useTheme'
+import useInterval from 'src/hooks/useInterval'
+
+declare global {
+ interface Window {
+ twttr: any
+ __twttr: any
+ }
+}
+
+const EMBED_DELAY = 500
+
+const TwitterEmbed = ({ id, showCard }: { id: string; showCard?: boolean }) => {
+ const wrapperEl = useRef(null)
+ const [twitterLoaded, setTwitterLoaded] = useState(false)
+ const { spaces } = useTheme()
+ useInterval(
+ () => {
+ if (
+ window.twttr &&
+ window.twttr.widgets &&
+ window.twttr.widgets.createTweet &&
+ wrapperEl.current
+ ) {
+ wrapperEl.current.innerHTML = ''
+ window.twttr.widgets
+ .createTweet(id, wrapperEl.current, {
+ dnt: true,
+ cards: showCard ? undefined : 'hidden',
+ align: 'center',
+ conversation: 'none'
+ })
+ .then(() => {
+ setTwitterLoaded(true)
+ })
+ }
+ },
+ twitterLoaded ? null : EMBED_DELAY
+ )
+ return (
+
+ )
+}
+
+export default TwitterEmbed
diff --git a/src/components/TwitterLink.tsx b/src/components/TwitterLink.tsx
index 279a493..2de0c54 100644
--- a/src/components/TwitterLink.tsx
+++ b/src/components/TwitterLink.tsx
@@ -1,5 +1,6 @@
import React from 'react'
import Emoji from 'src/components/Emoji'
+import { A } from 'src/components/ContentTags'
const TwitterLink = ({
title,
@@ -14,9 +15,9 @@ const TwitterLink = ({
url
)}&via=chibicode&text=${encodeURIComponent(title)}`
return (
-
+
{children}
-
+ + Page not found! +
+Take a look at the articles below:
+ > + ) + } + ]} + /> +If you’re (1) new to TypeScript, (2) new to{' '} generics, and (3) struggling to @@ -29,10 +29,9 @@ const Page = () => ( Back then, I was studying the Java programming language in college. Generics were a relatively new feature for Java at the time. I was a beginner programmer then, and{' '} - generics felt very difficult. So I gave up on actually - understanding generics and used them without knowing what I was - doing. I never really understood generics until I had to use them - a lot for my job. + generics felt very difficult. So I gave up on + understanding generics at the time. I had to re-learn generics + when I got a full time job.
Similarly, if you feel that TypeScript generics are too difficult, - this tutorial is for you! I’ll try to help you actually understand + this tutorial is for you! I’ll help you actually understand generics.
> @@ -57,11 +56,11 @@ const Page = () => ( Note: If you already understand generics, you won’t find anything new in this tutorial. However,{' '}
- Note: If you’ve used React, you might realize
- that it’s kind of like the useState() hook.
+ Note: If you’ve used React, you might have
+ realized that makeState() is
+ similar to the useState(){' '}
+ hook.
- To fix this, we can change the types from number to{' '}
- string:
+ To fix this, we can change the type of state and{' '}
+ x from number to string:
Now that we got the basics down, here’s a challenge question:
+Now that we got the basics down, here’s a challenge for you:
+ Question:{' '}
Here’s what I mean:
- Our first
How can we modify
- This does NOT work. If you use this, you’ll end
- up creating a state that allows both numbers and strings, which is
- not what we want.
+ This does NOT work. You’ll end up creating a
+ state that allows both numbers and strings, which is not what we
+ want. Instead, we want
- Instead, we want
Then, inside the function definition of
@@ -289,8 +288,8 @@ const Page = () => (
}
/>
- And to create a string-only state, you can pass{' '}
-
- That’s it! And we call
You might be wondering: Why did we name the
- type parameter as “
Answer: It could actually be any name, but
usually people use the first letter of a word that describes
what the type is representing. In this case, I chose “
-
@@ -361,7 +365,7 @@ const Page = () => (
Maybe we might NOT want this to be allowed.{' '}
Suppose that don’t want makeState() such that, it can create
two different states:
@@ -207,10 +209,10 @@ const Page = () => (
makeState() created number-only states, and
- our second makeState() created string-only states.
- However, it couldn’t create both number-only states and
- string-only states.
+ Earlier, our first makeState() created number-only
+ states, and our second makeState() created
+ string-only states. However, it couldn’t create both number-only
+ states and string-only states.
makeState() to achieve our goal?
@@ -231,16 +233,13 @@ const Page = () => (
}
/>
makeState() to support
+ creating two different states: one that allows only numbers, and
+ the other that allows only strings.
makeState() to support creating two
- different states: one that allows only numbers, and the other that
- allows only strings.
- makeState(),{' '}
- S will be come number:
+ S will become number:
number as S when you call{' '}
+ On the other hand, to create a string-only state, you can pass{' '}
+ string as S when you call{' '}
makeState():
makeState<S>() a
- “generic function” because it’s flexible—you have a choice to make
- it number-only or string-only. You know it’s a generic function if
- it takes a type parameter when you call it.
+ Note: We call makeState<S>() a{' '}
+ “generic function” because it’s literally
+ generic—you have a choice to make it number-only or string-only.
+ And you know it’s a generic function if it takes a type parameter.
S”?
+ type parameter as “S”?
S” because it’s describing the type of a{' '}
- “S”tate. The following names are also common:
+ S” because it’s describing the
+ type of a “S”tate. The following names are also
+ common:
>
@@ -348,7 +352,7 @@ const Page = () => (
}
},
{
- title: <>But you can create a boolean state!>,
+ title: <>Problem: You can create a boolean state!>,
content: (
<>
T (for “T”ype)
+ T (for “T”
+ ype)
E (for “E”lement)
+ E (for “E”
+ lement)
K (for “K”ey)
+ K (for “K”
+ ey)
V (for “V”alue)
+ V (for “V”
+ alue)
makeState() to be able to
create non-number or non-string states (like boolean
- ). How can we make sure that this is the case?
+ ). How can we ensure this?
makeState(), you’d only
be able to pass number, string, or any
other type that extends either number or{' '}
- string into S.
+ string as S.
Let’s see what happens now when you try to pass{' '}
- boolean into S.{' '}
+ boolean as S.{' '}
It resulted in an error, which is what we want!
+
+ It resulted in an error, which is what we want! We have
+ successfully prevented makeState() from creating
+ non-number or non-string states.
+
As you just saw, you can specify what’s allowed for the type
parameter(s) of a generic function.
@@ -442,8 +451,8 @@ const Page = () => (
Can we make it so that <number> is the
default type parameter of makeState()?
{' '}
- We want to make it so that, if the type is missing, it’s set as{' '}
- number by default.
+ We want to make it so that, if S is unspecified, it’s
+ set as number by default.
@@ -476,14 +485,14 @@ const Page = () => ( Before we continue, let’s do a quick recap.
- What you should remember is that,{' '}
+ What you should remember is that{' '}
makePair
+ >
+ ),
+ content: (
+ <>
+
+ Let’s take a look at the new function called{' '}
+ makePair(). It’s similar to makeState(),
+ but instead of storing a single value,{' '}
+ {`{ first: ?, second: ? }`}
+
+ Let’s try it out!{' '}
+
+ Now, just as we did for makeState(), let’s turn{' '}
+ makePair() into a generic function.
+
makePair
+ >
+ ),
+ content: (
+ <>
+
+ Here’s a generic version of makePair.
+
F and{' '}
+ S (for “F”irst and “S”econd).
+ first will be F.
+ second will be S.
+
+ Here’s an example usage. By calling makePair with{' '}
+ <number, string>, it forces first{' '}
+ to be number and second to be{' '}
+ string.
+
+ To summarize, you can create a generic function that takes{' '} + multiple type parameters. +
+
+ Of course, you can also use the extends keyword or
+ default types like before:
+
+ You can even make the second type (S) to be related
+ to the first type (F). Here’s an example:
+
+ Let’s go back to our previous implementation of{' '}
+ makePair(). Now, take a look at the type of{' '}
+ pair:
+
+ This works as is, but if we want to,{' '}
+ {`{ first: F, second: S }`} into an{' '}
+ interface or a type alias so
+ it can be reused.
+
+ Let’s first extract the type of pair into a{' '}
+ generic interface. I’ll use A and{' '}
+ B as type parameter names to distinguish them from
+ the type parameters of makePair().
+
+ We can then use this interface to declare the type for{' '}
+ pair.
+
+ By extracting into a generic interface (an interface that takes + type parameters), we can reuse it in other places if necessary. +
++ Alternatively, we can extract it into a{' '} + generic type alias. For object types, type + aliases are basically identical to interfaces, so you can use + whichever one you prefer. +
++ To summarize, you can create generic interfaces and type aliases, + just as you can create generic functions. +
+ > + ), + footer: { + content: ( + <> ++ To learn more about interfaces v.s. type aliases,{' '} + + read this StackOverflow answer + + . As of TypeScript 3.7, which added a{' '} + + support for recursive type aliases + + , type aliases can cover pretty much all of the use cases of + interfaces. +
+ > + ) + } + }, + { + title: <>Generic classes>, + content: ( + <> +
+ makeState(). This
+ is the generic version that doesn’t use extends or
+ default type parameters.
+
makeState()
+ >
+ }
+ />
+
+ We can turn makeState() into a generic class called{' '}
+ State like below. It looks similar to{' '}
+ makeState(), right?
+
+ To use this, you just need to pass a type parameter on + initialization. +
++ To summarize, generic classes are just like generic functions. + Generic functions take a type parameter when we call them, but + generic classes take a type parameter when we instantiate them. +
+ > + ), + footer: { + content: ( + <> +
+ Note: You need to set{' '}
+
+ "strictPropertyInitialization": false
+ {' '}
+ on TypeScript config (
+ tsconfig.json) to get the
+ above code to compile.
+
+ Thanks for reading! That’s all you need to know about generics in + TypeScript. Hope I made generics less scary to you. +
+
+ If you’d like me to write about some other topics on TypeScript,
+ or if you have feedback, please let me know on{' '}
+
+
+ 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.” + + . +
+ > + ) + } + } ]} /> ) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index f90619c..f391ed6 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,78 +1,12 @@ /** @jsx jsx */ import { css, jsx } from '@emotion/core' -import Page from 'src/components/Page' import BubbleQuotes from 'src/components/BubbleQuotes' import { P } from 'src/components/ContentTags' -import InternalLink from 'src/components/InternalLink' import useTheme from 'src/hooks/useTheme' -import { articlesList, articlesData } from 'src/lib/articles' -import { dateString } from 'src/lib/date' import { siteTitle, baseUrl, siteDescription, siteOgImage } from 'src/lib/meta' import Head from 'next/head' import { useState } from 'react' - -const ArticleLink = ({ - title, - href, - date -}: { - title: string - href: string - date: string -}) => { - const { - colors, - ns, - fontSizes, - spaces, - lineHeights, - letterSpacings - } = useTheme() - return ( -
-
- {date} -
-