Created
July 7, 2019 20:08
-
-
Save pasaran/8c2f0f0a167452a75fd4a9e945bd54ba to your computer and use it in GitHub Desktop.
Redux replacement in typescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.exports = { | |
presets: [ | |
[ | |
'@babel/preset-env', | |
{ | |
targets: { | |
node: true, | |
}, | |
}, | |
], | |
'@babel/preset-react', | |
], | |
plugins: [ | |
'@babel/plugin-syntax-dynamic-import', | |
], | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
build | |
node_modules | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package-lock=false | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Store } from './Store'; | |
interface State { | |
count: number; | |
} | |
const initial_state: State = { | |
count: 0, | |
}; | |
export default new Store( initial_state, { | |
increment( n = 1 ) { | |
this.update( ( state ) => ( { ...state, count: state.count + n } ) ); | |
}, | |
decrement( n = 1 ) { | |
this.update( ( state ) => ( { ...state, count: state.count - n } ) ); | |
}, | |
} ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<body> | |
<div id="root"></div> | |
<script src="build/index.js"></script> | |
</body> | |
</html> | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
import * as ReactDOM from 'react-dom'; | |
import Items from './Items'; | |
const root = document.getElementById( 'root' ); | |
ReactDOM.render( React.createElement( React.Fragment, null, | |
React.createElement( Items, { by: 5 } ), | |
React.createElement( Items ) | |
), root ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
import { connect, ConnectedProps } from './Store'; | |
import items_store from './ItemsStore'; | |
import count_store from './CountStore'; | |
const stores = { | |
items: items_store, | |
count: count_store, | |
}; | |
interface State { | |
clicks: number; | |
} | |
interface OwnProps { | |
by: number; | |
} | |
type Props = OwnProps & ConnectedProps<typeof stores>; | |
class Items extends React.Component<Props, State> { | |
public static defaultProps: Partial<OwnProps> = { | |
by: 1, | |
} | |
state: State = { | |
clicks: 0, | |
} | |
componentDidMount() { | |
this.props.items.load_more(); | |
} | |
render() { | |
let more; | |
let loading; | |
if ( this.props.items.loading ) { | |
loading = 'Loading...'; | |
} else { | |
more = ( | |
<div className="Items__load-more" onClick={ this.props.items.load_more }>More...</div> | |
); | |
} | |
const items = this.props.items.items.map( item => { | |
return ( | |
<div className="Item" key={ item }>{ item }</div> | |
); | |
} ); | |
return ( | |
<div className="Items"> | |
{ items } | |
{ more } | |
{ loading } | |
<div className="Items__count"> | |
{ this.props.count.count } | |
<div onClick={ () => this.increment() }>[ + ]</div> | |
<div onClick={ () => this.decrement() }>[ - ]</div> | |
</div> | |
<div className="Items__clicks"> | |
clicks: { this.state.clicks } | |
</div> | |
</div> | |
); | |
} | |
increment() { | |
this.setState({ | |
clicks: this.state.clicks + 1, | |
}) | |
this.props.count.increment( this.props.by ); | |
} | |
decrement() { | |
this.setState({ | |
clicks: this.state.clicks + 1, | |
}) | |
this.props.count.decrement( this.props.by ); | |
} | |
} | |
export default connect( stores, Items ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Store } from './Store'; | |
type State = { | |
loading: boolean, | |
items: Array<number>, | |
}; | |
const initial_state: State = { | |
loading: false, | |
items: [], | |
}; | |
export default new Store( initial_state, { | |
load_more() { | |
const state = this.state; | |
if ( state.loading ) { | |
return; | |
} | |
this.update( ( state ) => ( { ...state, loading: true } ) ); | |
setTimeout( () => { | |
this.update( ( state ) => { | |
const items = state.items; | |
const more_items = [ 1, 2, 3 ].map( () => Math.floor( Math.random() * 10000 ) ); | |
return { | |
...state, | |
loading: false, | |
items: items.concat( more_items ), | |
}; | |
} ); | |
}, 1000 ); | |
}, | |
} ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"dependencies": { | |
"react": "*", | |
"react-dom": "*" | |
}, | |
"devDependencies": { | |
"@babel/core": "*", | |
"@babel/preset-env": "*", | |
"@babel/preset-react": "*", | |
"@babel/preset-typescript": "*", | |
"@types/react": "*", | |
"@types/react-dom": "*", | |
"babel-loader": "*", | |
"ts-loader": "*", | |
"typescript": "*", | |
"webpack": "*", | |
"webpack-cli": "*" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
type StoreSubscribeCallback<State> = ( state: State ) => void; | |
type StoreUnsubscribeCallback = () => void; | |
export class Store<State, Actions> { | |
private _state: State; | |
get state() { | |
return this._state; | |
} | |
private callbacks: Array<StoreSubscribeCallback<State>>; | |
readonly actions: Actions & ThisType<Store<State, Actions>>; | |
constructor( initial_state: State, actions: Actions & ThisType<Store<State, Actions>> ) { | |
this._state = initial_state; | |
this.callbacks = []; | |
this.actions = {} as Actions & ThisType<Store<State, Actions>>; | |
if ( actions ) { | |
for ( const name in actions ) { | |
const action = actions[ name ]; | |
this.actions[ name ] = ( action as unknown as Function).bind( this ); | |
} | |
} | |
} | |
update( reducer: ( state: State ) => State | State ) { | |
const new_state = ( typeof reducer === 'function' ) ? reducer( this.state ) : reducer; | |
if ( new_state !== undefined && new_state !== this.state ) { | |
this._state = new_state; | |
Promise.resolve().then( () => { | |
this.callbacks.forEach( ( callback ) => callback( new_state ) ); | |
} ); | |
} | |
} | |
subscribe( callback: StoreSubscribeCallback<State> ): StoreUnsubscribeCallback { | |
this.callbacks.push( callback ); | |
return () => { | |
this.callbacks = this.callbacks.filter( ( item ) => item !== callback ); | |
}; | |
} | |
} | |
type ConnectedProp<State, Actions> = State & Actions & ThisType<Store<State, Actions>>; | |
export type ConnectedProps<Stores> = { | |
[ K in keyof Stores ]: Stores[ K ] extends Store<infer State, infer Actions> ? ConnectedProp<State, Actions> : never; | |
} | |
export function connect< | |
Stores extends { | |
[ K in keyof Stores ]: Stores[ K ] extends Store<infer State, infer Actions> ? Store<State, Actions> : never; | |
}, | |
OwnProps = {}, | |
OwnState = {} | |
>( stores: Stores, component: React.ComponentClass<ConnectedProps<Stores> & OwnProps, OwnState> ): React.ComponentClass<OwnProps> { | |
const name = `Connect(${ component.name })`; | |
const o = { | |
[ name ]: class extends React.Component<OwnProps, ConnectedProps<Stores>> { | |
unsubscribes: Array<StoreUnsubscribeCallback>; | |
constructor( props: OwnProps ) { | |
super( props ); | |
const state = {} as Record<string, any>; | |
for ( let key in stores ) { | |
const store = stores[ key ]; | |
state[ key ] = { ...store.state, ...store.actions }; | |
} | |
this.state = state as ConnectedProps<Stores>; | |
} | |
componentDidMount() { | |
this.unsubscribes = []; | |
for ( let key in stores ) { | |
const store = stores[ key ]; | |
this.unsubscribes.push( store.subscribe( ( new_state ) => { | |
this.setState( { | |
[ key ]: { ...new_state, ...store.actions } | |
} as ConnectedProps<Stores> ); | |
} ) ); | |
} | |
} | |
componentWillUnmount() { | |
this.unsubscribes.forEach( unsubscribe => unsubscribe() ); | |
} | |
render() { | |
return React.createElement( component, { ...this.props, ...this.state } ); | |
} | |
}, | |
}; | |
return o[ name ]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
{ | |
"compilerOptions": { | |
"alwaysStrict": true, | |
"strictBindCallApply": true, | |
"noImplicitAny": true, | |
"noImplicitThis": true, | |
"noEmitOnError": true, | |
"strictNullChecks": true, | |
"keyofStringsOnly": true, | |
"jsx": "react", | |
"target": "es2015", | |
"lib": [ | |
"DOM", | |
"ES2015", | |
"ScriptHost" | |
], | |
"module": "commonjs", | |
"moduleResolution": "node" | |
}, | |
"files": [ | |
"index.tsx" | |
] | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const path_ = require( 'path' ); | |
const webpack = require( 'webpack' ); | |
module.exports = { | |
mode: 'development', | |
entry: { | |
index: './index.tsx', | |
}, | |
output: { | |
filename: '[name].js', | |
path: path_.resolve(__dirname, 'build'), | |
publicPath: '/build/', | |
}, | |
module: { | |
rules: [ | |
{ | |
test: /\.tsx?$/, | |
exclude: /node_modules/, | |
use: 'ts-loader', | |
}, | |
{ | |
test: /\.jsx?$/, | |
exclude: [ | |
/node_modules/, | |
], | |
use: { | |
loader: 'babel-loader', | |
// options: babelOptions, | |
}, | |
}, | |
], | |
}, | |
resolve: { | |
extensions: [ '.tsx', '.ts', '.js' ], | |
}, | |
plugins: [ | |
new webpack.NoEmitOnErrorsPlugin(), | |
], | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment