This is the second pass at my Typescript conversion project. The first part is chronicled in an older post titled: Typescript Conversion Pass 1.
Experiment Parameters
- Merge within: ~1 hour (small amount of work)
- Must include: a post with content from this document
- Must include: A commit with some converted Typescript files
Strategy
- Convert files importing only libraries
- Import files which only import other tsc converted files
Outcome
$ ./scripts/typescript-vs-javascript
46% (19 Typescript / 22 Javascript)
Summary Of Changes
$ git diff --stat master
package.json | 4 +++
scripts/typescript-vs-javascript | 2 +-
src/components/AlbumArtwork/{index.js => index.tsx} | 5 ++--
src/components/AlbumMediaCard/{index.js => index.tsx} | 6 ++--
src/components/BaseMeta/{index.js => index.tsx} | 2 +-
src/components/Breadcrumbs/{index.jsx => index.tsx} | 6 ++--
src/components/Half/index.js | 10 -------
src/components/Half/index.tsx | 18 ++++++++++++
src/components/LatestRelease/{index.js => index.jsx} | 0
src/components/{LocationMap.js => LocationMap.tsx} | 3 +-
src/components/ResponsiveMenu/{Margin.js => Margin.tsx} | 0
src/components/Table/{dateSort.js => dateSort.ts} | 0
src/components/Table/{index.js => index.tsx} | 87 +++++++++++++++++++++++++++++-----------------------------
src/components/Tabs/index.js | 29 --------------------
src/components/Tabs/index.tsx | 37 +++++++++++++++++++++++++
src/components/disqus/{component.js => component.tsx} | 0
src/posts/2018-12-18-naive-typescript-conversion-pass-1.md | 4 +--
src/posts/2018-12-19-naive-typescript-conversion-pass-2.md | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/utils/{momentify.js => momentify.ts} | 3 +-
src/utils/{presenters.js => presenters.ts} | 9 +++---
yarn.lock | 44 +++++++++++++++++++++++++++--
21 files changed, 332 insertions(+), 104 deletions(-)
Learnings
I learned a few important things during this phase of the conversion. I expect these learnings to be useful in future passes.
Importing Type Definitions
During this excersize, I was faced with a new error:
Could not find a declaration file for module 'react-image-fallback'. 'node_modules/react-image-fallback/lib/index.js' implicitly has an 'any' type.
In Typescript, a library is expected to export its Type Definitions. If these are not present, then the compiler will complain in a way similar to the above.
This is usually resolved by a simple:
yarn add '@types/react-image-fallback'
Angular University's Type Definitions Article notes a few interesting things about custom type definitions. It's worth a full read, but the relevant parts for this post are:
This @types scoped package is where we can find a ton of useful type definitions, such as for example the type definitions of node that allow us to use require for example.
...The @types scope package contains type definitions for a lot of libraries, like Express, Sequelize, JQuery, and many others. So definitively have a look there if you are missing some type definitions, but make sure of two things first:
- check if the package you are using already has types built-in, and if so prefer those
- check if type definitions are already shipped with the compiler, more on this later
This commit required a few Type definitions to be added:
$ git diff master -- package.json
diff --git a/package.json b/package.json
index 4560e16..10c70f4 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,10 @@
"tsc:watch": "tsc --noEmit --watch"
},
"dependencies": {
+ "@types/react": "^16.7.17",
+ "@types/react-dom": "^16.0.11",
+ "@types/react-helmet": "^5.0.7",
+ "@types/react-image-fallback": "^8.0.0",
"fs-exists-sync": "^0.1.0",
"gatsby": "^2.0.0",
"gatsby-link": "^2.0.7",
Import CSS Modules
In Typescript, CSSModules require some massaging to work. Artem Sapegin's CSSModules with Typescript Article describes why and how to get them working. He also describes an easy way to bypass Typescript for importing CSSModules:
const styles = require('./styles.module.css');
In this commit, I went with the simple solution.
Simple Types In React
React requires its own custom type definitions (@types/react
and @types/react-dom
).
Additionally, there where a few other things that needed to be done to convert jsx files.
Consider this example of a simple file taken from the commit:
$ git diff master -- src/components/Half/index.tsx
diff --git a/src/components/Half/index.tsx b/src/components/Half/index.tsx
new file mode 100644
index 0000000..d59e551
--- /dev/null
+++ b/src/components/Half/index.tsx
@@ -0,0 +1,18 @@
+import React from "react";
+const styles = require('./styles.module.css');
+
+type Props = {
+ style: any,
+}
+
+type State = {
+
+}
+
+export default class Half extends React.Component<Props, State>{
+ render(){
+ return <section className={styles.half} style={this.props.style}>
+ {this.props.children}
+ </section>
+ }
+}
This file defines two simple types: Props
and State
.
The Half
class uses these types in its interface React.Component<Props, State>
.
React State
When declaring the immutable state of a React component, some more explicit types are sometimes required. In these cases I employed this pattern:
const initialState = { activeTab: 0 }
type State = Readonly<typeof initialState>
export default class Tabs extends React.Component<Props, State> {
readonly state: State = initialState
This was taken from a Stackoverflow question on readonly state.
More Types
In the same file, there are a few new type keywords that I employed:
const initialState = { activeTab: 0 }
type State = Readonly<typeof initialState>
type TabsType = {
content: () => JSX.Element,
title: string
}
type Props = {
tabs: Array<TabsType>
}
export default class Tabs extends React.Component<Props, State> {
readonly state: State = initialState
render() {
const activeTab = this.props.tabs[this.state.activeTab];
{activeTab ? activeTab.content() : ''}
}
}
In the TabsType
definition, the content
key declares a () => JSX.Element
type.
This is because in the render
method of the Tabs
component, the content
prop returns a JSX element.
There is also an Array of TabsType
specified in the Props
definition: Array<TabsType>
Conclusion
I am trying to convert my codebase in a few small chunks, with as little change as possible. This is to set myself a good foundation to explore Typescript.
My hypothesis is that I have two more one hour passes to complete this project.