رایج‌ترین اشتباهاتی که هنگام استفاده از React ممکن است انجام دهید

ترجمه و تالیف : امیرحسین بَزی
تاریخ انتشار : 02 اسفند 98
خواندن در 3 دقیقه
دسته بندی ها : react

وقتی به سوالات مربوط به React در stack overflow‌ جواب می‌دادم؛ متوجه شدم که تعدادی از موضوعات اصلی است که تعداد زیادی از فرادی که با React کار می‌کنند به آن برمی‌خورند و دچار اشتباه می‌شوند. من تصمیم گرفتم در مورد رایج‌ترین این مشکلات بنویسم و نشان دهم که چطور می‌شود از این اشتباهات فرار کرد.

۱. تغییر دادن state به طور مستقیم

state در React در واقع تغییرناپذیر است و نمی‌شود آن‌را به‌طور مستقیم تغییر داد برای این کار بهتر است از متد خاص setState و از تابع تنظیم کننده useState هوک استفاده کرد. مثال زیر را در نظر بگیرید، جایی که می‌خواهیم به‌روزرسانی کنیم فیلد checked از یک شی خاص یک آرایه را، با کمک state یک checkbox.

const updateFeaturesList = (e, idx) => {
      listFeatures[idx].checked = e.target.checked;
      setListFeatures(listFeatures);
    };

مساله‌ای که در این کد وجود دارد این است که تغییرات در state در واسط کاربر منعکس نخواهد شد، چون state با همان مرجع مورد نظر به روز شده و در نتیجه باعث رندر مجدد آن نمی‌شود. دلیل مهم دیگری که نباید state را به طور مستقیم تغییر دهید، ماهیت ناهم‌زمانی به‌روزرسانی state های فعلی است که ممکن است مواردی که مستقیم برای state ها ساخته شده است را نادیده بگیرد و منجر به برخی از مشکلات شود. روش صحیح در این حالت استفاده از متد تنظیم کننده useState است. 

const updateFeaturesList = (e, idx) => {
      const { checked } = e.target;
      setListFeatures(features => {
        return features.map((feature, index) => {
          if (idx === index) {
            feature = { ...feature, checked };
          }
          return feature;
        });
      });
    };

با کمک map و شی گسترش ( object spread ) مطمئن می‌شویم که موارد اصلی را تغییر ندهیم.

آموزش کامل React در وبسایت راکت

۲. اشتباه در مقدار دهی مقدار پیش فرض ( مقدار اولیه )

تنظیم مقادیر اولیه state به یک رشته خالی یا null و سپس دستیابی به ویژگی‌های این مقدار یک اشتباه رایج است. این اشتباه برای اشیا تو در تو نیز صدق می‌کند که مقدار اولیه را یک رشته خالی یا null قرار می‌دهند و سپس می‌خواهند به ویژگی‌های آن دسترسی داشته باشند.

class UserProfile extends Component {
      constructor(props) {
        super(props);

        this.state = {
          user: null
        };
      }

      componentDidMount() {
        fetch("/api/profile").then(data => {
          this.setState({ user: data });
        });
      }

      render() {
        return (
          <div>
            <p>User name:</p>
            <p>{this.state.user.name}</p> // Cannnot read property 'name' of null
          </div>
        );
      }
    }

خطای مشابه با تنظیم مقدار اولیه state با یک آرایه خالی و سپس تلاش برای دسترسی به مقادیر آن اتفاق می‌افتد. در حالی‌که داده‌ها از یک API گرفته می‌شوند، این کامپوننت با state اولیه ارائه شده رندر می‌شود، و تلاش برای دسترسی به یک ویژگی در عنصر null یا undefined، باعث ایجاد خطا می‌شود. بنابراین مهم است که حالت اولیه را به دقت نمایش داده باشیم. در این مثال، شروع اولیه صحیح به شرح زیر است:

class UserProfile extends Component {
      constructor(props) {
        super(props);

        this.state = {
          user: {
            name: ""
            // Define other fields as well
          }
        };
      }

      componentDidMount() {
        fetch("/api/profile").then(data => {
          this.setState({ user: data });
        });
      }

      render() {
        return (
          <div>
            <p>User name:</p>
            <p>{this.state.user.name}</p> // Renders without errors
          </div>
        );
      }

از نظر UX؛ بهتر است تا زمانی‌که داده‌ها بارگیری شوند، نوعی لود کننده نمایش داده شود.

۳. فراموش کردن این که setState غیر همزمان است

یک اشتباه رایج دیگ؛ر تلاش برای دستیابی به مقدار state درست بعد از تنظیم آن است.

handleChange = count => {
      this.setState({ count });
      this.props.callback(this.state.count); // Old state value
    };

تنظیم مقدار جدید بلافاصله اتفاق نمی‌افتد، به طور معمول در رندر بعدی انجام می‌شود، یا می‌تواند برای بهینه‌سازی عملکرد به صورت دسته‌ای استفاده شود. بنابراین دسترسی به یک مقدار state پس از تنظیم ممکن است آخرین بروزرسانی‌ها را منعکس نکند. این مسئله را می‌توان با استفاده از یک آرگومان دوم اختیاری برای setState، که یک تابع برگشتی است؛ حل کرد، که پس از به‌روزرسانی شدن state با آخرین مقادیر فراخوانی می‌شود.

handleChange = count => {
      this.setState({ count }, () => {
        this.props.callback(this.state.count); // Updated state value
      });
    };

این با hooks کاملاً متفاوت است، از آن‌جا که عملکرد تنظیم کننده از useState یک آرگومان برگشتی دوم با عملکرد setState ندارد. در این حالت روش رسمی توصیه شده استفاده از hook UseEffect است.

const [count, setCount] = useState(0)

    useEffect(() => {
      callback(count); // Will be called when the value of count changes
    }, [count, callback]);

    const handleChange = value => {
      setCount(value)
    };

لازم به ذکر است که setState به گونه‌ای ناهم‌زمان نیست که promise را برگرداند. بنابراین استفاده از async/await، کار نخواهد کرد (یکی دیگر از اشتباهات رایج).

۴. اتکا به state اولیه برای محاسبه state بعدی

این قسمت به موضوعی که در بالا مورد بحث قرار گرفته است مربوط می شود؛ زیرا این موضوع همچنین با عدم به‌روزرسانی state مرتبط است.

handleChange = count => {
      this.setState({ count: this.state.count + 1 }); // Relying on current value of the state to update it
    };

مسئله این رویکرد این است که ممکن است در لحظه تنظیم state جدید، مقدار count به درستی به‌روز نشده باشد، که منجر به تنظیم نادرست مقدار جدید state خواهد شد. یک روش صحیح در این‌جا استفاده از فرم عملکردی setState است.

increment = () => {
      this.setState(state => ({ count: state.count + 1 })); // The latest state value is used
    };

شکل کاربردی setState یک آرگومان دوم دارد. props در زمان اعمال بروزرسانی، گزارشی ارائه می دهد که می‌تواند به روش مشابهی به عنوان state استفاده شود. همین منطق در مورد hook useState اعمال می شود، در صورتی‌که، تنظیم کننده یک تابع را به عنوان یک آرگومان قبول کند.

const increment = () => {
     setCount(currentCount => currentCount + 1)
    };

۵. حذف آرایه وابستگی برای useEffect

این یکی از اشتباهات کمتر رایج است، اما با این وجود اتفاق می‌افتد. حتی اگر موارد کاملاً معتبری برای حذف آرایه وابستگی برای useEffect وجود داشته باشد، در صورت اصلاح پاسخ به state خود، ممکن است یک حلقه نامحدود ایجاد شود.

۶. انتقال اشیاء یا مقادیر دیگر از نوع غیر بدوی به آرایه وابستگی useEffect

مشابه مورد قبل، اما اشتباه ظریف‌تر، ردیابی اشیاء، آرایه‌ها یا سایر مقادیر غیر ابتدایی در آرایه وابستگی اثر hook است. کد زیر را در نظر بگیرید.

const features = ["feature1", "feature2"];

    useEffect(() => {
      // Callback 
    }, [features]);

در این‌جا وقتی یک آرایه را به عنوان یک وابستگی منتقل می‌کنیم، React فقط مرجع مربوط به آن را ذخیره می‌کند و آن را با مرجع قبلی آرایه مقایسه می‌کند. با این حال، از آن‌جا که در داخل کامپوننت تعریف شده است، آرایه ویژگی‌ها در هر رندر بازسازی می‌شود، به این معنی که هر بار مرجع جدید خواهد بود، بنابراین برابر با مقداری که توسط UseEffect ردیابی می‌شود نیست. در نهایت، تابع بازگشتی بر روی هر رندر اجرا می‌شود، حتی اگر آرایه تغییر نکرده باشد. این مسئله با مقادیر ابتدایی، مانند رشته‌ها و اعداد، مسئله‌ای نیست، زیرا آن‌ها با value مقایسه می‌شوند و نه در مرجع جاوااسکریپت.

چند روش برای حل این مسئله وجود دارد. گزینه اول تعریف متغیر در خارج از کامپوننت است، بنابراین در هر رندر دوباره بازیابی نمی‌شود. با این‌حال، در برخی موارد این امکان وجود ندارد، برای مثال اگر ما props یا وابستگی را دنبال کنیم بخشی از state کامپوننت است. گزینه دیگر استفاده از یک hook مقایسه شده عمیق سفارشی برای ردیابی صحیح منابع وابستگی است. یک راه‌حل آسان‌تر این است که مقدار را داخل hook UseMemo قرار دهید، که این امر در هنگام رندر مجدد، مرجع را حفظ می‌کند.

const features = useMemo(() => ["feature1", "feature2"], []);

    useEffect(() => {
      // Callback 
    }, [features]);

امیدوارم این لیست به شما کمک کند تا از معمول‌ترین مشکلات React اجتناب کنید و درک مشکلات اصلی را بهبود ببخشید.

منبع

گردآوری و تالیف امیرحسین بَزی

یک طراح گرافیک علاقمند به React JS