git checkout step-1
npm i webpack typescript awesome-typescript-loader --save-dev
src
.
dist
.
tsconfig.json
file in the project root directory
{ "compilerOptions": { "target": "es5", // ts js ES5 "module": "esnext" // } }
webpack.config.js
file in the root directory of the project:
const path = require('path'); const webpack = require('webpack'); const paths = { src: path.resolve(__dirname, 'src'), dist: path.resolve(__dirname, 'dist') }; module.exports = { context: paths.src, // entry: { app: './index' // , src/index.ts , - app }, output: { path: paths.dist, // filename: '[name].bundle.js' // , dist/app.bundle.js }, resolve: { extensions: ['.ts'] // , webpack , ( index, index.ts) }, devtool: 'inline-source-map', // , TypeScript source-map-loader tsconfig - "sourceMap": true module: { rules: [ { test: /\.ts$/, loader: 'awesome-typescript-loader' } // .ts ] } };
src
create an index.ts
file with any code that uses TypeScript syntax, for example:
interface Props { world: string; } function hello(props: Props) { alert(`Hello, ${props.world}`); } hello({ world: 'TypeScript!' });
webpack
- one-time project build
dist/app.bundle.js
inside the webpack modules you will see a neat and readable JavaScript code of the version of the standard we have chosen.
git checkout step-2
npm i webpack react react-dom --save
npm i webpack @types/react @types/react-dom html-webpack-plugin clean-webpack-plugin --save-dev
src
create an index.html
file with elements to mount the root react component:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>React and Typescript</title> </head> <body> <div id="root"></div> </body> </html>
const path = require('path'); const webpack = require('webpack'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const paths = { src: path.resolve(__dirname, 'src'), dist: path.resolve(__dirname, 'dist') }; const config = { context: paths.src, entry: { app: './index' }, output: { path: paths.dist, filename: '[name].bundle.js' }, resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'] // tsx react }, devtool: 'inline-source-map', module: { rules: [ { test: /\.tsx?$/, // tsx react loader: 'awesome-typescript-loader' } ] }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ template: './index.html' }) // html- ] }; module.exports = config;
{ "compilerOptions": { "target": "es5", "module": "esnext", "jsx": "react" // JSX } }
index.ts
to index.tsx
. Let's write the code of our component, and display it on the page:
// react TS import React from 'react' - TS import * as React from 'react'; import * as ReactDOM from 'react-dom'; // props state interface IAppProps { title: string; } // const App = (props: IAppProps) => <h1>{props.title}</h1>; ReactDOM.render( <App title="Hello, React!" />, document.getElementById('root') );
webpack-dev-server
- we are raising the server with our application, the index.html page will be available at http://localhost:8080/
.
git checkout step-3
simple.tsx
, which will display a controlled input field:
import * as React from 'react'; /** * . * React.HTMLProps * . */ interface Props extends React.HTMLProps<HTMLInputElement> { customProperty: string; } // interface State { value: string; } // React.Component class Simple extends React.Component<Props, State> { // , state: State = { value: '' } /* * onChange , * onChange JSX input. * - MouseEvent, FocusEvent, KeyboardEvent */ handleChange = (event: React.FormEvent<HTMLInputElement>) => { const value = event.currentTarget.value; this.setState(() => ({ value })); } render() { /* * HTMLProps HTMLInputElement, * , , * , . */ const { customProperty, ...inputProps } = this.props; const { value } = this.state; /* * <input {...inputProps} /> - * JSX ( placeholder={inputProps.placeholder} * ) * value onChange {...inputProps}, * , inputProps */ return ( <div> <h4>{customProperty}</h4> <input {...inputProps} value={value} onChange={this.handleChange} /> </div> ); } } export default Simple;
render
method, passing it its properties and state.
(Component) => WrapComponent => Component
hoc
, in it the component displayCount.tsx
and hoc withCount.tsx
import * as React from 'react'; import { InjectedProps } from './withCount'; // interface OriginProps { title: string; } /* * , * InjectedProps, withCount */ const DisplayCount = (props: OriginProps & InjectedProps) => ( <div> <h4>{props.title}</h4> <div>Count: {props.count}</div> </div> ); export default DisplayCount;
import * as React from 'react'; // , hoc export interface InjectedProps { count: number; } // , hoc interface ExternalProps { increment: number; } // hoc, InjectedProps, interface State { count: number; } /** * , , , * OriginProps - . * React.ComponentType - ComponentClass StatelessComponent, * , , . * , , * hoc - OriginProps & InjectedProps */ function withCount<OriginProps>(Component: React.ComponentType<OriginProps & InjectedProps>) { // type ResultProps = OriginProps & ExternalProps; return class extends React.Component<ResultProps, State> { /** * name displayName, * , React DevTools */ static displayName = `WithCount(${Component.displayName || Component.name})`; state: State = { count: 0 } increment = () => { const { increment } = this.props; this.setState((prevState: State) => ({ count: prevState.count + increment })); } render() { // {...this.props} {...this.state} - . return ( <div> <Component {...this.props} {...this.state} /> <button type="button" onClick={this.increment} > + </button> </div> ) } } } export default withCount;
const Counter = withCount(DisplayCount); /* * title - DisplayCount * increment - */ const App = () => <Counter title="High Order Component" increment={1} /> ;
increment
property, if necessary, you can get rid of it, using for example the omit method in lodash.
import('module.ts').then((module) => { // , , default const defaultExport = module.default; // , export function foo() {} - , // module.foo const otherExport = module.OtherExport; });
lazy
, in it the components lazyComponent.tsx
and lazyLoad.tsx
import * as React from 'react'; const LazyComponent = () => <h3>I'm so lazy!</h3>; export default LazyComponent;
import * as React from 'react'; /* * load : * () => import('path/to/module') * import(), * default. * - * [key: string]: React.ComponentType */ interface LazyLoadProps { load: () => Promise<{ default: React.ComponentType }>; } // interface LazyLoadState { Component: React.ComponentType; } class LazyLoad extends React.Component<LazyLoadProps, LazyLoadState> { // null , state: LazyLoadState = { Component: null } // async await , async componentDidMount() { const { load } = this.props; try { // - const module = await load(); // default const Component = module.default; // state this.setState({ Component }); } catch (e) { // } } render() { const { Component } = this.state; // , . // , LazyLoad return ( <div> <h4>Lazy load component</h4> {Component ? <Component /> : '...'} </div> ); } } export default LazyLoad;
const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const paths = { src: path.resolve(__dirname, 'src'), dist: path.resolve(__dirname, 'dist') }; const config = { context: paths.src, entry: { app: './index' }, output: { path: paths.dist, filename: '[name].bundle.js', chunkFilename: '[name].bundle.js' // chunk' }, resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'] }, devtool: 'inline-source-map', module: { rules: [ { test: /\.tsx?$/, loader: 'awesome-typescript-loader' } ] }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ template: './index.html' }) ] }; module.exports = config;
{ "compilerOptions": { "lib": [ "es5", "es6", "es7", "dom" ], "target": "es5", "module": "esnext", "jsx": "react" } }
// webpackChunkName - // chunkFilename: '[name].bundle.js' lazy-component.bundle.js const load = () => import(/* webpackChunkName: 'lazy-component' */'./lazy/lazyComponent'); const App = ({title}: IAppProps) => <LazyLoad load={load} />;
renderProps
, in it the displaySize.tsx
component and the windowQueries.tsx
component
import * as React from 'react'; import { IRenderProps } from './windowQueries'; // IRenderProps, , // . interface IProps extends IRenderProps { title: string; } const DisplaySize = ({ title, width, height }: IProps) => ( <div> <h4>{title}</h4> <p>Width: {width}px</p> <p>Height: {height}px</p> </div> ); export default DisplaySize;
import * as React from 'react'; // React.ReactNode - . interface IProps { children?: ((props: IRenderProps) => React.ReactNode); render?: ((props: IRenderProps) => React.ReactNode); } interface IState { width: number; height: number; } export interface IRenderProps { width?: number; height?: number; } /** * . */ class WindowQueries extends React.Component<IProps, IState> { state: IState = { width: window.innerWidth, height: window.innerHeight, } componentDidMount() { window.addEventListener('resize', this.handleWindowResize); } componentWillUnmount() { window.removeEventListener('resize', this.handleWindowResize); } handleWindowResize = () => { this.setState({ width: window.innerWidth, height: window.innerHeight, }) } gerRenderProps = (): IRenderProps => { const { width, height } = this.state; return { width, height }; } // , render children // , . // . render() { const { children, render } = this.props; if (render) { return render(this.gerRenderProps()) } if (children) { return children(this.gerRenderProps()) } return null; } } export default WindowQueries;
<WindowQueries> {({ width, height }) => <DisplaySize title="render children" width={width} height={height} />} </WindowQueries> <WindowQueries render={ ({ width, height }) => <DisplaySize title="render property" width={width} height={height} /> } />
interface Props { children: React.ReactNode; }
interface Props { header: JSX.Element, body: JSX.Element } <Component header={<h1></h1>} body={<div></div>} />
Source: https://habr.com/ru/post/340650/