كيفية كتابة مكونات React أفضل بأسلوب حديث وواضح

دقائق القراءة: 7
شرح حديث لأساليب كتابة مكونات React بشكل أفضل باستخدام JavaScript وHooks

أضاف معيار ES6 وما بعده مجموعة كبيرة من الميزات التي جعلت كتابة تطبيقات JavaScript أكثر اختصاراً ووضوحاً وأسهل في الصيانة. وعند إنشاء مشروع عبر create-react-app، فإن هذه الميزات تكون مدعومة تلقائياً بفضل Babel.js الذي يحوّل شيفرة ES6+ إلى ES5 المتوافق مع معظم المتصفحات.

في هذا الدليل، سنعيد بناء الأفكار الأساسية التي تساعدك على كتابة مكونات React بصورة أفضل، مع تقليل التكرار، وتحسين قابلية القراءة، وتبني أساليب حديثة مثل class properties وdestructuring وReact Hooks.

لماذا يجب تحسين طريقة كتابة مكونات React؟

تحسين أسلوب كتابة المكونات لا يقتصر على الشكل فقط، بل ينعكس مباشرة على جودة المشروع. فعندما يكون الكود مختصراً ومنظماً، يصبح:

  • أسهل في الفهم عند العودة إليه لاحقاً.
  • أسرع في التطوير وإضافة الميزات الجديدة.
  • أقل عرضة للأخطاء الناتجة عن التكرار.
  • أبسط في الاختبار والصيانة.

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

تجنب الربط اليدوي لمعالجات الأحداث

في المكونات المبنية على الأصناف Class Components، قد تواجه مشكلة فقدان ارتباط this عند تمرير معالج حدث مثل onChange أو onClick. لذلك كان الحل الشائع قديماً هو استخدام .bind() داخل الدالة البانية constructor.

<input onChange={this.onFirstInputChange} />

وغالباً ما كان المطور يكتب شيئاً شبيهاً بهذا:

constructor(props) {
  super(props);
  this.onFirstInputChange = this.onFirstInputChange.bind(this);
  this.onSecondInputChange = this.onSecondInputChange.bind(this);
  this.handleAdd = this.handleAdd.bind(this);
  this.handleSubtract = this.handleSubtract.bind(this);
}

هذه الطريقة تعمل، لكنها مرهقة وغير عملية كلما زاد عدد المعالجات.

استخدام خصائص الأصناف لتبسيط الربط

يمكن تجاوز هذه المشكلة عبر صياغة المعالجات كدوال سهمية arrow functions داخل الصنف نفسه باستخدام class properties. بهذه الطريقة، يتم الحفاظ على ارتباط this تلقائياً دون الحاجة إلى .bind().

onFirstInputChange = (event) => {
  const value = event.target.value;
  this.setState({ number1: value });
};

وبالمثل يمكن تحويل بقية الدوال:

onSecondInputChange = (event) => {
  // your code
};

handleAdd = (event) => {
  // your code
};

handleSubtract = (event) => {
  // your code
};

بعدها يمكن حذف أكواد الربط من constructor، بل وحتى الاستغناء عن constructor كلياً إذا كان دوره الوحيد هو تهيئة الحالة state.

export default class App extends React.Component {
  state = {
    number1: "",
    number2: "",
    result: "",
    errorMsg: ""
  };

  render() {
  }
}

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

استخدم معالج أحداث واحداً لجميع الحقول

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

الحل الأفضل هو إنشاء معالج موحد لجميع الحقول، بشرط أن يتطابق الاسم الموجود في الخاصية name مع اسم المفتاح داخل state.

<input
  type="text"
  name="number1"
  placeholder="Enter a number"
  onChange={this.onInputChange}
/>

<input
  type="text"
  name="number2"
  placeholder="Enter a number"
  onChange={this.onInputChange}
/>

ثم نضيف معالجاً عاماً:

onInputChange = (event) => {
  const name = event.target.name;
  const value = event.target.value;
  this.setState({ [name]: value });
};

تعتمد هذه الطريقة على المفاتيح الديناميكية في ES6. فعندما يكتب المستخدم في الحقل الأول، ستكون قيمة event.target.name هي number1، وعند الكتابة في الحقل الثاني ستكون number2. وهكذا يتم تحديث المفتاح الصحيح تلقائياً.

النتيجة هنا مهمة جداً:

  • عدد أقل من الدوال.
  • مرونة أعلى عند إضافة حقول جديدة.
  • تقليل واضح في التكرار.

دمج عمليات الحساب في دالة واحدة

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

بدلاً من هذا:

<button type="button" className="btn" onClick={this.handleAdd}>
  Add
</button>

<button type="button" className="btn" onClick={this.handleSubtract}>
  Subtract
</button>

يمكن كتابة الآتي:

<button
  type="button"
  className="btn"
  onClick={() => this.handleOperation('add')}
>
  Add
</button>

<button
  type="button"
  className="btn"
  onClick={() => this.handleOperation('subtract')}
>
  Subtract
</button>

ثم تكون دالة المعالجة بالشكل التالي:

handleOperation = (operation) => {
  const number1 = parseInt(this.state.number1, 10);
  const number2 = parseInt(this.state.number2, 10);
  let result;

  if (operation === "add") {
    result = number1 + number2;
  } else if (operation === "subtract") {
    result = number1 - number2;
  }

  if (isNaN(result)) {
    this.setState({ errorMsg: "Please enter valid numbers." });
  } else {
    this.setState({ errorMsg: "", result: result });
  }
};

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

استفد من تفكيك القيم باستخدام destructuring

صياغة destructuring من ميزات ES6 المفيدة جداً، لأنها تجعل الكود أنظف وأقل تكراراً.

بدلاً من:

const name = event.target.name;
const value = event.target.value;

يمكن كتابة:

const { name, value } = event.target;

والفكرة نفسها تنطبق على state. فبدلاً من تكرار this.state.number1 وthis.state.number2 في أكثر من سطر، يمكن أولاً تفكيك القيم:

let { number1, number2 } = this.state;
number1 = parseInt(number1, 10);
number2 = parseInt(number2, 10);

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

اختصار الكائنات باستخدام enhanced object literals

عندما يكون اسم المفتاح في الكائن مطابقاً تماماً لاسم المتغير، يمكنك استخدام الصياغة المختصرة للكائنات.

بدلاً من:

this.setState({ errorMsg: "", result: result });

اكتب:

this.setState({ errorMsg: "", result });

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

تحويل المكونات الصنفية إلى React Hooks

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

تعريف مكون وظيفي

const App = () => {
};

export default App;

إضافة الحالة باستخدام useState

import React, { useState } from "react";

const App = () => {
  const [state, setState] = useState({ number1: "", number2: "" });
  const [result, setResult] = useState("");
  const [errorMsg, setErrorMsg] = useState("");
};

export default App;

لاحظ أننا استخدمنا useState لتخزين أرقام الإدخال داخل كائن واحد، واستخدمنا حالتين إضافيتين لقيمة النتيجة ورسالة الخطأ.

تحديث الحقول في Hooks

const onInputChange = (event) => {
  const { name, value } = event.target;
  setState((prevState) => {
    return { ...prevState, [name]: value };
  });
};

في Hooks لا يتم دمج خصائص الكائن تلقائياً عند التحديث كما في setState داخل المكونات الصنفية، لذلك نستخدم معامل النشر ... للحفاظ على القيم السابقة ثم نحدّث الحقل المطلوب فقط.

تحديث منطق العمليات الحسابية

const handleOperation = (operation) => {
  let { number1, number2 } = state;
  number1 = parseInt(number1, 10);
  number2 = parseInt(number2, 10);
  let result;

  if (operation === "add") {
    result = number1 + number2;
  } else if (operation === "subtract") {
    result = number1 - number2;
  }

  if (isNaN(result)) {
    setErrorMsg("Please enter valid numbers.");
  } else {
    setErrorMsg("");
    setResult(result);
  }
};

بعد ذلك، يمكنك إعادة نفس بنية JSX السابقة، مع إزالة الإشارات إلى this وthis.state.

الإرجاع الضمني للكائنات في الدوال السهمية

يمكن تبسيط بعض الدوال السهمية إذا كانت تحتوي على تعبير واحد فقط. على سبيل المثال:

const add = (a, b) => {
  return a + b;
};

يمكن اختصارها إلى:

const add = (a, b) => a + b;

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

const getUser = () => ({ name: 'David', age: 35 });

وهذه الفكرة مفيدة جداً مع setState في Hooks:

setState((prevState) => {
  return { ...prevState, [name]: value };
});

يمكن اختصارها إلى:

setState((prevState) => ({ ...prevState, [name]: value }));

هذا الأسلوب شائع جداً في كتابة مكونات React وفي أدوات مثل react-redux وRedux.

أمثلة شائعة على الإرجاع الضمني في React

تعريف مكون وظيفي

const User = () => (
  <div>
    <h1>Welcome, User</h1>
    <p>You're logged in successfully.</p>
  </div>
);

داخل mapStateToProps

const mapStateToProps = (state, props) => ({
  users: state.users,
  details: state.details
});

في دوال إنشاء الأوامر داخل Redux

const addUser = (user) => ({
  type: 'ADD_USER',
  user
});

نصيحة إضافية لكتابة مكونات React بشكل أذكى

أحياناً ترغب في طباعة props داخل وحدة التحكم console لأغراض الاختبار أو التصحيح، دون تحويل المكون من صياغة مختصرة إلى صياغة طويلة.

بدلاً من كتابة:

const User = (props) => {
  console.log(props);
  return (
    <div>
      <h1>Welcome, User</h1>
      <p>You're logged in successfully.</p>
    </div>
  );
};

يمكن استخدام العامل المنطقي || بهذه الصورة:

const User = (props) =>
  console.log(props) || (
    <div>
      <h1>Welcome, User</h1>
      <p>You're logged in successfully.</p>
    </div>
  );

السبب أن الدالة console.log() لا تعيد قيمة مفيدة، فتُعامل كقيمة غير صادقة falsy، وبالتالي ينتقل التنفيذ إلى الجزء التالي بعد || ويتم إرجاع واجهة JSX.

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

أفضل الممارسات النهائية عند بناء مكونات React

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

الخلاصة التقنية

كتابة مكونات React بشكل أفضل لا تعني فقط تقليل عدد الأسطر، بل تعني بناء كود منظم وسهل التوسع وقليل التكرار. استخدام ميزات ES6 مثل class properties وdestructuring وenhanced object literals، إلى جانب الانتقال المدروس إلى React Hooks، يمنحك قاعدة أقوى لبناء واجهات أكثر نظافة ومرونة. من الناحية التقنية، كل خطوة تقلل التكرار وتزيد وضوح النية داخل الكود تُعد استثماراً حقيقياً في جودة المشروع على المدى الطويل.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *