Definitelytyped: Об использовании Pick для @ types / response setState

Созданный на 25 июл. 2017  ·  53Комментарии  ·  Источник: DefinitelyTyped/DefinitelyTyped

Я понимаю, что Pick использовалось для типа setState потому что возврат undefined для ключа, который не должен быть неопределенным, приведет к тому, что React установит для ключа значение undefined.

Однако использование Pick вызывает другие проблемы. Во-первых, автозаполнение службы компилятора становится бесполезным, поскольку он использует результат Pick для автозаполнения, а когда вы запрашиваете завершение, результат Pick еще не содержит ключа, который вы можете хотите автозаполнение. Но проблемы особенно серьезны при написании setState с аргументом обратного вызова:

  1. Список ключей получен из вашего первого оператора return ; если вы не вернете конкретный ключ в своем операторе возврата, вы также не сможете прочитать его в аргументе, не принудительно сбросив список ключей до never . Может быть сложно написать несколько операторов возврата, если они возвращают разные ключи, особенно если у вас где-то есть неопределенный возврат (например, if (state.busy) { return } ).

    • Это можно обойти, всегда используя входные данные в спреде (например, this.setState(input => ({ ...input, count: +input.count + 1 })) ), но это избыточно и деоптимизация, особенно для больших состояний, поскольку setState передаст возвращаемое значение обратного вызова в Object.assign .

  2. Если по какой-то причине тип, который вы возвращаете, несовместим с типом ввода, Pick выберет never для своих ключей, и функция сможет вернуть _anything_. Даже ключи, которые совпадают с существующим ключом, позволяют эффективно использовать any в качестве значения - если оно не подходит, это просто не Pick ed и рассматривается как лишнее свойство для {} , который не проверяется.
  3. Если never выбран в качестве универсального аргумента по любой из причин, перечисленных выше, аргумент обратного вызова может фактически рассматриваться как аргумент объектной формы setState ; это приводит к тому, что аргументы обратного вызова набираются any вместо {} . Я не уверен, почему это не подразумеваемая ошибка.
interface State {
  count: string // (for demonstration purposes)
}

class Counter extends React.Component<{}, State> {
  readonly state: Readonly<State> = {
    count: '0'
  }

  render () {
    return React.createElement('span', { onClick: this.clicked }, this.state.count)
  }

  private readonly clicked = () => {
    this.setState(input => ({
      count: +input.count + 1 // not a type error
      // the setState<never>(input: Pick<State, never>) overload is being used
    }))
  }
}

Подводя итог, можно сказать, что в то время как использование Pick , несмотря на некоторые неудобства, помогает выявлять ошибки типа в форме setState без обратного вызова, оно полностью контрпродуктивно в форме обратного вызова; где он не только не выполняет намеченную задачу запрета undefined но также отключает любую проверку типов на входах или выходах обратного вызова.

Возможно, его следует изменить, по крайней мере, для формы обратного вызова, на Partial и надеяться, что пользователи знают, что не следует возвращать значения undefined , как это было сделано в более старых определениях.

Самый полезный комментарий

Недавнее «исправление» вызывает у меня проблемы с множественными операторами возврата в обратном вызове setState() .

Typescript: 2.6.2 со всеми включенными параметрами "strict", кроме "strictFunctionTypes"
типы / реакция: 16.0.30

Пример кода:

interface TestState {
    a: boolean,
    b: boolean
}

class TestComponent extends React.Component<{}, TestState> {
    private foo(): void {
        this.setState((prevState) => {
            if (prevState.a) {
                return {
                    b: true
                };
            }

            return {
                a: true
            };
        });
    }
}

Ошибка компилятора:

error TS2345: Argument of type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to parameter of type '((prevState: Readonly<TestState>, props: {}) => Pick<TestState, "b"> & Partial<TestState>) | (Pick<TestState, "b"> & Partial<TestState>)'.
  Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b"> & Partial<TestState>'.
    Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b">'.
      Property 'b' is missing in type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }'.

522         this.setState((prevState) => {
                          ~~~~~~~~~~~~~~~~

Все 53 Комментарий

Спасибо за ваш отзыв. Вы поднимаете очень интересный случай.

Мне нужно немного подумать о последствиях, прежде чем высказывать свое мнение.

В какой-то момент @ahejlsberg захотел трактовать опциональность иначе, чем | undefined . Таким образом, foo?: string будет означать, что foo либо не задано, либо это строка.

При чтении значения свойства разница не имеет значения в 99,9% случаев, но для записи, особенно в случае Partial<> , различие чрезвычайно важно.

К сожалению, это критическое изменение языка, поэтому мы должны дождаться версии 3.0 или оставить ее за флажком.

Если бы мы сказали «изменение», Partial<> стал бы чрезвычайно полезным для многих вместо моей нынешней догмы, которая заключается в немедленном отказе от его использования.

@ahejlsberg Я знаю, что вы занятой человек, насколько сложно это реализовать? так что undefined не является неявным присваиваемым значением?

Хорошо, потратив сегодня утром некоторое время на размышления о проблеме, я вижу несколько «решений» проблемы, которые вы предлагаете, каждое с довольно серьезными побочными эффектами.

1. Измените опцию ( interface State { foo?: string } ) на строку или не задано.

Пример:

interface State {
  foo: string;
  bar: string;
}

const bleh: Partial<State> = { foo: "hi" }; // OKAY
const bleh: Partial<State> = { foo: undefined; } // BREAK

Это технически решит проблему и позволит нам использовать Partial<> за счет того, что в 98% случаев это будет сложнее. Это ломает мир, поэтому на самом деле это было бы невозможно сделать до 3.x, и, как правило, очень немногие библиотеки / варианты использования действительно заботятся о различии между unset и undefined.

2. Просто переключитесь на частичное

Пример:

interface State {
  foo: string;
  bar: string;
}

setState(prevState => {foo: "hi"}); // OKAY
setState(prevState => {foo: undefined}); // OKAY BUT BAD!

3. Ничего не делать

Пример:

interface State {
  foo: string;
  bar: string;
}

// The following errors out because {foo: ""} can't be assigned to Pick<State, "foo" | "bar">
// Same with {bar: "" }
setState((prevState) => {
  if (randomThing) {
    return { foo: "" };
  }
  return { bar: "" };
});

// A work around for the few places where people need this type of thing, which works
setState((prevState) => {
  if (randomThing) {
    return { foo: "" } as State
  }
  return { bar: "" } as State
});

// This is fine because the following is still an error
const a = {oops: ""} as State

4. Добавьте вложенную логику к соединяемым типам литералов.

Прямо сейчас, когда у нас есть несколько путей возврата, компилятор просто объединяет все потенциальные ключи в один гигантский Pick<State, "foo" | "bar"> .

Однако изменение обратной совместимости должно позволить группировать литералы, например Pick<State, ("foo") | ("bar")> или в более сложном случае: Pick<State, ("foo" | "bar") | ("baz")>

Отсутствие скобок предполагает, что это всего лишь один набор значений, который является существующей функциональностью.

Теперь, когда мы пытаемся преобразовать {foo: number} в Pick<State, ("foo") | ("bar")> , мы можем добиться успеха. То же самое с {bar: number} .

Pick<State, ("foo") | ("bar")> также можно преобразовать в Partial<State> и в {foo: number} | {bar: number} .

Мой личный вывод

(1) и (2) просто невыполнимы. Один вносит сложность почти для всех, несмотря ни на что, а другой помогает людям создавать код, который компилируется, но явно неверен.

(3) сегодня полностью годен для использования, и хотя я не помню, почему я добавил | S в качестве потенциального возвращаемого значения функции в setState , я не могу понять какой-либо другой причины для этого.

(4) может обойти обходной путь в (3), но это дело разработчиков TypeScript, и, возможно, если мы заинтересуем @ahejlsberg , мы сможем это увидеть раньше, чем позже.

Это заставляет меня думать, что типы сегодня более правильные, чем если бы мы их изменили.

Проблема с подходом «ничего не делать» заключается в том, что компилятор ведет себя не так, как вы описываете, в случае, если вы действительно делаете ошибку реального типа. Если я изменю ваш пример, скажем, установив значение 0 вместо пустой строки:

interface State {
  foo: string;
  bar: string;
}

// The following does not error at all because the compiler picks `Pick<State, never>`
// The function overload of `setState` is not used -- the object overload is, and it
// accepts the function as it is accepting anything (`{}`).
setState((prevState) => {
  if (randomThing) {
    return { foo: 0 };
  }
  return { bar: 0 };
});

Я вижу сейчас. Вернуться к доске для рисования. Я собираюсь посмотреть, сможем ли мы найти для этого умное решение. Если мы не можем придумать что-то одно, то нам нужно оценить предлагаемое вами решение Partial<> . Важно учитывать, будет ли неожиданный undefined для значения более распространенным / раздражающим, чем неожиданный неправильный тип.

Чтобы уточнить, у нас есть две перегрузки для этого ...

setState<K extends keyof S>(f: (prevState: Readonly<S>, props: P) => Pick<S, K>, callback?: () => any): void;
setState<K extends keyof S>(state: Pick<S, K>, callback?: () => any): void;

Установка одного или обоих значений на Partial<S> вместо Pick<S, K> не решит вашу проблему. Он по-прежнему переходит к версии объекта, которая по какой-то причине принимает неправильный тип:

`` тс
interface State {
бар: строка;
foo: number;
}

class Foo расширяет React.Component <{}, State> {
public blah () {
this.setState ((prevState) => ({bar: 1})); // Нет ошибки: /
}
} `` `

Можем ли мы каким-то образом заставить его отклонить функцию, используя extends object
ограничение?
Пт, 28 июля 2017 г., в 0:00 Эрик Андерсон написал на [email protected] :

Чтобы уточнить, у нас есть две перегрузки для этого ...

setState(f: (prevState: только для чтения , реквизиты: P) => Выбрать , обратный вызов ?: () => any): void; setState(состояние: Выбрать , обратный вызов ?: () => любое): void;

Установка для одного или обоих из них значения Partial вместо Pickне решает вашу проблему.

interface State {
бар: строка;
foo: number;
}
class Foo расширяет React.Component <{}, State> {
public blah () {
this.setState ((prevState) => ({bar: 1})); // Нет ошибки: /
}
} `` `

-
Вы получаете это, потому что вы являетесь автором темы.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/DefinitiTyped/DefinitiTyped/issues/18365#issuecomment-318388471 ,
или отключить поток
https://github.com/notifications/unsubscribe-auth/AAEdfeEJ_Ejfana14fRII1OZuS7qTuyuks5sSKX3gaJpZM4OiDrc
.

Мы пробовали это. Функции - это объекты.

Эрик Л. Андерсон
отправлено из моего Айфона

Я пришел сюда, чтобы сообщить о связанной проблеме, а именно о том, что рефакторинг типа состояния нарушен этим изменением.

Например, см. Https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx - если вы откроете это в VS Code и F2 для рефакторинга / переименования something в строке 6 (https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx#L6), он обновит эту строку и https://github.com/tomduncalf/react-types-issue/ blob / master / Test.tsx # L14 , но пропустит использование этого в вызове setState на https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx # L20

Единственный способ заставить его работать правильно - это явно ввести мой объект as IState при вызове setState , что немного неудобно и довольно легко забыть.

Я точно не знаком с обоснованием этого изменения, но похоже, что при работе с состоянием безопасность типов в значительной степени нарушена, поэтому было бы здорово, если бы был способ решить эту проблему!

Спасибо,
Том

Да, опять же, я считаю, что было бы лучше использовать Partial поскольку он лучше сохраняет эту информацию о взаимосвязи.

Возможно, неправильный рефакторинг Pick является ограничением / ошибкой кода рефакторинга, который можно улучшить, но я до сих пор не верю, что Pick обеспечивает безопасность типа, которую https: // github.com/DefinitiTyped/DefinitiTyped/issues/18365#issuecomment -318385945 упоминается. В то время как Partial допускает undefined где undefined не должно быть, и тогда пользователь должен быть осторожен, чтобы не делать этого, Pick разрешает все, потому что любой тип, который не соответствует интерфейсу, вызовет вместо ошибки типа Pick чтобы вместо этого сгенерировать пустой интерфейс, который принимает что угодно.

Что касается https://github.com/DefinitiTyped/DefinitiTyped/issues/18365#issuecomment -318388471, может ли это быть ошибкой в ​​проверке лишних свойств? Или это было проверено до того, как проверка лишних свойств была сделана более строгой в 2.3 или 2.4 (я забыл)?

Еще одна проблема, которую я только что заметил, заключается в том, что если у компонента либо нет типа состояния (т.е. только один параметр типа для React.Component ), либо тип состояния пуст ( {} ), Typescript все равно разрешит вызывает setState без выдачи ошибки, что мне кажется неправильным - см. https://github.com/tomduncalf/react-types-issue/blob/master/Test2.tsx

Решение, использующее частичные звуки, для меня предпочтительнее - я мог бы попробовать исправить его сам и посмотреть, как это работает.

Спасибо,
Том

Всем привет!
Не знаю почему, но если я присоединю объявления setState к одному объявлению:

setState<K extends keyof S>(state: ((prevState: Readonly<S>, props: P) => Pick<S, K>) | Pick<S, K>, callback?: () => any): void;

У меня все работает так, как ожидалось:

import * as React from 'react';

export class Comp extends React.Component<{}, { foo: boolean, bar: boolean }> {
  public render() {
    this.handleSomething();
    return null;
  }

  private handleSomething = () => {
    this.setState({ foo: '' }); // Type '""' is not assignable to type 'boolean'.
    this.setState({ foo: true }); // ok!
    this.setState({ foo: true, bar: true }); // ok!
    this.setState({}); // ok!
    this.setState({ foo: true, foo2: true }); // Object literal may only specify
    // known properties, and 'foo2' does not exist in type
    this.setState(() => ({ foo: '' })); // Property 'foo' is missing in type '() => { foo: string; }'.
    this.setState(() => ({ foo: true })); // ok!
    this.setState(() => ({ foo: true, bar: true })); // ok!
    this.setState(() => ({ foo: true, foo2: true })); // Property 'foo' is missing in type
    // '() => { foo: true; foo2: boolean; }'
    this.setState(() => ({ foo: '', foo2: true })); // Property 'foo' is missing in
    // type '() => { foo: string; foo2: boolean; }'.
    this.setState(() => ({ })); // ok!
  };
}

Может ли это измениться достаточно, чтобы исправить исходную проблему?

@mctep Хорошая находка.

И если я возьму то, что вы сделали, и немного увеличу его до Partial<S> & Pick<S, K> вместо Pick<S, K> местами, intellisense предложит вам ключевые имена. К сожалению, в названиях ключей указано, что значение свойства - «something | undefined», но когда вы его компилируете, он знает лучше:

declare class Component<P, S> {
    setState<K extends keyof S>(state: ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>)) | (Partial<S> & Pick<S, K>), callback?: () => any): void;
}

interface State {
    foo: number;
    bar: string;
    baz?: string;
}

class Foo extends Component<{}, State> {
    constructor() {
        super();
        this.setState(() => { // error
            return {
                foo: undefined
            }
        });
        this.setState({ // error
            foo: undefined
        })
        this.setState({
            foo: 5,
            bar: "hi",
            baz: undefined
        })
    }
}

Я внесу это изменение сегодня позже

Недавнее «исправление» вызывает у меня проблемы с множественными операторами возврата в обратном вызове setState() .

Typescript: 2.6.2 со всеми включенными параметрами "strict", кроме "strictFunctionTypes"
типы / реакция: 16.0.30

Пример кода:

interface TestState {
    a: boolean,
    b: boolean
}

class TestComponent extends React.Component<{}, TestState> {
    private foo(): void {
        this.setState((prevState) => {
            if (prevState.a) {
                return {
                    b: true
                };
            }

            return {
                a: true
            };
        });
    }
}

Ошибка компилятора:

error TS2345: Argument of type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to parameter of type '((prevState: Readonly<TestState>, props: {}) => Pick<TestState, "b"> & Partial<TestState>) | (Pick<TestState, "b"> & Partial<TestState>)'.
  Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b"> & Partial<TestState>'.
    Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b">'.
      Property 'b' is missing in type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }'.

