Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
141 views
in Technique[技术] by (71.8m points)

reactjs - InteliJ: Typescript auto check {} object against an interface?

Here is example offending code of React component that is incorrectly connected to Redux.

import React from 'react';
import { View, StyleSheet, ViewStyle, Text } from 'react-native';
import { connect } from "react-redux";
import { StoreState } from "../../store/storeState/storeState";
import { LogFactory, Logger } from "../../vlabs-js/general/logging/log";

interface DevCompProps {
    counter: number
}

class UnconnectedDevComp extends React.Component<DevCompProps> {
    private logger: Logger;

    constructor(props: DevCompProps) {
        super(props);

        this.logger = LogFactory.fromComp('DevComp');
    }

    render() {
        this.logger.info(`render(): props='${this.props.counter}'`);

        return <View style={{flexDirection: 'row', borderWidth: 3, borderColor: 'black', padding: 5}}>
            <Text>Counter:</Text>
            <Text>{this.props.counter}</Text>
        </View>
    }
}

const mapStateToProps = (state: StoreState) => {
    return {
        number: state.devState.counter
    };
};


export const DevComp = connect(mapStateToProps)(UnconnectedDevComp)

For the code to work correctly mapStateToProps should be


const mapStateToProps = (state: StoreState) => {
    return {
        counter: state.devState.counter
    };
};

Instead of


const mapStateToProps = (state: StoreState) => {
    return {
        number: state.devState.counter
    };
};

Is there a way to Compile/InteliJ-Warn check that the object returned by mapStateToProps matched the interface

interface DevCompProps {
    counter: number
}

So such a mistake would be flagged by IDE and not have to found by hand.

question from:https://stackoverflow.com/questions/65929004/intelij-typescript-auto-check-object-against-an-interface

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The short answer is "no" because all the solutions here suck. You'd probably have better luck dealing with function components and useSelector hooks.

When you map with a wrong key, your composed DevComp will be a component which requires that a counter prop be passed in when you call it so typescript gets that part right.

You can get the error to be raised by setting some of the generics on the connect function, but this seems like a pain. The variables that we would set are the first, which is the props returned from mapStateToProps, and the third, which is the props that you expect the component to be called with.

const DevComp = connect<DevCompProps, {}, {}>(mapStateToProps)(UnconnectedDevComp)

Likewise, you can get an error to be raised by setting the return type on mapStateToProps to Partial<DevCompProps>.

You would need to override some of the types in the react-redux package in order to get that inference automatically. But that sucks too.

We want to make it such that the props returned from mapStateToProps cannot have any keys which are not present in the component props.

You can't do it by manipulating the MapStateToProps type. That has three variables, but none represent the type of the component to be mapped. What you need is for TStateProps to be restricted.

export type MapStateToProps<TStateProps, TOwnProps, State = DefaultRootState> =
    (state: State, ownProps: TOwnProps) => TStateProps;

Ultimately the issue is the two-step nature of the connect HOC. The creation of the "connector" is separate from applying it to a component, so the connector cannot depend on that component.

It's perfectly fine to do this:

const myWrapper = connect(mapStateToProps);

and get the type

InferableComponentEnhancerWithProps<{ number: number; } & DispatchProp<AnyAction>, {}>

myWrapper is a function which can wrap any component and will add a prop number to it. It doesn't know anything about the type of component that it will be applied to and does not care whether that component actually wants a prop number.

You would need to define your own function that combines the two steps into one function with two arguments so that the arguments can infer types from each other. I don't have a complete solution. connect has 15 different overloads and this type signature only describes one. Also the implementation is an error, but the function typing does what you want.

const myConnect = <InnerProps, MappedProps extends Partial<InnerProps> , State = DefaultRootState>(
  mapStateToProps: (state: State) => MappedProps, Component: ComponentType<InnerProps>
  ): ComponentType<Omit<InnerProps, keyof MappedProps>> => {
    return connect(mapStateToProps)(Component);
}

export const DevComp = myConnect(mapStateToProps, UnconnectedDevComp)

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...