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

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
(أنشأ الصفحة ب'<noinclude>{{DISPLAYTITLE:خاصيات التصيير}}</noinclude>')
 
(تحديث)
 
(7 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:خاصيات التصيير}}</noinclude>
+
<noinclude>{{DISPLAYTITLE:خاصيات التصيير في React}}</noinclude>
 +
يُشير مصطلح [https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce خاصيّة التصيير (render prop)] إلى تقنية بسيطة لمشاركة الشيفرة بين مكوّنات React باستخدام خاصية والتي قيمتها هي عبارة عن دالة.
 +
 
 +
يأخذ المكوّن الذي يمتلك خاصيّة تصيير دالة تُعيد عنصر React ويستدعيها بدلًا من تنفيذ منطق التصيير الخاص به:<syntaxhighlight lang="javascript">
 +
<DataProvider render={data => (
 +
  <h1>أهلًا {data.target}</h1>
 +
)}/>
 +
 
 +
</syntaxhighlight>تتضمّن المكتبات التي تستخدم خاصيّات التصيير [https://reacttraining.com/react-router/web/api/Route/Route-render-methods React Router] و [https://github.com/paypal/downshift Downshift] و  [https://github.com/jaredpalmer/formik Formik].
 +
 
 +
سنناقش في هذه الصفحة فائدة خاصيّات التصيير وكيفية كتابة خاصيّات التصيير الخاصّة بك.
 +
 
 +
== استخدام خاصيّات التصيير للاهتمامات المشتركة ==
 +
تُعد المكوّنات الوحدة الأساسية من الشيفرة القابلة لإعادة الاستخدام في React، ولكن ليس من الواضح كيفيّة مشاركة الحالة أو السلوك من مكوّن إلى مكوّن آخر يحتاج نفس الحالة.
 +
 
 +
على سبيل المثال يتتبع المكوّن التالي موقع الفأرة في تطبيق الويب:<syntaxhighlight lang="javascript">
 +
class MouseTracker extends React.Component {
 +
  constructor(props) {
 +
    super(props);
 +
    this.handleMouseMove = this.handleMouseMove.bind(this);
 +
    this.state = { x: 0, y: 0 };
 +
  }
 +
 
 +
  handleMouseMove(event) {
 +
    this.setState({
 +
      x: event.clientX,
 +
      y: event.clientY
 +
    });
 +
  }
 +
 
 +
  render() {
 +
    return (
 +
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
 +
        <h1>حرك الفأرة</h1>
 +
        <p>موقع الفأرة الحالي هو ({this.state.x}, {this.state.y})</p>
 +
      </div>
 +
    );
 +
  }
 +
}
 +
 
 +
 
 +
</syntaxhighlight>بينما يتحرك المؤشر على الشاشة، يعرض المكوّن إحداثياته <code>(x, y)</code> ضمن العنصر ‎<code><nowiki><p></nowiki></code>‎.
 +
 
 +
السؤال الآن هو كيفيّة إعادة استخدام هذا السلوك في مكوّن آخر؟ أي بمعنى آخر إن احتاج مكوّن آخر إلى معرفة مكان المؤشر، فهل نستطيع تغليف هذا السلوك لمشاركته بسهولة مع ذلك المكوّن؟
 +
 
 +
لمّا كانت المكوّنات الوحدة الأساسية في React لإعادة استخدام الشيفرة، فلنجرّب إعادة ترتيب الشيفرة قليلًا لتستخدم المكوّن <code>‎<Mouse>‎</code> والذي يُغلِّف السلوك الذي نريد إعادة استخدامه في مكان ما:<syntaxhighlight lang="javascript">
 +
// يغلف المكون <Mouse> السلوك الذي نحتاجه
 +
class Mouse extends React.Component {
 +
  constructor(props) {
 +
    super(props);
 +
    this.handleMouseMove = this.handleMouseMove.bind(this);
 +
    this.state = { x: 0, y: 0 };
 +
  }
 +
 
 +
  handleMouseMove(event) {
 +
    this.setState({
 +
      x: event.clientX,
 +
      y: event.clientY
 +
    });
 +
  }
 +
 
 +
  render() {
 +
    return (
 +
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
 +
 
 +
        {/* ... ولكن كيف نصير شيء آخر غير العنصر <p>? */}
 +
        <p>موقع الفأرة الحالي هو: ({this.state.x}, {this.state.y})</p>
 +
      </div>
 +
    );
 +
  }
 +
}
 +
 
 +
class MouseTracker extends React.Component {
 +
  render() {
 +
    return (
 +
      <>
 +
        <h1>Move the mouse around!</h1>
 +
        <Mouse />
 +
      </>
 +
    );
 +
  }
 +
}
 +
</syntaxhighlight>يُغلِّف المكوّن ‎<code><Mouse></code>‎ الآن السلوك المرتبط بالاستماع إلى أحداث تحريك الفأرة <code>mousemove</code> وتخزين إحداثيات <code>(x, y)</code> لموقع المؤشر، ولكنّه ليس قابلًا لإعادة الاستخدام حتى الآن.
 +
 
 +
فلنفترض أننا نمتلك المكوّن ‎<code><Cat></code>‎ والذي يُصيِّر صورة لقطة تُطارِد الفأرة ضمن الشاشة. بإمكاننا استخدام الخاصيّة ‎<code><Cat mouse = { { x, y } } ></code>‎ لإخبار المكوّن بإحداثيات الفأرة بحيث تعلم أي تضع الصورة في الشاشة.
 +
 
 +
كمحاولة أولى جرّب تصيير المكوّن ‎<code><Cat></code>‎ بداخل تابع التصيير للمكوّن ‎<code><Mouse></code>‎ كما يلي:<syntaxhighlight lang="javascript">
 +
class Cat extends React.Component {
 +
  render() {
 +
    const mouse = this.props.mouse;
 +
    return (
 +
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
 +
    );
 +
  }
 +
}
 +
 
 +
class MouseWithCat extends React.Component {
 +
  constructor(props) {
 +
    super(props);
 +
    this.handleMouseMove = this.handleMouseMove.bind(this);
 +
    this.state = { x: 0, y: 0 };
 +
  }
 +
 
 +
  handleMouseMove(event) {
 +
    this.setState({
 +
      x: event.clientX,
 +
      y: event.clientY
 +
    });
 +
  }
 +
 
 +
  render() {
 +
    return (
 +
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
 +
 
 +
        {/*
 +
  كان بإمكاننا فقط تبديل العنصر p بالمكون <Cat> هنا
 +
  ولكن سيتوجب علينا إنشاء مكون <MouseWithSomethingElse> منفصل
 +
  في كل مرة نحتاج لاستخدامه. لذا لن يكون <MouseWithCat> قابلًا لإعادة الاستخدام حتى الآن
 +
        */}
 +
        <Cat mouse={this.state} />
 +
      </div>
 +
    );
 +
  }
 +
}
 +
 
 +
class MouseTracker extends React.Component {
 +
  render() {
 +
    return (
 +
      <div>
 +
        <h1>Move the mouse around!</h1>
 +
        <MouseWithCat />
 +
      </div>
 +
    );
 +
  }
 +
}
 +
</syntaxhighlight>ستعمل هذه الطريقة لحالتنا، لكننا لم نحقق حتى الآن هدفنا الحقيقي من تغليف هذا السلوك بطريقة قابلة لإعادة الاستخدام، ففي كل مرة نريد فيها الحصول على موقع الفأرة لحالة استخدام مختلفة يجب علينا إنشاء مكوّن جديد (وهو ‎<code><MouseWithCat></code>‎) والذي يُصيِّر شيء ما مُخصَّص لتلك الحالة.
 +
 
 +
هنا تأتي فائدة خاصيّات التصيير، فبدلًا من كتابة المكوّن ‎<code><Cat></code>‎ بشكل حرفي في المكوّن ‎<code><Mouse></code>‎ وتغيير ناتجه، بإمكاننا تزويد المكوّن <code>‎<Mouse></code>‎ بخاصيّة على شكل دالة تستخدمها لتحدد بشكل ديناميكي ما هي خاصيّة التصيير:<syntaxhighlight lang="javascript">
 +
class Cat extends React.Component {
 +
  render() {
 +
    const mouse = this.props.mouse;
 +
    return (
 +
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
 +
    );
 +
  }
 +
}
 +
 
 +
class Mouse extends React.Component {
 +
  constructor(props) {
 +
    super(props);
 +
    this.handleMouseMove = this.handleMouseMove.bind(this);
 +
    this.state = { x: 0, y: 0 };
 +
  }
 +
 
 +
  handleMouseMove(event) {
 +
    this.setState({
 +
      x: event.clientX,
 +
      y: event.clientY
 +
    });
 +
  }
 +
 
 +
  render() {
 +
    return (
 +
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
 +
 
 +
        {/*
 +
  بدلًا من تزويد تمثيل ثابت لما يصيره المكون <Mouse>
 +
  استخدم خاصية التصيير لتحدد بشكل دينامكي ما الذي ينبغي تصييره
 +
        */}
 +
        {this.props.render(this.state)}
 +
      </div>
 +
    );
 +
  }
 +
}
 +
 
 +
class MouseTracker extends React.Component {
 +
  render() {
 +
    return (
 +
      <div>
 +
        <h1>Move the mouse around!</h1>
 +
        <Mouse render={mouse => (
 +
          <Cat mouse={mouse} />
 +
        )}/>
 +
      </div>
 +
    );
 +
  }
 +
}
 +
</syntaxhighlight>والآن بدلًا من نسخ المكوّن <code><Mouse></code> وكتابة شيفرة حرفية بداخل التابع <code>render</code> الخاص به لحل المشكلة لحالة محددة، فسنُزوِّد خاصيّة <code>render</code> والتي يستخدمها <code><Mouse></code> ليحدد بشكل ديناميكي ما الذي ينبغي تصييره.
 +
 
 +
إنّ خاصيّة التصيير هي بشكل مبسّط خاصيّة على شكل دالة يستخدمها المكوّن ليعلم ما يجب تصييره.
 +
 
 +
تجعل هذه التقنية من السلوك الذي نحتاجه للمشاركة قابلًا للنقل بسهولة. للحصول على هذا السلوك صيِّر المكوّن <code><Mouse></code> مع خاصيّة <code>render</code> والتي تخبره ما يجب عليه تصييره مع الإحداثيات الحالية للمؤشر.
 +
 
 +
من الأشياء المثيرة للاهتمام التي تميّز خاصيّات التصيير هي أنّه بإمكانك تطبيقه على [[React/higher order components|المكوّنات ذات الترتيب الأعلى]] باستخدام مكوّن اعتيادي مع خاصيّة تصيير. على سبيل المثال إن كنت تفضل أن يكون لديك المكوّن ذو الترتيب الأعلى <code>withMouse</code> بدلًا من المكوّن ‎<code><Mouse></code>‎ فبإمكانك بسهولة إنشاء واحد باستخدام المكوّن ‎<code><Mouse></code>‎ مع خاصيّة تصيير:<syntaxhighlight lang="javascript">
 +
// إن أردت حقا مكون ذو ترتيب أعلى فبإمكانك بسهولة
 +
// إنشاء واحد باستخدام مكوّن عادي مع خاصية تصيير
 +
function withMouse(Component) {
 +
  return class extends React.Component {
 +
    render() {
 +
      return (
 +
        <Mouse render={mouse => (
 +
          <Component {...this.props} mouse={mouse} />
 +
        )}/>
 +
      );
 +
    }
 +
  }
 +
}
 +
 
 +
</syntaxhighlight>لذا من الممكن باستخدام خاصية التصيير الانتقال إلى نمط آخر.
 +
 
 +
== استخدام خاصيات أخرى غير render ==
 +
من الهام معرفة أنّه ليس بالضرورة إذا كان اسم هذا النمط خاصيّات التصيير (render props) أن تستخدم الخاصيّة التي اسمها <code>render</code>. فبالحقيقة [https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce أي دالة يستخدمها المكوّن ليعرف ما الذي ينبغي تصييره هي عمليًّا خاصيّة تصيير].
 +
 
 +
على الرغم من استخدام المثال السابق للاسم <code>render</code> فبإمكاننا استخدام أي اسم مثل الخاصيّة <code>children</code>:<syntaxhighlight lang="javascript">
 +
<Mouse children={mouse => (
 +
  <p>موقع الفأرة هو: {mouse.x}, {mouse.y}</p>
 +
)}/>
 +
 
 +
</syntaxhighlight>ليس من الضروري أن تكون الخاصيّة <code>children</code> موجودة في قائمة الخاصيّات في عنصر JSX لديك، فبإمكانك وضعها بشكل مباشر بداخل العنصر:<syntaxhighlight lang="javascript">
 +
<Mouse>
 +
  {mouse => (
 +
    <p>موقع الفأرة هو: {mouse.x}, {mouse.y}</p>
 +
  )}
 +
</Mouse>
 +
 
 +
</syntaxhighlight>سترى هذه الطريقة مستخدمة في <code>[https://github.com/chenglou/react-motion react-motion]</code> API.
 +
 
 +
بما أنّ هذه الطريقة غير معتادة قليلًا فقد ترغب بالإعلان عن أنّ <code>children</code> يجب أن تكون دالة من خلال <code>propTypes</code>:<syntaxhighlight lang="javascript">
 +
Mouse.propTypes = {
 +
  children: PropTypes.func.isRequired
 +
};
 +
 
 +
</syntaxhighlight>
 +
 
 +
== محاذير ==
 +
 
 +
=== انتبه عند استخدام خاصية التصيير مع <code>React.PureComponent</code> ===
 +
قد يلغي استخدام خاصية التصيير الفائدة المرجوة من استخدام <code>[https://reactjs.org/docs/react-api.html#reactpurecomponent React.PureComponent]</code> إن أنشأت الدالة بداخل التابع <code>render</code>، وذلك لأنّ المقارنة بين الخاصيّات ستعيد دومًا <code>false</code> للخاصيّات الجديدة، وسيُولِّد كل تابع <code>render</code> في هذه الحالة قيمة جديدة للخاصيّة <code>render</code>.
 +
 
 +
فمثلًا بالمتابعة مع المكوّن السابق <code>‎<Mouse>‎</code>، إن كان هذا المكوّن يمتد إلى <code>React.PureComponent</code> بدلًا من <code>React.Component</code>، فسيبدو مثالنا كما يلي:<syntaxhighlight lang="javascript">
 +
class Mouse extends React.PureComponent {
 +
  // نفس التنفيذ السابق
 +
}
 +
 
 +
class MouseTracker extends React.Component {
 +
  render() {
 +
    return (
 +
      <div>
 +
        <h1>حرّك الفأرة</h1>
 +
 
 +
        {/*
 +
  هذا سيّء! حيث ستكون قيمة خاصية التصيير
 +
  مختلفة عند كل تصيير
 +
        */}
 +
        <Mouse render={mouse => (
 +
          <Cat mouse={mouse} />
 +
        )}/>
 +
      </div>
 +
    );
 +
  }
 +
}
 +
 
 +
</syntaxhighlight>في هذا المثال عند كل تصيير للمكوّن ‎<code><MouseTracker></code>‎ فسيُولِّد دالة جديدة كقيمة للخاصية ‎<code><Mouse render></code>‎، وبذلك يلغي تأثير المكوّن ‎<code><Mouse></code>‎ الذي يمتد إلى <code>React.PureComponent</code> في المقام الأول.
 +
 
 +
لحل هذه المشكلة تستطيع تعريف الخاصيّة كنسخة من تابع كما يلي:<syntaxhighlight lang="javascript">
 +
class MouseTracker extends React.Component {
 +
  // معرف كنسخة تابع
 +
  // يشير this.renderTheCat دومًا إلى نفس الدالة عند استخدامها للتصيير
 +
 
 +
  renderTheCat(mouse) {
 +
    return <Cat mouse={mouse} />;
 +
  }
 +
 
 +
  render() {
 +
    return (
 +
      <div>
 +
        <h1>حرك الفأرة</h1>
 +
        <Mouse render={this.renderTheCat} />
 +
      </div>
 +
    );
 +
  }
 +
}
 +
 
 +
</syntaxhighlight>في الحالات التي لا تستطيع فيها تعريف الخاصيّة بشكل ثابت (مثلًا إذا كنت تحتاج إلى تغيير خاصية أو حالة المكوّن) فيجب أن يمتد المكوّن ‎<code><Mouse>‎</code> إلى <code>React.Component</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/portals|المداخل (Portals) في React]]
 +
* [[React/error boundaries|حدود الأخطاء]]
 +
* [[React/web components|مكونات الويب]]
 +
* [[React/higher order components|المكونات ذات الترتيب الأعلى]]
 +
* [[React/forwarding refs|تمرير المراجع]]
 +
* [[React/integrating with other libraries|تكامل React مع المكتبات الأخرى]]
 +
* [[React/accessibility|سهولة الوصول]]
 +
* [[React/code splitting|تقسيم الشيفرة]]
 +
* [[React/strict mode|الوضع الصارم (Strict Mode)]]
 +
== مصادر==
 +
*[https://reactjs.org/docs/render-props.html صفحة خاصيات التصيير في توثيق React الرسمي].
 +
[[تصنيف:React]]
 +
[[تصنيف:React Advanced Guides]]

المراجعة الحالية بتاريخ 10:33، 4 نوفمبر 2020

يُشير مصطلح خاصيّة التصيير (render prop) إلى تقنية بسيطة لمشاركة الشيفرة بين مكوّنات React باستخدام خاصية والتي قيمتها هي عبارة عن دالة.

يأخذ المكوّن الذي يمتلك خاصيّة تصيير دالة تُعيد عنصر React ويستدعيها بدلًا من تنفيذ منطق التصيير الخاص به:

<DataProvider render={data => (
  <h1>أهلًا {data.target}</h1>
)}/>

تتضمّن المكتبات التي تستخدم خاصيّات التصيير React Router و Downshift و  Formik.

سنناقش في هذه الصفحة فائدة خاصيّات التصيير وكيفية كتابة خاصيّات التصيير الخاصّة بك.

استخدام خاصيّات التصيير للاهتمامات المشتركة

تُعد المكوّنات الوحدة الأساسية من الشيفرة القابلة لإعادة الاستخدام في React، ولكن ليس من الواضح كيفيّة مشاركة الحالة أو السلوك من مكوّن إلى مكوّن آخر يحتاج نفس الحالة.

على سبيل المثال يتتبع المكوّن التالي موقع الفأرة في تطبيق الويب:

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
        <h1>حرك الفأرة</h1>
        <p>موقع الفأرة الحالي هو ({this.state.x}, {this.state.y})</p>
      </div>
    );
  }
}

بينما يتحرك المؤشر على الشاشة، يعرض المكوّن إحداثياته (x, y) ضمن العنصر ‎<p>‎.

السؤال الآن هو كيفيّة إعادة استخدام هذا السلوك في مكوّن آخر؟ أي بمعنى آخر إن احتاج مكوّن آخر إلى معرفة مكان المؤشر، فهل نستطيع تغليف هذا السلوك لمشاركته بسهولة مع ذلك المكوّن؟

لمّا كانت المكوّنات الوحدة الأساسية في React لإعادة استخدام الشيفرة، فلنجرّب إعادة ترتيب الشيفرة قليلًا لتستخدم المكوّن ‎<Mouse>‎ والذي يُغلِّف السلوك الذي نريد إعادة استخدامه في مكان ما:

// يغلف المكون <Mouse> السلوك الذي نحتاجه
class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>

        {/* ... ولكن كيف نصير شيء آخر غير العنصر <p>? */}
        <p>موقع الفأرة الحالي هو: ({this.state.x}, {this.state.y})</p>
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <>
        <h1>Move the mouse around!</h1>
        <Mouse />
      </>
    );
  }
}

يُغلِّف المكوّن ‎<Mouse>‎ الآن السلوك المرتبط بالاستماع إلى أحداث تحريك الفأرة mousemove وتخزين إحداثيات (x, y) لموقع المؤشر، ولكنّه ليس قابلًا لإعادة الاستخدام حتى الآن.

فلنفترض أننا نمتلك المكوّن ‎<Cat>‎ والذي يُصيِّر صورة لقطة تُطارِد الفأرة ضمن الشاشة. بإمكاننا استخدام الخاصيّة ‎<Cat mouse = { { x, y } } >‎ لإخبار المكوّن بإحداثيات الفأرة بحيث تعلم أي تضع الصورة في الشاشة.

كمحاولة أولى جرّب تصيير المكوّن ‎<Cat>‎ بداخل تابع التصيير للمكوّن ‎<Mouse>‎ كما يلي:

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

class MouseWithCat extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>

        {/*
		  كان بإمكاننا فقط تبديل العنصر p بالمكون <Cat> هنا
		  ولكن سيتوجب علينا إنشاء مكون <MouseWithSomethingElse> منفصل
		  في كل مرة نحتاج لاستخدامه. لذا لن يكون <MouseWithCat> قابلًا لإعادة الاستخدام حتى الآن
        */}
        <Cat mouse={this.state} />
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <MouseWithCat />
      </div>
    );
  }
}

ستعمل هذه الطريقة لحالتنا، لكننا لم نحقق حتى الآن هدفنا الحقيقي من تغليف هذا السلوك بطريقة قابلة لإعادة الاستخدام، ففي كل مرة نريد فيها الحصول على موقع الفأرة لحالة استخدام مختلفة يجب علينا إنشاء مكوّن جديد (وهو ‎<MouseWithCat>‎) والذي يُصيِّر شيء ما مُخصَّص لتلك الحالة. هنا تأتي فائدة خاصيّات التصيير، فبدلًا من كتابة المكوّن ‎<Cat>‎ بشكل حرفي في المكوّن ‎<Mouse>‎ وتغيير ناتجه، بإمكاننا تزويد المكوّن ‎<Mouse>‎ بخاصيّة على شكل دالة تستخدمها لتحدد بشكل ديناميكي ما هي خاصيّة التصيير:

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>

        {/*
		  بدلًا من تزويد تمثيل ثابت لما يصيره المكون <Mouse>
		  استخدم خاصية التصيير لتحدد بشكل دينامكي ما الذي ينبغي تصييره
        */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

والآن بدلًا من نسخ المكوّن <Mouse> وكتابة شيفرة حرفية بداخل التابع render الخاص به لحل المشكلة لحالة محددة، فسنُزوِّد خاصيّة render والتي يستخدمها <Mouse> ليحدد بشكل ديناميكي ما الذي ينبغي تصييره.

إنّ خاصيّة التصيير هي بشكل مبسّط خاصيّة على شكل دالة يستخدمها المكوّن ليعلم ما يجب تصييره.

تجعل هذه التقنية من السلوك الذي نحتاجه للمشاركة قابلًا للنقل بسهولة. للحصول على هذا السلوك صيِّر المكوّن <Mouse> مع خاصيّة render والتي تخبره ما يجب عليه تصييره مع الإحداثيات الحالية للمؤشر.

من الأشياء المثيرة للاهتمام التي تميّز خاصيّات التصيير هي أنّه بإمكانك تطبيقه على المكوّنات ذات الترتيب الأعلى باستخدام مكوّن اعتيادي مع خاصيّة تصيير. على سبيل المثال إن كنت تفضل أن يكون لديك المكوّن ذو الترتيب الأعلى withMouse بدلًا من المكوّن ‎<Mouse>‎ فبإمكانك بسهولة إنشاء واحد باستخدام المكوّن ‎<Mouse>‎ مع خاصيّة تصيير:

// إن أردت حقا مكون ذو ترتيب أعلى فبإمكانك بسهولة
// إنشاء واحد باستخدام مكوّن عادي مع خاصية تصيير 
function withMouse(Component) {
  return class extends React.Component {
    render() {
      return (
        <Mouse render={mouse => (
          <Component {...this.props} mouse={mouse} />
        )}/>
      );
    }
  }
}

لذا من الممكن باستخدام خاصية التصيير الانتقال إلى نمط آخر.

استخدام خاصيات أخرى غير render

من الهام معرفة أنّه ليس بالضرورة إذا كان اسم هذا النمط خاصيّات التصيير (render props) أن تستخدم الخاصيّة التي اسمها render. فبالحقيقة أي دالة يستخدمها المكوّن ليعرف ما الذي ينبغي تصييره هي عمليًّا خاصيّة تصيير.

على الرغم من استخدام المثال السابق للاسم render فبإمكاننا استخدام أي اسم مثل الخاصيّة children:

<Mouse children={mouse => (
  <p>موقع الفأرة هو: {mouse.x}, {mouse.y}</p>
)}/>

ليس من الضروري أن تكون الخاصيّة children موجودة في قائمة الخاصيّات في عنصر JSX لديك، فبإمكانك وضعها بشكل مباشر بداخل العنصر:

<Mouse>
  {mouse => (
    <p>موقع الفأرة هو: {mouse.x}, {mouse.y}</p>
  )}
</Mouse>

سترى هذه الطريقة مستخدمة في react-motion API. بما أنّ هذه الطريقة غير معتادة قليلًا فقد ترغب بالإعلان عن أنّ children يجب أن تكون دالة من خلال propTypes:

Mouse.propTypes = {
  children: PropTypes.func.isRequired
};

محاذير

انتبه عند استخدام خاصية التصيير مع React.PureComponent

قد يلغي استخدام خاصية التصيير الفائدة المرجوة من استخدام React.PureComponent إن أنشأت الدالة بداخل التابع render، وذلك لأنّ المقارنة بين الخاصيّات ستعيد دومًا false للخاصيّات الجديدة، وسيُولِّد كل تابع render في هذه الحالة قيمة جديدة للخاصيّة render.

فمثلًا بالمتابعة مع المكوّن السابق ‎<Mouse>‎، إن كان هذا المكوّن يمتد إلى React.PureComponent بدلًا من React.Component، فسيبدو مثالنا كما يلي:

class Mouse extends React.PureComponent {
  // نفس التنفيذ السابق
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>حرّك الفأرة</h1>

        {/*
		  هذا سيّء! حيث ستكون قيمة خاصية التصيير
		  مختلفة عند كل تصيير
        */}
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

في هذا المثال عند كل تصيير للمكوّن ‎<MouseTracker>‎ فسيُولِّد دالة جديدة كقيمة للخاصية ‎<Mouse render>‎، وبذلك يلغي تأثير المكوّن ‎<Mouse>‎ الذي يمتد إلى React.PureComponent في المقام الأول. لحل هذه المشكلة تستطيع تعريف الخاصيّة كنسخة من تابع كما يلي:

class MouseTracker extends React.Component {
  // معرف كنسخة تابع
  // يشير this.renderTheCat دومًا إلى نفس الدالة عند استخدامها للتصيير
  
  renderTheCat(mouse) {
    return <Cat mouse={mouse} />;
  }

  render() {
    return (
      <div>
        <h1>حرك الفأرة</h1>
        <Mouse render={this.renderTheCat} />
      </div>
    );
  }
}

في الحالات التي لا تستطيع فيها تعريف الخاصيّة بشكل ثابت (مثلًا إذا كنت تحتاج إلى تغيير خاصية أو حالة المكوّن) فيجب أن يمتد المكوّن ‎<Mouse>‎ إلى React.Component بدلًا من ذلك.

انظر أيضًا

 مصادر