الفرق بين المراجعتين ل"React/portals"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
(تحديث)
 
(7 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة)
سطر 25: سطر 25:
 
   );
 
   );
 
}
 
}
</syntaxhighlight>الحالة النموذجية لاستخدام المداخل هي عندما يمتلك المكوّن الأب التنسيق overflow: hidden أو z-index ولكنك تريد من المكوّن الابن أن يظهر خارج حاويته، على سبيل المثال مربعات الحوار (dialogs) وتلميحات الأدوات (tooltips).
+
</syntaxhighlight>الحالة النموذجية لاستخدام المداخل هي عندما يمتلك المكوّن الأب التنسيق <code>overflow: hidden</code> أو <code>z-index</code> ولكنك تريد من المكوّن الابن أن يظهر خارج حاويته، على سبيل المثال مربعات الحوار (dialogs) وتلميحات الأدوات (tooltips).
  
'''ملاحظة:''' تذكر عند التعامل مع المداخل أنّ إدارة تركيز لوحة المفاتيح تصبح أمرًا هامًّا.
+
'''ملاحظة:''' تذكر عند التعامل مع المداخل أنّ [[React/accessibility|إدارة تركيز لوحة المفاتيح]] تصبح أمرًا هامًّا.
  
من أجل مربعات الحوار تأكد من قدرة جميع المستخدمين على التعامل معها عن طريق اتباع هذه الإرشادات.
+
من أجل مربعات الحوار تأكد من قدرة جميع المستخدمين على التعامل معها عن طريق اتباع [https://www.w3.org/TR/wai-aria-practices-1.1/#dialog_modal هذه الإرشادات].
  
جرب المثال على CodePen.
+
[https://codepen.io/gaearon/pen/yzMaBd جرب المثال على CodePen].
  
تضخيم الأحداث عن طريق المداخل
+
== انتشار الأحداث عن طريق المداخل ==
 +
بالرغم من أن المداخل يُمكِن لها أن تكون في أي مكان من شجرة DOM، فهي تتصرف مثل عناصر React الأبناء في كل طريقة أخرى. تعمل ميزات مثل السياق بنفس الطريقة بالضبط بغض النظر إن كان العنصر الابن مدخل أم لا، لأنّ المدخل يبقى موجودًا في شجرة React بغض النظر عن موقعه في شجرة DOM.
 +
 
 +
يتضمّن ذلك انتشار الأحداث (event bubbling)، حيث أنّ الحدث الذي أُطلِق بداخل المدخل سيتصاعد إلى العناصر الأسلاف في شجرة React حتى ولو لم تكن هذه العناصر أسلافًا في شجرة DOM. بافتراض بنية HTML التالية:<syntaxhighlight lang="html">
 +
<html>
 +
  <body>
 +
    <div id="app-root"></div>
 +
    <div id="modal-root"></div>
 +
  </body>
 +
</html>
 +
</syntaxhighlight>سيكون المكوّن الأب <code>Parent</code> في <code>‎#app-root</code>‎ قادرًا على الإمساك بالحدث المُضاعَف من العقدة الشقيقة له وهي ‎<code>#modal-root‎</code>.<syntaxhighlight lang="javascript">
 +
// الحاويتان التاليتان هما أشقاء في DOM
 +
const appRoot = document.getElementById('app-root');
 +
const modalRoot = document.getElementById('modal-root');
 +
 
 +
class Modal extends React.Component {
 +
  constructor(props) {
 +
    super(props);
 +
    this.el = document.createElement('div');
 +
  }
 +
 
 +
  componentDidMount() {
 +
// يُدخَل عنصر المدخل في شجرة DOM
 +
// بعد وصل أبناء الـ modal
 +
// مما يعني وصل الأبناء إلى عقدة DOM منفصلة
 +
// إن احتاج المكون الابن وصله إلى عقدة DOM مباشرة عند الوصل
 +
// مثلا لقياس عقدة DOM
 +
// أو استخدام التركيز التلقائي
 +
// فأضف حالة إلى الـ Modal وصير فقط الابن
 +
// عند إدخال الـ Modal في شجرة DOM
 +
    modalRoot.appendChild(this.el);
 +
  }
 +
 
 +
  componentWillUnmount() {
 +
    modalRoot.removeChild(this.el);
 +
  }
 +
 
 +
  render() {
 +
    return ReactDOM.createPortal(
 +
      this.props.children,
 +
      this.el
 +
    );
 +
  }
 +
}
 +
 
 +
class Parent extends React.Component {
 +
  constructor(props) {
 +
    super(props);
 +
    this.state = {clicks: 0};
 +
    this.handleClick = this.handleClick.bind(this);
 +
  }
 +
 
 +
  handleClick() {
 +
// سيطلَق هذا عند الضغط على الزر في المكون الابن
 +
// مما يحدث حالة المكون الأب
 +
// حتى ولو لم يكن الزر منحدرًا بشكل مباشر في DOM.
 +
 +
    this.setState(state => ({
 +
      clicks: state.clicks + 1
 +
    }));
 +
  }
 +
 
 +
  render() {
 +
    return (
 +
      <div onClick={this.handleClick}>
 +
        <p>عدد النقرات {this.state.clicks}</p>
 +
        <p>
 +
  افتح أدوات تطوير المتصفح لتلاحظ أن الزر ليس ابنًا
 +
  للعنصر div
 +
  الذي يمتلك معالج الأحداث onClick
 +
        </p>
 +
        <Modal>
 +
          <Child />
 +
        </Modal>
 +
      </div>
 +
    );
 +
  }
 +
}
 +
 
 +
function Child() {
  
بالرغم من أن المداخل يُمكِن لها أن تكون في أي مكان من شجرة DOM، فهي تتصرف مثل عناصر React الأبناء في كل طريقة أخرى. تعمل ميزات مثل السياق بنفس الطريقة بالضبط بغض النظر إن كان العنصر الابن مدخل أم لا، لأنّ المدخل يبقى موجودًا في شجرة React بغض النظر عن موقعه في شجرة DOM.
+
  // سيتضاعف حدث النقر على هذا الزر إلى المكون الأب
 +
  // بسبب عدم وجود خاصية onClick معرفة
 +
  return (
 +
    <div className="modal">
 +
      <button>Click</button>
 +
    </div>
 +
  );
 +
}
 +
 
 +
ReactDOM.render(<Parent />, appRoot);
 +
 
 +
 
 +
</syntaxhighlight>[https://codepen.io/gaearon/pen/jGBWpE جرب المثال على موقع CodePen].
 +
 
 +
يسمح الإمساك بالحدث المُضاعَف من المدخل في المكوّن الأب بتطوير تجريدات مرنة والتي لا تعتمد على المداخل. على سبيل المثال إن صيرنا المكوّن ‎<code><Modal /></code>‎، فبإمكان المكوّن الأب له التقاط أحداثه بغض النظر عمّا إذا كان يعتمد المداخل.
 +
== انظر أيضًا ==
 +
* [[React/jsx in depth|شرح JSX بالتفصيل]]
 +
* [[React/static type checking|التحقق من الأنواع الثابتة]]
 +
* [[React/typechecking with proptypes|التحقق من الأنواع باستخدام PropTypes]]
 +
* [[React/refs and the dom|استخدام المراجع مع DOM]]
 +
* [[React/uncontrolled components|المكونات غير المضبوطة]]
 +
* [[React/optimizing performance|تحسين الأداء]]
 +
* [[React/react without es6|React بدون ES6]]
 +
* [[React/react without jsx|React بدون JSX]]
 +
* [[React/reconciliation|المطابقة (Reconciliation)]]
 +
* [[React/context|استخدام السياق (Context) في React]]
 +
* [[React/fragments|استخدام الأجزاء (Fragments) في React]]
 +
* [[React/error boundaries|حدود الأخطاء]]
 +
* [[React/web components|مكونات الويب]]
 +
* [[React/higher order components|المكونات ذات الترتيب الأعلى]]
 +
* [[React/forwarding refs|تمرير المراجع]]
 +
* [[React/render props|خاصيات التصيير]]
 +
* [[React/integrating with other libraries|تكامل React مع المكتبات الأخرى]]
 +
* [[React/accessibility|سهولة الوصول]]
 +
* [[React/code splitting|تقسيم الشيفرة]]
 +
* [[React/strict mode|الوضع الصارم (Strict Mode)]]
 +
== مصادر==
 +
*[https://reactjs.org/docs/portals.html صفحة المداخل (Portals) في React في توثيق React الرسمي].
 +
[[تصنيف:React]]
 +
[[تصنيف:React Advanced Guides]]

المراجعة الحالية بتاريخ 23:29، 3 نوفمبر 2020

تُزوّدنا المداخل بطريقة ممتازة لتصيير المكونات الأبناء إلى عقدة DOM موجودة خارج تسلسل DOM للمكونات الآباء.

ReactDOM.createPortal(child, container)

الوسيط الأول (child) هو عبارة عن أي مكوّن ابن قابل للتصيير في React، مثل العناصر، والسلاسل النصية، والأجزاء (fragments). الوسيط الثاني (container) هو عنصر DOM.

الاستخدام

عندما تُعيد عنصر من تابع تصيير المكوّن فبشكل اعتيادي يُوصَل إلى DOM كمكوّن ابن لأقرب عقدة أب:

render() {
  // تصل React عنصر div جديد وتصير الأبناء إليه
  return (
    <div>
      {this.props.children}
    </div>
  );
}

ولكن من المفيد أحيانًا إدخال الابن في مواقع متعددة من DOM:

render() {
  // لا تنشئ React عنصر div جديد، فهي تصير الأبناء إلى domNode
  // domNode هي اي عقدة DOM صحيحة بغض النظر عن موقعها في DOM
  
  return ReactDOM.createPortal(
    this.props.children,
    domNode
  );
}

الحالة النموذجية لاستخدام المداخل هي عندما يمتلك المكوّن الأب التنسيق overflow: hidden أو z-index ولكنك تريد من المكوّن الابن أن يظهر خارج حاويته، على سبيل المثال مربعات الحوار (dialogs) وتلميحات الأدوات (tooltips).

ملاحظة: تذكر عند التعامل مع المداخل أنّ إدارة تركيز لوحة المفاتيح تصبح أمرًا هامًّا.

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

جرب المثال على CodePen.

انتشار الأحداث عن طريق المداخل

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

يتضمّن ذلك انتشار الأحداث (event bubbling)، حيث أنّ الحدث الذي أُطلِق بداخل المدخل سيتصاعد إلى العناصر الأسلاف في شجرة React حتى ولو لم تكن هذه العناصر أسلافًا في شجرة DOM. بافتراض بنية HTML التالية:

<html>
  <body>
    <div id="app-root"></div>
    <div id="modal-root"></div>
  </body>
</html>

سيكون المكوّن الأب Parent في ‎#app-root‎ قادرًا على الإمساك بالحدث المُضاعَف من العقدة الشقيقة له وهي ‎#modal-root‎.

// الحاويتان التاليتان هما أشقاء في DOM
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');

class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }

  componentDidMount() {	
	// يُدخَل عنصر المدخل في شجرة DOM
	// بعد وصل أبناء الـ modal
	// مما يعني وصل الأبناء إلى عقدة DOM منفصلة
	// إن احتاج المكون الابن وصله إلى عقدة DOM مباشرة عند الوصل
	// مثلا لقياس عقدة DOM
	// أو استخدام التركيز التلقائي
	// فأضف حالة إلى الـ Modal وصير فقط الابن
	// عند إدخال الـ Modal في شجرة DOM
    modalRoot.appendChild(this.el);
  }

  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }

  render() {
    return ReactDOM.createPortal(
      this.props.children,
      this.el
    );
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {clicks: 0};
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
	// سيطلَق هذا عند الضغط على الزر في المكون الابن
	// مما يحدث حالة المكون الأب
	// حتى ولو لم يكن الزر منحدرًا بشكل مباشر في DOM.
	
    this.setState(state => ({
      clicks: state.clicks + 1
    }));
  }

  render() {
    return (
      <div onClick={this.handleClick}>
        <p>عدد النقرات {this.state.clicks}</p>
        <p>
		  افتح أدوات تطوير المتصفح لتلاحظ أن الزر ليس ابنًا
		  للعنصر div
		  الذي يمتلك معالج الأحداث onClick
        </p>
        <Modal>
          <Child />
        </Modal>
      </div>
    );
  }
}

function Child() {

  // سيتضاعف حدث النقر على هذا الزر إلى المكون الأب
  // بسبب عدم وجود خاصية onClick معرفة
  return (
    <div className="modal">
      <button>Click</button>
    </div>
  );
}

ReactDOM.render(<Parent />, appRoot);

جرب المثال على موقع CodePen.

يسمح الإمساك بالحدث المُضاعَف من المدخل في المكوّن الأب بتطوير تجريدات مرنة والتي لا تعتمد على المداخل. على سبيل المثال إن صيرنا المكوّن ‎<Modal />‎، فبإمكان المكوّن الأب له التقاط أحداثه بغض النظر عمّا إذا كان يعتمد المداخل.

انظر أيضًا

 مصادر