tsx 搜索box.tsx

search-box.tsx
// src/components/search-box/search-box.tsx

import { Component, State } from '@stencil/core'

@Component({
  tag: 'search-box',
  styleUrl: 'search-box.css'
})
export class SearchBox {
  @State() value: string = ''

  render() {
    return (
      <div>
        <input value={this.value} onInput={this.handleChange} />
        <button onClick={this.search}>Search</button>
      </div>
    )
  }

  handleChange = e => {
    this.value = e.target.value
  }

  search = () => {
    const value = this.value.trim()
    if (value) {
      // call API
    } else {
      alert('Please enter at least 1 character')
    }
  }
}

tsx APP-home.tsx

app-home.tsx
// src/components/app-home/app-home.tsx

import { Component } from '@stencil/core'

@Component({
  tag: 'app-home'
})
export class AppHome {
  render() {
    return (
     <div>
       <search-box></search-box>
       <books-list></books-list>
     </div>
    )
  }
}

tsx APP-root.tsx

app-root.tsx
// src/components/app-root/app-root.tsx

import '@stencil/router'
import { Component } from '@stencil/core'

@Component({
  tag: 'app-root',
  styleUrl: 'app-root.css'
})
export class AppRoot {
  render() {
    return (
      <stencil-router>
        <stencil-route-link url='/' exact>Home</stencil-route-link>
        <stencil-route-link url='/saved'>My Books</stencil-route-link>
        <stencil-route-switch scrollTopOffset={0}>
          <stencil-route url='/' exact component='app-home' />
          <stencil-route url='/saved' component='saved-books' />
        </stencil-route-switch>
      </stencil-router>
    )
  }
}

tsx ihatethis.tsx

ihatethis.tsx
import React from 'react';
import { Container, Row, Col } from 'reactstrap';
import './css/HowBrigitWorks.css';

const HowBrigitWorks = (props) => {
  return (
    <Container className="mb-5 mt-1">
      <Row className="mt-4">
        <Col className="center-column mt-4">
          <div>
            <h2 className="justify-content-center stage-intro">How It Works</h2>
          </div>
          <div>
            <img className="brigit-icon mt-4" src="/plane.svg"/>
          </div>
          <div className="how-it-works-text">
            We send you a cash Safety Net when your balance is low and likely to run out
          </div>
          <img className="brigit-icon" src="/calendar.svg"/>
          <div className="how-it-works-text">
            We withdraw the cash when you can repay, like after a paycheck or deposit
          </div>
          <img className="brigit-icon" src="/repeat.svg"/>
          <div className="how-it-works-text">
            We alert you and automatically repeat the process everytime your balance runs low
          </div>
        </Col>
      </Row>
    </Container>
  );
};

export default HowBrigitWorks;

tsx contextMenu.tsx

contextMenuTarget.tsx
import { ContextMenu } from "./contextMenu";

// ...

    public render() {

// ...

        const onContextMenu = (e: React.MouseEvent<HTMLElement>) => {
            e.persist();
            e.preventDefault();
            this.setState({ e } as any);
        };

        const menu = this.state.e ? this.renderContextMenu(this.state.e) : null;
        const root = React.cloneElement(element, { onContextMenu });
        return (
            <React.Fragment>
                {root}
                {menu && (
                    <ContextMenu menu={menu} offset={{ left: this.state.e.clientX, top: this.state.e.clientY }} />
                )}
            </React.Fragment>
        );
    }
}

// ...
contextMenu.tsx
import { Portal } from "../portal/portal";

// ...

export interface IContextMenuProps {
    // Move fields in state to props so that the fields can be passed by ContextMenuTarget components
    menu?: JSX.Element;
    offset?: IOffset;
    // ...

}

// ...

export class ContextMenu extends AbstractPureComponent<IContextMenuProps, IContextMenuState> {

    // ...

