ค้นหาเว็บไซต์

reactjs - วิธีใช้ TypeScript กับ withRouter, connect, React.Component และคุณสมบัติที่กำหนดเอง?


ฉันมี Component ของ React ที่ใช้ connect, withRouter และรับคุณสมบัติที่กำหนดเอง ฉันกำลังพยายามแปลงสิ่งนี้เป็น TypeScript และสงสัยว่าฉันทำถูกต้องหรือไม่ อย่างน้อยตอนนี้ฉันก็ไม่มีข้อผิดพลาด

นี่คือโค้ดที่แสดงแนวคิด:

import * as React from 'react'
import { connect } from 'react-redux';
import { withRouter, RouteComponentProps } from 'react-router';

import { 
  fetchTestLists,
  newTestList,
  displayTestList,
} from '../../../actions/index';

interface StateProps {
  testList: any;    // todo: use the type of state.myList to have validation on it
}

interface DispatchProps {
  fetchTestLists: () => void;
  newTestList: () => void;
  displayTestList: (any) => void;   // todo: replace any with the actual type
}

interface Props {      // custom properties passed to component
  className: string;
}

type PropsType = StateProps & DispatchProps & Props;


class MyTest extends React.Component<PropsType & RouteComponentProps<{}>, {}> {
  constructor(props) {
    super(props);
    this.handleCellClick = this.handleCellClick.bind(this);
    this.newTestList = this.newTestList.bind(this);
  }

  componentDidMount() {
    this.props.fetchTestLists();
  }

  handleCellClick(row, column, event) {
    this.props.displayTestList(row);
  }

  newTestList(e) {
    this.props.newTestList()
  }

  render() {
    return (
      <div className={this.props.className}>
      </div>
    );
  }
}

const mapStateToProps = (state): StateProps => ({
  testList: state.myList,   // todo: define a type for the root state to have validation here
});

const dispatchToProps = {
  fetchTestLists,
  newTestList,
  displayTestList,
};

export default withRouter<Props & RouteComponentProps<{}>>(connect<StateProps, DispatchProps>(
  mapStateToProps,
  dispatchToProps,
)(MyTest) as any);

คอมโพเนนต์ถูกใช้งานดังนี้: <MyTest className={"active"} />

ฉันต้องทดลองหลายวิธีเพื่อให้สิ่งนี้ทำงานได้ ตัวอย่างเช่น:

1) เมื่อฉันละเว้นประเภทสำหรับ withRouter แบบนี้: export default withRouter(connect... จากนั้นฉันจะได้รับ TS2339: Property 'className' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<Pick<RouteComponentProps<any>, never>, C...'. เรื่องนี้ถูกแนะนำที่นี่: React router in TypeScript- both router and own props แม้ว่าฉันจะไม่เข้าใจแนวคิดนั้นเลย

2) ถ้าคุณสงสัยเกี่ยวกับบรรทัดสุดท้าย as any นี่เกี่ยวข้องกับ https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18999 และฉันได้รับข้อผิดพลาดนี้โดยไม่ได้ใช้มัน:

 TS2345: Argument of type 'ComponentClass<Pick<any, never>> & { WrappedComponent: ComponentType<any>; }' is not assignable to parameter of type 'ComponentType<Props & RouteComponentProps<{}>>'.
 Type 'ComponentClass<Pick<any, never>> & { WrappedComponent: ComponentType<any>; }' is not assignable to type 'StatelessComponent<Props & RouteComponentProps<{}>>'.
 Type 'ComponentClass<Pick<any, never>> & { WrappedComponent: ComponentType<any>; }' provides no match for the signature '(props: Props & RouteComponentProps<{}> & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.

งั้นนี่คือวิธีที่ถูกต้องในการทำใช่ไหม? คุณเห็นปัญหาตรงไหนบ้าง? โดยพื้นฐานแล้วฉันกำลังใช้เวอร์ชันล่าสุดทั้งหมด นี่คือส่วนหนึ่งจากไฟล์ package.json ของฉัน:

"react": "^16.2.0",
"redux": "^3.7.2",
"react-dom": "^16.2.0",
"react-redux": "^5.0.6",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"react-router-redux": "^4.0.8",
...
"typescript": "^2.7.2",
"@types/react-redux": "^5.0.15",
"@types/react-router": "^4.0.22",
"@types/react-router-dom": "^4.2.4",
"@types/react": "^16.0.38",
"@types/react-dom": "^16.0.4",

คำตอบ #1