522         this.setState((prevState) => {
                          ~~~~~~~~~~~~~~~~

Также наблюдается проблема, описанная @UselessPickles в результате этого изменения.

Я почти уверен, что это всегда было проблемой.

Я более чем достаточно уверен, что это не всегда было проблемой. У меня есть проект с несколькими операторами возврата в обратных вызовах setState() , который компилируется без проблем в течение 2+ месяцев. Я следил за обновлениями зависимостей NPM каждые 2 недели или около того, и эта ошибка компилятора только начала происходить для меня сегодня после обновления до последней версии types / react. Предыдущая версия не вызывала этой ошибки компилятора.

Это проблема с момента перехода с Partial на Pick. Обходной путь - предоставить список ключей, которые вы собираетесь вернуть, универсальному параметру setState , но тогда вы будете вынуждены всегда возвращать все ключи ...

Что я делаю в этих случаях, так это либо всегда возвращаю все ключи, при этом для немодифицированных ключей установлено значение key: prevState.key , либо возвращаю спред с помощью prevState ( { ...prevState, newKey: newValue } ).

Может быть, у меня действительно есть более конкретный крайний случай в моем коде, который раньше работал случайно? Фактический пример из моего проекта больше похож на этот, где он либо возвращает пустой объект (чтобы не изменять какое-либо состояние), либо возвращает непустой объект:

interface TestState {
    a: boolean,
    b: boolean
}

class TestComponent extends React.Component<{}, TestState> {
    private foo(newValue: boolean): void {
        this.setState((prevState) => {
            if (prevState.a === newValue) {
                // do nothing if there's no change
                return { };
            }

            return {
                a: newValue,
                // force "b" to false if we're changing "a"
                b: false
            };
        });
    }
}

return null и return undefined также являются вполне приемлемыми возвращаемыми значениями, если состояние не меняется (они пройдут через Object.assign и поэтому не изменят this.state ).

Напоминает, что текущая подпись не позволяет ни то, ни другое. Может быть, null следует разрешить в качестве возвращаемого значения, по крайней мере, поскольку это не то, что может возникнуть из-за того, что случайно забыли return .


В любом случае, в случаях, когда подпись неоднозначна, TypeScript, кажется, выбирает только первый оператор return в исходном порядке и использует его для получения общих параметров. Кажется, что можно объединить типы для простых универсальных шаблонов (например, Array или Promise ), но он никогда не объединяет их, если контекстный тип является отображаемым типом, например Pick .

Я вижу некоторые регрессы в нефункциональной версии с самыми последними типами. В частности, тип аргумента "состояние" при передаче аргумента изменился с:

setState( состояние: Выбрать , обратный вызов ?: () => любое): void;

к:

состояние: ((prevState: Readonly \ , props ( Выбрать & частичное \),

https://github.com/DefinitiTyped/DefinitiTyped/commit/62c2219a6ed6dc34ea969b8c2c87a41d31002660#diff -96b72df8b13a8a590e4f160cbc51f40c

Добавление & Partial<S> похоже, ломает то, что раньше работало.

Вот минимальное воспроизведение:

export abstract class ComponentBaseClass<P, S = {}> extends React.Component<P, S & { baseProp: string }>
{
    foo()
    {
        this.setState( { baseProp: 'foobar' } );
    }
}

это не с ошибкой:

(429,18): аргумент типа '{baseProp: "foobar"; } 'не может быть назначен параметру типа' ((prevState: Readonly Type '{baseProp: "foobar";}' не может быть назначен типу 'Pick & Partial Type' {baseProp: "foobar";} 'не может быть назначен для тип 'Частичный

Изменение типа состояния с S &{ baseProp: string } на просто { baseProp: string } также устранит ошибку (хотя это приведет к поломке реальных классов, определяющих тип S).

Это интересно. Я счастлив отменить частичное и выбрать часть изменения

Я скажу, что то, что вы сообщаете, звучит как ошибка в TS, а именно:

Введите '{baseProp: "foobar"; } 'не может быть присвоено типу' Частичный

Хорошо. Может быть, TS не может знать, что случилось, потому что нет никакой связи между S и baseProp.

Можно передать S типа { baseProp: number }, и в этом случае правильно, что вы не можете присвоить строку baseProp.

Возможно, если S расширил версию baseProp?

Раньше я пробовал что-то подобное:

interface BaseState_t
{
    baseProp: string
}

export abstract class ComponentBaseClass<P, S extends BaseState_t> extends React.Component<P, S >
{
    foo()
    {
        this.state.baseProp;
        this.setState( { baseProp: 'foobar' } );
    }
}

пока доступ в порядке, вызов setState имеет ту же ошибку:

Аргумент типа '{baseProp: "foobar"; } 'не может быть назначен параметру типа' ((prevState: Readonly \ , props (Выбрать & ... '.Введите '{baseProp: "foobar";
} 'не может быть назначен типу' Частичный \ '.

Вероятно, это не лучший способ его структурировать - в конце концов, какой-то дочерний класс может объявлять переменные состояния, которые сталкиваются с переменными базового класса. Так что машинописный текст может быть прав, если жаловаться на то, что он не может быть уверен, что там произойдет. Странно, что у него нет проблем с доступом к этим реквизитам.

Хм. Сейчас это определенно похоже на ошибку в TS.

Я поиграю с этим сегодня и, возможно, вытащу & Partial.

Я добавлю это как тестовый пример и попробую другую идею, чтобы сделать intellisense счастливым.

Привет,
Такая же проблема здесь ...

export interface ISomeComponent {
    field1: string;
    field2: string;
}

interface SomeComponentState {
    field: string;
}

export class SomeComponent<
    TProps extends ISomeComponent = ISomeComponent,
    TState extends SomeComponentState = SomeComponentState> extends React.Component<TProps, TState>{

    doSomething() {
        this.setState({ field: 'test' });
    }

    render() {
        return (
            <div onClick={this.doSomething.bind(this)}>
                {this.state.field}
            </div>
        );
    }
}

Ошибка в setState:
TS2345 (TS) Argument of type '{ field: "test"; }' is not assignable to parameter of type '((prevState: Readonly<TState>, props: TProps) => Pick<TState, "field"> & Partial<TState>) | (Pick...'. Type '{ field: "test"; }' is not assignable to type 'Pick<TState, "field"> & Partial<TState>'. Type '{ field: "test"; }' is not assignable to type 'Partial<TState>'.

Ошибка только начала происходить после этого изменения. В версии 16.0.10 все работало нормально.

У Typescript есть несколько связанных проблем, они закрыли их как работающие как задумано:

https://github.com/Microsoft/TypeScript/issues/19388

Я привел здесь несколько примеров: https://gist.github.com/afarnsworth-valve/93d1096d1410b0f2efb2c94f86de9c84

Хотя поведение по-прежнему кажется странным. В частности, вы можете присвоить переменной, типизированной как базовый класс, без приведения, а затем без проблем выполнять те же вызовы с этим. Эта проблема, кажется, предполагает, что присвоение и сравнение - это две разные вещи.

Не забыл вас, ребята. Скоро исправлю

Указанный PR должен решить эту проблему. Я также добавил тесты, которые должны защитить от повторного взлома этого крайнего случая.

@ericanderson Спасибо за быстрый ответ :)

Есть ли у нового исправления какие-либо ограничения / недостатки?

Ничего из того, о чем я сейчас знаю. Мне удалось сохранить intellisense (на самом деле лучше, чем раньше, поскольку он решает некоторые крайние случаи).

Чтобы уточнить, решение & Partial<S> заключалось в том, чтобы обмануть intellisense, чтобы выявить возможные параметры, но он сделал это, заявив, что они равны X | undefined . Это, конечно, не скомпилируется, но это немного сбивает с толку. Получение | S исправляет intellisense, так что он предлагает все правильные параметры, и теперь он не показывает ложный тип.

Я попытался добавить null в качестве возможного возвращаемого значения из обратного вызова setState (что вы можете вернуть в состояние, в котором вы не хотите изменять какое-либо состояние, не делая return; также действителен), но вместо этого вывод типа полностью отказался и выбрал never в качестве ключей 😢

На данный момент в обратных вызовах, где я действительно хочу пропустить состояние обновления, я использовал return null! . Тип never этого возврата заставляет TypeScript игнорировать возврат для вывода универсального типа.

Привет, народ...

Последняя фиксация устранила мою проблему.
Спасибо за быстрый ответ :)

В какой версии есть исправление? Это опубликовано в npm? Я попал в случай, когда версия обратного вызова setState сообщает мне о несоответствии свойств в возвращаемом значении, точно так же, как обнаружено @UselessPickles .

Должно быть хорошо на последних @ types / react

Я исправил это для 15 и 16 серий

import produce from 'immer';

interface IComponentState
{
    numberList: number[];
}

export class HomeComponent extends React.Component<ComponentProps, IComponentState>

Определение старого типа React ..

// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
// Also, the ` | S` allows intellisense to not be dumbisense
setState<K extends keyof S>(
    state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> | S)) | (Pick<S, K> | S),
    callback?: () => void
): void;

..выводит эту ошибку:

screen shot 2018-05-18 at 2 36 44 pm

Я попробовал это предложение:

setState<K extends keyof S>(
    state:
        ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>))
        | (Partial<S> & Pick<S, K>),
    callback?: () => any
): void;

Intellisense теперь может определять свойство из состояния React. Однако теперь воспринимаемое свойство воспринимается как possibly undefined .

screen shot 2018-05-18 at 2 37 32 pm

Придется использовать оператор утверждения NULL, даже если numberList не является неопределенным и не допускающим значения NULL:

screen shot 2018-05-18 at 2 38 51 pm

Я просто останусь на старом определении типа, пока не улучшится распознавание типов. А пока я просто явно укажу тип в универсальном параметре immer productions. produce<IComponentState> легче рассуждать, чем list! .

screen shot 2018-05-18 at 2 51 43 pm

Ваша первая ошибка связана с тем, что вы ничего не возвращаете. SetState работает не так.

Он работает, даже если переменная не возвращается в продукте. Я просто последовал примеру здесь (не TypeScript):

https://github.com/mweststrate/immer

onBirthDayClick2 = () => {
    this.setState(
        produce(draft => {
            draft.user.age += 1
            // no need to return draft
        })
    )
}

Единственное, что мешает TypeScript запускать этот код, - это неправильный вывод типа из определения типа React. Определение типа сообщает об ошибке numberList does not exist on type Pick<IComponentState, never> . Просто можно устранить ошибку компиляции, явно передав тип в общий параметр продукта, то есть produce<IComponentState> .

Я даже попытался вернуть переменную в продукте и посмотреть, поможет ли это определение типа React вывести тип (хотя проблема с цыпленком и яйцом), но все же определение типа React не может определить правильный тип состояния. Следовательно, intellisense для черновика не отображается:

screen shot 2018-05-18 at 10 38 04 pm

Или, возможно, у меня неправильные ожидания от компилятора :) Компилятор не может создать тип для переменной draft на основе типа setState, поскольку компилятор обрабатывает код изнутри. Однако предлагаемое определение типа каким-то образом заставило меня подумать, что компилятор может обрабатывать код извне, что он может выбрать лучший тип, который он может передать из внешнего кода ( setState ) во внутренний код ( produce ).

setState<K extends keyof S>(
    state:
        ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>))
        | (Partial<S> & Pick<S, K>),
    callback?: () => any
): void;

С помощью приведенного выше определения типа компилятор может определить, что черновик имеет свойство numberList . Он определяет это как possibly undefined хотя:

image

Поработав дальше, я сделал компилятор способным передавать тип состояния для создания черновика, добавив S в определение типа setState:

setState<K extends keyof S>(
    state:
        ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K> & S))
        | (Partial<S> & Pick<S, K>),
    callback?: () => any
): void;

Код сейчас компилируется :)

screen shot 2018-05-18 at 11 21 03 pm

Ваша ошибка не связана с setState, ваша ошибка находится внутри продукта. Каково ваше определение типа продукции?

В моем PR выше есть ошибка в тестовом сценарии ОпределенноТипед, я не тестировал локально. Так что сейчас тестирую локально.

Вот определение типа погружения / производства.

/**
 * Immer takes a state, and runs a function against it.
 * That function can freely mutate the state, as it will create copies-on-write.
 * This means that the original state will stay unchanged, and once the function finishes, the modified state is returned.
 *
 * If the first argument is a function, this is interpreted as the recipe, and will create a curried function that will execute the recipe
 * any time it is called with the current state.
 *
 * <strong i="7">@param</strong> currentState - the state to start with
 * <strong i="8">@param</strong> recipe - function that receives a proxy of the current state as first argument and which can be freely modified
 * <strong i="9">@param</strong> initialState - if a curried function is created and this argument was given, it will be used as fallback if the curried function is called with a state of undefined
 * <strong i="10">@returns</strong> The next state: a new state, or the current state if nothing was modified
 */
export default function<S = any>(
    currentState: S,
    recipe: (this: S, draftState: S) => void | S
): S

// curried invocations with default initial state
// 0 additional arguments
export default function<S = any>(
    recipe: (this: S, draftState: S) => void | S,
    initialState: S
): (currentState: S | undefined) => S
// 1 additional argument of type A
export default function<S = any, A = any>(
    recipe: (this: S, draftState: S, a: A) => void | S,
    initialState: S
): (currentState: S | undefined, a: A) => S
// 2 additional arguments of types A and B
export default function<S = any, A = any, B = any>(
    recipe: (this: S, draftState: S, a: A, b: B) => void | S,
    initialState: S
): (currentState: S | undefined, a: A, b: B) => S
// 3 additional arguments of types A, B and C
export default function<S = any, A = any, B = any, C = any>(
    recipe: (this: S, draftState: S, a: A, b: B, c: C) => void | S,
    initialState: S
): (currentState: S | undefined, a: A, b: B, c: C) => S
// any number of additional arguments, but with loss of type safety
// this may be alleviated if "variadic kinds" makes it into Typescript:
// https://github.com/Microsoft/TypeScript/issues/5453
export default function<S = any>(
    recipe: (this: S, draftState: S, ...extraArgs: any[]) => void | S,
    initialState: S
): (currentState: S | undefined, ...extraArgs: any[]) => S

// curried invocations without default initial state
// 0 additional arguments
export default function<S = any>(
    recipe: (this: S, draftState: S) => void | S
): (currentState: S) => S
// 1 additional argument of type A
export default function<S = any, A = any>(
    recipe: (this: S, draftState: S, a: A) => void | S
): (currentState: S, a: A) => S
// 2 additional arguments of types A and B
export default function<S = any, A = any, B = any>(
    recipe: (this: S, draftState: S, a: A, b: B) => void | S
): (currentState: S, a: A, b: B) => S
// 3 additional arguments of types A, B and C
export default function<S = any, A = any, B = any, C = any>(
    recipe: (this: S, draftState: S, a: A, b: B, c: C) => void | S
): (currentState: S, a: A, b: B, c: C) => S
// any number of additional arguments, but with loss of type safety
// this may be alleviated if "variadic kinds" makes it into Typescript:
// https://github.com/Microsoft/TypeScript/issues/5453
export default function<S = any>(
    recipe: (this: S, draftState: S, ...extraArgs: any[]) => void | S
): (currentState: S, ...extraArgs: any[]) => S
/**
 * Automatically freezes any state trees generated by immer.
 * This protects against accidental modifications of the state tree outside of an immer function.
 * This comes with a performance impact, so it is recommended to disable this option in production.
 * It is by default enabled.
 */
export function setAutoFreeze(autoFreeze: boolean): void

/**
 * Manually override whether proxies should be used.
 * By default done by using feature detection
 */
export function setUseProxies(useProxies: boolean): void

@ericanderson, не могли бы вы указать мне на обсуждение того, почему Pick используется вместо Partial ? Это вызывало у меня многочасовое горе (используя простой setState(obj) , а не версию обратного вызова), и сейчас я использую this.setState(newState as State) в качестве обходного пути. Я просто хочу понять, почему это было изменено, потому что мне что-то не хватает.

Привет @ericanderson!

У меня проблема с последним определением.

Вкратце мой вариант использования выглядит так:

interface AppState {
  valueA: string;
  valueB: string;
  // ... something else
} 
export default class App extends React.Component <{}, AppState> {
  onValueAChange (e:React.ChangeEvent<HTMLInputElement>) {
    const newState: Partial<AppState> = {valueA: e.target.value}
    if (this.shouldUpdateValueB()) {
      newState.valueB = e.target.value;
    }
    this.setState(newState); // <-- this leads to a compiling error
  }
  // ... other methods
}

Сообщение об ошибке выглядит примерно так:

Argument of type 'Partial<AppState>' is not assignable to parameter of type 'AppState | ((prevState: Readonly<AppState>, props: {}) => AppState | Pick<AppState, "valueA" | "v...'.
  Type 'Partial<AppState>' is not assignable to type 'Pick<AppState, "valueA" | "valueB" | "somethingElse">'.
    Types of property 'valueA' are incompatible.
      Type 'string | undefined' is not assignable to type 'string'.
        Type 'undefined' is not assignable to type 'string'.

Похоже, что Partial<AppState> несовместимо с подписью setState . Конечно, я могу решить эту проблему с помощью утверждения типа, например

this.setState(newState as Pick<AppState, 'valueA' | 'valueB'>)

но это не идеально, потому что:
1) этот синтаксис очень подробный
2) что более важно, утверждение типа может нарушать мои фактические данные. Например, newState as Pick<AppState, 'somethingElse'> также проходит проверку, хотя и не соответствует моим данным.

Я думаю Частичнодолжен как-то быть совместим с Pick, поскольку частичнаяпросто выбирает неопределенное количество ключей из T. Я не уверен, что правильно понимаю. В любом случае идеальным использованием с моей точки зрения должно быть то, что я могу передать переменную типа Partialпрямо в setState.

Не могли бы вы рассмотреть мое предложение или указать на мое недоразумение, если оно есть? Спасибо!

Во-первых, это действительно старое изменение. Так что, если кто-то недавно это не изменил, вы, вероятно, лаете не на то дерево.

Тем не менее. Частично допускает неопределенные значения.

const a: Partial <{foo: string}> = {foo: undefined}

a действителен, но очевидно, что результат этого обновления вашего состояния ставит ваше состояние с foo как undefined, даже если вы заявили, что это невозможно.

Следовательно, частичное не может быть присвоено Пик. И Pick - правильный ответ, чтобы убедиться, что ваши типы не лгут

Я чувствую, что не позволяю:

setState((prevState) => {
  if (prevState.xyz) {
    return { foo: "" };
  }
  return { bar: "" };
});

супер ограничительный.

Обходной путь

Можно ли что-нибудь сделать для поддержки этого (я бы сказал) довольно распространенного шаблона?

Единственное, что можно сделать, это убрать безопасность типов.

Может кто-нибудь объяснить причину Pick<S, K> | S | null ?

        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;

Я даже не понимаю, почему указанная выше подпись работает даже для частичных обновлений состояния, поскольку K определяется как keyof S поэтому Pick<S, K> сути воссоздает S ?

Разве Partial<S> | null тоже не должны выполнять эту работу?

        setState(
            state: ((prevState: Readonly<S>, props: Readonly<P>) => (Partial<S> | null)) | (Partial<S> | null),
            callback?: () => void
        ): void;

Может кто-нибудь объяснить ...

Это было ясно объяснено несколькими ответами.

setState((prevState) => {
  if (prevState.xyz) {
    return { foo: "" };
  }
  return { bar: "" };
});

супер ограничительный.

Обходной путь

Я только что столкнулся с этой проблемой, но я не вижу упоминания о Ковенском в теме (может быть, кто-то изменил свое имя пользователя?). Может ли кто-нибудь указать мне на рекомендуемый в настоящее время обходной путь

@ timrobinson33 Этот комментарий объясняет обходной путь https://github.com/DefinitiTyped/DefinitiTyped/issues/18365#issuecomment -351884578

И хорошо, что с крючками нам больше не нужно об этом беспокоиться 🙂

@ timrobinson33 Этот комментарий объясняет обходной путь # 18365 (комментарий)

Большое спасибо за это. В конце концов, я подумал, что мой код выглядел лучше в виде нескольких небольших вызовов setState с операторами If за их пределами, хотя это означает, что некоторые пути будут вызывать setState более одного раза.

Я предполагаю, что это на самом деле похоже на то, как мы работаем с хуками, рассматривая состояние как несколько мелких вещей, которые мы обновляем независимо.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги
bleepcoder.com использует общественно лицензированную информацию GitHub для предоставления решений разработчикам по всему миру. Мы не аффилированы с GitHub, Inc. или любым другим разработчиком, использующим GitHub для своих проектов. Мы не размещаем видео или изображения на наших серверах. Все права принадлежат их соответствующим владельцам.
Источник для этой страницы: Источник

Популярные языки программирования
Популярные проекты GitHub
Больше проектов GitHub

© 2024 bleepcoder.com - Contact
Made with in the Dominican Republic.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.