    public render() {
        const content = <div onContextMenu={this.cancelContextMenu}>{this.props.menu}</div>;

        // ...
    
        return (
            // Wrapping with Portal
            <Portal>
                <div className={Classes.CONTEXT_MENU_POPOVER_TARGET} style={this.props.offset}>
                    <Popover
                        
                    // ...

                    </Popover>
                </div>
            </Portal>
        );
    }

// ...
  

tsx Typescript中的Stateless / dumb React组件

Typescript中的Stateless / dumb React组件

statelessComponent.tsx
// stateless/dumb component in React Typescript
import * as React from 'react';

interface IWelcomeProps {
    name: string,
}

const Welcome: React.SFC<IWelcomeProps> = ({ name }) => {
    return <h1>Hello, {name}</h1>;
}
	
Welcome.defaultProps = {
    name: 'Guest User', // This value is adopted when name prop is omitted
}
	
export default Welcome;

// via https://medium.com/@iktakahiro/react-stateless-functional-component-with-typescript-ce5043466011
// via https://firstdoit.com/how-to-write-a-react-stateless-functional-component-in-typescript-5d150419bbbc

tsx 打字网格

打字网格

App.tsx
import * as React from 'react'
import Grid from './Grid'
import './App.css'

interface BaseLineItem {
  id: string
  name: string
  cost: number
}

interface LineItemPackage extends BaseLineItem {
  type: 'package'
  lineItems: BaseLineItem[]
}

const DataGrid: React.ComponentType<Grid.Props<BaseLineItem>> = Grid
function Column(props: Grid.Column.Props) {
  return props.children
}

export default props =>
  <DataGrid
    data={[
      { id: 'a', name: 'AAAAAA', cost: 3 },
      { id: 'b', name: 'BBBBBB', cost: 4 },
      { id: 'b', name: 'BBBBBB', type: 'package',
        lineItems: [
          { id: 'a', name: 'AAAAAA', cost: 3 },
          { id: 'b', name: 'BBBBBB', cost: 4 }
        ],
        get cost() {
          return 2
        }
      } as LineItemPackage
    ]}
    columnOrdering={['id', 'cost', 'name']}
    rowComponent={(props: Grid.Row.Props<LineItemPackage>) =>
      props.record.type === 'package'
        ? <>
          <td colSpan={100}>Package {props.record.name} {props.record.cost}</td>
          {props.record.lineItems.map(lineItem => {
            const Cell: Grid.Cell = (props) => {
              const { type: Col, props: colProps } = props.children
              return <Grid.Cell {...props}>
                <Col {...colProps}>{lineItem[props.columnName]}</Col>
              </Grid.Cell>
            }
            return <Grid.Row {...props} record={lineItem} cellComponent={Cell}/>
          })}
        </>
        : <Grid.Row {...props} />
    }
    columns={{
      id: {
        header: 'ID',
        component: Column
      },
      name: {
        header: 'Name',
        component: Column
      },
      cost: {
        header: 'Cost',
        component: Column
      }
    }}
    onClick={() => false}
  />
Grid.tsx
import * as React from 'react'
import { ReactElement, ComponentType, ReactNode } from 'react'

// "context" in the sense that these props get passed down from the grid to the cell, not via the actual react context
type RowContext<Record=any> = {
  isSelected?: boolean
  index: number
  id: number | string
  record: Record
}

type ColumnContext = {
  columnName: string
}

module Grid {
  export type Props<Record, Keys extends string = keyof Record> =
    Pick<Row.Props<Record>, 'onClick'> &
    {
      rowComponent?: Row // main extension point
      headerComponent?: Header
      columns: {
        [colName in Keys]:
          {
            header: string
            component: Column
          }
      }
      keyBy?: string
      data: Record[]
      columnOrdering: Array<Keys>
    }

  export type Component<Record> = ComponentType<Grid.Props<Record>>

  export namespace Column {
    export type Props<ValueType=any> =
      RowContext &
      ColumnContext &
      {
        children: ValueType
      }
  }
  export type Column<ValueType=any> = ComponentType<Column.Props<ValueType>>

  export namespace Cell {
    export type Props<ValueType=any> =
      RowContext &
      ColumnContext &
      {
        columnComponent?: Column<ValueType>
        children: ReactElement<Props> 
      }
  }
  export type Cell<ValueType=any> = ComponentType<Cell.Props<ValueType>>

