خاصيات التصيير

من موسوعة حسوب

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

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

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

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

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

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

تُعد المكوّنات الوحدة الأساسية من الشيفرة القابلة لإعادة الاستخدام في 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: '100%' }} 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: '100%' }} onMouseMove={this.handleMouseMove}>

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

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

يُغلِّف المكوّن ‎<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: '100%' }} onMouseMove={this.handleMouseMove}>

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

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>حرك الفأرة</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: '100%' }} onMouseMove={this.handleMouseMove}>

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

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>حرك الفأرة</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} />
        )}/>
      );
    }
  }
}