Definitelytyped: `connect` react-redux tidak dapat digunakan sebagai dekorator: ketik "tidak dapat ditetapkan untuk mengetik 'void'"

Dibuat pada 4 Jul 2016  ·  32Komentar  ·  Sumber: DefinitelyTyped/DefinitelyTyped

Mencoba menggunakan connect react-redux sebagai dekorator kelas dapat menyebabkan pesan kesalahan dalam bentuk Type 'foo' is not assignable to type 'void' . Masalah dengan pengetikan ini tampaknya adalah bahwa TypeScript tidak mengizinkan ClassDecorator s untuk mengubah tanda tangan dari benda yang mereka hias , tetapi ini adalah implementasi saat ini dari pengetikan reaksi-redux dan dilakukan dengan sengaja, karena react-redux mengembalikan instance berbeda dengan tanda tangan berbeda untuk props .

Menggunakan connect sebagai fungsi berfungsi seperti yang diharapkan; hanya penggunaan sebagai dekorator yang menyebabkan masalah tipe.

Saran diterima, tetapi solusi apa pun yang diusulkan harus merupakan peningkatan ketat pada apa yang saat ini dapat diungkapkan oleh pengetikan, yaitu, mengorbankan kebenaran pengetikan penggunaan sebagai fungsi saat ini tidak dapat diterima (sebagian karena itu akan menjadi regresi parah dan sebagian karena dekorator dianggap "eksperimental" dan oleh karena itu, saya tegaskan, sekunder untuk penggunaan fungsi standar).

Saya tidak tahu bahwa solusi lengkap untuk masalah pengetikan dekorator dimungkinkan sementara https://github.com/Microsoft/TypeScript/issues/4881 luar biasa. Yang mengatakan, ada peningkatan tambahan dalam kasus ini, seperti dengan (pass pertama) mengeluarkan segala jenis React.ComponentClass sehingga kode setidaknya dikompilasi, kemudian (pass kedua) mengeluarkan komponen yang menerima persimpangan semua props (memiliki dan juga terhubung), meskipun itu akan terlalu lunak, jadi tipe kode memeriksa setidaknya beberapa props.

Satu-satunya solusi yang saya miliki saat ini adalah tidak menggunakan connect sebagai dekorator. Beberapa orang mungkin menganggapnya jelek secara gaya, tetapi jenisnya diperiksa dengan benar, tidak lebih panjang dan dapat digunakan di lebih banyak tempat daripada dekorator.

Dari pada:

@connect(mapStateToProps, mapDispatchToProps)
class MyComponent extends React.Component<...> { ... }

gunakan (sesuatu di sepanjang baris):

class MyComponentImpl extends React.Component<...> { ... }
const MyComponent = connect(mapStateToProps, mapDispatchToProps)(MyComponentImpl);

Perhatikan bahwa https://github.com/DefinitelyTyped/DefinitelyTyped/issues/8787 ikut bermain di sini dan terkadang digabungkan dengan masalah ini. Anda mungkin juga menginginkan petunjuk tipe atau pemeran untuk memastikan komponen keluaran memiliki tipe yang tepat.

Sunting: https://github.com/Microsoft/TypeScript/pull/12114 dapat membantu dengan kasus ini: dekorator berpotensi dapat ditulis untuk membuat versi semua-opsional dari alat peraga untuk komponen yang dihasilkan, yang tidak sepenuhnya ideal tetapi terus membiarkan implementasi Anda lebih tegas tentang prop nullity.

Komentar yang paling membantu

Untuk menggunakan @connect secara langsung (jadi tanpa memperkenalkan dekorator khusus) saya menggunakan solusi berikut:

@(connect(mapStateToProps, mapDispatchToProps) as any)
class MyComponent extends React.Component<...> {...}

Tapi saya sangat setuju dengan @seansfkelley bahwa pengetikan apa pun bukanlah yang kami inginkan...

Semua 32 komentar

Saya mengalami masalah serupa beberapa hari yang lalu dan menjatuhkannya untuk fungsi koneksi. Tipe-tipenya terlihat tidak aktif dan menggunakan tipe reaksi lama. Saya dapat mencoba untuk memperbaiki ini dan mengirimkan PR?