  export namespace Row {
    export type Props<Record=any, Children=Array<ReactElement<Cell.Props>>> =
      RowContext<Record> &
      {
        onClick(cell: any, col: string, row: Record): boolean | void
        cellComponent?: Cell
        isSelectable?: boolean
        children: Children
      }
  }
  export type Row<
    Col0=any, Col1=any, Col2=any, Col3=any, Col4=any, Col5=any, Col6=any
  > = ComponentType<
    Row.Props &
    {
      children: Row.Props['children'] & {
        0?: ReactElement<Cell.Props<Col0>>
        1?: ReactElement<Cell.Props<Col1>>
        2?: ReactElement<Cell.Props<Col2>>
        3?: ReactElement<Cell.Props<Col3>>
        4?: ReactElement<Cell.Props<Col4>>
        5?: ReactElement<Cell.Props<Col5>>
        6?: ReactElement<Cell.Props<Col6>>
      }
    }
  >

  export namespace Header {
    export type Props = {
      children: ColumnContext['columnName']
    }
  }
  export type Header = ComponentType<Header.Props>

  export namespace Table {
    export type Props<Row=ReactElement<Row.Props>> = {
      headerElements: ReactNode
      children: Array<Row>
    }
  }
}

class Row extends React.Component<Grid.Row.Props> {
  static defaultProps = {
    cellComponent(props: Grid.Cell.Props) {
      const Column = props.columnComponent as Grid.Column
      props = { ...props }
      props.columnComponent = undefined
      return <td><Column {...props} /></td>
    }
  }

  render() {
    const Cell = this.props.cellComponent as Grid.Cell
    return <>
      {this.props.children.map((cell: ReactElement<Grid.Cell.Props>) =>
        <Cell {...cell.props} columnComponent={cell.type as Grid.Cell} />
      )}
    </>
  }
}

class Grid<Record> extends React.Component<Grid.Props<Record>> {
  // default components to reuse when override
  static Cell: Grid.Cell = Row.defaultProps.cellComponent
  static Row: Grid.Row = Row

  static Header(props) {
    return props.children
  }

  static Table(props: Grid.Table.Props) {
    const { map } = React.Children
      
    return <table>
      <tr>{map(props.headerElements, col => <th>{col}</th>)}</tr>
      <tbody>{map(props.children, row => <tr>{row}</tr>)}</tbody>
    </table>
  }

  static defaultProps = {
    rowComponent: Grid.Row,
    headerComponent: Grid.Header,
    keyBy: 'id',
    onClick() {}
  }

  private renderRow = (record: any, index: number) => {
    const Row = this.props.rowComponent as Grid.Row
    const id = '' + record[this.props.keyBy as any]
    const rowContext = { index, id, record }
    const props: Partial<Grid.Row.Props> = {
      ...rowContext,
      children: this.props.columnOrdering.map((col: string) => {
        const {header, component: Column} = this.props.columns[col]
        return <Column {...rowContext} columnName={header} key={header}>
          {record[col] as any}
        </Column>
      })
    }

    return <Row key={id} {...props as Grid.Row.Props} />
  }

  render() {
    const Header = this.props.headerComponent as Grid.Header
    const props = {
      headerElements: this.props.columnOrdering.map((col: string) =>
        <th><Header children={this.props.columns[col].header} /></th>
      ),
      onClick: this.props.onClick,
      children: this.props.data.map(this.renderRow)
    }

    return <Grid.Table {...props} />
  }
}

export default Grid

tsx react material-ui svg图标列表

react material-ui svg图标列表

example-svgiconlist.tsx
import React = require('react');
import * as svgIcons from 'material-ui/svg-icons';

function parseCategory(iconName: string) {
  let cat: string[] = [iconName.charAt(0)];
  for (let i = 1; i < iconName.length; i++) {
    let c = iconName.charAt(i);
    if (c == c.toUpperCase()) {
      break;
    }
    cat.push(c);
  }
  return cat.join('');
}

let iconBlockStyle: React.CSSProperties = {
  display: 'inline-block',
  width: 40,
  height: 40
}

let childrenNodes: React.ReactNode[] = [];
let count = 0;
let lastCategory: string
for (let iconName in svgIcons) {
  let cat = parseCategory(iconName);
  if(cat!=lastCategory){
    childrenNodes.push(<h2>{cat}</h2>);
    lastCategory = cat;
  }
  let Icon: any = (svgIcons as any)[iconName];//force cast
  childrenNodes.push(<div style={iconBlockStyle} title={iconName}>
    <Icon key={count++}/>
  </div>);
}

export default class SVGIconList extends React.Component<any, any> {

  render() {
    return (
      <div>
        {childrenNodes}
      </div>
    );
  }
}

tsx AbstractContainer.tsx

AbstractContainer.tsx
import React = require('react');

import AnyCancellablePromise = require('../interface/AnyCancellablePromise');
import AbstractContainerPropsInterface = require('./AbstractContainerPropsInterface');
import PreloaderComponent = require('./PreloaderComponent');
import ErrorHandlerComponent = require('./ErrorHandlerComponent');

abstract class AbstractContainer<P extends AbstractContainerPropsInterface, S> extends React.Component<P, S> {
  protected requestList: AnyCancellablePromise[] = [];

  public componentDidMount(): void {
    this.doAfterMount();
    this.fetchData();
  }

  public componentWillUnmount(): void {
    this.doBeforeUnmount();
    this.cancelAllRequests();
  }

  protected addRequest(promise: AnyCancellablePromise): void {
    this.requestList.push(promise);
  }

  protected abstract doAfterMount(): void;

  protected abstract doBeforeUnmount(): void;

  protected abstract fetchData(): void;

  protected abstract renderContent(): JSX.Element;

  private cancelAllRequests(): void {
    this.requestList.map(
      (promise: AnyCancellablePromise) => {
        promise.cancel();
      }
    );
  }

  public render(): JSX.Element {
    if (this.requestList.length > 0) {
      return this.props.loaderComponent || <PreloaderComponent />;
    }

    try {
      return this.renderContent();
    } catch (error) {
      return this.props.errorHandlerComponent || <ErrorHandlerComponent />;
    }
  }
}

export = AbstractContainer;

tsx 在TypeScript中反应高阶组件

在TypeScript中反应高阶组件

ReactHOC.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import HOCNameAndAge from './HOCNameAndAge';

ReactDOM.render(<HOCNameAndAge name='Hello'/>, document.getElementById('root'));
NameAndAge.tsx
import * as React from 'react';
import { Component } from 'react';

export default class NameAndAge extends Component<{ name: string, age: number }, void> {
    render() {
        return <div>Name: {this.props.name}, Age: {this.props.age}</div>;
    }
}
HOCStateToProps.tsx
import * as React from 'react';
import { Component } from 'react';
import HOCBaseRender from './HOCBaseRender';

export default function HOCStateToProps<Props, State, ComponentState>(
    Comp: new() => Component<Props & State, ComponentState>, getState: () => State) {
    return class HOCWrapper extends HOCBaseRender<Props, State, ComponentState>(Comp) {
        // ... Implementation
        constructor() {
            super();
            this.state = getState();
        }
    }
}
HOCNameAndAge.tsx
import * as React from 'react';
import NameAndAge from './NameAndAge';
import HOCStateToProps from './HOCStateToProps';

export default HOCStateToProps<
    { name: string },
    { age: number },
    void>(NameAndAge, () => ({ age: 12 }));
HOCMounted.tsx
import * as React from 'react';
import { Component } from 'react';
import HOCBaseRender from './HOCBaseRender';

export default function HOCMounted<Props, ComponentState>(
    Comp: new() => Component<Props, ComponentState>, onMount: () => void, onUnmount: () => void) {
    return class HOCWrapper extends HOCBaseRender<Props, void, ComponentState>(Comp) {
        // ... Implementation
        componentWillMount() {
            onMount.call(this);
        }
        componentWillUnmount() {
            onUnmount.call(this);
        }
    }
}
HOCBaseRender.tsx
import * as React from 'react';
import { Component } from 'react';

export default function HOCBaseRender<Props, State,  ComponentState>(
    Comp: new() => Component<Props & State, ComponentState>) {
    return class HOCBase extends Component<Props, State> {
        render() {
            return <Comp {...this.props} {...this.state}/>;
        }
    }
}