Definitelytyped: @types/react의 setState에 대한 Pick 사용

에 만든 2017년 07월 25일  ·  53코멘트  ·  출처: DefinitelyTyped/DefinitelyTyped

PicksetState 유형에 사용되었다는 것을 이해합니다. 정의되지 않아야 하는 키에 대해 undefined 를 반환하면 React에서 키가 정의되지 않은 것으로 설정되기 때문입니다.

그러나 Pick 하면 다른 문제가 발생합니다. 첫째, 컴파일러 서비스의 자동 완성은 자동 완성을 위해 Pick 의 결과를 사용하고 완료를 요청할 때 Pick 의 결과에 아직 키가 포함되어 있지 않기 때문에 쓸모가 없습니다. 자동 완성을 원합니다. 그러나 콜백 인수로 setState 를 작성할 때 문제는 특히 나쁩니다.

  1. 키 목록은 첫 번째 return 문에서 파생됩니다. return 문에서 특정 키를 반환하지 않으면 키 목록을 강제로 never 로 재설정하지 않고 인수에서 읽을 수 없습니다. 여러 반환 문은 다른 키를 반환하는 경우 작성하기 어려울 수 있습니다. 특히 어딘가에 정의되지 않은 반환이 있는 경우(예: if (state.busy) { return } ).

    • 이 문제는 항상 스프레드(예: this.setState(input => ({ ...input, count: +input.count + 1 })) )의 입력을 사용하여 해결할 수 있지만 setState 는 콜백의 반환 값을 전달하므로 특히 더 큰 상태에서는 중복되고 최적화가 해제됩니다. Object.assign .

  2. 어떤 이유로 반환하는 형식이 입력 형식과 호환되지 않는 경우 Pick 는 키에 대해 never 를 선택하고 함수는 _anything_을 반환할 수 있습니다. 기존 키와 일치하는 키라도 any 를 값으로 효과적으로 허용합니다. 맞지 않으면 Pick 가 아닌 {} 대한 초과 속성으로 처리됩니다.
  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: "strictFunctionTypes"를 제외한 모든 "strict" 옵션이 활성화된 2.6.2
유형/반응: 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 당신이 바쁜 사람

좋습니다. 오늘 아침에 문제에 대해 생각하는 데 시간을 보낸 후, 귀하가 제안한 문제에 대한 몇 가지 "해결책"이 보입니다. 각각은 꽤 심각한 부작용이 있습니다.

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<> 를 사용할 수 있게 해주지

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) 오늘 완전히 사용할 수 있으며 setState 의 함수에 잠재적인 반환 값으로 | S 를 추가한 이유는 기억나지 않지만 그렇게 해야 하는 다른 이유를 이해할 수 없습니다.