Tentu saja! Banyak yang telah berubah sejak jenis ini awalnya ditulis, meskipun saya masih skeptis bahwa tanpa dukungan dekorator bermutasi, sesuatu yang bermanfaat mungkin terjadi. Tapi saya telah mengatasi kekurangan lain dalam sistem tipe sebelumnya, jadi cobalah.

Mungkin tipe yang dipetakan bisa membuat Anda menjadi bagian dari jalan ke sana, jadi setidaknya Anda memiliki _some_ informasi tipe yang berguna dalam output?

YOLO

export function myConnect(mapStateToProps, mapDispatchToProps) {
    return function (target) {
        return <any>connect(mapStateToProps, mapDispatchToProps)(target);
    }
}

Saya tidak berpikir ada banyak gunanya mengetik yolo ini, karena solusi yang tercantum di sini dan di # 8787, khususnya tentang menggunakan petunjuk untuk pemanggilan fungsi, tidak terlalu buruk (terutama dengan templat IDE untuk kelas komponen) dan memiliki komponen tipe apa pun menyedihkan. :( Sambungan pengetikan apa pun hanya untuk menggunakannya sebagai dekorator alih-alih panggilan fungsi benar-benar menempatkan kereta di depan kuda.

Untuk menggunakan @connect secara langsung (jadi tanpa memperkenalkan dekorator khusus) saya menggunakan solusi berikut:

@(connect(mapStateToProps, mapDispatchToProps) as any)
class MyComponent extends React.Component<...> {...}

Tapi saya sangat setuju dengan @seansfkelley bahwa pengetikan apa pun bukanlah yang kami inginkan...

Meskipun sintaks dekorator keren, saya masih berpikir menggunakan HOC seperti connect dengan cara standar adalah praktik yang lebih baik.

1) Ini memodifikasi jenis kelas a yang bertentangan dengan spesifikasi dekorator.
2) Connect bukan dekorator sama sekali, ini adalah HOC.
3) Ini merusak portabilitas kode dengan memiliki sintaks yang berbeda untuk komponen stateless dan stateful.

Jadi saya ingin mengatakan tolong, jangan berikan definisi yang memungkinkan dev menggunakannya dengan cara yang salah :)

@npirotte Setuju. Anda benar sekali, menggunakan HOC sebagai dekorator melanggar spesifikasi dekorator. Ini bukan kelas dasar lagi tetapi kelas yang sama sekali berbeda.

Saya mengalami masalah yang sama persis dengan dekorator khusus dan melihat status dukungan mereka di TS (mereka masih di belakang bendera dan AFAICS tidak ada rencana untuk mengaktifkannya secara default) Saya akan menghentikan dukungan untuk mereka. Apalagi dekorator masih dalam draft w3c.

Cara terbaik untuk membungkus komponen ke yang baru adalah dengan menggunakan fungsi urutan yang lebih tinggi daripada mencoba untuk _decorate_ kelas yang ada.

Saya memiliki kesalahan yang sama jika saya mengimpornya. Namun, berikut ini berfungsi untuk saya:

const { connect } = require('react-redux');

@connect(mapStateToProps, mapDispatchToProps)
class MyComponent extends React.Component<...> { ... }

@TriStarGod itu tergantung pada bagaimana Anda mengetik require . Sepertinya Anda berakhir dengan connect diketik sebagai any dalam kasus itu.

Solusi/solusi lain.

Karena saya sudah memiliki Status Aplikasi saya sendiri, saya memerlukan beberapa fungsi yang sangat singkat dalam bentuk:

export interface PageProps {
    routing: RouterState;
}

export interface PageDispatch {
    navigate: () => void
}

@connect<PageProps, PageDispatch>(
    state => ({
        routing: state.routing
    }),
    dispatch => ({
        navigate: () => dispatch(push("/"))
    })
)
export class Page extends React.Component<PageProps & PageDispatch> {
...
}

Dan inilah metode koneksi yang dibungkus itu:

import { connect } from "react-redux";
import { ApplicationState } from './rootReducer';

interface MapPropsParam<TProps>{
    (state: ApplicationState, ownProps?: TProps): TProps
}

interface MapDispatchParam<TProps, TDispatchProps>{
   (dispatch: Redux.Dispatch<ApplicationState>, ownProps?: TProps): TDispatchProps;
}

export interface ConnectedComponent<TProps> {
    <TComponent extends React.ComponentType<TProps>>(component: TComponent): TComponent;
}

function connectToAppState<TProps, TDispatchProps = {}>(mapProps: MapPropsParam<TProps>, mapDispatch?: MapDispatchParam<TProps, TDispatchProps>) : ConnectedComponent<TProps> {
    return connect<TProps, TDispatchProps, TProps>(mapProps, mapDispatch) as ConnectedComponent<TProps & TDispatchProps>;    
}

export {
    connectToAppState as connect
}

Thx untuk pekerjaan Anda. Berlangganan dan menunggu kemajuan tentang masalah ini.

@offbeatful tolong berikan deklarasi tipe variasi lain dari mapDispatchToProps

Jika sebuah objek dilewatkan, setiap fungsi di dalamnya dianggap sebagai pembuat tindakan Redux. Objek dengan nama fungsi yang sama, tetapi dengan setiap pembuat tindakan yang dibungkus ke dalam panggilan pengiriman sehingga dapat dipanggil secara langsung, akan digabungkan ke dalam prop komponen.

solusi lain berdasarkan komentar @offbeatful

myConnect.ts

import {
    connect as originalConnect, MapDispatchToPropsParam, MapStateToPropsParam, MergeProps, Options
} from "react-redux";
import * as React from "react";

export interface InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> {
    <TComponent extends React.ComponentType<TInjectedProps & TNeedsProps>>(component: TComponent): TComponent;
}

interface MyConnect {
    <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}>(
        mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps>,
        mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>
    ): InferableComponentEnhancerWithProps<TStateProps & TDispatchProps, TOwnProps>;

    <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, TMergedProps = {}>(
        mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps>,
        mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
        mergeProps?: MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps>,
        options?: Options<TStateProps, TOwnProps, TMergedProps>
    ): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>;

}

export const connect = originalConnect as MyConnect;

Diperbarui berdasarkan cuplikan @pravdomil dan jenis terbaru (5.0.13)

import { ApplicationState } from "./rootReducer";

import * as React from "react";
import {
    connect as originalConnect, MapDispatchToPropsParam, MapStateToPropsParam, MergeProps, Options
} from "react-redux";

export type InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> = <TComponent extends React.ComponentType<TInjectedProps & TNeedsProps>>(component: TComponent) => TComponent;

interface MyConnect {
    <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}>(
        mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps, ApplicationState>,
        mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>
    ): InferableComponentEnhancerWithProps<TStateProps & TDispatchProps, TOwnProps>;

    <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, TMergedProps = {}>(
        mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps, ApplicationState>,
        mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
        mergeProps?: MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps>,
        options?: Options<TStateProps, TOwnProps, TMergedProps>
    ): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>;

}

export const connect = originalConnect as MyConnect;

Hai @offbeatful (cc: @pravdomil saya kira?), Saya baru-baru ini mengajukan pertanyaan Stack Overflow terkait dengan masalah ini di mana disimpulkan bahwa ini saat ini tidak didukung. Sebagai bagian dari pertanyaan itu, saya menyiapkan repositori untuk menampilkan apa yang saya coba. Disimpulkan bahwa saat ini tidak didukung, jadi saya senang melihat cuplikan kode Anda hari ini dan melanjutkan untuk mencoba dan memperbarui repositori saya untuk menggunakan ini.

Di sini Anda dapat melihat komit dengan cuplikan kode Anda terintegrasi

Ini tidak lagi berteriak dengan kesalahan yang saya dapatkan sebelumnya, tetapi saya telah menemukan bahwa dalam kasus penggunaan saya di mana komponen saya terhubung, tetapi juga memiliki alat peraga sendiri, tanda tangan baru ini sekarang akan membuatnya sehingga bahkan alat peraga negara diperlukan sebagai sendiri (TSX) alat peraga. cd my-app && yarn start di repositori saya akan menampilkan apa yang saya maksud.

Apakah menurut Anda ini adalah sesuatu yang bisa diatasi juga atau tidak mungkin?

Anda mungkin dapat menggunakan versi connect ini

connect(
        mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
        mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
        mergeProps: null | undefined,
        options: Options<State, TStateProps, TOwnProps>
): InferableComponentEnhancerWithProps<TStateProps & TDispatchProps, TOwnProps>

jika demikian, Anda hanya perlu memperbaiki parameter options menjadi Options<any, any, any> dengan cara ini Anda tidak akan memiliki masalah TStateProps & TDispatchProps karena itu akan menjadi TStateProps & any

pendeknya:

const options = { withRef:true } as Options<any, any, any>;
export const Component = connect(mapStateToProps, mapDispatchToProps, null, options)

keren seperti kamu

@TomasHubelbauer Baru saja mencoba metode Anda, jika Anda kembali ke metode koneksi redux default
itu akan bekerja cukup aneh.

Yang terbaik adalah memastikan Anda menggunakan tipe bersama di antara ketiganya:

export type CounterStateProps = {
    count: number;
};

export type CounterDispatchProps = {
    onIncrement: () => void;
};

export type CounterOwnProps = {
    initialCount: number;
};

export type CounterProps = CounterStateProps & CounterDispatchProps & CounterOwnProps;

Kemudian implementasikan komponen stateful

export class StatefulCounter extends React.Component<CounterProps, CounterStateProps> {
    timer: number;

    componentDidMount() {
        this.timer = setInterval(this.props.onIncrement, 5000);
    }

    componentWillUnmount() {
        clearInterval(this.timer);
    }

    render() {
      return (
        <StatelessCounter count={this.props.count}/>
      );
    }
}

Dan akhirnya buat kelas konektor seperti itu menggunakan redux's build in connect BUKAN kode koneksi khusus.

const mapStateToProps =
    (state: RootState, ownProps: CounterOwnProps): CounterStateProps => ({
        count: countersSelectors.getReduxCounter(state) + ownProps.initialCount,
    });

const mapDispatchToProps =
    (dispatch: Dispatch<CounterActionType>): CounterDispatchProps => bindActionCreators({
        onIncrement: CounterActions.increment,
    }, dispatch);

export const ConnectedCounter =
    connect(mapStateToProps, mapDispatchToProps)(StatefulCounter);

@JohnHandley Ya, saya tahu ini berfungsi, dalam contoh saya, saya menunjukkan bahwa sebelum saya mencoba beberapa saran untuk membuatnya berfungsi sebagai dekorator. Saya berhasil menggunakan varian non-dekorator, tetapi saya juga ingin membuat dekorator berfungsi.

Juga, saya pikir Anda mencampuradukkan tipe Anda, Anda menggunakan CounterStateProps (yang merupakan tipe pengembalian mapDispatchToProps sebagai komponen CounterProps (tidak apa-apa, ia memiliki status Redux props, Redux dispatch props dan JSX own props), tetapi juga sebagai tipe untuk status komponen. Sebagai gantinya state harus memiliki tipenya sendiri yang tidak terlibat dalam tanda tangan tipe komponen luar dengan cara apa pun.

Ada kemungkinan saya tidak memahami solusi Anda sepenuhnya, jadi jika Anda dapat membuat ini berfungsi di repositori saya (di mana saya menggunakan kedua props sendiri di TSX dan Redux state props) tanpa mendapatkan kesalahan yang mengatakan Anda perlu menentukan props status Redux di TSX , juga, itu bagus!

+1 pada masalah ini

Saya memperbarui cuplikan dari @offbeatful dan saya berhasil menggunakannya sekarang.

connect.ts ( IAppState adalah antarmuka status redux)

import React from 'react'
import {
    connect as originalConnect,
    MapDispatchToPropsParam,
    MapStateToPropsParam,
    MergeProps,
    Options,
} from 'react-redux'


export type InferableComponentEnhancerWithProps<IInjectedProps, INeedsProps> =
    <IComponent extends React.ComponentType<IInjectedProps & INeedsProps>>(component: IComponent) => IComponent

export interface IConnect {
    <IStateProps = {}, IDispatchProps = {}, IOwnProps = {}>(
        mapStateToProps?: MapStateToPropsParam<IStateProps, IOwnProps, IAppState>,
        mapDispatchToProps?: MapDispatchToPropsParam<IDispatchProps, IOwnProps>,
    ): InferableComponentEnhancerWithProps<IStateProps & IDispatchProps, IOwnProps>

    <IStateProps = {}, IDispatchProps = {}, IOwnProps = {}, IMergedProps = {}>(
        mapStateToProps?: MapStateToPropsParam<IStateProps, IOwnProps, IAppState>,
        mapDispatchToProps?: MapDispatchToPropsParam<IDispatchProps, IOwnProps>,
        mergeProps?: MergeProps<IStateProps, IDispatchProps, IOwnProps, IMergedProps>,
        options?: Options<IStateProps, IOwnProps, IMergedProps>,
    ): InferableComponentEnhancerWithProps<IMergedProps, IOwnProps>

}

export const connect = originalConnect as IConnect

Kemudian komponen saya yang terhubung terlihat seperti ini:

import {connect} from 'path-to-my-connect/connect'

interface IOwnProps {
    ... props exposed for the real parent component
}

interface IStateProps {
    ... props from mapStateToProps
}

interface IDispatchProps {
    ... props from mapDispatchToProps
}

interface IProps extends IStateProps, IDispatchProps, IOwnProps {}

@connect<IStateProps, IDispatchProps, IOwnProps>(
    (state: IAppState) => {
            return {
                foo: getFoo(state), // getFoo is a selector using 'reselect'
            }
        },
    {setFoo, increment, decrement, ... your action creators},
)
class MyComponent extends React.PureComponent<IProps> {
        // your component implementation
}

export default (MyComponent as any) as React.ComponentType<IOwnProps>

Casting langsung MyComponent as React.ComponentType<IOwnProps> akan gagal, jadi saya mengetiknya sebagai any terlebih dahulu. Saya tahu ini peretasan tetapi berfungsi dengan baik untuk induk dan untuk komponen itu sendiri juga.

Ini adalah solusi yang paling layak, namun masih terbatas yang dapat saya buat.

"react-redux": "^5.0.6",
"typescript": "^2.8.1",
"@types/react-redux": "^6.0.0",

ada berita?

@ctretyak tidak, Anda akhirnya harus membuat file deklarasi sendiri dan memodifikasi dekorator. Sepertinya React Eco dan TS bukanlah teman baik, oleh karena itu saya pindah darinya.

const DecoratedComponent = require('../DecoratedComponent').default;

impor semacam ini memungkinkan IDE menunjukkan alat peraga dan menyembunyikan kesalahan ts. agak jelek, tetapi dekorator terlihat lebih baik daripada koneksi sederhana. terutama jika Anda mengonfigurasi untuk menambahkan beberapa pemilih secara otomatis seperti terjemahan atau navigasi

Ini bukan masalah reaksi-redux. Saya memiliki perilaku yang sama dengan @withRouter (dari React-Router) dan @graphql

TypeScript tampaknya tidak mengerti bahwa seorang dekorator dapat menyuntikkan alat peraga ...

TypeScript tampaknya tidak mengerti bahwa seorang dekorator dapat menyuntikkan alat peraga ...

Ugh, ini masih menjadi masalah? Baru saja mengalami ini.

Saya merasa sedih ada sesuatu yang merusak otak kita, tetapi itu dirancang untuk membuat kita lebih nyaman :(

Saya merasa sedih ada sesuatu yang merusak otak kita, tetapi itu dirancang untuk membuat kita lebih nyaman :(

saya juga :(

Ini mungkin tidak mencakup semua kasus penggunaan tetapi telah bekerja dengan baik untuk saya.
Ini juga memberi saya kesempatan untuk menambahkan jenis toko saya (MyAppStore) di satu tempat.

export function connectTs<TDispatchProps={}, TOwnProps=any>(
  mapStateToProps?: (state: MyAppStore, ownProps?: TOwnProps) => any,
  mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>
): any {
  return connect(mapStateToProps, mapDispatchToProps)
}

apakah Anda memiliki pembaruan?

Saya ingin menggunakan connect sebagai dekorator juga.

Adakah solusi resmi yang disediakan???
Saya pikir ada banyak pengembang yang lebih suka menggunakan @connect daripada connect(mapStateToProps,mapDispatchToProps)(components)

Apakah halaman ini membantu?
0 / 5 - 0 peringkat