Элегантно используя TypeScript в реакции

внешний интерфейс переводчик TypeScript React.js

пиши на фронт

  • Чтобы лучше использовать ts в реакции, давайте обсудим
  • Как разумно использовать некоторые функции ts в React, чтобы сделать код более надежным

Обсудить несколько вопросов, объявление реактивных компонентов? Декларация и использование реагирующих компонентов более высокого порядка? Использование реквизита и состояния в компонентах класса? ...

Несколько принципов и изменений использования ts в реакции

  • все использованоjsxФайлы грамматики должны начинаться сtsxсуффиксное наименование
  • при использовании объявления компонентаComponent<P, S>Объявления общих параметров вместо PropTypes!
  • Глобальные переменные или пользовательские свойства объекта окна, унифицированные в корне проектаglobal.d.tsдекларативное определение в
  • Для объектов данных интерфейса, обычно используемых в проекте, вtypes/Определите его объявление структурированного типа в каталоге

Объявление компонентов React

  • Компоненты в реакции делятся на компоненты класса и функциональные компоненты с точки зрения способа их определения.

  • объявление компонента класса

class App extends Component<IProps, IState> {
    static defaultProps = {
        // ...
    }
    
    readonly state = {
        // ...
    }; 
    // 小技巧:如果state很复杂不想一个个都初始化,可以结合类型断言初始化state为空对象或者只包含少数必须的值的对象:  readonly state = {} as IState;
}

Следует подчеркнуть, что если вы используетеstate, за исключением передачи его через общий параметр при объявлении компонентаstateструктура, также должна быть инициализированаstateобъявлен какreadonly

Это потому, что мы используемclass propertiesсинтаксическая параstateПри инициализации он будет перезаписанComponent<P, S>средняя параstateизreadonlyлоготип.

Декларация функциональных компонентов

// SFC: stateless function components
// v16.7起,由于hooks的加入,函数式组件也可以使用state,所以这个命名不准确。新的react声明文件里,也定义了React.FC类型^_^
const List: React.SFC<IProps> = props => null

Нужно ли компонентам класса указывать реквизиты и типы состояний?

  • 是的. пока он используется внутри компонентаpropsа такжеstate, необходимо указать его тип при объявлении компонента.
  • Однако вы можете обнаружить, что пока мы инициализируемstate, похоже, что даже если тип состояния не объявлен, его можно вызвать нормально иsetState. Да, реальная ситуация действительно такова, но это фактически приводит к потере компонента.stateдоступ и проверка типов!
// bad one
class App extends Component {
    state = {
        a: 1,
        b: 2
    }
 
    componentDidMount() {
        this.state.a // ok: 1
 
        // 假如通过setState设置并不存在的c,TS无法检查到。
        this.setState({
            c: 3
        });
        
        this.setState(true); // ???
    }
    // ...
}
 
// React Component
class Component<P, S> {
        constructor(props: Readonly<P>);
        setState<K extends keyof S>(
            state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
            callback?: () => void
        ): void;
        forceUpdate(callBack?: () => void): void;
        render(): ReactNode;
        readonly props: Readonly<{ children?: ReactNode }> & Readonly<P>;
        state: Readonly<S>;
        context: any;
        refs: {
            [key: string]: ReactInstance
        };
    }
 
 
// interface IState{
//    a: number,
//    b: number
// }

// good one
class App extends Component<{}, { a: number, b: number }> {
   
    readonly state = {
        a: 1,
        b: 2
    }
    
    //readonly state = {} as IState,断言全部为一个值
 
    componentDidMount() {
        this.state.a // ok: 1
 
        //正确的使用了 ts 泛型指示了 state 以后就会有正确的提示
        // error: '{ c: number }' is not assignable to parameter of type '{ a: number, b: number }'
        this.setState({
            c: 3
        });
    }
    // ...
}

Использование компонентов React более высокого порядка

На что реагируют компоненты высшего порядка? декоратор?

  • Поскольку компонент более высокого порядка в реакции — это, по сути, вызов функции более высокого порядка, мы можем использовать либо вызов функционального метода, либо декоратор для использования компонента более высокого порядка. Однако в TS компилятор проверит непротиворечивость подписи значения декоратора, и мы обычно возвращаем новый компонент в компоненте более высокого порядка, и значение применяемого компонента будет проверено.propsВносить изменения (дополнения, удаления) и т.д. Это приведет к сбою проверки согласованности подписи,TSБудет выдано сообщение об ошибке. Это порождает две проблемы:

Во-первых, возможно ли использовать синтаксис декоратора для вызова компонентов более высокого порядка?

  • Этот ответ также указывает на ситуацию: если этот компонент более высокого порядка правильно объявляет свою сигнатуру функции, то он должен использовать функциональный вызов, напримерwithRouter:
import { RouteComponentProps } from 'react-router-dom';
 
const App = withRouter(class extends Component<RouteComponentProps> {
    // ...
});
 
// 以下调用是ok的
<App />

В приведенном выше примере, когда мы объявляем компонент, мы аннотируем реквизиты компонента для маршрутизации.RouteComponentPropsТип структуры, но нам не нужно передавать его, когда мы вызываем компонент приложенияRouteComponentPropsговорят, что естьlocation,historyэквивалентно, потому чтоwithRouterЭта функция выравнивается с правильным объявлением типа.

Во-вторых, как насчет использования синтаксиса декоратора или компонентов более высокого порядка без сигнатур типов функций?


Как правильно объявить компоненты более высокого порядка?

  • То есть объявить, что свойства, введенные компонентом более высокого порядка, являются необязательными (черезPartialэтот сопоставленный тип) или объявить его в дополнительномinjectedв свойствах экземпляра компонента. Давайте сначала посмотрим на общее объявление компонента:
import { RouteComponentProps } from 'react-router-dom';
 
// 方法一
@withRouter
class App extends Component<Partial<RouteComponentProps>> {
    public componentDidMount() {
        // 这里就需要使用非空类型断言了
        this.props.history!.push('/');
    }
    // ...
});
 
// 方法二
@withRouter
class App extends Component<{}> {
    get injected() {
        return this.props as RouteComponentProps
    }
 
    public componentDidMount() {
        this.injected.history.push('/');
    }
    // ...

Как правильно объявить компоненты более высокого порядка?

interface IUserCardProps {
    name: string;
    avatar: string;
    bio: string;
 
    isAdmin?: boolean;
}
class UserCard extends Component<IUserCardProps> { /* ... */}

Вышеупомянутый компонент требует трех обязательных параметров атрибута: имя, аватар, био и isAdmin является необязательным. На этом этапе мы хотим объявить компонент более высокого порядка для передачи дополнительного логического свойства, видимого для UserCard, Нам также нужно использовать это значение в UserCard, затем нам нужно добавить это значение к типу его реквизита:

interface IUserCardProps {
    name: string;
    avatar: string;
    bio: string;
    visible: boolean;
 
    isAdmin?: boolean;
}
@withVisible
class UserCard extends Component<IUserCardProps> {
    render() {
        // 因为我们用到visible了,所以必须在IUserCardProps里声明出该属性
        return <div className={this.props.visible ? '' : 'none'}>...</div>
    }
}
 
function withVisiable(WrappedComponent) {
    return class extends Component {
        render() {
            return <WrappedComponent {..this.props}  visiable={true} />
        }
    }
}
  • Но таким образом у нас возникает проблема при вызове UserCard, потому что атрибут visible помечен как обязательный, поэтому TS выдаст ошибку. Это свойство вводится компонентом более высокого порядка, поэтому мы, конечно же, не можем запросить его повторную передачу.

Возможно, вы уже подумали об этом, объявить visible необязательным. Да, это решает проблему, связанную с необходимостью передачи visible при вызове компонентов. Это действительно решение проблемы. Но, как упоминалось в предыдущем вопросе, это решение должно иметь дело с компонентами более высокого порядка, которые не имеют объявления типа или объявлены неправильно.

Итак, это требует от нас правильного объявления компонентов более высокого порядка:

interface IVisible {
    visible: boolean;
}
 
 //排除 IVisible
function withVisible<Self>(WrappedComponent: React.ComponentType<Self & IVisible>): React.ComponentType<Omit<Self, 'visible'>> {
    return class extends Component<Self> {
        render() {
            return <WrappedComponent {...this.props}  visible={true} />
        }
    }
}

Как и выше, когда мы объявляем компонент более высокого порядка с помощью Visible, используя дженерики и вывод типа, мы делаем объявления типа для нового компонента, возвращаемого компонентом более высокого порядка, и реквизиты полученного компонента параметра.

Ссылаться на:

  • Вики больших парней в группе