أتفهم أن Pick
تم استخدامه لنوع setState
لأن إرجاع undefined
لمفتاح لا يجب أن يكون غير معرّف سيؤدي إلى ضبط المفتاح على undefined بواسطة React.
ومع ذلك ، فإن استخدام Pick
يسبب مشاكل أخرى. أولاً ، يصبح الإكمال التلقائي لخدمة المترجم عديم الفائدة ، لأنه يستخدم نتيجة Pick
للإكمال التلقائي ، وعندما تطلب الإكمال ، لا تحتوي نتيجة Pick
حتى الآن على المفتاح الذي قد تحتاجه تريد الإكمال التلقائي. لكن المشاكل تكون سيئة بشكل خاص عند كتابة setState
باستخدام وسيطة رد نداء:
return
؛ إذا لم تعيد مفتاحًا معينًا في بيان الإرجاع ، فلن تتمكن أيضًا من قراءته في الوسيطة دون إجبار قائمة المفاتيح على إعادة التعيين إلى never
. قد يكون من الصعب كتابة عبارات الإرجاع المتعددة إذا كانت تُرجع مفاتيح مختلفة ، خاصةً إذا كان لديك عائد غير محدد في مكان ما (على سبيل المثال if (state.busy) { return }
).this.setState(input => ({ ...input, count: +input.count + 1 }))
) ولكن هذا زائد عن الحاجة وإلغاء التحديد ، خاصة بالنسبة للولايات الأكبر ، حيث أن setState
سيمرر قيمة الإرجاع لرد الاتصال إلى Object.assign
.Pick
never
لمفاتيحه ، وسيسمح للوظيفة بإرجاع _ أي شيء_. حتى المفاتيح التي تتطابق مع مفتاح موجود تسمح فعليًا any
كقيمة - إذا لم تكن مناسبة ، فهي ليست فقط Pick
ed ، ويتم التعامل معها على أنها خاصية زائدة مقابل {}
، الذي لم يتم فحصه.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
، كما حدث في التعريفات القديمة.
شكرا لملاحظاتك. هذه قضية مثيرة للاهتمام للغاية تطرحها.
أحتاج إلى التفكير في الآثار قليلًا قبل أن ألتزم برأي.
في مرحلة ما ، أراد ahejlsberg معاملة الاختيارية بشكل مختلف عن | undefined
. وبالتالي فإن foo?: string
يعني أن foo إما غير مضبوط أو سلسلة.
عند قراءة قيمة خاصية ، يكون الفرق غير ذي صلة بنسبة 99.9٪ من الوقت ، ولكن بالنسبة للكتابات ، خاصة في حالة Partial<>
، يكون التمييز مهمًا للغاية.
لسوء الحظ ، يعد هذا تغييرًا مفاجئًا في اللغة ، لذا يجب أن ننتظر 3.0 أو نضعها خلف العلم.
إذا قلنا التغيير ، يصبح Partial<>
مفيدًا للغاية للكثيرين بدلاً من عقيدتي الحالية وهي رفض استخدامه على مرأى من الجميع.
ahejlsberg أعلم أنك رجل مشغول ، ما مدى صعوبة التنفيذ؟ بحيث لا يكون غير المعرف قيمة ضمنية قابلة للتخصيص؟
حسنًا ، بعد قضاء بعض الوقت هذا الصباح في التفكير في المشكلة ، أرى بعض "الحلول" للمشكلة التي تقترحها ، ولكل منها بعض الآثار الجانبية الثقيلة جدًا.
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 vs unefined.
مثال:
interface State {
foo: string;
bar: string;
}
setState(prevState => {foo: "hi"}); // OKAY
setState(prevState => {foo: undefined}); // OKAY BUT BAD!
مثال:
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
في الوقت الحالي ، عندما يكون لدينا مسارات عودة متعددة ، يقوم المترجم فقط بكل المفاتيح المحتملة في 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>
لا يحل مشكلتك. لا يزال يسقط إلى إصدار الكائن ، والذي يقبل النوع الخطأ لسبب ما:
""
حالة الواجهة {
شريط: خيط ؛
foo: رقم ؛
}
يمتد class Foo إلى React.Component <{}، State> {
العامة بلاه () {
this.setState ((prevState) => ({bar: 1})) ؛ // لا يوجد خطأ: /
}
} ``
هل يمكننا بطريقة ما إجبارها على رفض دالة باستخدام extends object
قيد؟
في الجمعة ، 28 يوليو 2017 ، الساعة 0:00 ، كتب Eric Anderson [email protected] :
للتوضيح ، لدينا حملين زائدين لهذا ...
setState
(f: (prevState: Readonly ، props: P) => Pick، callback ؟: () => any): void؛ setState(state: Pick، callback ؟: () => any): void؛تعيين أحدهما أو كليهما على جزئي
بدلاً من انتقاءلا يحل مشكلتك.حالة الواجهة {
شريط: خيط ؛
foo: رقم ؛
}
يمتد class Foo إلى React.Component <{}، State> {
العامة بلاه () {
this.setState ((prevState) => ({bar: 1})) ؛ // لا يوجد خطأ: /
}
} ``-
أنت تتلقى هذا لأنك قمت بتأليف الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/DefinitelyTyped/DefinitelyTyped/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/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>
في الأماكن ، يقترح intellisense أسماء رئيسية لك. لسوء الحظ ، تشير أسماء المفاتيح إلى أن قيمة الخاصية هي "شيء | غير محدد" ولكن عندما تقوم بتجميعها ، فإنها تعرف بشكل أفضل:
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 مع تمكين كافة الخيارات "المقيدة" باستثناء "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()
تم تجميعها بدون مشكلة لمدة شهرين أو أكثر. لقد كنت أواكب ترقيات الاعتماد على 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 null
و return undefined
هي أيضًا قيم إرجاع مقبولة تمامًا لعدم تغيير الحالة (ستمر خلال Object.assign
وبالتالي لا تقم بأي تغييرات على this.state
).
يذكرني أن التوقيع الحالي لا يسمح بأي منهما. ربما يجب السماح بـ null
كقيمة مرتجعة ، على الأقل ، لأنه ليس شيئًا يمكن أن ينتج عن النسيان بالصدفة إلى return
على الإطلاق.
على أي حال ، في الحالات التي يكون فيها التوقيع غامضًا ، يبدو أن TypeScript يختار أول عبارة return
بترتيب المصدر ويستخدمها لاشتقاق المعلمات العامة. يبدو أنه قادر على دمج الأنواع للأدوية البسيطة (مثل Array
أو Promise
) ، لكنه لا يدمجها أبدًا إذا كان النوع السياقي من النوع المعيّن مثل Pick
.
أرى بعض الانحدارات في الإصدار غير الوظيفي مع أحدث الأنواع. على وجه الخصوص ، تم تغيير نوع الوسيطة "state" عند تمرير وسيطة من:
setState
( state: Pick ، callback ؟: () => any): void؛
إلى:
الحالة: ((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: للقراءة فقط
النوع '{baseProp: "foobar"؛}' غير قابللنوع 'Pickللتخصيص
سيؤدي تغيير نوع الحالة من 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: P) => Pick& Partial \) |} 'غير قابل للتخصيص لكتابة' Pick& Partial \'.اكتب '{baseProp: "foobar" ؛
ربما لا تكون طريقة جيدة لهيكلة - بعد كل شيء ، يمكن لبعض الفئات الفرعية أن تعلن عن متغيرات الحالة التي تصطدم بمتغيرات في الفئة الأساسية. لذا ، قد يكون من الصواب أن يشتكي من أنه لا يمكن أن يكون متأكدًا مما سيحدث هناك. من الغريب أنه ليس لديه مشكلة في الوصول إلى تلك الدعائم بالرغم من ذلك.
همم. هذا بالتأكيد يبدو وكأنه خطأ في TS الآن.
سوف ألعب بهذا اليوم وربما أخرج الجزئي &.
سأضيف هذا كحالة اختبار أيضًا وأجرّب فكرة أخرى لإسعاد التحسس
أهلا،
نفس المشكلة هنا...
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 كان يعمل بشكل جيد.
يحتوي الطباعي على العديد من المشكلات ذات الصلة ، وقد أغلقوها على أنها تعمل كما تم تصميمها:
https://github.com/Microsoft/TypeScript/issues/19388
لقد قدمت موجزًا لبعض الأمثلة هنا: https://gist.github.com/afarnsworth-valve/93d1096d1410b0f2efb2c94f86de9c84
على الرغم من أن السلوك لا يزال يبدو غريبا. على وجه الخصوص ، يمكنك التخصيص إلى متغير مكتوب كفئة أساسية بدون إرسال ، ثم إجراء نفس الاستدعاءات مع ذلك بدون مشكلة. يبدو أن هذه المشكلة تشير إلى أن التخصيص والمقارنة شيئان مختلفان.
لم أنساكم يا رفاق. سوف يصلح قريبا
يجب أن يحل العلاقات العامة المشار إليها هذه المشكلة. لقد أضفت أيضًا اختبارات يجب أن تحمي من كسر هذه الحالة الحادة مرة أخرى.
ericanderson شكرا على الرد السريع :)
هل هناك أي قيود / جوانب سلبية للإصلاح الجديد؟
لا شيء أعرفه حاليًا. لقد تمكنت من الحفاظ على التحسس (في الواقع أفضل من ذي قبل ، لأنه يحل بعض الحالات الحادة).
للتوضيح ، كان الحل & Partial<S>
هو خداع intellisense للكشف عن المعلمات المحتملة ، لكنها فعلت ذلك بالقول إنها X | undefined
. هذا بالطبع سيفشل في التجميع ولكنه كان مربكًا بعض الشيء. يؤدي الحصول على | S
إصلاح intellisense بحيث يقترح جميع المعلمات الصحيحة ولا يُظهر الآن نوعًا خاطئًا.
لقد حاولت النظر في إضافة null
كقيمة إرجاع محتملة من رد الاتصال setState
(ما يمكنك إرجاعه إلى الحالة لا تريد تغيير أي حالة ، دون جعل return;
صالح أيضًا) ، ولكن هذا بدلاً من ذلك جعل الاستدلال من النوع يستسلم تمامًا واختر never
كمفاتيح 😢
في الوقت الحالي ، في عمليات الاسترجاعات حيث أرغب بالفعل في تخطي حالة التحديث ، كنت أستخدم return null!
. النوع never
لهذا الإرجاع يجعل TypeScript يتجاهل إرجاع نوع الاستدلال العام.
اهلا ياجماعة...
آخر التزام أصلح مشكلتي.
شكرا على الاستجابة السريعة :)
ما هو الإصدار الذي يحتوي على الإصلاح؟ هل تم نشره حتى npm؟ لقد وصلت إلى الحالة التي يخبرني فيها إصدار رد الاتصال من setState أن هناك عدم تطابق في الخاصية في قيمة الإرجاع ، تمامًا مثل UselessPickles الذي تم العثور عليه.
يجب أن يكون جيدًا في أحدث أنواع @ / رد فعل
لقد أصلحته لسلسلة 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;
.. ينتج هذا الخطأ:
لقد جربت هذا الاقتراح:
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
.
سيتعين عليك استخدام عامل التوكيد الفارغ على الرغم من أن numberList ليست undefined أو nullable:
سأبقى فقط على تعريف النوع القديم حتى يتم تحسين استشعار الكتابة. في هذه الأثناء ، سأذكر صراحة النوع في المعلمة العامة الخاصة بالمنتج الغامر. produce<IComponentState>
الأسهل تفسير list!
.
خطأك الأول هو أنك لا تعيد أي شيء. هذه ليست الطريقة التي يعمل بها 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 لاكتشاف النوع الصحيح للحالة. ومن ثم لا يظهر التحسس الخاص بالمسودة:
أو ربما لدي توقع خاطئ من المترجم :) لا يستطيع المترجم إنشاء نوع لمتغير المسودة بناءً على نوع 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
الرغم من:
بعد إجراء المزيد من التعديلات ، جعلت المترجم قادرًا على تمرير نوع الحالة لإنتاج مسودة عن طريق إضافة 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;
يتم تجميع الكود الآن :)
الخطأ الخاص بك ليس مع setState ، الخطأ الخاص بك داخل المنتج. ما هو تعريف نوع المنتج الخاص بك؟
يوجد خطأ في العلاقات العامة أعلاه في البرنامج النصي للاختبار من نوع DefinitelyTyped ، ولم أختبر محليًا. لذلك أنا أختبرها محليًا الآن.
إليك تعريف نوع immer / product.
/**
* 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'>
الاختبار أيضًا ، على الرغم من أنه لا يتناسب مع بياناتي.
أعتقد أن جزئية
هل يمكنك التفضل بالنظر في اقتراحي أو الإشارة إلى سوء فهمي إذا كان هناك؟ شكرا لك!
هذا تغيير قديم حقًا أولاً. لذلك ما لم يغير شخص آخر هذا مؤخرًا ، فمن المحتمل أن تنبح الشجرة الخطأ.
هكذا قال. يسمح الجزئي بقيم غير محددة.
const a: Partial <{foo: string}> = {foo: undefined}
a صالح ولكن من الواضح أن نتيجة هذا التحديث لحالتك تضع حالتك مع foo غير محدد على الرغم من أنك أعلنت أن ذلك مستحيل.
لذلك لا يمكن تخصيص جزء لبيك. والاختيار هو الإجابة الصحيحة للتأكد من أن أنواعك لا تكذب
أشعر أن عدم السماح:
setState((prevState) => {
if (prevState.xyz) {
return { foo: "" };
}
return { bar: "" };
});
مقيد للغاية.
حلKovensky هو الحل المعقول الوحيد الذي أعرفه ، لكن الكتابة لا تزال مؤلمة.
هل هناك أي شيء يمكن القيام به لدعم هذا النمط الشائع (سأقول)؟
الشيء الوحيد الذي يمكن القيام به هو إزالة أمان الكتابة
هل يمكن لشخص أن يشرح أسباب 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 أنا لا أعرف حتى الآن لماذا يعمل التوقيع أعلاه حتى مع تحديثات الحالة الجزئية حيث يتم تعريف 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: "" }; });
مقيد للغاية.
حلKovensky هو الحل المعقول الوحيد الذي أعرفه ، لكن الكتابة لا تزال مؤلمة.
لقد واجهت هذه المشكلة بالضبط ، لكن لا يمكنني رؤية أي إشارة إلى Kovensky في سلسلة الرسائل (ربما قام شخص ما بتغيير اسم المستخدم الخاص به؟). هل يمكن لأي شخص أن يوجهني إلى الحل البديل الموصى به حاليًا
@ timrobinson33 هذا التعليق يشرح الحل https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -351884578
وهو أمر جيد لا داعي للقلق بشأن هذا بعد الآن باستخدام الخطافات 🙂
@ timrobinson33 هذا التعليق يشرح الحل # 18365 (تعليق)
شكرا جزيلا لذلك. في النهاية ، اعتقدت أن الكود الخاص بي يبدو أجمل حيث أن العديد من مكالمات setState الصغيرة مع بيانات
If
خارجها ، على الرغم من أن هذا يعني أن بعض المسارات ستستدعي setState أكثر من مرة.
أعتقد أن هذا في الواقع مشابه لكيفية عملنا مع الخطافات ، حيث نرى الحالة كعدة أشياء صغيرة نقوم بتحديثها بشكل مستقل.
التعليق الأكثر فائدة
يتسبب "الإصلاح" الأخير في حدوث مشكلات بالنسبة لي الآن مع العديد من كشوف الإرجاع ضمن رد اتصال
setState()
.Typescript: 2.6.2 مع تمكين كافة الخيارات "المقيدة" باستثناء "strictFunctionTypes"
أنواع / رد فعل: 16.0.30
مثال على الكود:
خطأ المترجم: