React: createPortal: دعم الخيار لوقف انتشار الأحداث في شجرة React

تم إنشاؤها على ٢٧ أكتوبر ٢٠١٧  ·  103تعليقات  ·  مصدر: facebook/react

هل تريد طلب ميزة أو الإبلاغ عن خطأ ؟
ميزة ، ولكن أيضًا خطأ يتسبب في كسر واجهة برمجة التطبيقات الجديدة القديمة unstable_rendersubtreeintocontainer

ما هو السلوك الحالي؟
لا يمكننا إيقاف انتشار جميع الأحداث من البوابة إلى أسلافها من شجرة React. آلية الطبقات لدينا مع الوسائط / popovers مكسورة تمامًا. على سبيل المثال ، لدينا زر قائمة منسدلة. عندما نضغط عليها ، انقر فوق يفتح popover. نريد أيضًا إغلاق هذا العنصر المنبثق عند النقر فوق الزر نفسه. باستخدام createPortal ، انقر داخل الحرائق المنبثقة ، انقر فوق الزر ، ويتم إغلاقها. يمكننا استخدام stopPropagation في هذه الحالة البسيطة. لكن لدينا الكثير من هذه الحالات ، ونحتاج إلى استخدام stopPropagation لجميع هذه الحالات. أيضا ، لا يمكننا إيقاف كل الأحداث.

ما هو السلوك المتوقع؟
يجب أن يكون لدى createPortal خيار لإيقاف انتشار الأحداث التركيبية من خلال شجرة React دون إيقاف كل حدث يدويًا. ماذا تعتقد؟

DOM Feature Request

التعليق الأكثر فائدة

حتى هذا يبدو معقدًا بلا داعٍ بالنسبة لي. لماذا لا تضيف ببساطة علم منطقي اختياري لإنشاء البوابة مما يسمح بحظر السلوك الفقاعي؟

ال 103 كومينتر

أيضًا ، يبدو انتشار mouseOver / Leave غير متوقع تمامًا.
image

هل يمكنك نقل البوابة خارج الزر؟

على سبيل المثال

return [
  <div key="main">
    <p>Hello! This is first step.</p>
    <Button key="button" />
  </div>,
  <Portal key="portal" />
];

ثم لن يتدفق من خلال الزر.

كان هذا أول ما فكرت به ، ولكن!) تخيل ، أن لدينا MouseEnter Handler في حاوية المكونات هذه:

image

باستخدام unstable_rendersubtreeintocontainer لست بحاجة إلى أي شيء يتعلق بالأحداث في ButtonWithPopover - يعمل الماوس ببساطة عندما يدخل الماوس حقًا div وعنصر DOM للزر ، ولا يتم تشغيله عندما يكون الماوس فوق المنبثقة. مع البوابة الإلكترونية ، يتم تشغيل الحدث عند تحريك الماوس فوق المنبثق - وفي الواقع ليس أكثر من div في هذه اللحظة. لذلك ، أنا بحاجة لوقف التكاثر. إذا قمت بذلك في مكون ButtonWithPopover ، فسوف أوقف إطلاق الحدث عندما يكون الماوس فوق الزر. إذا قمت بذلك في popover وكنت أستخدم بعض مكونات popover الشائعة لهذا التطبيق ، فيمكنني أيضًا كسر المنطق في أجزاء التطبيق الأخرى.

أنا حقًا لا أفهم الغرض من ظهور الفقاعات عبر شجرة React. إذا كنت بحاجة إلى أحداث من مكون البوابة - يمكنني ببساطة تمرير المعالجات عبر الدعائم. لقد فعلنا ذلك unstable_rendersubtreeintocontainer وعمل بشكل مثالي.

إذا قمت بفتح نافذة مشروطة من زر ما في عمق شجرة التفاعل ، فسوف أتلقى إطلاقًا غير متوقع للأحداث تحت الوسائط. stopPropagation سيتوقف أيضًا عن الانتشار في DOM ، ولن أحصل على الأحداث التي أتوقع إطلاقها حقًا (

gaearon أود أن أقترح أن هذا خطأ أكثر من طلب ميزة. لدينا عدد من الأخطاء الجديدة التي تسببها أحداث الماوس التي تظهر عبر البوابات (حيث كنا نستخدم في السابق unstable_rendersubtreeintocontainer ). لا يمكن إصلاح بعض هذه العناصر حتى مع وجود طبقة div إضافية لتصفية أحداث الماوس لأننا على سبيل المثال نعتمد على أحداث تحريك الماوس التي تنتشر حتى المستند لتنفيذ مربعات حوار قابلة للسحب.

هل هناك طريقة لحل هذه المشكلة قبل تناولها في إصدار مستقبلي؟

أعتقد أنه يطلق عليه طلب ميزة ، لأن سلوك الفقاعة الحالي للبوابات متوقع ومقصود. الهدف هو أن تعمل الشجرة الفرعية مثل الأبناء الحقيقيين لوالديهم.

ما قد يكون مفيدًا هو حالات أو مواقف استخدام إضافية (مثل تلك التي تراها) التي لا تشعر أن التنفيذ الحالي يخدمها ، أو يصعب حلها

أتفهم أن هذا السلوك مقصود ، لكنني أعتقد أنه خطأ كبير لا يمكن تعطيله.

في رأيي ، يجب أن تحافظ مكتبة العمل مع DOM على سلوك تنفيذ DOM وليس كسره.

على سبيل المثال:

class Container extends React.Component {
  shouldComponentUpdate = () => false;
  render = () => (
    <div
      ref={this.props.containerRef}
      // Event propagation on this element not working
      onMouseEnter={() => { console.log('handle mouse enter'); }}
      onClick={() => { console.log('handle click'); }}
    />
  )
}

class Root extends React.PureComponent {
  state = { container: null };
  handleContainer = (container) => { this.setState({ container }); }

  render = () => (
    <div>
      <div
        // Event propagation on this element not working also
        onMouseEnter={() => { console.log('handle mouse enter'); }}
        onClick={() => { console.log('handle click'); }}
      >
        <Container containerRef={this.handleContainer} />
      </div>
      {this.state.container && ReactDOM.createPortal(
        <div>Portal</div>,
        this.state.container
      )}
    </div>
  );
}

عندما أعمل مع DOM ، أتوقع تلقي أحداث مثل تنفيذ DOM. في المثال الخاص بي ، يتم نشر الأحداث من خلال Portal ، والعمل حول والديها DOM ، ويمكن اعتبار هذا خطأ .

أشكرك جميعًا على المناقشة ، ولكن لا أعتقد أنه من المفيد جدًا مناقشة ما إذا كان هناك خطأ أم لا. بدلاً من ذلك ، سأكون أكثر إنتاجية لمناقشة حالات الاستخدام والأمثلة التي لا يفي بها السلوك الحالي ، حتى نتمكن من فهم ما إذا كانت الطريقة الحالية هي أفضل طريقة للمستقبل.

بشكل عام ، نريد أن تتعامل واجهة برمجة التطبيقات مع مجموعة متنوعة من حالات الاستخدام بينما نأمل ألا تفرط في تقييد الآخرين. لا يمكنني التحدث باسم الفريق الأساسي ، لكني أتخيل أن جعله قابلاً للتكوين ليس حلاً محتملاً. يميل React عمومًا إلى الحصول على واجهة برمجة تطبيقات متسقة على تلك القابلة للتكوين.

أفهم أيضًا أن هذا السلوك ليس طريقة عمل DOM ، لكنني لا أعتقد أن هذا في حد ذاته سبب وجيه للقول إنه لا ينبغي أن يكون على هذا النحو. يختلف الكثير من سلوك رد فعل دوم عن طريقة عمل DOM ، فقد تكون الأحداث مختلفة بالفعل عن الإصدار الأصلي. onChange على سبيل المثال يختلف تمامًا عن حدث التغيير الأصلي ، وكل أحداث التفاعل تتدهور بغض النظر عن نوعها ، على عكس DOM.

بدلاً من ذلك ، سأكون أكثر إنتاجية لمناقشة حالات الاستخدام والأمثلة التي لا يفي بها السلوك الحالي

إليك مثالين معطلين بالنسبة لنا في انتقالنا إلى React 16.

أولاً ، لدينا مربع حوار قابل للسحب يتم تشغيله بواسطة زر. حاولت إضافة عنصر "ترشيح" إلى استخدام البوابة الخاص بنا والذي يسمى StopPropagation على أي فأرة * مفتاح * أحداث. ومع ذلك ، فإننا نعتمد على القدرة على ربط حدث mousemove بالمستند من أجل تنفيذ وظيفة السحب - وهذا أمر شائع لأنه إذا قام المستخدم بتحريك الماوس بأي معدل مهم ، فإن المؤشر يترك حدود مربع الحوار وتحتاج إلى لتتمكن من التقاط حركة الماوس بمستوى أعلى. تصفية هذه الأحداث يكسر هذه الوظيفة. ولكن مع البوابات ، تظهر أحداث الفأرة والمفاتيح من داخل مربع الحوار إلى الزر الذي أطلقها ، مما يتسبب في عرض تأثيرات بصرية مختلفة وحتى استبعاد مربع الحوار. لا أعتقد أنه من الواقعي توقع قيام كل مكون سيتم إطلاقه عبر بوابة بإلزام معالجات الأحداث 10-20 لإيقاف انتشار هذا الحدث.

ثانيًا ، لدينا قائمة سياق منبثقة يمكن تشغيلها عن طريق النقر بالماوس الأساسي أو الثانوي. أحد المستهلكين الداخليين لمكتبتنا لديه معالجات فأرة مرتبطة بالعنصر الذي يقوم بتشغيل هذه القائمة ، وبالطبع تحتوي القائمة أيضًا على معالجات نقر للتعامل مع اختيار العنصر. تظهر القائمة الآن مرة أخرى عند كل نقرة حيث تطفو أحداث الماوس / الماوس احتياطيًا إلى الزر الذي يبدأ تشغيل القائمة.

لا يمكنني التحدث باسم الفريق الأساسي ، لكني أتخيل أن جعله قابلاً للتكوين ليس حلاً محتملاً. يميل React عمومًا إلى الحصول على واجهة برمجة تطبيقات متسقة على تلك القابلة للتكوين.

أناشدك (والفريق) إعادة النظر في هذا الموقف في هذه الحالة بالذات. أعتقد أن انفجار الحدث سيكون مثيرًا للاهتمام في حالات استخدام معينة (على الرغم من أنني لا أستطيع التفكير في أي شيء مرتجل). لكنني أعتقد أنه سيكون معوقًا للآخرين ، وهو يقدم تناقضًا كبيرًا في واجهة برمجة التطبيقات. على الرغم من أن unstable_rendersubtreeintocontainer لم يكن مدعومًا بشكل فائق مطلقًا ، إلا أنه كان ما اعتاد الجميع على تقديمه خارج الشجرة المباشرة ، ولم يعمل بهذه الطريقة. تم إهمالها رسميًا لصالح البوابات ، لكن البوابات تكسر الوظيفة بهذه الطريقة الحاسمة ، ولا يبدو أن هناك حلًا سهلًا. أعتقد أن هذا يمكن وصفه بأنه غير متسق تمامًا.

أفهم أيضًا أن هذا السلوك ليس طريقة عمل DOM ، لكنني لا أعتقد أن هذا في حد ذاته سبب وجيه للقول إنه لا ينبغي أن يكون على هذا النحو.

أتفهم من أين أتيت من هنا ، لكنني أعتقد في هذه الحالة (أ) أنه سلوك أساسي (ب) ليس له حل بديل حاليًا ، لذلك أعتقد أن "DOM لا يعمل بهذه الطريقة" هي حجة قوية ، إن لم يكن مقنعًا تمامًا.

ولكي أكون واضحًا: إن طلبي بأن يعتبر هذا خطأ في الغالب بحيث يتم منحه الأولوية للإصلاح عاجلاً وليس آجلاً.

نموذجي العقلي للبوابة هو أنه يتصرف كما لو كان في نفس المكان في الشجرة ، لكنه يتجنب مشاكل مثل "الفائض: مخفي" ويتجنب التمرير لأغراض الرسم / التخطيط.

هناك العديد من الحلول "المنبثقة" المشابهة التي تحدث بشكل مضمّن بدون بوابة إلكترونية. على سبيل المثال ، الزر الذي يوسع المربع المجاور له.

خذ كمثال مربع الحوار "اختر رد فعلك" هنا على GitHub. يتم تنفيذ ذلك كقسم div بجوار الزر مباشرةً. هذا يعمل بشكل جيد الآن. ومع ذلك ، إذا كانت تريد أن يكون لها فهرس z مختلف ، أو أن يتم رفعها من منطقة overflow: scroll التي تحتوي على هذه التعليقات ، فستحتاج إلى تغيير موضع DOM. هذا التغيير ليس آمنًا ما لم يتم الحفاظ أيضًا على أشياء أخرى مثل فقاعة الأحداث.

كلا نمطي "النوافذ المنبثقة" أو "النوافذ المنبثقة" شرعيان. إذن كيف يمكنك حل المشكلة نفسها عندما يكون المكون مضمنًا في التخطيط بدلاً من الطفو خارجه؟

الحل الذي نجح معي هو استدعاء stopPropagation مباشرةً ضمن عرض البوابة الإلكترونية:

return createPortal(
      <div onClick={e => e.stopPropagation()}>{this.props.children}</div>,
      this.el
    )

يعمل هذا بشكل رائع بالنسبة لي نظرًا لأن لدي مكون تجريد واحد يستخدم بوابات ، وإلا فستحتاج إلى إصلاح جميع مكالماتك createPortal .

methyl يفترض هذا أنك تعرف كل حدث تحتاج إلى منعه من ظهور فقاعات فوق الشجرة. وفي الحالة التي أشرت إليها باستخدام مربعات الحوار القابلة للسحب ، نحتاج إلى mousemove لتكوين فقاعة حتى تصل إلى المستند ، ولكن ليس لتظهر شجرة العرض.

كلا نمطي "النوافذ المنبثقة" أو "النوافذ المنبثقة" شرعيان. إذن كيف يمكنك حل المشكلة نفسها عندما يكون المكون مضمنًا في التخطيط بدلاً من الطفو خارجه؟

sebmarkbage لست متأكدًا من أن هذا السؤال منطقي. إذا كانت لدي هذه المشكلة في تضمين المكون ، فلن أقوم بتضمينه.

أعتقد أن بعض المشاكل هنا هي أن بعض حالات استخدام renderSubtreeIntoContainer يتم نقلها إلى createPortal عندما تقوم الطريقتان بأشياء مختلفة من الناحية المفاهيمية. كان مفهوم البوابة مثقلًا على ما أعتقد.

أوافق على أنه في حالة الحوار Modal ، لا تريد أبدًا أن يتصرف المشروط مثل طفل الزر الذي فتحه. يقوم مكون المشغل بعرضه فقط لأنه يتحكم في حالة open . أعتقد أنه من الخطأ القول أن تنفيذ البوابة الإلكترونية خاطئ ، بدلاً من القول إن createPortal في الزر ليس الأداة المناسبة لذلك. في هذه الحالة ، لا يكون المودال تابعًا للمشغل ، ولا يجب عرضه كما لو كان كذلك. أحد الحلول الممكنة هو الاستمرار في استخدام renderSubtreeIntoContainer ، خيار آخر للمستخدم هو الحصول على ModalProvider بالقرب من جذر التطبيق الذي يتعامل مع نماذج العرض ، ويمرر (عبر السياق) طريقة لعرض عنصر شرطي تعسفي بحاجة إلى الجذر

لا يمكن استدعاء renderSubtreeIntoContainer من داخل render أو طرق دورة الحياة في React 16 ، مما يحول إلى حد كبير دون استخدامه للحالات التي ناقشتها (في الواقع ، جميع مكوناتنا التي كانت فعل هذا كسر تماما في الهجرة إلى 16). البوابات هي التوصية الرسمية: https://reactjs.org/blog/2017/09/26/react-v16.0.html#breaking -changes

أوافق على أن مفهوم البوابات ربما انتهى به الأمر إلى زيادة التحميل. لست متأكدًا من أنني أحب حل المكون العالمي والسياق الخاص به ، على الرغم من ذلك. يبدو أن هذا يمكن حله بسهولة عن طريق علامة في createPortal تحدد ما إذا كان يجب أن تنفجر الأحداث. ستكون علامة اشتراك تحافظ على توافق واجهة برمجة التطبيقات مع 16+.

سأحاول توضيح حالة استخدامنا للبوابات ولماذا نود أن نرى خيارًا لوقف انتشار الأحداث. في تطبيق ManyChat ، نستخدم بوابات لإنشاء "طبقات". لدينا نظام الطبقات للتطبيق بأكمله الذي تستخدمه عدة أنواع من المكونات: popovers ، والقوائم المنسدلة ، والقوائم ، والشروط. يمكن لكل طبقة أن تعرض طبقة جديدة ، على سبيل المثال ، يمكن أن يؤدي الزر الموجود في المستوى الثاني من القائمة إلى تشغيل النافذة المشروطة حيث يمكن للزر الآخر فتح النافذة المنبثقة. في معظم الحالات ، تكون الطبقة هي الفرع الجديد لـ UX الذي يحل مهمته الخاصة. وعندما يتم فتح طبقة جديدة ، يجب على المستخدم التفاعل مع هذه الطبقة الجديدة ، وليس مع الآخرين في الأسفل. لذلك ، بالنسبة لهذا النظام ، أنشأنا مكونًا شائعًا للتصيير للطبقة:

class RenderToLayer extends Component {
  ...
  stop = e => e.stopPropagation()

  render() {
    const { open, layerClassName, useLayerForClickAway, render: renderLayer } = this.props

    if (!open) { return null }

    return createPortal(
      <div
        ref={this.handleLayer}
        style={useLayerForClickAway ? clickAwayStyle : null}
        className={layerClassName}
        onClick={this.stop}
        onContextMenu={this.stop}
        onDoubleClick={this.stop}
        onDrag={this.stop}
        onDragEnd={this.stop}
        onDragEnter={this.stop}
        onDragExit={this.stop}
        onDragLeave={this.stop}
        onDragOver={this.stop}
        onDragStart={this.stop}
        onDrop={this.stop}
        onMouseDown={this.stop}
        onMouseEnter={this.stop}
        onMouseLeave={this.stop}
        onMouseMove={this.stop}
        onMouseOver={this.stop}
        onMouseOut={this.stop}
        onMouseUp={this.stop}

        onKeyDown={this.stop}
        onKeyPress={this.stop}
        onKeyUp={this.stop}

        onFocus={this.stop}
        onBlur={this.stop}

        onChange={this.stop}
        onInput={this.stop}
        onInvalid={this.stop}
        onSubmit={this.stop}
      >
        {renderLayer()}
      </div>, document.body)
  }
  ...
}

يوقف هذا المكون الانتشار لجميع أنواع الأحداث من مستندات React ، ويسمح لنا بالتحديث إلى React 16.

هل هذا يحتاج إلى أن يكون مرتبطا بالبوابات؟ بدلاً من بوابات وضع الحماية ، ماذا لو كان هناك (على سبيل المثال) <React.Sandbox>...</React.Sandbox> ؟

حتى هذا يبدو معقدًا بلا داعٍ بالنسبة لي. لماذا لا تضيف ببساطة علم منطقي اختياري لإنشاء البوابة مما يسمح بحظر السلوك الفقاعي؟

gaearon هذا وضع مؤسف جدًا لشريحة معينة منا - هل يمكنك أنت أو أي شخص عزيز عليك إلقاء نظرة على هذا؟ :)

أود أن أضيف أن تفكيري الحالي هو أنه يجب دعم حالتي الاستخدام. هناك بالفعل حالات استخدام حيث تحتاج إلى سياق يتدفق من الأصل الحالي إلى الشجرة الفرعية ولكن لا تجعل هذه الشجرة الفرعية تعمل كطفل منطقي من حيث DOM. النماذج المعقدة هي أفضل مثال ، فأنت لا تريد أبدًا أن تنتشر الأحداث من نموذج في نافذة مشروطة حتى زر المشغل ، ولكنك تحتاج بالتأكيد إلى السياق الذي يمر عبره (i18n ، السمات ، إلخ)

سأقول أنه يمكن حل حالة الاستخدام هذه في الغالب باستخدام ModalProvider أقرب إلى جذر التطبيق الذي يتم عرضه عبر createPortal كافية بحيث لا يؤثر انتشار الحدث على أي شيء ، ولكن هذا يبدأ في الشعور وكأنه حل بديل بدلاً من تصميم معماري جيد. كما أنه يجعل النماذج التي توفرها المكتبة أكثر إزعاجًا للمستخدمين نظرًا لأنها لم تعد محتواة ذاتيًا.

أود أن أضيف ذلك فيما يتعلق بواجهة برمجة التطبيقات ، ولا أعتقد أن createPortal يجب أن يفعل كلا الأمرين ، فالحالة المشروطة تريد حقًا استخدام ReactDOM.render (skool القديم) لأنها قريبة جدًا من شجرة مميزة _ باستثناء_ غالبًا ما تكون هناك حاجة إلى نشر السياق

كان علينا فقط إصلاح خطأ يصعب تشخيصه في كود إدارة التركيز الخاص بتطبيقنا الخارجي كنتيجة لاستخدام الحل البديل الذي نشره

على وجه التحديد: استدعاء stopPropagation في حدث التركيز الاصطناعي لمنعه من الخروج من البوابة يتسبب أيضًا في استدعاء stopPropagation في حدث التركيز الأصلي في معالج React المُلتقط على #document ، مما يعني أنه لم يصل إلى معالج آخر تم التقاطه على <body> . لقد أصلحنا ذلك بتحريك معالجنا للأعلى إلى #document ، لكننا تجنبنا فعل ذلك على وجه التحديد في الماضي حتى لا نتقدم على أصابع React.

يبدو السلوك الجديد في Portals حقًا مثل حالة الأقلية بالنسبة لي. سواء أكان ذلك رأيًا أم حقيقة ، هل يمكننا من فضلك الحصول على بعض الزخم بشأن هذه المسألة؟ ربماgaearon؟ عمرها أربعة أشهر وتسبب ألما حقيقيا. أعتقد أنه يمكن وصف هذا إلى حد ما بأنه خطأ نظرًا لأنه تغيير في واجهة برمجة التطبيقات في React 16 بدون حل بديل آمن تمامًا.

craigkovatch ما زلت أشعر بالفضول حول كيفية حل المثال المضمن الخاص بي. لنفترض أن النافذة المنبثقة تضغط على حجم الصندوق لأسفل. يعد تضمين شيء ما أمرًا مهمًا لأنه يدفع شيئًا ما لأسفل في التخطيط نظرًا لحجمه. لا يمكن أن تحوم فوقها فقط.

يمكنك قياس العنصر المنبثق وإدخال عنصر نائب فارغ بنفس الحجم ومحاولة محاذاته في الأعلى ولكن هذا ليس ما يفعله الأشخاص.

لذا ، إذا احتاجت النافذة المنبثقة إلى توسيع المحتوى في مكانه ، كما هو الحال بجوار الزر مباشرةً ، فكيف ستحل المشكلة؟ أظن أن النمط الذي يعمل هناك ، سيعمل في كلتا الحالتين ويجب أن نوصي فقط بالنمط الواحد.

أعتقد بشكل عام أن هذا هو النمط الذي يعمل في كلا السيناريوهين:

class Foo extends React.Component {
  state = {
    highlight: false,
    showFlyout: false,
  };

  mouseEnter() {
    this.setState({ highlight: true });
  }

  mouseLeave() {
    this.setState({ highlight: false });
  }

  showFlyout() {
    this.setState({ showFlyout: true });
  }

  hideFlyout() {
    this.setState({ showFlyout: false });
  }

  render() {
    return <>
      <div onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave} className={this.state.highlight ? 'highlight' : null}>
        Hello
        <Button onClick={this.showFlyout} />
      </div>
      {this.state.showFlyout ? <Flyout onHide={this.hideFlyout} /> : null}
    </>;
  }
}

إذا كانت Flyout عبارة عن بوابة ، فإنها تعمل ولن تحصل على أي مؤشر الماوس فوق الأحداث عند المرور فوق البوابة الإلكترونية. ولكن الأهم من ذلك ، أنها تعمل أيضًا إذا لم تكن بوابة ، ويجب أن تكون قائمة منبثقة مضمنة. لا يوجد توقف

إذن ما هو هذا النمط الذي لا يصلح لحالة الاستخدام الخاصة بك؟

sebmarkbage نحن نستخدم البوابات الإلكترونية بطريقة مختلفة تمامًا ، حيث يتم <body> والذي يتم وضعه بعد ذلك ، أحيانًا باستخدام z-index. تشير وثائق React إلى أن هذا أقرب إلى نية التصميم ؛ أي الظهور في مكان مختلف تمامًا في DOM. لا يبدو لي أن حالات الاستخدام الخاصة بنا متشابهة بدرجة كافية بحيث تنتمي المناقشة إلى هذا الموضوع. ولكن إذا كنت ترغب في تبادل الأفكار / استكشاف الأخطاء وإصلاحها معًا ، فسيسعدني جدًا إجراء المزيد من المناقشة في منتدى آخر.

لا حالة الاستخدام الخاصة بي هي كلاهما . احيانا واحد واحيانا الاخر هذا هو السبب في أنها ذات صلة.

يمكن أن يختار <Flyout /> التقديم إلى العنصر الفرعي الأخير من الجسد أم لا ، ولكن طالما أنك ترفع البوابة نفسها إلى أخ للمكوِّن الذي تم تحريكه بدلاً من طفل له ، فإن السيناريو الخاص بك يعمل.

أعتقد أن هناك سيناريو معقول حيث يكون هذا غير مريح وتريد طريقة لنقل الأشياء من مكونات متداخلة بعمق ولكن في هذا السيناريو ربما تكون على ما يرام مع كون السياق هو السياق من النقطة الوسيطة. لكني أفكر في هاتين القضيتين على أنهما مسألتان منفصلتان.

ربما نحتاج إلى واجهة برمجة تطبيقات فتحات لذلك.

class Foo extends React.Component {
  state = {
    showFlyout: false,
  };

  showFlyout() {
    this.setState({ showFlyout: true });
  }

  hideFlyout() {
    this.setState({ showFlyout: false });
  }

  render() {
    return <>
      Hello
      <Button onClick={this.showFlyout} />
      <SlotContent name="flyout">
        {this.state.showFlyout ? <Flyout onHide={this.hideFlyout} /> : null}
      </SlotContent>
    </>;
  }
}

class Bar extends React.Component {
  state = {
    highlight: false,
  };

  mouseEnter() {
    this.setState({ highlight: true });
  }

  mouseLeave() {
    this.setState({ highlight: false });
  }

  render() {
    return <>
      <div onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave} className={this.state.highlight ? 'highlight' : null}>
        <SomeContext>
          <DeepComponent />
        </SomeContext>
      </div>
      <Slot name="flyout" />
    </>;
  }
}

ستحصل البوابة بعد ذلك على سياق Bar ، وليس DeepComponent. السياق والحدث بعد ذلك لا يزالان يشتركان في نفس مسار الشجرة.

sebmarkbage عادةً ما تتطلب الحالة المشروطة سياقًا من النقطة التي يتم عرضها فيها. إنها فريدة من نوعها إلى حد ما في حالة أعتقد أن المكون هو عنصر فرعي منطقي للشيء الذي قدمه ولكنه _ ليس _ بنيويًا (لعدم وجود كلمة أفضل) ، على سبيل المثال ، عادةً ما تريد أشياء مثل سياق النموذج (مرحل ، فورميك ، نموذج إعادة ، أيا كان) ولكن لا تمر أحداث DOM. ينتهي الأمر أيضًا بجعل هذه النماذج عميقة إلى حد ما في الأشجار ، بجانب مشغلاتها ، بحيث تظل مكونة وقابلة لإعادة الاستخدام ، أكثر من كونها تنتمي إلى هناك هيكليًا

أعتقد أن هذه الحالة تختلف بشكل عام عن حالة خدمة createPortal المنسدلة / المنسدلة. Tbc أعتقد أن سلوك الفقاعات للبوابات جيد ، لكن ليس للوسائط. أعتقد أيضًا أن هذا يمكن التعامل معه مع السياق ونوع من ModalProvider بشكل معقول ، ولكن هذا أمر مزعج نوعًا ما خاصة للمكتبات.

طالما أنك ترفع البوابة الإلكترونية نفسها إلى أخ للمكوِّن الذي تم تحريكه بدلاً من طفل له ، فإن السيناريو الخاص بك يعمل.

لست متأكدا من أن أتابع. لا تزال هناك مشكلة مثل أحداث keyDown التي تظهر من خلال شجرة DOM غير متوقعة.

jquense لاحظ أنه في المثال الخاص بي ، لا تزال الفتحة داخل مكون <Form><Bar /></Form> .

حتى إذا تم عرض البوابة الإلكترونية في نص المستند.

لذلك فهي مثل اثنين من المراوغات (المدخلات): عميق -> شقيق بار -> جسم الوثيقة.

لذا فإن سياق البوابة لا يزال هو سياق النموذج ، وكذلك سلسلة فقاعات الحدث ، ولكن لا يوجد أي منهما في سياق الشيء الذي تم تحريكه.

نعم آسف فاتني ذلك 😳 إذا كنت أقرأها بشكل صحيح ، فلا يزال لديك فقاعة عند <Slot> على الرغم من ذلك؟ هذا بالتأكيد أفضل ، على الرغم من أنني أعتقد أنه في حالة مربع حوار الوسائط ربما لا يرغب المرء في حدوث فقاعات. مثل التفكير من منظور قارئ الشاشة ، فأنت تريد عكس كل شيء خارج النموذج أثناء تشغيله. لا أعلم ، أعتقد في هذه الحالة أن الفقاعة مسكت ، ولا يتوقع أحد أن تنفجر نقرة داخل مربع حوار في أي مكان.

ربما لا تكمن المشكلة هنا في البوابات ، لكن لا توجد طريقة جيدة لمشاركة السياق عبر الأشجار؟ جزء من سياق الشيء ReactDOM.render مناسب حقًا للنماذج ، وربما طريقة تفكير أكثر "صحيحة" بشأنه على أي حال ...

تفكيري هنا هو أن هناك بعض الفقاعات لأنها لا تزال تنتقل من النموذج إلى div إلى الجسم إلى المستند إلى النافذة. ومن الناحية المفاهيمية خارج الإطار إلى النافذة المحتوية وما إلى ذلك.

هذا ليس نظريًا في شيء مثل محتوى ART أو GL المعروض (وإلى حد ما React Native) حيث قد لا تكون هناك شجرة دعم موجودة للحصول على تلك الدلالات منها. لذلك يجب أن تكون هناك طريقة للقول إن هذا هو المكان الذي تتواجد فيه الفقاعات.

في بعض التطبيقات توجد نماذج في الوسائط. على سبيل المثال ، في FB ، توجد نافذة دردشة قد تكون أعلى مشروطًا أو قد تكون جزءًا من نافذة الدردشة. لذلك ، حتى النموذج يحتوي على بعض السياق فيما يتعلق بالمكان الذي ينتمي إليه في الشجرة. انها ليست قائمة بذاتها تماما.

هذا لا يعني أنه لا يمكن أن يكون لدينا دلالات مختلفة لظهور الحدث والسياق. كلاهما صريح حول هذا ويمكنك الدخول إلى أحدهما دون الآخر وما إلى ذلك.

يعد الحصول على ضمان أن كلاهما يتبع نفس المسار أمرًا قويًا حقًا على الرغم من أنه يعني أنه يمكن تنفيذ فقاعة الحدث بالكامل لأحداث مساحة المستخدم كما هو الحال في المتصفح.

على سبيل المثال ، يحدث هذا مع سياقات Redux المختلفة اليوم. تخيل أن this.context.dispatch("Hover") هو حدث فضاء مستخدم يتدفق. يمكننا حتى تنفيذ أحداث React كجزء من السياق. من المعقول التفكير في أنه يمكنني استخدام هذا بنفس الطريقة ، وبكل طريقة يمكنك الآن. أعتقد أنه إذا قمنا بتقسيم هذين السياقين ، فربما ينتهي بنا الأمر بواجهة برمجة تطبيقات أخرى لسياق مساحة مستخدم تتبع بنية DOM بالتوازي مع السياق العادي - إذا كانا مختلفين حقًا.

لهذا السبب أنا أدفع ضدها قليلاً لمعرفة ما إذا كانت الفتحات قد تكون كافية ، حيث أ) تحتاج إلى أن تكون واضحًا بشأن السياق الذي يحدث على أي حال. ب) يمكن أن تتجنب تفرع العالم وامتلاك نظامين كاملين للسياق.

على وجه التحديد: استدعاء stopPropagation في حدث التركيز الاصطناعي لمنعه من الخروج من البوابة يتسبب أيضًا في استدعاء stopPropagation في حدث التركيز الأصلي في معالج React المُلتقط على #document ، مما يعني أنه لم يصل إلى معالج آخر تم التقاطه على

. لقد أصلحنا ذلك بتحريك معالجنا للأعلى إلى #document ، لكننا تجنبنا فعل ذلك على وجه التحديد في الماضي حتى لا نتقدم على أصابع React.

craigkovatch ، هل استخدمت حدث onFocusCapture في المستند؟ في الحل البديل الخاص بي ، لا ينبغي إيقاف الأحداث التي تم التقاطها. هل يمكنك تقديم مثال أكثر تفصيلاً عن كيف كانت وماذا فعلت لحل مشكلتك؟
أيضًا ، أعتقد أن الكود الخاص بي به مشكلة في إيقاف حدث blur - لا ينبغي إيقافه. لذلك ، سأبحث في هذا السؤال بشكل أعمق وسأحاول إيجاد حل أكثر موثوقية.

@ kib357 لا أقترح وجود مشكلة في الحل البديل الخاص بك ، أعتقد أن هناك خطأ منفصل في React هناك (على سبيل المثال ، لا ينبغي إلغاء نشر أحداث التركيز الأصلي في مرحلة الالتقاط عند استدعاء stopPropagation في أحداث التركيز التركيبي في مرحلة الفقاعة).

يستخدم الكود المعني مستمعًا أصليًا لحدث الالتقاط ، مثل document.body.addEventListener('focus', handler, true)

يبدو craigkovatch مثيرًا للاهتمام ، نظرًا لحقيقة أنك استخدمت المعالج الذي تم التقاطه. ومع ذلك ، ليس لدي أي أفكار لماذا يحدث هذا.

إذن ، يا رفاق ، لدينا سيناريوهان مختلفان لاستخدام عرض البوابة:

  1. لمنع حدوث مشكلات في CSS مثل overflow: hidden وما إلى ذلك في عناصر واجهة مستخدم بسيطة ، مثل أزرار القائمة المنسدلة أو القوائم ذات المستوى الواحد
  2. لإنشاء طبقة UX جديدة لحالات أكثر قوة مثل:
  3. مشروط
  4. القوائم المتداخلة
  5. popovers-with-Forms-with-dropdowns -... - جميع الحالات ، عند دمج الطبقات

أعتقد أن واجهة برمجة التطبيقات الحالية createPortal تلبي السيناريو الأول فقط. اقتراح استخدام React.render جديد للثانية غير قابل للاستخدام - من السيئ جدًا إنشاء تطبيق منفصل مع كل مزوديه لكل طبقة.
ما هي المعلومات الإضافية التي يمكننا تقديمها للمساعدة في حل هذه المشكلة؟
ما عيوب البارامتر المقترح في واجهة برمجة تطبيقات createPortal ؟

sebmarkbage سؤالي الفوري مع واجهة برمجة تطبيقات الفتحات هو: هل سأتمكن من إدراج SlotContents في واحد Slot في نفس الوقت؟ ليس من غير المألوف في واجهتنا فتح العديد من "النوافذ المنبثقة" أو "الوسائط" في وقت واحد. في عالمي المثالي ، ستبدو واجهة برمجة تطبيقات Popup كما يلي:

import { App } from './app'
import { PopupSlot } from './popups'

let root = (
  <div>
    <App />
    <PopupSlot />
  </div>
)

ReactDOM.render(root, document.querySelector('#root'))

// some dark corner of our app

import { Popup } from './popups'

export function SoManyPopups () {
  return <>
    <Popup>My Entire</Popup>
    <Popup>Interface</Popup>
    <Popup>Is Popups</Popup>
  </>
}

لدينا مشكلة جديدة في هذا الأمر ولم أتمكن تمامًا من العثور على حل بديل لها. باستخدام نهج "حدث فخ" المقترح أعلاه ، يتم حظر أحداث React Synthetic فقط من الخروج من البوابة. لا تزال الأحداث الأصلية تنفجر ، وبما أن شفرة React الخاصة بنا مستضافة داخل تطبيق في الغالب jQuery ، فإن معالج jQuery keyDown العالمي على <body> لا يزال يحصل على الحدث.

حاولت إضافة حدث. StopPropagation مستمع لعنصر الحاوية الأصلي داخل البوابة عبر مرجع مثل هذا ، لكن هذا يحيد تمامًا جميع الأحداث الاصطناعية داخل البوابة - افترضت بشكل غير صحيح أن مستمع React ذو المستوى الأعلى كان يشاهد مرحلة الالتقاط.

لست متأكدًا مما يمكن فعله هنا ، بخلاف التغييرات على React.

const allTheEvents: string[] = 'click contextmenu doubleclick drag dragend dragenter dragexit dragleave dragover dragstart drop mousedown mouseenter mouseleave mousemove mouseover mouseout mouseup keydown keypress keyup focus blur change input invalid submit'.split(' ');
const stop = (e: React.SyntheticEvent<HTMLElement>): void => { e.stopPropagation(); };
const nativeStop = (e: Event): void => e.stopPropagation();
const handleRef = (ref: HTMLDivElement | null): void => {
  if (!ref) { return; }
  allTheEvents.forEach(eventName => ref.addEventListener(eventName, nativeStop));
};


/** Prevents https://reactjs.org/docs/portals.html#event-bubbling-through-portals */
export function PortalEventTrap(children: React.ReactNode): JSX.Element {
  return <div
      onClick={stop}
      ...

      ref={handleRef}
    >
      {children}
    </div>;
}

يعتمد ذلك على الترتيب الذي تتم فيه تهيئة ReactDOM و JQuery. إذا تمت تهيئة JQuery أولاً ، فسيتم تثبيت معالجات أحداث المستوى الأعلى من JQuery أولاً ، وبالتالي سيتم تشغيلها قبل تشغيل معالجات ReactDOM الاصطناعية.

يفضل كل من ReactDOM و JQuery أن يكون لهما مستمع واحد فقط من المستوى الأعلى يقوم بعد ذلك بمحاكاة الفقاعات الداخلية ، إلا إذا كان هناك حدث معين لن ينفجر المتصفح مثل scroll .

Kovensky كان ما فهمته هو أن jQuery لم تفعل "

سيكون هذا هو الحال بالنسبة للأحداث المفوضة. على سبيل المثال ، $(document.body).on('click', '.my-selector', e => e.stopPropagation()) .

انظر ، يمكن حل هذا في React ، إذا أقنعني شخص ما فقط أنه لا يمكن حل هذا من خلال تصميمي المقترح أعلاه والذي يتطلب إعادة هيكلة بعض التعليمات البرمجية الخاصة بك. لكني لم أر أي سبب لا يمكن فعله بخلاف مجرد محاولة إيجاد حل سريع للإصلاح.

sebmarkbage اقتراحك لا يحل سوى قضية نشر الأحداث إلى المالك المباشر. ماذا عن بقية الشجرة؟

إليك حالة استخدام أعتقد أنه لا يمكن حلها جيدًا باستخدام Slots أو createPortal

<Form defaultValue={fromValue}>
   <more-fancy-markup />
   <div>
     <Field name="faz"/>
     <ComplexFieldModal>
       <Field name="foo.bar"/>
       <Field name="foo.baz"/>
     </ComplexFieldModal>
  </div>
</Form>

وهنا صورة gif بإعدادات مشابهة ولكنها مختلفة قليلاً ، حيث أستخدم createPortal لموقع متجاوب ، لنقل حقل نموذج إلى شريط أدوات التطبيق (أعلى الشجرة كثيرًا). في هذه الحالة أيضًا ، لا أريد حقًا عودة الأحداث إلى محتوى الصفحة ، لكنني بالتأكيد أريد أن يتماشى سياق النموذج معها. تطبيقي راجع للشغل هو بعض الأشياء ذات الفتحات باستخدام السياق ...

large gif 640x320

sebmarkbage unstable_renderSubtreeIntoContainer سمح بالوصول المباشر إلى أعلى التسلسل الهرمي بغض النظر عن موضع المكون ، إما ضمن التسلسل الهرمي أو كجزء من إطار عمل منفصل.

بالمقارنة ، أرى بعض المشكلات في حل Slot:

  • الحل يفترض أن لديك حق الوصول إلى موقع التسلسل الهرمي حيث "مناسب" لأحداث الفقاعة. هذا بالتأكيد ليس هو الحال بالنسبة للمكونات وأطر المكونات.
  • يفترض أنه من "المقبول" إنشاء فقاعات الأحداث على أي مستوى آخر من التسلسل الهرمي.
  • ستستمر الأحداث في الظهور من موقع Slot. (كما ذكر craigkovatch )

لدي أيضًا حالة استخدام (ربما تشبه تلك التي سبق ذكرها).

لدي سطح حيث يمكن للمستخدمين تحديد الأشياء باستخدام الماوس باستخدام "لاسو". هذا هو في الأساس عرض / ارتفاع بنسبة 100٪ ويقع في جذر تطبيقي واستخدم حدث onMouseDown . يوجد في هذا السطح أيضًا أزرار تفتح بوابات مثل الوسائط والقوائم المنسدلة. يتم اعتراض حدث MouseDown داخل البوابة فعليًا بواسطة مكون تحديد lasso في جذر التطبيق.

أرى الكثير لإصلاح المشكلة:

  • جعل المدخل خطوة واحدة فوق مكون الجذر lasso ، ولكن هذا ليس مناسبًا جدًا وربما يحتاج إلى اللجوء إلى lib القائم على السياق مثل بوابة رد الفعل؟ (أو ربما ذكر نظام الفتحة).
  • أوقف الانتشار يدويًا داخل جذر البوابة ، ولكن قد يؤدي إلى آثار جانبية غير مرغوبة مذكورة أعلاه
  • القدرة على وقف الانتشار في بوابات React (+1 راجع للشغل)
  • تصفية الأحداث عندما تأتي من البوابة

الآن الحل هو تصفية الأحداث.

const appRootNode = document.getElementById('root');

const isInPortal = element => isNodeInParent(element, appRootNode);


    handleMouseDown = e => {
      if (!isInPortal(e.target)) {
        return;
      }
      ...
    };

من الواضح أن هذا لن يكون الحل الأفضل لنا جميعًا ولن يكون لطيفًا جدًا إذا كان لديك بوابات متداخلة ، ولكن بالنسبة لحالة الاستخدام الحالية الخاصة بي (وهي الوحيدة حاليًا) فهي تعمل. لا أريد إضافة lib سياق جديد أو القيام بإعادة بناء معقد لحل هذه المشكلة. أردت فقط مشاركة الحل الخاص بي.

لقد تمكنت من إنجاز حدث منع ظهور فقاعات كما هو مذكور في مكان آخر في هذا الموضوع.

لكن هناك مشكلة أخرى تبدو أكثر تعقيدًا هي حدث SyntheticEvent onMouseEnter ، والذي لا يظهر. بل إنه ينتقل من الأصل المشترك لمكون from إلى المكون to كما هو موضح هنا . هذا يعني أنه إذا دخل مؤشر الماوس من خارج نافذة المتصفح ، فسيتم تشغيل كل معالج onMouseEnter من أعلى DOM وصولاً إلى المكون في createPortal بهذا الترتيب ، مما يتسبب في إطلاق جميع أنواع الأحداث لم تفعل مع unstable_renderSubtreeIntoContainer . نظرًا لأن onMouseEnter لا يظهر في شكل فقاعة ، فلا يمكن حظره على مستوى البوابة. (لا يبدو أن هذه مشكلة في unstable_renderSubtreeIntoContainer لأن الحدث onMouseEnter لم يحترم التسلسل الهرمي الظاهري ولم يتسلسل عبر محتوى الجسم ، بل ينحدر مباشرة إلى الشجرة الفرعية.)

إذا كان لدى أي شخص أي أفكار حول كيفية منع أحداث onMouseEnter من الانتشار من أعلى تسلسل DOM الهرمي أو التحويل مباشرة إلى الشجرة الفرعية للبوابة الإلكترونية ، فيرجى إبلاغي بذلك.

JasonGore لقد لاحظت أيضًا هذا السلوك.

على سبيل المثال.

لدي قائمة سياق يتم عرضها عند تشغيل div onMouseOver ، ثم أقوم بفتح Modal باستخدام createPortal بالنقر فوق أحد العناصر الموجودة في القائمة. عندما أخرج الماوس من نافذة المتصفح ، ينتشر حدث onMouseLeave إلى قائمة السياق ، ويغلق قائمة السياق (وبالتالي النموذج) ...

واجهت نفس المشكلة حيث كان لدي عنصر قائمة أردت أن يكون قابلاً للنقر بالكامل (كرابط) ، لكنني أردت زر حذف على الملصقات الموجودة أسفل الاسم والذي سيفتح شكلاً للتأكيد.

screenshot 2018-10-31 at 11 42 47

كان الحل الوحيد بالنسبة لي هو منع ظهور الفقاعات على وحدة div الوسيطة كما يلي:

// components/Modal.js

onClick(e) {
    e.stopPropagation();
}

return createPortal(
        <div onClick={this.onClick} ...
            ...

ستمنع الفقاعات على كل مشروط نعم ، لكن ليس لدي أي حالة أريد أن يحدث فيها ذلك حتى الآن ، لذا فهو يعمل بالنسبة لي.

هل هناك مشاكل محتملة مع هذا النهج؟

jnsandrew لا تنس أن هناك ما

فقط اضغط على هذا. يبدو لي محرجًا أن تتصرف React بطريقتها الخاصة التي تختلف عن فقاعات حدث DOM.

+1 لهذا. نحن نستخدم React.createPortal للعرض داخل إطار iframe ، (لكل من عزل الأنماط والأحداث) ولن نكون قادرين على منع الأحداث من الخروج من الصندوق يعد مشكلة.

يبدو أن هذه هي المشكلة رقم 12 الأكثر إبهامًا في الأعمال المتراكمة في React. على الأقل المستندات مفتوحة حول هذا الموضوع https://reactjs.org/docs/portals.html#event -bubbling-through-portals - ومع ذلك لم يذكروا الجوانب السلبية أو الحلول البديلة ويلاحظون بدلاً من ذلك أنها تتيح "تجريدات أكثر مرونة ":

يجب على المستندات على الأقل توضيح أن هذا يمكن أن يسبب مشاكل ويقترح حلولاً بديلة. في حالتي ، إنها حالة استخدام مباشرة جدًا ، باستخدام https://github.com/reactjs/react-modal : لدي أزرار تفتح أشياء مثل القوائم المنسدلة ، وداخل هذه الأزرار تنشئ نماذج. النقر فوق الفقاعات المشروطة لأعلى الزر ، مما يؤدي إلى القيام بأشياء غير مرغوب فيها. يتم تغليف النماذج في مكون متماسك وسحب فواصل الجزء المدخل التي تغلف ، مما يؤدي إلى تجريد متسرب. قد يكون أحد الحلول هو قلب علامة لتعطيل هذه الأزرار أثناء فتح النموذج. وبالطبع يمكنني أيضًا إيقاف التكاثر كما هو مقترح أعلاه ، ولكن في بعض الحالات قد لا أرغب في القيام بذلك.

لست متأكدًا من مدى فائدة الفقاعة والتقاط الصورة بشكل عام (رغم أنني أعلم أن React يعتمد على الفقاعات تحت الغطاء) - فهي بالتأكيد تمتلك تاريخًا مكتوبًا ، لكنني أفضل تمرير رد اتصال أو نشر حدث أكثر تحديدًا (على سبيل المثال ، redux action) من الفقاعة لأعلى أو للأسفل ، لأن مثل هذه الأشياء ربما تكون من خلال مجموعة من الوسطاء غير الضروريين. هناك مقالات مثل https://css-tricks.com/dangers-stopping-event-propagation/ وأنا أعمل على التطبيقات التي تعتمد على الانتشار في الجسم ، وغالبًا لإغلاق الأشياء عند النقر على "الخارج" ، ولكني أفضل ضع تراكبًا غير مرئي فوق كل شيء وأغلقه عند النقر فوقه. بالطبع ، لم أستطع استخدام بوابة React لإنشاء مثل هذا التراكب غير المرئي ...

هناك أيضًا كابوس متعلق بالصيانة هنا - حيث تتم إضافة أحداث جديدة إلى DOM ، فإن أي بوابات "مختومة" بالتقنية الموضحة أعلاه "ستسرب" هذه الأحداث الجديدة حتى يتمكن المشرفون من إضافتها إلى القائمة السوداء (الشاملة).

هناك مشكلة تصميم رئيسية هنا تحتاج إلى معالجة. لا تزال القدرة على الاشتراك أو إلغاء الاشتراك في الفقاعة عبر البوابة تبدو أفضل خيار لواجهة برمجة التطبيقات بالنسبة لي. لست متأكدًا من صعوبة التنفيذ فيه ، لكننا ما زلنا نتلقى أخطاء في الإنتاج حول هذا الأمر في Tableau ، بعد أكثر من عام.

اقض ساعتين في محاولة معرفة سبب إرسال النموذج الخاص بي من النموذج لنموذج آخر.
أخيرًا اكتشفت ذلك بفضل هذه المشكلة!

أنا أعاني حقًا لمعرفة متى يكون التكاثر onSubmit ضروريًا. على الأرجح سيكون دائمًا أشبه بخلل أكثر من كونه ميزة.

على الأقل ، يجدر إضافة بعض معلومات التحذير للتفاعل مع المستندات . شيء من هذا القبيل:
بينما تعد Event Bubbling Through Portals ميزة رائعة في بعض الأحيان ، فقد ترغب في منع انتشار بعض الأحداث. يمكنك تحقيق ذلك عن طريق إضافة onSubmit={(e) => {e.stopPropagation()}}

+1 لهذا أيضًا. نحن نستخدم Draftjs heavilly مع النص القابل للنقر الذي يعرض النماذج. وجميع الأحداث على الوسائط مثل التركيز ، والاختيار ، والتغيير ، والضغط على مفتاح إلخ.

IMO ، سلوك الوكيل للحدث معطل بشكل أساسي (ويسبب لي أخطاءً أيضًا) ، لكنني أدرك أن هذا مثير للجدل. يشير هذا الخيط بقوة إلى أن هناك حاجة إلى بوابة تحتوي على سياق الثقوب الدودية وليس الأحداث . هل يوافق الفريق الأساسي؟ في كلتا الحالتين ، ما هي الخطوة التالية هنا؟

لا أستطيع أن أدرك حقًا ، لماذا يُعد نشر الأحداث من البوابة سلوكًا مقصودًا؟ هذا يقف تماما ضد الفكرة الرئيسية للتكاثر. اعتقدت أن البوابات تم إنشاؤها بالضبط لتجنب هذا النوع من الأشياء (مثل التعشيش اليدوي ، ونشر الأحداث ، وما إلى ذلك).

أستطيع أن أؤكد أنه إذا وضعت البوابة بالقرب من شجرة العناصر ، فسوف تنشر الأحداث:

class SomeComponent extends React.Component<any, any> {
  render() {
    return <>
      <div className="some-tree">
        // Portal here will bubble events
      </div>
      // Portal here will also bubble events, just checked
    </>
  }
}

+1 لطلب هذه الميزة

في DOM ، تنبثق الأحداث عن شجرة DOM. في React ، تظهر الأحداث في فقاعة لشجرة المكونات.

أنا أعتمد على السلوك الحالي قليلاً ، ومن الأمثلة على ذلك popouts التي قد تكون متداخلة ؛ إنها جميعًا بوابات لتجنب المشكلات مع overflow: hidden ، ولكن من أجل جعل المنبثقة تتصرف بشكل صحيح ، أحتاج إلى اكتشاف النقرات الخارجية لمكون المنبثقة (والذي يختلف عن اكتشاف النقرات خارج عناصر DOM المعروضة) . قد تكون هناك أمثلة أفضل.

أعتقد أن المناقشة القوية هنا أوضحت أن هناك أسبابًا وجيهة لامتلاك كلا السلوكين. نظرًا لأن createPortal يعرض مكوّن React داخل عقدة حاوية "DOM عادي" ، لا أعتقد أنه سيكون من العملي بالنسبة لـ React's Synthetic Events أن تنتشر من Portal إلى شجرة DOM القديمة البسيطة.

نظرًا لأن البوابات قد توقفت لفترة طويلة الآن ، فقد يكون الوقت قد فات لتغيير السلوك الافتراضي إلى "عدم الانتشار بعد حدود البوابة الإلكترونية".

بناءً على كل المناقشات حتى الآن ، فإن أبسط اقتراحي هو (لا يزال): إضافة علامة اختيارية لإنشاء البوابة التي تمنع انتشار أي حدث يتجاوز حدود البوابة الإلكترونية.

قد يكون الشيء الأكثر قوة هو القدرة على توفير قائمة بيضاء للأحداث التي يجب السماح لها "بتخطي" الحدود ، مع إيقاف الباقي.

gaearon هل وصلنا إلى المرحلة التي يمكن لفريق

أريد أن أضيف دعمي لهذا ، وأختلف مع تعليقات

تعد القدرة على نقل سياق البوابة من مكان واحد في DOM إلى آخر مفيدًا لتنفيذ جميع أنواع التراكبات مثل تلميحات الأدوات والقوائم المنسدلة وبطاقات التمرير ومربعات الحوار ، حيث يتم وصف محتوى التراكب وعرضه في سياق ، الزناد. نظرًا لأن السياق هو مفهوم React ، فإن هذه الآلية تحل مشكلة React. من ناحية أخرى ، فإن القدرة على انتقال حدث DOM البوابة من مكان إلى آخر في DOM هي خدعة خيالية تتيح لك التظاهر بأن بنية DOM مختلفة عما أعددته بشكل صريح. هذا يحل مشكلة استخدام حدث DOM الذي ينفجر من أجل التفويض ، عندما تريد التفويض إلى جزء مختلف من DOM. ربما يجب عليك استخدام عمليات الاسترجاعات (أو السياق) على أي حال ، إذا كان لديك React ، بدلاً من الاعتماد على أحداث DOM التي تتدفق من الداخل إلى الخارج من التراكب. كما أشار آخرون ، نادرًا ما تريد "الوصول" والتعامل مع حدث يحدث داخل التراكب ، عن قصد أو عن غير قصد.

يحل حدث DOM الفقاعي بشكل أساسي مشكلة مطابقة أحداث DOM مع أهداف DOM. كل نقرة هي في الواقع نقرة على مجموعة كاملة من العناصر المتداخلة. ليس من الأفضل اعتبارها آلية تفويض عالية المستوى ، IMO ، واستخدام أحداث DOM للتفويض عبر حدود مكونات React ليس تغليفًا رائعًا ، إلا إذا كانت المكونات عبارة عن مكونات مساعدة خاصة صغيرة تُستخدم لتقديم وحدات يمكن التنبؤ بها من DOM.

event.target === event.currentTarget يساعدني في حل هذه المشكلة. لكن هذا حقا صداع.

هذا الشيء لي اليوم أثناء محاولة ترحيل مكون منبثق باستخدام unstable_renderSubtreeIntoContainer لاستخدام createPortal . يحتوي المكون المعني على عناصر قابلة للسحب ويتم تقديمه باعتباره تابعًا لعنصر آخر قابل للسحب. هذا يعني أن كلاً من العنصر الرئيسي والعناصر المنبثقة يحتويان على معالجات أحداث للماوس واللمس ، والتي بدأ كلاهما في إطلاق النار عند التفاعل مع العنصر المنبثق في portal.

نظرًا لأنه يتم إهمال unstable_renderSubtreeIntoContainer (؟) ، فإن الحل البديل ضروري - لا يبدو أن أيًا من الحلول المقدمة أعلاه يمكن أن تكون حلولًا قابلة للتطبيق على المدى الطويل.

مهلا! شكرا على كل هذه الاقتراحات يا شباب!
لقد ساعدني في إصلاح إحدى مشاكلي.
هل ترغب في قراءة مقال رائع وغني بالمعلومات حول أهمية وقدرات فريق React ؟ أعتقد أنه سيكون مفيدًا لكل من يهتم بالتنمية. حظا طيبا وفقك الله!

غالبًا ما تريد IMO أن تمنحك البوابة الإلكترونية إمكانية الوصول إلى السياق ، ولكن ليس الأحداث المنبثقة. مرة أخرى عندما استخدمنا Angular 1.x ، كتبنا خدمة النوافذ المنبثقة الخاصة بنا والتي قد تتطلب $scope وسلسلة قالب ، وقمنا بتجميع / عرض هذا القالب وإلحاقه بالجسم. قمنا بتنفيذ جميع النوافذ المنبثقة / النماذج / القوائم المنسدلة الخاصة بتطبيقنا مع تلك الخدمة ، ولم نفقد مرة واحدة عدم ظهور فقاعات للحدث.

يبدو أن الحل البديل stopPropagation() يمنع مستمعي الأحداث المحليين على window من التشغيل (في حالتنا تمت إضافته بواسطة react-dnd-html5-backend ).

إليك الحد الأدنى من إعادة طرح المشكلة: https://codepen.io/mogel/pen/xxKRPbQ

إذا لم تكن هناك خطة لتوفير طريقة لتجنب الفقاعات الاصطناعية عبر البوابات ، فربما يكون لدى شخص ما حل بديل لا يكسر فقاعة الأحداث المحلية؟

يبدو أن الحل stopPropagation () يمنع تشغيل مستمعي الأحداث الأصليين على النافذة

صيح. :(

إذا لم تكن هناك خطة لتوفير طريقة لتجنب الفقاعات الاصطناعية عبر البوابات

على الرغم من صمت الفريق الأساسي ، أنا وكثيرين آخرين في هذا الموضوع ، آمل حقًا أن تكون هناك مثل هذه الخطط.

ربما شخص ما لديه حل بديل لا يكسر فقاعة الحدث المحلي؟

كان الحل البديل لفريقي هو حظر البوابات بالكامل بسبب هذه المشكلة الصارخة. نقدم أجزاءً تحتوي على خطاف في حاوية تعيش ضمن سياقات أخرى للتطبيق ، بحيث تحصل على سياقات على مستوى الجذر مجانًا ؛ أي أشخاص آخرين نمرهم يدويًا. ليست رائعة ، ولكنها أفضل من معالجات أحداث الضرب الخلد التي لا طائل من ورائها.

لقد مر 17 شهرًا منذ الرد الأخير من قبل أي شخص من الفريق الأساسي. ربما يلفت الأمر ping بعض الاهتمام لهذه المشكلة :) sebmarkbage أوgaearon

كان الحل البديل لفريقي هو حظر البوابات بالكامل بسبب هذه المشكلة الصارخة. نقدم أجزاءً تحتوي على خطاف في حاوية تعيش ضمن سياقات أخرى للتطبيق ، بحيث تحصل على سياقات على مستوى الجذر مجانًا ؛ أي أشخاص آخرين نمرهم يدويًا. ليست رائعة ، ولكنها أفضل من معالجات أحداث الضرب الخلد التي لا طائل من ورائها.

لا يمكنني التفكير في أي مقاربات عامة لتمرير السياق إلى "البوابة المزيفة" عبر الدعائم دون الرجوع إلى الاعتماد على الدعائم المتتالية :(

كان عدد لا يحصى من الأخطاء التي اكتشفتها على https://github.com/reakit/reakit والتي كانت مرتبطة بهذه المشكلة. أستخدم React Portal كثيرًا ولا يمكنني التفكير في حالة واحدة أردت فيها انتقال الحدث من البوابات إلى المكونات الأصلية.

لقد كان الحل البديل إما التحقق منه داخل معالجات أحداث الوالدين:

event.currentTarget.contains(event.target);

أو استخدام الأحداث المحلية بدلاً من ذلك:

const onClick = () => {};
React.useEffect(() => {
  ref.current.addEventListener("click", onClick);
  return () => ref.current.removeEventListener("click", onClick);
});

أستخدم هذه الأساليب داخليًا في المكتبة. لكن لا أحد منهم مثالي. ونظرًا لأن هذه مكتبة مكونات مفتوحة المصدر ، لا يمكنني التحكم في كيفية تمرير الأشخاص لمعالجات الأحداث إلى المكونات.

خيار تعطيل فقاعة الحدث من شأنه أن يحل كل هذه المشاكل.

لقد اخترقت معًا حلًا شبه بديل يمنع ظهور فقاعات React بينما أعيد أيضًا إنشاء نسخة من الحدث على window . يبدو أنه يعمل في Chrome و Firefox و Safari على OSX ، ولكن تم استبعاد IE11 بسبب عدم السماح بتعيين event.target يدويًا. حتى الآن ، لا يهتم سوى بأحداث الماوس والمؤشر ولوحة المفاتيح والعجلة. لست متأكدًا مما إذا كان من الممكن استنساخ أحداث السحب.

لسوء الحظ ، لا يمكن استخدامه في قاعدة التعليمات البرمجية الخاصة بنا نظرًا لأننا نحتاج إلى دعم IE11 ، ولكن ربما يمكن لشخص آخر تكييفه لاستخدامه الخاص.

ما يجعل هذا الأمر محيرًا بشكل خاص ، هو أن فقاعات السلوك "الافتراضي" _ down_ شجرة المكون مرة أخرى. خذ الشجرة التالية:

<Link>
   <Menu (portal)>
      <form onSubmit={...}>
         <button type="submit">

لقد كنت أسحب شعري لساعات ، لماذا مع هذه المجموعة الدقيقة من المكونات لم يتم استدعاء نموذجي onSubmit - بغض النظر عما إذا قمت بالنقر فوق زر الإرسال أو الضغط على Enter في حقل إدخال داخل النموذج.

أخيرًا ، اكتشفت ذلك لأن مكون React Router Link يحتوي على onClick الذي يقوم بتنفيذ e.preventDefault() لمنع المتصفح من إعادة التحميل. ومع ذلك ، فإن هذا له تأثير جانبي مؤسف وهو حظر السلوك الافتراضي للنقر على زر الإرسال ، والذي يحدث لإرسال النموذج. لذا فإن ما تعلمته اليوم هو أن المستعرض يتم استدعاء onSubmit بالفعل ، كإجراء افتراضي للضغط على زر الإرسال. حتى عند الضغط على مفتاح الإدخال ، يؤدي ذلك إلى النقر على زر الإرسال ، وبالتالي يؤدي إلى إرسال نموذج.

لكنك ترى كيف يجعل ترتيب الحدث هذا أمرًا غريبًا حقًا.

  1. <input> [ضغط مفتاح الإدخال]
  2. <button type="submit"> [نقرة محاكاة]
  3. <Menu> [ينتشر الحدث خارج البوابة]
  4. <Link> [وصول الانتشار إلى الأصل Link ]
  5. <Link> [calls e.preventDefault() ]
  6. => تم إلغاء استجابة المتصفح الافتراضية عند النقر على زر الإرسال
  7. => لم يتم تقديم النموذج

يحدث هذا على الرغم من أننا مررنا بالفعل الزر والنموذج في DOM ، و Link لا علاقة له بهما كما أنه لم يقصد منع هذا السلوك على الإطلاق.

كان الحل بالنسبة لي (إذا واجه أي شخص نفس المشكلة) هو الحل الشائع الاستخدام لتغليف محتوى <Menu> في div بـ onClick={e => e.stopPropagation()} . لكن وجهة نظري هي أنني فقدت الكثير من الوقت في تتبع المشكلة لأن السلوك غير بديهي حقًا.

كان الحل بالنسبة لي (إذا واجه أي شخص نفس المشكلة) هو الحل الشائع الاستخدام لتغليف محتوى <Menu> في div بـ onClick={e => e.stopPropagation()} . لكن وجهة نظري هي أنني فقدت الكثير من الوقت في تتبع المشكلة لأن السلوك غير بديهي حقًا.

نعم - كل _ مثيل فردي للمشكلة _ له نفس الحل السهل ، _ بمجرد أن تختبر الخطأ وتعرفته بشكل صحيح_. إنها حفرة شديدة الانحدار من الفشل قام فريق React بتقطيعها هنا ، ومن المحبط عدم سماع أي اعتراف منهم بذلك.

لقد قضيت عدة أيام أحاول تصحيح مشكلة أخرى مع mouseenter تخرج من البوابات بشكل غير متوقع. حتى مع وجود onMouseEnter={e => e.stopPropagation()} على موقع div الخاص بالبوابة ، لا تزال الأحداث تتدفق إلى زر المالك ، كما هو الحال في https://github.com/facebook/react/issues/11387#issuecomment -340009465 (الأول التعليق على هذه المسألة). ليس من المفترض أن يظهر mouseenter / mouseleave فقاعة في المقام الأول ...

ربما يكون الأمر أكثر غرابة ، عندما أرى فقاعة حدث اصطناعية mouseenter خارج البوابة إلى زر ، فإن e.nativeEvent.type هو mouseout . تقوم React بإطلاق حدث اصطناعي غير فقاعي بناءً على حدث أصلي يتدفق - وعلى الرغم من استدعاء stopPropagation في الحدث التركيبي.

gaearontrueadm تسببت هذه المشكلة في إحباط كبير ومتسق لأكثر من عامين حتى الآن. هذا الموضوع هو أحد أهم المشكلات النشطة في React. من فضلك ، هل يمكن لأحد من الفريق المساهمة هنا؟

في حالتي ، فإن فتح مكون Window عن طريق النقر فوق زر جعل النافذة تختفي نظرًا لأن النقر فوق Window تسبب في نقرة على الزر مما تسبب في تغيير الحالة

أنا جديد في React ، فأنا في الغالب أستخدم jQuery و Vanillia JS ولكن هذا خطأ مذهل. قد يكون هناك مثل 1٪ من الحالات التي يكون فيها هذا السلوك متوقعًا ...

أنا مثل الحلين منdiegohaz، ولكن ما زلت اعتقد createPortal يجب أن يكون لديك خيار لمحتدما الحدث توقف.

كانت حالة الاستخدام الخاصة بي مع معالجات تلميح الأدوات onMouseLeave و onMouseEnter معالجات التي يتم تشغيلها بواسطة تابع بوابة الطفل - وهو أمر غير مرغوب فيه. أصلحت الأحداث الأصلية هذا الأمر من خلال تجاهل أحفاد البوابة لأنهم ليسوا أحفادًا.

+1 لخيار إيقاف الفقاعات في البوابات. تم اقتراح وضع البوابة كأخ (بدلاً من طفل) للمكون الذي نشأ فيه مستمع الحدث ، لكنني أعتقد أن هذا لا يعمل في العديد من حالات الاستخدام (بما في ذلك حالات الاستخدام الخاصة بي).

يبدو أخيرًا أنه ستتم إزالة ReactDOM.unstable_renderSubtreeIntoContainer ، مما يعني أنه لن يكون هناك قريبًا أي حلول معقولة متبقية لهذه المشكلة ...

^ ساعدنا trueadm -nobi ، أنت أملنا الوحيد

يبدو أن الأمر ping لهم على GitHub لا يعمل 😞
ربما يمكن لشخص ما لديه حساب Twitter نشط أن يغرد عن هذا ، مع وضع علامة على أحد المساهمين؟

إضافة +1 الخاص بي لهذه المشكلة. في Notion ، نستخدم حاليًا تنفيذ بوابة مخصصة تسبق React.createPortal ، ونقوم يدويًا بإعادة توجيه موفري السياق إلى الشجرة الجديدة. حاولت استخدام React.createPortal ولكن تم حظره من خلال السلوك الفقاعي غير المتوقع:

اقتراحsebmarkbage بنقل <Portal> خارج المكوِّن <MenuItem> ليصبح أحد الأخوة لا يحل المشكلة إلا لمستوى تداخل واحد. تظل المشكلة إذا كان لديك عدة عناصر قائمة متداخلة (على سبيل المثال) التي تخرج القوائم الفرعية.

تم وضع علامة على هذه المشكلة على أنها قديمة تلقائيًا. إذا كانت هذه المشكلة لا تزال تؤثر عليك ، فالرجاء ترك أي تعليق (على سبيل المثال ، "bump") ، وسوف نبقيها مفتوحة. نأسف لأننا لم نتمكن من تحديد أولوياتها حتى الآن. إذا كان لديك أي معلومات إضافية جديدة ، يرجى تضمينها مع تعليقك!

صدم.

ترك دان تعليقًا على قضية ذات صلة:

mogelbrod ليس لدي حاليًا أي شيء لأضيفه إلى ذلك ، ولكن شيئًا من هذا القبيل ( # 11387 (تعليق) ) يبدو معقولًا بالنسبة لي إذا كنت تقوم بترحيل مكون موجود.

متابعة دان في نفس العدد :

شكرا على السياق حول الحل. نظرًا لأن لديك بالفعل معرفة بهذا المجال ، فمن المحتمل أن تكون أفضل خطوة تالية هي كتابة RFC للسلوك الذي تريده والبدائل التي فكرت فيها: https://github.com/reactjs/rfcs. ضع في اعتبارك أنه من غير المحتمل أن يكون قول RFC "لنغير هذا" مفيدًا. تتطلب كتابة طلب طلب تقديمي جيد فهم سبب امتلاكنا للسلوك الحالي _و_ خطة لتغييره بطريقة تناسب حالات الاستخدام الخاصة بك دون التراجع عن الآخرين.

بغض النظر عن ذلك ، لا يتم دعم unstable_renderSubtreeIntoContainer ، لذلك دعونا نفك التشابك بين هذين النقاشين. لن نضيف نشر السياق إليه لأن واجهة برمجة التطبيقات بأكملها مجمدة ولن يتم تحديثها.

يجب علينا بالتأكيد نشر React RFC لاقتراح إضافة العلامة التي تمت مناقشتها ، أو ربما حل آخر. لا أحد يشعر مهتمة بشكل خاص في صياغة واحدة (ربماjustjake،craigkovatch، أوjquense)؟ إذا لم يكن الأمر كذلك ، فسأرى ما يمكنني التوصل إليه!

على الرغم من أنني مهتم بتطوير واجهة برمجة التطبيقات هذه ، إلا أنني لست مهتمًا بصياغة RFC. في الغالب قبل الميلاد ، إنها مجموعة من الأعمال ولا توجد فرصة تقريبًا لقبولها ، ولا أعتقد أن الفريق الأساسي يدرس فعليًا طلبات التعليقات التي ليست موجودة بالفعل في خارطة الطريق الخاصة بهم.

jquense لا أعتقد أن هذا دقيق. نعم ، من غير المحتمل أن ندمج RFC الذي لا يتماشى مع الرؤية نظرًا لأن إضافة واجهة برمجة تطبيقات جديدة دائمًا ما تكون شاملة وتؤثر على جميع الميزات الأخرى المخطط لها. ومن العدل أننا لا نعلق في كثير من الأحيان على تلك التي لا تعمل. ومع ذلك ، فإننا نقرأها بالفعل ، وخاصة عندما نتناول موضوعًا يتمتع النظام البيئي بخبرة أكبر فيه. كأمثلة ، https://github.com/reactjs/rfcs/pull/38 ، https://github.com/ رد فعل / rfcs / سحب / 150 ، https://github.com/reactjs/rfcs/pull/118 ، https://github.com/reactjs/rfcs/pull/109 ، https://github.com/reactjs/ لقد كان لكل من

بمعنى آخر ، نتعامل مع طلبات التعليقات (RFC) جزئيًا كآلية بحث مجتمعية. هذا التعليق من mogelbrod (https://github.com/facebook/react/issues/16721#issuecomment-674748100) حول سبب إزعاج الحل البديل هو بالضبط نوع الشيء الذي نود رؤيته في RFC. يمكن أن يكون النظر في الحلول الحالية وعيوبها أكثر قيمة من اقتراح اقتراح محدد لواجهة برمجة التطبيقات.

gaearon تعليقي لا يعني أن الفريق لا يستمع إلى التعليقات الخارجية. سوف تقوم بعمل جيد في ذلك. أعتقد أن تعليقي دقيق مع ذلك. لا ينتج عن _العملية _ كما يتم تشغيله على RFC repo قبول RFC من الأشخاص الآخرين. بالنظر إلى RFCs التي تم دمجها ، يكون أعضاء الفريق الأساسيون بالكامل أو موظفو fb وليس أي شخص آخر. عادةً ما تكون الميزات التي تجعلها مختلفة قليلاً ولا تشارك في عملية RFC على الإطلاق (مثل المعرفات المتشابهة).

يسعدني جدًا أن أسمع أنك ستلقي نظرة على RFCs الأخرى وأنهم يساهمون في تصميم الميزات ، ولكن "لقد تأثرنا بهذه العناصر الخارجية لـ RFC على الرغم من أننا لم نعلق عليها مطلقًا" ، أعتقد أنه يوضح وجهة نظري ، وليس تحديها.

بمعنى آخر ، نتعامل مع طلبات التعليقات (RFC) جزئيًا كآلية بحث مجتمعية.

هذا معقول للغاية ، ولكن ليس ما يقوله RFC repo _its_ النهج ، وليس كيف يفكر الآخرون عمومًا في RFC. عادة ما تكون عملية RFC عبارة عن رابط ونقطة اتصال بين الفريق والمجتمع ، بالإضافة إلى شيء من ساحة اللعب المتكافئة من حيث النظر في الميزة والعملية.

نقاط أكبر حول إدارة المجتمع جانبا. إن مطالبة الأشخاص بقضاء بعض الوقت في كتابة مقترحات مفصلة ثم الدفاع عنها للمشاركين الخارجيين الآخرين بينما يقابلهم الصمت من فريق رد الفعل أمر محبط ويعزز الانطباع بأن FB يهتم فقط باحتياجاته الخاصة لـ OSS. الذي ينتن لأنني أعلم أنك لن تشعر أو تصمم بهذه الطريقة.

إذا كان من المفترض أن تكون عملية RFC: "هنا يمكنك تحديد مخاوفك واستخدام الحالات وسنقرأها ، عندما / إذا وصلنا إلى نقطة القدرة على تنفيذ هذه الميزة". بصراحة ، هذا نهج جيد. أعتقد أن المجتمع سيستفيد من ذلك الذي يتم توضيحه صراحةً ، وإلا فإن الأشخاص (ويفعلون) سيتحملون نفس مستوى المشاركة والمشاركة الذي غالبًا ما تتمتع به عمليات RFC الأخرى ثم يتم تثبيطها بشكل نشط عندما لا يتم ذلك. لدي بالتأكيد هذا الانطباع حتى مع وجود رؤية أكثر قليلاً من المساهمين الآخرين.

بالتأكيد ، أعتقد أنني أتفق مع كل ذلك. لا أريد تحويل هذا إلى سلسلة محادثات ، ولكن فقط أقول أنه نظرًا لأن الأشخاص يستمرون في إجراء اختبار ping حول هذا الموضوع ، فإن الشيء الأكثر فاعلية الذي يجب القيام به للمضي قدمًا هو كتابة اقتراح حول كيفية عمله والذي يأخذ المخاوف بشأن كليهما الجوانب في الاعتبار ويجمع فهمًا عميقًا لمشكلة الفضاء . أتفهم تمامًا ما إذا كان هذا شيئًا لا يرغب الناس في إغراق أسنانهم فيه (يعتمد جزئيًا على كيفية استجابتنا لـ RFCs) ، وهذا هو السبب في أنني لم أقترحه سابقًا - ولكن منذ أن استمر في تلقي الأصوات الشخصية ، أردت أن أقترح ذلك كخيار لشخص لديه الدافع.

عادل بما فيه الكفاية ، هذا ليس المكان المناسب للحصول على meta على RFCs :)

gaearon ، هذا هو العدد السادس الأكثر تصويتًا والمفتوح حاليًا على React ، والرابع الأكثر تعليقًا. لقد كان مفتوحًا منذ إصدار React 16 ، وهو على بعد شهرين فقط من أن يصبح عمره 3 سنوات الآن. ومع ذلك ، كان هناك تفاعل ضئيل للغاية من فريق React الأساسي. إنه شعور بالغ الرفض أن تقول "إن الأمر متروك للمجتمع لاقتراح حل" بعد مرور هذا الوقت الطويل ووقوع الألم. يرجى إدراك أنه على الرغم من أنه يحتوي على بعض التطبيقات المفيدة للغاية ، إلا أن هذا السلوك الافتراضي كان خطأ في التصميم. لا ينبغي أن يكون الأمر متروكًا للمجتمع لـ RFC لإصلاحه.

يؤسفني تعليقي على هذه المشكلة وسحب اقتراحي حول RFC للمجتمع. أنت على حق ، ربما تكون فكرة سيئة. يجب أن أضيف أن هذه القضية أصبحت مشحونة جدًا من الناحية العاطفية ، وكإنسان أجد صعوبة في التعامل معها - على الرغم من أنني أفهم أنها مهمة ويشعر الكثير من الناس بها بشدة.

اسمحوا لي أن أرد بإيجاز على حالة هذا الموضوع.

أولا ، أود أن أعتذر للأشخاص الذين علقوا وأصابوا بالإحباط بسبب عدم استمرارنا في المتابعة في هذا الموضوع. إذا كنت أقرأ هذه المشكلة من الخارج ، فمن المحتمل أن يكون انطباعي أن فريق React قد ارتكب خطأ ، ولا يرغب في الاعتراف به ، ومستعد للجلوس على حل بسيط ("فقط أضف قيمة منطقية واحدة ، ما مدى صعوبة يكون! ") لأكثر من عامين لأنهم لا يهتمون بالمجتمع. أستطيع أن أفهم تمامًا كيف يمكن أن تصل إلى هذا الاستنتاج.

أنا أعلم أن هذه القضية تم التصويت عليها بشدة. لقد تم طرح هذا عدة مرات في هذا الموضوع ، ربما من منظور أنه إذا كان فريق React قد أدرك أن هذه نقطة ألم كبيرة ، فسنكون قد عالجناها عاجلاً. نحن نعلم أن هذه نقطة ألم - يراسلنا الناس بانتظام بشكل خاص حول هذا الموضوع ، أو يقدمونها كمثال على كيف أن فريق React لا يهتم بالمجتمع. بينما أقر تمامًا بأن الصمت كان محبطًا ، فإن الضغط المتزايد من أجل "القيام بشيء ما" زاد من صعوبة التعامل مع هذه القضية بشكل مثمر.

هذه المشكلة لها حلول بديلة - مما يجعلها مختلفة عن الثغرة الأمنية أو التعطل الذي يجب التعامل معه بشكل عاجل. نحن نعلم أن wokarounds تعمل (ولكنها ليست مثالية ويمكن أن تكون مزعجة) لأننا نستخدم بعضًا منها بأنفسنا ، خاصةً حول التعليمات البرمجية التي تمت كتابتها قبل React 16. أعتقد أنه يمكننا الاتفاق على أنه في حين أن هذه المشكلة كانت بلا شك محبطة لعدد كبير عدد الأشخاص ، فهو لا يزال في فئة مشاكل مختلفة عن التعطل أو مشكلة أمنية يجب الرد عليها في إطار زمني محدد.

بالإضافة إلى ذلك ، أنا لا أتفق مع الإطار القائل بوجود حل بسيط يمكننا تنفيذه غدًا. حتى لو اعتبرنا السلوك الأولي خطأ (لست متأكدًا من أنني أتفق معه) ، فهذا يعني أن شريط التعامل مع السلوك التالي لمجموعة كاملة من حالات الاستخدام أعلى من ذلك . إذا أصلحنا بعض الحالات ولكننا كسرنا البعض الآخر ، فإننا لم نحرز أي تقدم ، وخلقنا الكثير من المشاكل. ضع في اعتبارك أننا لن نسمع عن الحالات التي يعمل فيها السلوك الحالي بشكل جيد في هذه المشكلة. سنسمع عنها فقط بعد أن نكسرها.

لإعطائك مثالاً ، السلوك الحالي مفيد حقًا لحالة استخدام الإدارة ذات التركيز التصريحي التي كنا نبحث عنها لبعض الوقت. من المفيد التعامل مع التركيز / التمويه على أنه يحدث "داخل" نموذج فيما يتعلق بشجرة الأجزاء ، على الرغم من كونها بوابة. إذا أردنا شحن الاقتراح "البسيط" createPortal(tree, boolean) المقترح في سلسلة الرسائل هذه ، فلن تعمل حالة الاستخدام هذه لأن البوابة نفسها لا يمكنها "معرفة" السلوك الذي نريده. يحتاج أي استكشاف لحل محتمل إلى النظر في العشرات من حالات الاستخدام وبعضها لم يتم فهمه بالكامل حتى الآن. هذا ضروري للقيام به في مرحلة ما بالتأكيد ، لكنه أيضًا التزام طويل الأمد للقيام بذلك بشكل صحيح ، وحتى الآن لم نتمكن من التركيز عليه.

الأحداث على وجه الخصوص هي منطقة شائكة ، على سبيل المثال ، لقد قمنا للتو بمجموعة من التغييرات التي تعالج مشاكل سنوات عديدة ، وكان هذا محور تركيز كبير هذا العام. لكن يمكننا فقط القيام بأشياء كثيرة في وقت واحد.

بشكل عام ، نحاول كفريق واحد التركيز على بعض المشكلات بعمق ، بدلاً من التركيز على العديد من المشكلات بشكل سطحي. لسوء الحظ ، هذا يعني أن بعض العيوب والفجوات المفاهيمية قد لا يتم سدها لسنوات لأننا في منتصف إصلاح فجوات مهمة أخرى أو ليس لدينا تصميم بديل تم وضعه لحل المشكلة إلى الأبد. أعلم أن هذا أمر محبط لسماعه ، وهذا جزء من سبب ابتعدي عن هذا الموضوع. تحولت بعض الخيوط المماثلة الأخرى إلى تفسيرات أعمق للمشكلات والحلول الممكنة ، وهي مفيدة ، ولكن هذا الحل تحول في الغالب إلى فيض من "+1" واقتراحات لإصلاح "بسيط" ، وهذا هو سبب صعوبة ذلك للتعامل معها بشكل هادف.

أعلم أن هذه ليست الإجابة التي أراد الناس سماعها ، لكنني آمل أن تكون أفضل من عدم وجود إجابة على الإطلاق.

شيء آخر يستحق الاستدلال عليه هو أن بعض نقاط الألم الموضحة في هذا الخيط ربما تم حلها بوسائل أخرى. على سبيل المثال:

على وجه التحديد: استدعاء stopPropagation في حدث التركيز الاصطناعي لمنعه من الخروج من البوابة يتسبب أيضًا في استدعاء stopPropagation في حدث التركيز الأصلي في معالج React المُلتقط على #document ، مما يعني أنه لم يصل إلى معالج آخر تم التقاطه على

لم يعد React يستخدم مرحلة الالتقاط لمحاكاة الفقاعة ، ولم يعد يستمع أيضًا إلى الأحداث الموجودة في المستند بعد الآن. لذلك بدون استبعاد الإحباط ، سيكون من الضروري بالتأكيد إعادة تقييم كل ما تم نشره حتى الآن في ضوء التغييرات الأخرى.

لا تزال الأحداث الأصلية في شكل فقاعة ، وبما أن شفرة React الخاصة بنا مستضافة داخل تطبيق في الغالب jQuery ، فإن معالج jQuery keyDown العالمي

لا يزال يحصل على الحدث.

وبالمثل ، فإن React 17 ستُلحق الأحداث بالجذور وحاويات البوابة (وتوقف الانتشار الأصلي بالفعل عند تلك النقطة) لذلك أتوقع أن يتم حلها أيضًا.

فيما يتعلق بالنقاط التي تتم إزالتها حول renderSubtreeIntoContainer . حرفيا الاختلاف الوحيد عن ReactDOM.render هو أنه ينشر السياق القديم. نظرًا لأن أي إصدار لا يتضمن renderSubtreeIntoContainer لن يتضمن أيضًا السياق القديم ، فإن ReactDOM.render سيبقى بديلاً متطابقًا بنسبة 100٪. هذا بالطبع لا يحل المشكلة الأوسع ، لكنني أعتقد أن الاهتمام بـ renderSubtree وجه التحديد هو في غير محله إلى حد ما.

تضمين التغريدة

فيما يتعلق بالنقاط التي تتم إزالتها حول renderSubtreeIntoContainer . حرفيا الاختلاف الوحيد عن ReactDOM.render هو أنه ينشر السياق القديم. نظرًا لأن أي إصدار لا يتضمن renderSubtreeIntoContainer لن يتضمن أيضًا السياق القديم ، فإن ReactDOM.render سيبقى بديلاً متطابقًا بنسبة 100٪. هذا بالطبع لا يحل المشكلة الأوسع ، لكنني أعتقد أن الاهتمام بـ renderSubtree وجه التحديد هو في غير محله إلى حد ما.

الآن بعد أن ذكرت ذلك ، أتساءل عما إذا كان الكود أدناه سيكون تطبيقًا صالحًا وآمنًا لبوابة React دون حدوث فقاعات:

function Portal({ children }) {
  const containerRef = React.useRef();

  React.useEffect(() => {
    const container = document.createElement("div");
    containerRef.current = container;
    document.body.appendChild(container);
    return () => {
      ReactDOM.unmountComponentAtNode(container);
      document.body.removeChild(container);
    };
  }, []);

  React.useEffect(() => {
    ReactDOM.render(children, containerRef.current);
  }, [children]);

  return null;
}

CodeSandbox مع بعض الاختبارات: https://codesandbox.io/s/react-portal-with-reactdom-render-m22dj؟file=/src/App.js

لا تزال هناك مشكلة في عدم اجتياز السياق الحديث ولكن هذه ليست مشكلة جديدة ( renderSubtree يتأثر أيضًا بها). الحل البديل هناك هو إحاطة شجرتك بمجموعة من موفري السياق. بشكل عام ، ليس من المثالي تداخل الأشجار ، لذلك لا أوصي بتغيير الطريقة إلى هذا النمط في أي شيء بخلاف سيناريوهات الكود القديمة الموجودة.

مرة أخرى ، شكرًا جزيلاً على الكتابةgaearon!

يبدو أن تجميع قائمة الحالات المعطلة + الحلول البديلة (المحدثة لـ React v17) سيكون الشيء الأكثر إنتاجية لشخص ما خارج الفريق الأساسي (صححني إذا كنت مخطئًا!).

أنا غارق في الأسابيع المقبلة ولكن أهدف إلى القيام بذلك في أسرع وقت ممكن. إذا كان أي شخص آخر قادرًا على القيام بذلك مسبقًا ، أو يتناغم مع المقتطفات (مثل diegohaz ) ، فسيكون ذلك رائعًا!

سيكون تجميع قائمة من الحالات مفيدًا بالتأكيد ، على الرغم من أنني أقول إنه لا يحتاج إلى تضمين الحالات المعطلة فحسب ، بل يشمل أيضًا الحالات التي يكون فيها السلوك الحالي منطقيًا.

إذا كانت هناك مساحة عامة لإضافتها ، فسيسعدني إضافة حالات استخدام من كل من تطبيقاتنا وكمؤلف مكتبة واجهة المستخدم. بشكل عام ، أتفق مع دان في أنه بينما يكون مزعجًا في بعض الأحيان ، من السهل حل المشكلة. في الحالات التي تريد فيها فقاعات React ، من الصعب جدًا تغطية الحالة دون مساعدة React.

سيكون تجميع قائمة من الحالات مفيدًا بالتأكيد ، على الرغم من أنني أقول إنه لا يحتاج إلى تضمين الحالات المعطلة فحسب ، بل يشمل أيضًا الحالات التي يكون فيها السلوك الحالي منطقيًا.

يسعدني تضمينها إذا كان بإمكان أي شخص توجيهي إلى بعض التعليمات البرمجية مفتوحة المصدر / الشفرة المستخرجة التي تعتمد عليها! كما ذكرت سابقًا ، يصعب العثور على هذه المشكلة نظرًا لأن الأشخاص الذين يعانون من مشكلات تتعلق بالسلوك الحالي هم فقط من يشاركون في هذه المشكلة 😅

إذا كانت هناك مساحة عامة لإضافتها ، فسيسعدني إضافة حالات استخدام من كل من تطبيقاتنا وكمؤلف مكتبة واجهة المستخدم. بشكل عام ، أتفق مع دان في أنه بينما يكون مزعجًا في بعض الأحيان ، من السهل حل المشكلة. في الحالات التي تريد فيها فقاعات React ، من الصعب جدًا تغطية الحالة دون مساعدة React.

أي مساحة محددة تفكر فيها ، أو هل مشاركة كود وصندوق واحد (أو jsfiddle ، وما إلى ذلك) لكل حالة تعمل كبداية؟ يمكنني محاولة تجميعها جميعًا بمجرد جمع بعض الحالات.

لقد بدأت موضوعًا هنا: https://github.com/facebook/react/issues/19637. دعونا نجعلها تركز على الأمثلة العملية ، بينما هذا المثال يبقى للمناقشة العامة.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات