├── .babelrc
├── .gitignore
├── .netlify
├── .prettierignore
├── .prettierrc
├── .storybook
├── addons.js
├── config.js
└── webpack.config.js
├── .vscode
└── settings.json
├── README.md
├── jest.config.js
├── package.json
├── src
├── withProvider
│ ├── README.md
│ ├── index.tsx
│ └── store.ts
├── withReduxMassive
│ ├── README.md
│ ├── components
│ │ ├── App.tsx
│ │ ├── __tests__
│ │ │ ├── App.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── App.test.tsx.snap
│ │ ├── atoms
│ │ │ ├── Button.tsx
│ │ │ └── Greeting.tsx
│ │ ├── organisms
│ │ │ └── counter
│ │ │ │ ├── Counter.tsx
│ │ │ │ └── index.ts
│ │ └── templates
│ │ │ └── DefaultLayout.tsx
│ ├── index.tsx
│ ├── reducers
│ │ ├── __tests__
│ │ │ └── counter.test.js
│ │ ├── counter.ts
│ │ └── index.ts
│ └── store
│ │ └── createStore.ts
└── withReduxStarter
│ ├── App.tsx
│ ├── README.md
│ ├── index.tsx
│ └── store.ts
├── stories
├── Welcome.stories.tsx
├── withProvider.stories.tsx
├── withReduxMassive.tsx
└── withReduxStarter.stories.tsx
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env", "react", "power-assert"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | storybook-static
--------------------------------------------------------------------------------
/.netlify:
--------------------------------------------------------------------------------
1 | {"site_id":"b70cde32-d108-4572-bfec-9250b7df59d8","path":"storybook-static"}
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | package.json
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false
3 | }
4 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-links/register';
3 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/react'
2 |
3 | // automatically import all files ending in *.stories.js
4 | const req = require.context('../stories', true, /.stories.tsx?/)
5 | function loadStories() {
6 | req.keys().forEach(filename => req(filename))
7 | }
8 |
9 | configure(loadStories, module)
10 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | resolve: {
3 | extensions: ['.ts', '.tsx', '.js']
4 | },
5 | module: {
6 | rules: [
7 | {
8 | test: /\.js$/,
9 | exclude: [/node_modules/],
10 | use: {
11 | loader: 'babel-loader',
12 | options: {
13 | forceEnv: 'development:client'
14 | }
15 | }
16 | },
17 | {
18 | test: /\.tsx?$/,
19 | use: {
20 | loader: 'awesome-typescript-loader',
21 | options: {
22 | // forceEnv: 'development:client'
23 | useBabel: true,
24 | babelOptions: {
25 | babelrc: false,
26 | presets: ['env', 'react', 'power-assert'],
27 | plugins: ['transform-object-rest-spread']
28 | },
29 | babelCore: 'babel-core'
30 | }
31 | }
32 | }
33 | ]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "git.ignoreLimitWarning": true
3 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Typescript redux patterns
2 |
3 | WIP
4 |
5 | ## Run
6 |
7 | ```
8 | yarn storybook
9 | ```
10 |
11 | See storybook http://jolly-wescoff-2811e4.netlify.com
12 |
13 | ## Stack
14 |
15 | * React
16 | * Redux
17 | * TypeScript
18 | * styled-components
19 | * prettier
20 | * jest
21 | * storybook
22 |
23 | ## Patterns
24 |
25 | * Massive
26 | * WithRedux
27 | * WithProvider
28 |
29 | ## LICENSE
30 |
31 | MIT
32 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'setupTestFrameworkScriptFile': './testSetup.js',
3 | 'moduleFileExtensions': [
4 | 'ts',
5 | 'tsx',
6 | 'js',
7 | 'jsx'
8 | ],
9 | 'transform': {
10 | '\\.(ts|tsx)$': 'ts-jest',
11 | '\\.(js|jsx)$': 'babel-jest',
12 | '\\.(css|styl|less|sass|scss)$': 'jest-css-modules-transform'
13 | },
14 | 'modulePaths': [
15 | './src'
16 | ],
17 | 'testMatch': [
18 | '**/**/*.test.+(ts|tsx|js)'
19 | ]
20 | };
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-ts-patterns",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "webpack --mode production",
8 | "build:dev": "webpack --mode development --watch --progress",
9 | "lint": "tslint src/**/*.ts src/**/*tsx --format stylish",
10 | "fmt": "prettier --config .prettierrc --write 'src/**' && tslint src/**/*.ts src/**/*tsx --fix",
11 | "test": "jest",
12 | "test:watch": "jest --watchAll",
13 | "storybook": "start-storybook -p 6006",
14 | "build-storybook": "build-storybook"
15 | },
16 | "devDependencies": {
17 | "@storybook/addon-actions": "^3.4.2",
18 | "@storybook/addon-links": "^3.4.2",
19 | "@storybook/addons": "^3.4.2",
20 | "@storybook/react": "^3.4.2",
21 | "@types/react": "^16.3.12",
22 | "@types/react-redux": "^5.0.19",
23 | "@types/react-test-renderer": "^16.0.1",
24 | "@types/redux-logger": "^3.0.6",
25 | "@types/redux-promise": "^0.5.28",
26 | "@types/storybook__addon-actions": "^3.0.3",
27 | "@types/storybook__react": "^3.0.7",
28 | "awesome-typescript-loader": "4",
29 | "babel-core": "^6.26.3",
30 | "babel-jest": "^22.4.1",
31 | "babel-loader": "^7.1.3",
32 | "babel-preset-env": "^1.6.1",
33 | "babel-preset-power-assert": "^2.0.0",
34 | "babel-preset-react": "^6.24.1",
35 | "css-loader": "^0.28.11",
36 | "jest": "^22.4.2",
37 | "power-assert": "^1.5.0",
38 | "prettier": "^1.12.1",
39 | "react-test-renderer": "^16.3.2",
40 | "source-map-loader": "^0.2.3",
41 | "ts-jest": "^22.4.2",
42 | "tslint": "^5.9.1",
43 | "tslint-config-prettier": "^1.12.0",
44 | "typescript": "^2.8.1",
45 | "webpack": "^4.1.0",
46 | "webpack-cli": "^2.0.10",
47 | "webpack-dev-middleware": "^3.1.2"
48 | },
49 | "dependencies": {
50 | "@types/jest": "^22.2.2",
51 | "@types/power-assert": "^1.4.29",
52 | "@types/prop-types": "^15.5.2",
53 | "@types/react-dom": "^16.0.4",
54 | "react": "^16.3.2",
55 | "react-dom": "^16.3.2",
56 | "react-redux": "^5.0.7",
57 | "recompose": "^0.27.0",
58 | "redux": "^4.0.0",
59 | "redux-logger": "^3.0.6",
60 | "redux-promise": "^0.5.3",
61 | "redux-thunk": "^2.3.0",
62 | "styled-components": "^3.2.6"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/withProvider/README.md:
--------------------------------------------------------------------------------
1 | # with Provider
2 |
3 | React v16 の Provider を使ったシンプルなパターン
4 |
5 | WIP
6 |
--------------------------------------------------------------------------------
/src/withProvider/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ReactDOM from "react-dom"
3 | import { connect, Provider } from "react-redux"
4 | import { applyMiddleware, createStore } from "redux"
5 | import store from "./store"
6 |
7 | const StoreContext = React.createContext("store")
8 |
9 | class StoreProvider extends React.Component {
10 | public state: { theme: string } = { theme: "light" }
11 | public render() {
12 | return (
13 |
14 | {this.props.children}
15 |
16 | )
17 | }
18 | }
19 |
20 | export default () => {
21 | return (
22 |
23 | {val => {val}
}
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/src/withProvider/store.ts:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, createStore } from "redux"
2 | import loggerMiddleware from "redux-logger"
3 | import thunkMiddleware from "redux-thunk"
4 |
5 | const INCREMENT = "counter/increment"
6 |
7 | type Increment = {
8 | type: typeof INCREMENT
9 | }
10 |
11 | export function increment(): Increment {
12 | return {
13 | type: INCREMENT
14 | }
15 | }
16 |
17 | type Action = Increment
18 |
19 | // state
20 | export type State = {
21 | value: number
22 | }
23 |
24 | const initialState: State = {
25 | value: 0
26 | }
27 |
28 | const counter = (state: State = initialState, action: Action) => {
29 | switch (action.type) {
30 | case INCREMENT: {
31 | return {
32 | ...state,
33 | value: state.value + 1
34 | }
35 | }
36 | default: {
37 | return state
38 | }
39 | }
40 | }
41 |
42 | const store = createStore(
43 | counter,
44 | applyMiddleware(loggerMiddleware, thunkMiddleware)
45 | )
46 |
47 | export default store
48 |
--------------------------------------------------------------------------------
/src/withReduxMassive/README.md:
--------------------------------------------------------------------------------
1 | # WithReduxMassive
2 |
3 | 巨大な Redux アプリケーションの例
4 |
5 | ## 採用したもの
6 |
7 | * typescript
8 | * prettier
9 | * tslint
10 | * react
11 | * redux
12 | * jest
13 | * styled-components
14 | * dux pattern
15 |
16 | ## 重視したこと
17 |
18 | * 中規模のコードを管理する
19 | * 実装パターンを容易する
20 | * react component
21 | * reducer
22 | * そのテスト
23 | * 骨組みを決めれば自動的にコードの場所が決まる
24 | * コピペからはじめられる
25 |
26 | ## 状態の考え方
27 |
28 | すべてを reducer に集約する。副作用で起きる場所を一箇所に集約する(redux store)
29 | シングルコンポーネントの setState でもいいが、結果として自前の Store 層になりがち「redux を小さく使う」のがどういう路線になっても無駄がないと思う。
30 |
31 | ## なぜ型を持っているか考える
32 |
33 | フロントエンドは DOM のテストを書くのが難しい。なので静的検査の力を借りたい。
34 |
35 | React の開発で一番問題が起きるのは component 間の props の不整合であり、最悪ここの静的検査があれば十分。また、ルートに近い要素で、Snapshot テストで差分が発生したか、しなかったを意識する。リファクタしたはずなのに差分が発生しているというミスが検知できる。
36 |
37 | そして、いわゆるドメインロジックというものは Store に集中する。 Action と Reducer の関係を適切にテストしておけばよい。しかし外部 IO がメインになると、静的検査が効かなくなり、Reducer のテストに意味はなくなっていく。
38 |
39 | ## styled-components
40 |
41 | * 前向きな理由: CSS のメンテナンス性の低下理由の殆どは、コンポーネントの親子関係を破壊するセレクタで、 CSS in JS はその Component に対して閉じた装飾を行う。これはコンポーネント志向の設計ならば良い方向に働く。
42 | * 後ろ向きな理由: Webpack のメンテナンス困難を引き起こすのはほとんど CSS Modules で、これを避けると CSS in JS から選ぶことになり、一番勢いがあるのが styled
43 | * おまけの理由: これを覚えたら ReactNative でも使える(CSS in JS しかない)
44 |
45 | typescript + vscode + styled-components ならインラインでもハイライトできるし、prettier の CSS 整形も掛かる。
46 |
47 | 難点は CSS の外注がしづらい。キャッチアップに 3 日は掛かるかもしれない。
48 |
49 | ## Lint の役割
50 |
51 | * レビューコストを下げる
52 | * デッドコードを出さない
53 | * アンチパターンを踏ませない
54 | * ホワイトスペース周りはフォーマッタに任せる
55 |
56 | ということで prettier に空白周りを全部任せつつ、tslint で経験上、コード品質に一番効くのは 未使用変数の警告で、最悪これさえ消しておけば良い。
57 |
58 | ## Reducer と Action Creator / Dux pattern
59 |
60 | `reduces/*.ts` で reducer を export default しつつ、そのスコープで action を export fuction する。
61 |
62 | 理由としては、 action と reducer は疎であることは稀で、基本的に一意に決まる。推論機に多くヒントを与えるためにも凝集性を重視する。
63 |
64 | Reducer は Object Rest Spread を使いつつ更新する
65 |
66 | ## インターフェースの命名規則
67 |
68 | MS の命名規則では、IState や IAction など、Interface には I を付けることが多く, tslint でもそうなっているが、GitHub に上がっているコードを見る限りこれに従っているのは C#派だけで、基本的に無視されている。個人的にも不要であると思う。
69 |
70 | ## アトミックデザインの借用
71 |
72 | React は コンポーネント志向のライブラリであるため、コンポーネント志向のデザインパターンと相性がいい。ということでアトミックデザインの用語と分類を借用する。
73 |
74 | * Atoms: 他に依存しないコンポーネント
75 | * Text
76 | * Button
77 | * Molecules: 複数の Atom からなるコンポーネント
78 | * Header
79 | * Organisms: 複数の Atom と Molecules からなるコンポーネント兼 Container
80 | * Counter
81 | * Pages: ルーティング時の複数のエントリポイント
82 | * Template: レイアウト
83 |
84 | 大事なのは、コンポーネントの再利用性のみで Component を分類し、**分類名でディレクトリを切らない**。
85 |
86 | 注意: components/container パターンではない。organisms の層で connect している。ここで、Pages/Templates はかけ離れてる点に注意
87 |
88 | [Atomic Design を実案件に導入して運用してみた結果はどうなのか - I'm kubosho\_](http://blog.kubosho.com/entry/atomic-design-on-abematv)
89 |
90 | [danilowoz/react-atomic-design: Boilerplate with the methodology Atomic Design using a few cool things.](https://github.com/danilowoz/react-atomic-design)
91 |
92 | ## 未解決
93 |
94 | * TypeScript のベストプラクティスを調べきれていない
95 | * redux v4 が壊れている
96 |
--------------------------------------------------------------------------------
/src/withReduxMassive/components/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Provider } from "react-redux"
3 | import createStore from "../store/createStore"
4 | import Greeting from "./atoms/Greeting"
5 | import Counter from "./organisms/counter"
6 |
7 | export default () => (
8 |
9 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/src/withReduxMassive/components/__tests__/App.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import * as renderer from 'react-test-renderer'
3 | import App from '../App'
4 |
5 | test('render ', () => {
6 | const tree = renderer.create().toJSON()
7 | expect(tree).toMatchSnapshot()
8 | })
9 |
--------------------------------------------------------------------------------
/src/withReduxMassive/components/__tests__/__snapshots__/App.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`render 1`] = `
4 |
7 |
10 |
13 | Counter
14 |
15 |
18 | value:
19 | 0
20 |
21 |
24 |
30 |
31 |
37 |
38 |
39 |
40 | `;
41 |
--------------------------------------------------------------------------------
/src/withReduxMassive/components/atoms/Button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import styled from "styled-components"
3 |
4 | type Props = {
5 | text: string
6 | onClick: (event: any) => void
7 | }
8 |
9 | export default function Button({ text, onClick }: Props) {
10 | return {text}
11 | }
12 |
13 | const StyledButton = styled.button`
14 | display: inline-block;
15 | padding: 0.5em 1em;
16 | text-decoration: none;
17 | background: #668ad8;
18 | color: #fff;
19 | border-bottom: solid 4px #627295;
20 | border-radius: 3px;
21 |
22 | :active {
23 | -ms-transform: translateY(4px);
24 | -webkit-transform: translateY(4px);
25 | transform: translateY(4px);
26 | border-bottom: none;
27 | }
28 | `
29 |
--------------------------------------------------------------------------------
/src/withReduxMassive/components/atoms/Greeting.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | type Props = {
4 | name: string
5 | }
6 |
7 | export default function Greeting({ name }: Props) {
8 | return (
9 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/withReduxMassive/components/organisms/counter/Counter.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { connect } from "react-redux"
3 | import styled from "styled-components"
4 | import Button from "../../atoms/Button"
5 | import DefaultLayout from "../../templates/DefaultLayout"
6 |
7 | type Props = {
8 | value: number
9 | add(n: number): void
10 | increment(): void
11 | }
12 |
13 | export default function Counter(props: Props) {
14 | const { value, add, increment } = props
15 | return (
16 |
17 |
18 | Counter
19 | value: {props.value}
20 |
21 |
35 |
36 |
37 | )
38 | }
39 |
40 | const Container = styled.div`
41 | padding: 10px;
42 | `
43 |
44 | const Title = styled.h1`
45 | color: #333333;
46 | `
47 |
48 | const Value = styled.span`
49 | color: #333333;
50 | `
51 |
52 | const ButtonsContainer = styled.div`
53 | padding: 20px;
54 | `
55 |
--------------------------------------------------------------------------------
/src/withReduxMassive/components/organisms/counter/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { connect } from "react-redux"
3 | import { State as RootState } from "../../../reducers"
4 | import * as CounterActions from "../../../reducers/counter"
5 | import Counter from "./Counter"
6 |
7 | type OuterProps = {}
8 |
9 | type Props = {
10 | value: number
11 | }
12 |
13 | // connect
14 | const mapStateToProps = (state: RootState, props: OuterProps): Props => {
15 | return {
16 | value: state.counter.value
17 | }
18 | }
19 |
20 | const enhancer = connect(mapStateToProps, {
21 | add: CounterActions.add,
22 | increment: CounterActions.increment
23 | })
24 |
25 | export default enhancer(Counter)
26 |
--------------------------------------------------------------------------------
/src/withReduxMassive/components/templates/DefaultLayout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import styled from "styled-components"
3 |
4 | type Props = {
5 | children: any
6 | }
7 |
8 | export default function DefaultLayout({ children }: Props) {
9 | return {children}
10 | }
11 |
12 | const Container = styled.div`
13 | background-color: #eee;
14 | width: 100%;
15 | height: 400px;
16 | `
17 |
--------------------------------------------------------------------------------
/src/withReduxMassive/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ReactDOM from "react-dom"
3 | import App from "./components/App"
4 |
5 | ReactDOM.render(, document.querySelector("#root"))
6 |
--------------------------------------------------------------------------------
/src/withReduxMassive/reducers/__tests__/counter.test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import counter, { add, increment } from '../counter'
3 |
4 | test('counter/add', () => {
5 | const initilaState = counter(undefined, {})
6 | assert(initilaState.value === 0)
7 | const add3 = add(3)
8 | const ret = counter(initilaState, add3)
9 | assert(ret.value === 3)
10 | })
11 |
12 | test('counter/increment', () => {
13 | const initilaState = counter(undefined, {})
14 | assert(initilaState.value === 0)
15 | const inc = increment()
16 | const ret = counter(initilaState, inc)
17 | assert(ret.value === 1)
18 | })
19 |
--------------------------------------------------------------------------------
/src/withReduxMassive/reducers/counter.ts:
--------------------------------------------------------------------------------
1 | // Action
2 |
3 | const ADD = "counter/add"
4 | const INCREMENT = "counter/increment"
5 |
6 | type Add = {
7 | type: typeof ADD
8 | payload: {
9 | amount: number
10 | }
11 | }
12 |
13 | type Increment = {
14 | type: typeof INCREMENT
15 | }
16 |
17 | type Action = Add | Increment
18 |
19 | // Action Creator
20 | export function add(amount: number): Add {
21 | return {
22 | type: ADD,
23 | payload: {
24 | amount
25 | }
26 | }
27 | }
28 |
29 | export function increment(): Increment {
30 | return {
31 | type: INCREMENT
32 | }
33 | }
34 |
35 | // state
36 | export type State = {
37 | value: number
38 | }
39 |
40 | const initialState: State = {
41 | value: 0
42 | }
43 |
44 | export default (state: State = initialState, action: Action) => {
45 | switch (action.type) {
46 | case ADD: {
47 | return {
48 | ...state,
49 | value: state.value + action.payload.amount
50 | }
51 | }
52 | case INCREMENT: {
53 | return {
54 | ...state,
55 | value: state.value + 1
56 | }
57 | }
58 | default: {
59 | return state
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/withReduxMassive/reducers/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux"
2 | import counter, { State as CounterState } from "./counter"
3 |
4 | export type State = {
5 | counter: CounterState
6 | }
7 |
8 | export default combineReducers({
9 | counter
10 | })
11 |
--------------------------------------------------------------------------------
/src/withReduxMassive/store/createStore.ts:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, createStore } from "redux"
2 | import loggerMiddleware from "redux-logger"
3 | import thunkMiddleware from "redux-thunk"
4 | import rootReducer from "../reducers"
5 |
6 | export default () => {
7 | return createStore(
8 | rootReducer,
9 | applyMiddleware(loggerMiddleware, thunkMiddleware)
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/src/withReduxStarter/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { connect } from "react-redux"
3 | import { increment, State } from "./store"
4 |
5 | // connect
6 | const mapStateToProps = (state: State) => {
7 | return {
8 | value: state.value
9 | }
10 | }
11 |
12 | type Props = {
13 | value: number
14 | increment(): void
15 | }
16 |
17 | export default connect(mapStateToProps, { increment })((props: Props) => {
18 | return (
19 |
20 |
Counter: Simple
21 |
22 | value: {props.value}
23 | props.increment()}>inc
24 |
25 |
26 | )
27 | })
28 |
--------------------------------------------------------------------------------
/src/withReduxStarter/README.md:
--------------------------------------------------------------------------------
1 | # WithReduxStarter
2 |
3 | Store 管理に redux を薄く使うシンプルなパターン。単一 reducer。
4 |
5 | 複雑になったときは、 withReduxMassive を参照
6 |
--------------------------------------------------------------------------------
/src/withReduxStarter/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ReactDOM from "react-dom"
3 | import { connect, Provider } from "react-redux"
4 | import { applyMiddleware, createStore } from "redux"
5 | import App from "./App"
6 | import store from "./store"
7 |
8 | export default () => {
9 | return (
10 |
11 |
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/src/withReduxStarter/store.ts:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, createStore } from "redux"
2 | import loggerMiddleware from "redux-logger"
3 | import thunkMiddleware, { ThunkAction } from "redux-thunk"
4 |
5 | const INCREMENT = "counter/increment"
6 |
7 | type Increment = {
8 | type: typeof INCREMENT
9 | }
10 |
11 | export function increment(): Increment {
12 | return {
13 | type: INCREMENT
14 | }
15 | }
16 |
17 | export function incrementAsync(): ThunkAction {
18 | return dispatch => {
19 | dispatch(increment())
20 | }
21 | }
22 |
23 | type Action = Increment
24 |
25 | // state
26 | export type State = {
27 | value: number
28 | }
29 |
30 | const initialState: State = {
31 | value: 0
32 | }
33 |
34 | const counter = (state: State = initialState, action: Action) => {
35 | switch (action.type) {
36 | case INCREMENT: {
37 | return {
38 | ...state,
39 | value: state.value + 1
40 | }
41 | }
42 | default: {
43 | return state
44 | }
45 | }
46 | }
47 |
48 | const store = createStore(
49 | counter,
50 | applyMiddleware(loggerMiddleware, thunkMiddleware)
51 | )
52 |
53 | export default store
54 |
--------------------------------------------------------------------------------
/stories/Welcome.stories.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from "@storybook/react"
2 | import * as React from "react"
3 |
4 | storiesOf("Welcome", module).add("to Storybook", () => Welcome
)
5 |
--------------------------------------------------------------------------------
/stories/withProvider.stories.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from "@storybook/react"
2 | import * as React from "react"
3 | import WithProvider from "../src/withProvider"
4 |
5 | storiesOf("WithProvider", module).add("to Storybook", () => )
6 |
--------------------------------------------------------------------------------
/stories/withReduxMassive.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from "@storybook/react"
2 | import * as React from "react"
3 | import WithReduxMassive from "../src/withReduxMassive/components/App"
4 |
5 | storiesOf("WithRedux", module).add("to Storybook", () => )
6 |
--------------------------------------------------------------------------------
/stories/withReduxStarter.stories.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from "@storybook/react"
2 | import * as React from "react"
3 | import Massive from "../src/withReduxStarter"
4 |
5 | storiesOf("Massive", module).add("to Storybook", () => )
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "baseUrl": "./src/",
5 | "outDir": "./dist/",
6 | "sourceMap": true,
7 | "noImplicitAny": true,
8 | "module": "commonjs",
9 | "target": "esnext",
10 | "jsx": "react",
11 | "allowJs": true
12 | },
13 | "include": ["./src/**/*"],
14 | "exclude": ["node_modules", "./src/**/*.test.ts", "./src/**/*.test.tsx"]
15 | }
16 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": ["tslint:latest", "tslint-config-prettier"],
4 | "jsRules": {},
5 | "rules": {
6 | "interface-over-type-literal": false,
7 | "object-literal-sort-keys": false,
8 | "no-implicit-dependencies": [true, "dev"]
9 | },
10 | "rulesDirectory": []
11 | }
12 |
--------------------------------------------------------------------------------