TypeScript ReadMe Updates (#282)

* update readme with typescript action/reducer philosophies

* convert directory strucuture to code block for styling

* Update readme with hoc typings (#296)
This commit is contained in:
Daniel Ternyak 2017-10-14 12:14:24 -07:00 committed by GitHub
parent dceeec738d
commit 39ae78b28b
1 changed files with 109 additions and 44 deletions

151
README.md
View File

@ -36,7 +36,7 @@ npm run dev:https
2. [dternyak/eth-priv-to-addr](https://hub.docker.com/r/dternyak/eth-priv-to-addr/) pulled from DockerHub 2. [dternyak/eth-priv-to-addr](https://hub.docker.com/r/dternyak/eth-priv-to-addr/) pulled from DockerHub
##### Docker setup instructions: ##### Docker setup instructions:
1. Install docker (on macOS, I suggest [Docker for Mac](https://docs.docker.com/docker-for-mac/)) 1. Install docker (on macOS, [Docker for Mac](https://docs.docker.com/docker-for-mac/)is suggested)
2. `docker pull dternyak/eth-priv-to-addr` 2. `docker pull dternyak/eth-priv-to-addr`
##### Run Derivation Checker ##### Run Derivation Checker
@ -48,7 +48,7 @@ npm run derivation-checker
``` ```
├── common - Your App ├── common
│ ├── actions - application actions │ ├── actions - application actions
│ ├── api - Services and XHR utils(also custom form validation, see InputComponent from components/common) │ ├── api - Services and XHR utils(also custom form validation, see InputComponent from components/common)
│ ├── components - components according to "Redux philosophy" │ ├── components - components according to "Redux philosophy"
@ -56,7 +56,7 @@ npm run derivation-checker
│ ├── containers - containers according to "Redux philosophy" │ ├── containers - containers according to "Redux philosophy"
│ ├── reducers - application reducers │ ├── reducers - application reducers
│ ├── routing - application routing │ ├── routing - application routing
│ ├── index.jsx - entry │ ├── index.tsx - entry
│ ├── index.html │ ├── index.html
├── static ├── static
├── webpack_config - Webpack configuration ├── webpack_config - Webpack configuration
@ -75,13 +75,12 @@ docker-compose up
The following are guides for developers to follow for writing compliant code. The following are guides for developers to follow for writing compliant code.
### Redux and Actions ### Redux and Actions
Each reducer has one file in `reducers/[namespace].js` that contains the reducer Each reducer has one file in `reducers/[namespace].ts` that contains the reducer
and initial state, one file in `actions/[namespace].js` that contains the action and initial state, one file in `actions/[namespace].ts` that contains the action
creators and their return types, and optionally one file in creators and their return types, and optionally one file in
`sagas/[namespace].js` that handles action side effects using `sagas/[namespace].ts` that handles action side effects using
[`redux-saga`](https://github.com/redux-saga/redux-saga). [`redux-saga`](https://github.com/redux-saga/redux-saga).
The files should be laid out as follows: The files should be laid out as follows:
@ -89,75 +88,141 @@ The files should be laid out as follows:
#### Reducer #### Reducer
* State should be explicitly defined and exported * State should be explicitly defined and exported
* Initial state should match state flow typing, define every key * Initial state should match state typing, define every key
* Reducer function should handle all cases for actions. If state does not change
as a result of an action (Because it merely kicks off side-effects in saga) then
define the case above default, and have it fall through.
```js ```ts
// @flow import { NamespaceAction } from "actions/[namespace]";
import type { NamespaceAction } from "actions/namespace"; import { TypeKeys } from 'actions/[namespace]/constants';
export type State = { /* Flowtype definition for state object */ }; export interface State { /* definition for state object */ };
export const INITIAL_STATE: State = { /* Initial state shape */ }; export const INITIAL_STATE: State = { /* Initial state shape */ };
export function namespace( export function [namespace](
state: State = INITIAL_STATE, state: State = INITIAL_STATE,
action: NamespaceAction action: NamespaceAction
): State { ): State {
switch (action.type) { switch (action.type) {
case 'NAMESPACE_NAME_OF_ACTION': case TypeKeys.NAMESPACE_NAME_OF_ACTION:
return { return {
...state, ...state,
// Alterations to state // Alterations to state
}; };
case 'NAMESPACE_NAME_OF_SAGA_ACTION':
default: default:
// Ensures every action was handled in reducer
// Unhandled actions should just fall into default
(action: empty);
return state; return state;
} }
} }
``` ```
#### Actions #### Actions
* Define each action creator in `actionCreator.ts`
* Define each action object type in `actionTypes.ts`
* Export a union of all of the action types for use by the reducer
* Define each action type as a string enum in `constants.ts`
* Export `actionCreators` and `actionTypes` from module file `index.ts`
* Define each action object type beside the action creator ```
* Export a union of all of the action types for use by the reducer ├── common
├── actions - application actions
```js ├── [namespace] - action namespace
├── actionCreators.ts - action creators
├── actionTypes.ts - action interfaces / types
├── constants.ts - string enum
├── index.ts - exports all action creators and action object types
```
##### constants.ts
```ts
export enum TypeKeys {
NAMESPACE_NAME_OF_ACTION = 'NAMESPACE_NAME_OF_ACTION'
}
```
##### actionTypes.ts
```ts
/*** Name of action ***/ /*** Name of action ***/
export type NameOfActionAction = { export interface NameOfActionAction {
type: 'NAMESPACE_NAME_OF_ACTION', type: TypeKeys.NAMESPACE_NAME_OF_ACTION,
/* Rest of the action object shape */ /* Rest of the action object shape */
}; };
export function nameOfAction(): NameOfActionAction {
return {
type: 'NAMESPACE_NAME_OF_ACTION',
/* Rest of the action object */
};
};
/*** Action Union ***/ /*** Action Union ***/
export type NamespaceAction = export type NamespaceAction =
| ActionOneAction | ActionOneAction
| ActionTwoAction | ActionTwoAction
| ActionThreeAction; | ActionThreeAction;
``` ```
##### actionCreators.ts
```ts
import * as interfaces from './actionTypes';
import { TypeKeys } from './constants';
#### Action Constants export interface TNameOfAction = typeof nameOfAction;
export function nameOfAction(): interfaces.NameOfActionAction {
return {
type: TypeKeys.NAMESPACE_NAME_OF_ACTION,
payload: {}
};
};
```
##### index.ts
```ts
export * from './actionCreators';
export * from './actionTypes';
```
### Higher Order Components
Action constants are not used thanks to flow type checking. To avoid typos, we #### Typing Injected Props
use `(action: empty)` in the default case which assures every case is accounted Props made available through higher order components can be tricky to type. Normally, if a component requires a prop, you add it to the component's interface and it just works. However, working with injected props from [higher order components](https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e), you will be forced to supply all required props whenever you compose the component.
for. If you need to use another reducer's action, import that action type into
your reducer, and create a new action union of your actions, and the other
action types used.
```
interface MyComponentProps {
name: string;
countryCode?: string;
router: InjectedRouter;
}
...
class OtherComponent extends React.Component<{}, {}> {
render() {
return (
<MyComponent
name="foo"
countryCode="CA"
// Error: 'router' is missing!
/>
);
}
```
Instead of tacking the injected props on to the MyComponentProps interface itself, put them on another interface that extends the main interface:
```
interface MyComponentProps {
name: string;
countryCode?: string;
}
interface InjectedProps extends MyComponentProps {
router: InjectedRouter;
}
```
Now you can add a [getter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) to the component to derive the injected props from the props object at runtime:
```
class MyComponent extends React.Component<MyComponentProps, {}> {
get injected() {
return this.props as InjectedProps;
}
render() {
const { name, countryCode } = this.props;
const { router } = this.injected;
...
}
}
```
All the injected props are now strongly typed, while staying private to the module, and not polluting the public props interface.
### Styling ### Styling
@ -165,12 +230,12 @@ Legacy styles are housed under `common/assets/styles` and written with LESS.
However, going forward, each styled component should create a a `.scss` file of However, going forward, each styled component should create a a `.scss` file of
the same name in the same folder, and import it like so: the same name in the same folder, and import it like so:
```js ```ts
import React from "react"; import React from "react";
import "./MyComponent.scss"; import "./MyComponent.scss";
export default class MyComponent extends React.component { export default class MyComponent extends React.component<{}, {}> {
render() { render() {
return ( return (
<div className="MyComponent"> <div className="MyComponent">