(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> 설정해도 문제가 해결되지 않습니다. 어떤 이유로 잘못된 유형을 허용하는 객체 버전으로 계속 떨어집니다.

```t
인터페이스 상태 {
막대: 문자열;
foo: 숫자;
}

클래스 Foo는 React.Component<{}, State>를 확장합니다. {
공개 blah() {
this.setState((이전 상태) => ({막대: 1})); // 오류 없음 :/
}
}```

extends object 를 사용하여 함수를 거부하도록 강제할 수 있습니까?
강제?
2017년 7월 28일 금요일 0:00 Eric Anderson [email protected]에서 다음과 같이 썼습니다.

명확히 하기 위해 우리는 이에 대한 두 가지 오버로드를 가지고 있습니다...

setState(f: (prevState: Readonly , props: P) => Pick , callback?: () => any): void;setState(상태: 선택 , 콜백?: () => 모두): 무효;

둘 중 하나 또는 둘 다를 선택 대신 부분으로 설정당신의 문제를 해결하지 않습니다.

인터페이스 상태 {
막대: 문자열;
foo: 숫자;
}
클래스 Foo는 React.Component<{}, State>를 확장합니다. {
공개 blah() {
this.setState((이전 상태) => ({막대: 1})); // 오류 없음 :/
}
}```


스레드를 작성했기 때문에 이 메시지를 받는 것입니다.
이 이메일에 직접 답장하고 GitHub에서 확인
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-318388471 ,
또는 스레드 음소거
https://github.com/notifications/unsubscribe-auth/AAEdfeEJ_Ejfana14fRII1OZuS7qTuyuks5sSKX3gaJpZM4OiDrc
.

우리는 그것을 시도했습니다. 함수는 객체입니다.

에릭 엘 앤더슨
내 iPhone에서 보낸

이 변경으로 인해 상태 유형의 리팩토링이 중단된다는 관련 문제를 보고하기 위해 여기에 왔습니다.

예를 들어 https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx를 참조 something 를 리팩터링/이름 변경하는 경우 (https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx#L6), 해당 라인을 업데이트하고 https://github.com/tomduncalf/react-types-issue/ blob/master/Test.tsx#L14 , 하지만 https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsxsetState 호출에서 이것을 사용하지 않을 것입니다. #L20

그것이 제대로 작동 내가 만들기 위해 찾은 유일한 방법은 명시 적으로 입력하는 것입니다 내 객체 as IState 호출 할 때 setState 아주 쉽게 잊을 수있는 비트 불편 및 어떤을.

나는 변경의 근거에 대해 정확히 익숙하지 않지만 state로 작업할 때 상당히 중요한 방식으로 type-safety를 깨뜨린 것 같으므로 그것을 해결할 방법이 있다면 좋을 것입니다!

감사 해요,

예, 다시 Partial 를 사용하는 것이 관계 정보를 더 잘 유지하기 때문에 더 좋을 것이라고 생각합니다.

틀림없이 Pick 가 올바르게 리팩토링되지 않는 것은 개선할 수 있는 리팩토링 코드의 제한/버그이지만 여전히 Pickhttps:// github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -318385945 언급. 하지만 Partial 허용 것이다 undefined 곳은 undefined 있어야하는데, 사용자가 그 작업을 수행하지 않도록주의 할 필요가되지 않으며, Pick 무엇 때문에 수 있습니다 인터페이스를 따르지 않는 모든 유형은 유형 오류 대신 Pick 가 대신 빈 인터페이스를 생성하여 무엇이든 허용합니다.

https://github.com/DefinitelyTyped/DefinitelyTyped/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> 장소에서, 인텔리 당신을위한 키 이름을 제안합니다. 불행히도 키 이름은 속성 값이 "무언가 | 정의되지 않음"이라고 말하지만 컴파일할 때 더 잘 알 수 있습니다.

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: "strictFunctionTypes"를 제외한 모든 "strict" 옵션이 활성화된 2.6.2
유형/반응: 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 에서 설명한 문제를 확인합니다.

나는 그것이 항상 문제였다고 꽤 확신합니다.

나는 그것이 항상 문제가 되지 않았다는 것을 상당히 확신합니다. 2개월 이상 문제 없이 컴파일된 setState() 콜백에 여러 반환 문이 있는 프로젝트가 있습니다. 나는 2주 정도마다 NPM 의존성 업그레이드를 따라잡고 있으며 최신 버전의 유형/반응으로 업그레이드한 후 오늘 이 컴파일러 오류가 발생하기 시작했습니다. 이전 버전에서는 이 컴파일러 오류가 발생하지 않았습니다.

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 nullreturn undefined 또한 상태를 변경하지 않는 완벽하게 허용 반환 값은 (그들이 거치게됩니다되어 Object.assign 등을 변경하지 않는다 this.state ).

현재 서명은 둘 중 하나를 허용하지 않음을 상기시킵니다. 아마도 null 는 실수로 return 를 잊어버려서 나올 수 있는 것이 아니므로 최소한 반환 값으로 허용되어야 합니다.


어쨌든 서명이 모호한 경우 TypeScript는 소스 순서에서 첫 번째 return 문만 선택하고 이를 사용하여 일반 매개변수를 파생시키는 것 같습니다. 간단한 제네릭(예: Array 또는 Promise )에 대한 유형을 병합할 수 있는 것처럼 보이지만 컨텍스트 유형이 Pick 와 같은 매핑된 유형이면 병합하지 않습니다.

가장 최근 유형의 비기능 버전에서 일부 회귀가 보입니다. 특히 인수를 전달할 때 "상태" 인수 유형이 다음과 같이 변경되었습니다.

setState( state: Pick , callback?: () => any): void;

에게:

state: ((prevState: Readonly\ , props: P) => (Pick & Partial\ )) |

https://github.com/DefinitelyTyped/DefinitelyTyped/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"; }' 유형의 매개변수에 할당할 수 없습니다 & Partial Type '{ baseProp: "foobar"; }' 유형에 할당할 수 없습니다. '일부

상태 유형을 S &{ baseProp: string } 에서 { baseProp: string } 변경하면 오류도 사라집니다(S 유형을 지정하는 실제 클래스가 중단되지만).

그 흥미 롭군요. 변경 사항의 일부를 롤백하고 일부를 선택하게 되어 기쁩니다.

나는 당신이 보고한 것이 TS의 버그처럼 들린다고 말할 것입니다. 구체적으로:

유형 '{ baseProp: "foobar"; }'은(는) 'Partial' 유형에 할당할 수 없습니다.

괜찮은. S와 baseProp 사이에 관계가 없기 때문에 TS가 무슨 일이 일어나고 있는지 모를 수도 있습니다.

{ baseProp:number } 유형인 S를 전달할

아마도 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: P) => Pick & Partial\ ) 유형의 매개변수에 할당할 수 없습니다 (픽 &...'.유형 '{ baseProp: "foobar";
}'은(는) 'Partial\ ' 유형에 할당할 수 없습니다

아마도 그것을 구조화하는 좋은 방법이 아닐 것입니다. 결국 일부 자식 클래스는 기본 클래스의 변수와 충돌하는 상태 변수를 선언할 수 있습니다. 따라서 typescript는 그곳에서 무슨 일이 일어날지 확신할 수 없다고 불평하는 것이 옳을 수 있습니다. 그래도 해당 소품에 액세스하는 데 문제가 없다는 것이 이상합니다.

흠. 이것은 확실히 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가 수정되어 모든 올바른 매개변수를 제안하고 이제 잘못된 유형을 표시하지 않습니다.

setState 콜백에서 가능한 반환 값으로 null 를 추가하려고 했습니다( return; 만들지 않고 상태를 변경하고 싶지 않은 상태로 돌아갈 수 있는 것 또한 유효함), 그러나 그 대신 유형 추론을 완전히 포기하고 never 를 키로 선택했습니다 😢

현재로서는 실제로 상태 업데이트를 건너뛰고 싶은 콜백에서 return null! 있습니다. 이 반환의 never 유형은 TypeScript가 일반 유형 유추에 대한 반환을 무시하도록 합니다.

안녕 얘들아...

마지막 커밋이 내 문제를 해결했습니다.
빠른 답변 감사합니다 :)

어떤 버전에 수정 사항이 있습니까? npm에 게시되나요? @UselessPickles가 발견된 것처럼 setState의 콜백 버전이 반환 값에 속성 불일치가 있다고 알려주는 경우입니다.

최신 @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

numberList가 정의되지 않았거나 nullable이 아니더라도 null 어설션 연산자를 사용해야 합니다.

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

유형 감지가 개선될 때까지 이전 유형 정의를 유지하겠습니다. 그동안 immer 제품의 일반 매개변수에 대한 유형을 명시적으로 설명하겠습니다. 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

아니면 컴파일러에서 잘못된 기대를 하고 있을지도 모릅니다. 컴파일러가 내부에서 코드를 처리하기 때문에 컴파일러는 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

추가로 수정하면서 setState의 유형 정의에 S 를 추가하여 컴파일러가 상태 유형을 생성 초안으로 전달할 수 있도록 했습니다.

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에는 ConfirmlyTyped의 테스트 스크립트에 오류가 있습니다. 로컬에서 테스트하지 않았습니다. 그래서 지금 로컬에서 테스트 중입니다.

침수/생산 유형 정의는 다음과 같습니다.

/**
 * 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 Partial 대신 Pick 가 사용되는 이유에 대한 토론을 알려 주시겠습니까? 이것은 나에게 몇 시간 동안 슬픔을 안겨 줬고(콜백 버전이 아닌 일반 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 : 부분<{foo: string}> = { foo: 정의되지 않음 }

유효하지만 분명히 상태 업데이트의 결과는 불가능하다고 선언했음에도 불구하고 foo가 정의되지 않은 상태를 넣습니다.

따라서 부분은 Pick에 할당할 수 없습니다. 그리고 Pick은 당신의 타입이 거짓말을 하지 않도록 하는 정답입니다.

다음을 허용하지 않는다고 생각합니다.

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

매우 제한적입니다.

@Kovensky 의 해결 방법은 내가 아는 유일한 합리적인 해결 방법이지만 여전히 쓰기가

상당히 일반적인 패턴을 지원하기 위해 할 수 있는 일이 있습니까?

할 수 있는 유일한 것은 typesafety를 제거하는 것입니다.

누군가 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;

Tbh Kkeyof 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: "" };
});

매우 제한적입니다.

@Kovensky 의 해결 방법은 내가 아는 유일한 합리적인 해결 방법이지만 여전히 쓰기가

방금 이 정확한 문제에 부딪혔지만 스레드에서 Kovensky에 대한 참조를 볼 수 없습니다(누군가 사용자 이름을 변경했을 수 있습니까?). 누구든지 현재 권장되는 해결 방법을 알려줄 수 있습니까?

@timrobinson33 이 댓글은 해결 방법을 설명합니다. https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -351884578

그리고 우리가 후크를 사용하여 더 이상 이것에 대해 걱정할 필요가 없다는 것은 좋은 일입니다 🙂

@timrobinson33 이 댓글은 해결 방법을 설명합니다. #18365(댓글)

감사합니다. 결국 내 코드가 If 문이 외부에 있는 몇 개의 작은 setState 호출로 더 멋져 보인다고 생각했습니다. 비록 이것이 일부 경로가 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.