วิธีที่ฉันทำในโครงการของเรา (นี่เป็นวิธีที่ง่ายที่สุดและคุณจะได้รับการเติมคำอัตโนมัติเมื่อคุณใช้มัน):

import * as React from 'react'
import { connect } from 'react-redux';
import { withRouter, RouteComponentProps } from 'react-router';

import { 
  fetchTestLists,
  newTestList,
  displayTestList,
} from '../../../actions/index';

interface StateProps {
  testList: any;    // todo: use the type of state.myList to have validation on it
}

interface DispatchProps {
  fetchTestLists: () => void;
  newTestList: () => void;
  displayTestList: (any) => void;   // todo: replace any with the actual type
}

interface Props extends RouteComponentProps {      // custom properties passed to component
  className: string;
}

type PropsType = StateProps & DispatchProps & Props;


class MyTest extends React.Component<PropsType> {
  constructor(props) {
    super(props);
    this.handleCellClick = this.handleCellClick.bind(this);
    this.newTestList = this.newTestList.bind(this);
  }

  componentDidMount() {
    this.props.fetchTestLists();
  }

  handleCellClick(row, column, event) {
    this.props.displayTestList(row);
  }

  newTestList(e) {
    this.props.newTestList()
  }

  render() {
    return (
      <div className={this.props.className}>
      </div>
    );
  }
}

const mapStateToProps = (state, ownProps: Props): StateProps => ({
  testList: state.myList,   // todo: define a type for the root state to have validation here
});

const dispatchToProps: DispatchProps = {
  fetchTestLists,
  newTestList,
  displayTestList,
};

export default withRouter(connect(
  mapStateToProps,
  dispatchToProps,
)(MyTest));

นอกจากนี้ถ้านี่เป็นสิ่งที่คุณจะพิมพ์บ่อย ๆ ผมแนะนำให้สร้างสนิพเพ็ตแบบกำหนดเอง (ถ้าคุณใช้โปรแกรมเช่น VS Code มันจะง่ายมาก)

คำตอบ #2

ฉันพยายามเขียนตัวอย่างของคุณใหม่และจบลงด้วยโค้ดนี้:

import * as React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router';

import { 
  fetchTestLists,
  newTestList,
  displayTestList,
} from '../../../actions/index';
import { Dispatch, bindActionCreators, AnyAction } from 'redux';

interface IStateProps {
  testList: IListType;    // todo: use the type of state.myList to have validation on it
}

interface IDispatchProps {
  fetchTestLists: () => AnyAction;
  newTestList: () => AnyAction;
  displayTestList: (value: string) => AnyAction;   // todo: replace any with the actual type
}

interface IProps {      // custom properties passed to component
  className: string;
}

type PropsType = IStateProps & IDispatchProps & IProps;

class MyTestComponent extends React.Component<PropsType & RouteComponentProps<{}>, {}> {
  constructor(props: PropsType & RouteComponentProps<{}>) {
    super(props);
    this.handleCellClick = this.handleCellClick.bind(this);
    this.newTestList = this.newTestList.bind(this);
  }

  public componentDidMount() {
    this.props.fetchTestLists();
  }

  public handleCellClick(row, column, event) {
    this.props.displayTestList(row);
  }

  public newTestList(e) {
    this.props.newTestList();
  }

  public render(): JSX.Element {
    return (
      <div className={this.props.className}>
      </div>
    );
  }
}

export const MyTest = connect(
  (state: IAppState, ownProps: IProps) => ({
      testList: state.testList,
      ...ownProps,
  }),
  (dispatch: Dispatch) => bindActionCreators<AnyAction, Pick<IDispatchProps, keyof IDispatchProps>>(
    { displayTestList, fetchTestLists, newTestList },
    dispatch,
),
)(withRouter(MyTestComponent));

interface IListType {
  someProp: string;
}

interface IAppState {
  testList: IListType;
  differentList: IListType;
}

ฉันได้เปลี่ยน export default มาเป็นการกำหนดผลลัพธ์ของคลาส MyTestComponent ที่ถูกห่อด้วย connect และ withRouter HOC ลงใน MyTest จากนั้นฉันก็ import คอมโพเนนต์ MyTest แบบนี้

import { MyTest } from './MyTest' 

ฉันได้เพิ่มอินเทอร์เฟซเพื่ออธิบายคุณสมบัติทั้งหมดที่ส่งผ่านจากส่วนประกอบหลักยังใช้ withRouter และเชื่อมต่อด้วยวิธีอื่น (อ่านได้มากขึ้นสําหรับฉัน)

ฉันหวังว่ามันจะเป็นประโยชน์

บทความที่เกี่ยวข้อง