دليل UseEffect الكامل

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


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

  • كيف تلعب componentDidMount باستخدام useEffect ؟
  • كيفية تحميل البيانات داخل useEffect ؟ ما هو [] ؟
  • هل يجب تحديد الوظائف كتبعيات للتأثير؟
  • لماذا ينتهي البرنامج أحيانًا بحلقة لا تنتهي من إعادة تحميل البيانات؟
  • لماذا تظهر الحالة القديمة أحيانًا داخل التأثيرات أم أن الخصائص القديمة موجودة؟

عندما بدأت استخدام الخطافات لأول مرة ، عذبتني هذه الأسئلة أيضًا. حتى عندما كنت أستعد الوثائق ، لم أستطع أن أقول أنني أتقن بعض التفاصيل الدقيقة. منذ ذلك الحين ، مررت بعدة لحظات ، عندما أدركت فجأة شيئًا مهمًا ، أردت حقًا أن أصرخ: "يوريكا!" حول ما أدركته في هذه اللحظات ، أريد أن أخبرك. ما تعلمته عن useEffect الآن سيسمح لك برؤية الإجابات الواضحة على الأسئلة أعلاه بوضوح.

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

لم يجتمع كل شيء في رأسي إلا بعد أن توقفت عن النظر إلى خطاف useEffect خلال منظور الأساليب المألوفة لدورة حياة المكونات القائمة على المكونات.

"يجب أن تنسى ما تعلمته"


habr.com/ru/company/ruvds/blog/445276/


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

إذا كان التنسيق الذي useEffect فيه useEffect ، بكل useEffect ، غير مناسب لك ، فيمكنك الانتظار قليلاً - حتى اللحظة التي تظهر فيها هذه التفسيرات في أدلة أخرى لا تعد ولا تحصى. إليكم نفس القصة مع مكتبة React نفسها ، والتي كانت في عام 2013 شيئًا جديدًا تمامًا. يستغرق مجتمع التطوير بعض الوقت حتى يتعرف على النموذج العقلي الجديد والمواد التعليمية المبنية على هذا النموذج لتظهر.

إجابات على الأسئلة


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

to كيف يمكن لعب componentDidMount باستخدام useEffect؟


على الرغم من أنه يمكنك استخدام useEffect(fn, []) لتشغيل وظيفة componentDidMount ، إلا أنها ليست المكافئ الدقيق لـ componentDidMount . وهي ، على عكس componentDidMount ، تلتقط الخصائص والدولة. لذلك ، حتى داخل رد الاتصال ، سترى الخصائص الأولية والحالة. إذا كنت تريد أن ترى أحدث إصدار من شيء ما ، يمكنك كتابته في رابط ref . ولكن عادة ما يكون هناك طريقة أبسط لهيكلة الكود ، لذا فإن القيام بذلك اختياري. تذكر أن نموذج التأثيرات العقلية يختلف عن ذلك المطبق على componentDidMount وطرق دورة حياة المكون الأخرى. لذلك ، فإن محاولة العثور على معادلاتها الدقيقة يمكن أن تضر أكثر مما تنفع. من أجل العمل بشكل منتج ، تحتاج ، إذا جاز التعبير ، إلى "التفكير في الآثار". أساس نموذجهم العقلي أقرب إلى تنفيذ التزامن من الاستجابة للأحداث في دورة حياة المكونات.

to كيفية تحميل البيانات بشكل صحيح داخل useEffect؟ ما هو []؟


فيما يلي دليل جيد حول تحميل البيانات باستخدام useEffect . حاول قراءتها بالكامل! انها ليست كبيرة مثل ذلك. الأقواس ، [] ، التي تمثل صفيفًا فارغًا ، تعني أن التأثير لا يستخدم القيم المشاركة في دفق بيانات React ، ولهذا السبب يمكن اعتبار استخدامه الفردي آمنًا. بالإضافة إلى ذلك ، يعد استخدام مجموعة فارغة من التبعيات مصدرًا شائعًا للأخطاء في حالة استخدام قيمة معينة بالفعل في التأثير. ستحتاج إلى إتقان العديد من الاستراتيجيات (المقدمة بشكل أساسي في شكل useReducer و useCallback ) التي يمكن أن تساعد في القضاء على الحاجة إلى التبعية بدلاً من التخلص من هذه التبعية بشكل غير معقول.

need هل يجب تحديد الوظائف كتبعيات للتأثير؟


يوصى بأن تؤخذ الوظائف التي لا تحتاج إلى خصائص أو حالة خارج المكونات ، وتوصى بوضع تلك الوظائف التي تستخدم فقط بواسطة التأثيرات داخل التأثيرات. إذا useCallback بعد ذلك في استخدام الوظائف الموجودة في نطاق التجسيد (بما في ذلك الوظائف من الخصائص) ، useCallback في useCallback حيث تم التصريح عنها ، وحاول استخدامها مرة أخرى. لماذا هذا مهم؟ يمكن للوظائف "رؤية" القيم من الخصائص والحالة ، بحيث تشارك في تدفق البيانات. إليك المزيد من المعلومات المفصلة حول هذا الأمر في الأسئلة الشائعة.

▍ لماذا ينتهي البرنامج أحيانًا بحلقة لا نهاية لها لإعادة تحميل البيانات؟


يمكن أن يحدث هذا عند تنفيذ تحميل البيانات بطريقة لا تحتوي على وسيطة ثانية تمثل التبعيات. بدونها ، يتم تنفيذ التأثيرات بعد كل عملية تجسيد - مما يعني أن تحديد الحالة سوف يتسبب في التذكير بهذه الآثار. يمكن أن تحدث حلقة لا نهائية أيضًا في حالة الإشارة إلى القيمة التي تتغير دائمًا في صفيف التبعية. تعرف على نوع القيمة الممكنة عن طريق إزالة التبعيات واحدة تلو الأخرى. ومع ذلك ، عادةً ما تكون إزالة التبعيات (أو استخدام rashly باستخدام [] الطريقة الخاطئة لحل مشكلة. بدلاً من ذلك ، يجب أن تجد مصدر المشكلة وحلها حقًا. على سبيل المثال ، يمكن أن تسبب الدالات مشكلة مماثلة. يمكنك المساعدة في حلها عن طريق وضعها في تأثيرات ، أو نقلها خارج المكونات ، أو عن طريق الالتفاف على useCallback . لتجنب إنشاء كائنات متعددة ، يمكنك استخدام useMemo .

sometimes لماذا تظهر الحالة القديمة في بعض الأحيان داخل الآثار أو يتم العثور على الخصائص القديمة؟


التأثيرات دائما "انظر" خصائص والدولة من تقديم التي أعلن عنها. يساعد هذا في منع الأخطاء ، ولكن في بعض الحالات يمكن أن يتداخل مع التشغيل العادي للمكون. في مثل هذه الحالات ، يمكنك استخدام ارتباطات ref القابلة للتغيير صراحة للعمل مع هذه القيم (يمكنك قراءة ذلك في نهاية المقالة المذكورة أعلاه). إذا كنت تعتقد أنك ترى خصائص أو حالة من العرض القديم ، ولكن لا تتوقع ذلك ، فقد تكون قد فاتتك بعض التبعيات. من أجل معرفة كيفية رؤيتهم ، استخدم قاعدة اللنت. في غضون يومين ، سوف يصبح مثل طبيعتك الثانية. أيضًا ، ألق نظرة على هذه الإجابة في الأسئلة الشائعة.

آمل أن تكون هذه الإجابات على الأسئلة مفيدة لأولئك الذين قرأوها. الآن دعونا نتحدث أكثر عن useEffect .

كل تقديم له خصائصه وحالته.


قبل أن نتمكن من مناقشة الآثار ، نحتاج إلى الحديث عن التقديم.

هنا هو مكون عداد وظيفي.

 function Counter() { const [count, setCount] = useState(0); return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>   </div> ); } 

ألق نظرة فاحصة على <p>You clicked {count} times</p> . ماذا تعني؟ هل الثابت الثابت بطريقة ما "يلاحظ" التغييرات في الحالة وهل يتم تحديثها تلقائيًا؟ يمكن اعتبار هذا الاستنتاج نوعًا من الفكرة الأولى القيمة لشخص يدرس React ، لكنها ليست نموذجًا عقليًا دقيقًا لما يحدث.

في مثالنا ، count مجرد رقم. هذا ليس نوعًا من "ربط البيانات" السحري ، وليس نوعًا من "كائن المراقب" أو "الوكيل" ، أو أي شيء آخر. أمامنا رقم جيد قديم ، مثل هذا:

 const count = 42; // ... <p>You clicked {count} times</p> // ... 

أثناء إخراج المكون الأول ، تكون قيمة count تم الحصول عليها من useState() هي 0. عندما نسمي setCount(1) ، يستدعي setCount(1) المكون مرة أخرى. سيكون هذا count الزمني 1. وهكذا:

 //     function Counter() { const count = 0; //  useState() // ... <p>You clicked {count} times</p> // ... } //       function Counter() { const count = 1; //  useState() // ... <p>You clicked {count} times</p> // ... } //        function Counter() { const count = 2; //  useState() // ... <p>You clicked {count} times</p> // ... } 

React يستدعي المكون كلما قمنا بتحديث الحالة. ونتيجة لذلك ، فإن كل عملية تجسيد "ترى" قيمتها الخاصة بالحالة counter ، والتي ، داخل الوظيفة ، ثابتة.

نتيجة لذلك ، لا يقوم هذا الخط بأي عملية ربط بيانات خاصة:

 <p>You clicked {count} times</p> 

يقوم فقط بتضمين قيمة عددية في الكود الذي تم إنشاؤه أثناء العرض. يتم توفير هذا الرقم بواسطة React. عندما نسمي setCount ، setCount للمكون مرة أخرى بقيمة count مختلفة. React ثم يقوم بتحديث DOM بحيث يطابق طراز كائن المستند أحدث إخراج للبيانات أثناء تقديم المكون.

الاستنتاج الأكثر أهمية الذي يمكن استخلاصه من ذلك هو أن count ثابت داخل أي عرض معين ولا يتغير مع مرور الوقت. يتغير المكون الذي يتم استدعاؤه مرارًا وتكرارًا. كل تجسيد "يرى" قيمة count الخاصة به ، والتي يتم عزلها لكل عملية تجسيد.

في هذه المادة ، يمكنك العثور على تفاصيل حول هذه العملية.

كل تقديم له معالجات الأحداث الخاصة به.


كل شيء لا يزال واضحا. ماذا عن معالجات الأحداث؟
نلقي نظرة على هذا المثال. هنا ، بعد ثلاث ثوانٍ من النقر فوق الزر ، يتم عرض مربع رسالة يحتوي على معلومات حول القيمة المخزنة في count :

 function Counter() { const [count, setCount] = useState(0); function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>     <button onClick={handleAlertClick}>       Show alert     </button>   </div> ); } 

افترض أنني أؤدي تسلسل الإجراءات التالي:

  • سأرفع قيمة count إلى 3 بالنقر Click me الزر "النقر Click me .
  • انقر فوق الزر " Show alert .
  • زيادة القيمة إلى 5 قبل انتهاء المهلة.


زيادة قيمة العدد بعد النقر فوق الزر "إظهار التنبيه"

ما رأيك يظهر في مربع الرسالة؟ هل سيتم عرض الرقم 5 هناك ، وهو ما يتوافق مع قيمة count في الوقت الذي تم فيه تشغيل المؤقت ، أو 3 - أي قيمة count في الوقت الذي يتم فيه الضغط على الزر؟

ستجد الآن إجابة على هذا السؤال ، ولكن إذا كنت ترغب في معرفة كل شيء بنفسك - إليك نسخة صالحة من هذا المثال.

إذا كان ما رأيته يبدو غير مفهوم بالنسبة لك - فإليك مثال أقرب إلى الواقع. تخيل تطبيق الدردشة الذي يتم فيه ، في الولاية ، تخزين ID المستلم الحالي للرسالة ، وهناك زر Send . في هذه المادة ، ما يحدث يعتبر بالتفصيل. في الواقع ، الإجابة الصحيحة على سؤال ما يظهر في مربع الرسالة هي 3.

آلية عرض مربع رسالة "استولت" على الحالة في الوقت الذي تم فيه النقر على الزر.

هناك طرق لتطبيق إصدار آخر من السلوك ، ولكن الآن سنتعامل مع السلوك القياسي للنظام. عند بناء النماذج الذهنية للتكنولوجيات ، من المهم التمييز بين "الطريق الأقل مقاومة" وبين جميع أنواع "مخارج الطوارئ".

كيف يعمل كل هذا؟

لقد قلنا بالفعل أن قيمة count ثابتة لكل مكالمة محددة لوظائفنا. أعتقد أنه من المفيد الحديث عن هذا بمزيد من التفصيل. النقطة المهمة هي أن وظيفتنا تسمى عدة مرات (مرة واحدة لكل عملية تجسيد) ، ولكن مع كل من هذه المكالمات ، count داخلها ثابتًا. تم ضبط هذا الثابت على قيمة معينة (تمثل حالة عملية تقديم معينة).

هذا السلوك للوظائف ليس شيئًا خاصًا لـ React - الوظائف العادية تتصرف بطريقة مماثلة:

 function sayHi(person) { const name = person.name; setTimeout(() => {   alert('Hello, ' + name); }, 3000); } let someone = {name: 'Dan'}; sayHi(someone); someone = {name: 'Yuzhi'}; sayHi(someone); someone = {name: 'Dominic'}; sayHi(someone); 

في هذا المثال ، someone إعادة تعيين المتغير الخارجي someone عدة مرات. يمكن أن يحدث نفس الشيء في مكان ما داخل React ، قد تتغير الحالة الحالية للمكون. ومع ذلك ، يوجد داخل دالة sayHi name ثابت محلي مرتبط person من مكالمة معينة. هذا الثابت محلي ، وبالتالي يتم عزل قيمه في استدعاءات الوظائف المختلفة عن بعضها البعض! نتيجةً لذلك ، بعد انقضاء مهلة زمنية ، "تتذكر" نافذة الرسالة المعروضة قيمة name الخاصة بها.

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

 //     function Counter() { const count = 0; //  useState() // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } // ... } //       function Counter() { const count = 1; //  useState() // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } // ... } //        function Counter() { const count = 2; //  useState() // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } // ... } 

نتيجة لذلك ، يقوم كل handleAlertClick ، في الواقع ، بإرجاع handleAlertClick "الإصدار" handleAlertClick . كل من هذه الإصدارات "يتذكر" قيمة count الخاصة به:

 //     function Counter() { // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + 0);   }, 3000); } // ... <button onClick={handleAlertClick} /> // ,   0 // ... } //       function Counter() { // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + 1);   }, 3000); } // ... <button onClick={handleAlertClick} /> // ,   1 // ... } //        function Counter() { // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + 2);   }, 3000); } // ... <button onClick={handleAlertClick} /> // ,   2 // ... } 

لهذا السبب ، في هذا المثال ، "تنتمي" معالجات الأحداث إلى عروض محددة ، وعند النقر فوق الزر ، يستخدم المكون حالة count من هذه العروض.

ضمن كل تجسيد معين ، تظل الخصائص والحالة دائمًا كما هي. لكن إذا استخدمت عمليات تقديم مختلفة خصائصها وحالتها ، يحدث نفس الشيء مع أي آليات تستخدمها (بما في ذلك معالجات الأحداث). هم أيضا "ينتمون" إلى عروض محددة. لذلك ، حتى "الدالات" غير المتزامنة داخل معالجات الأحداث "سترى" نفس قيم count .

تجدر الإشارة إلى أنه في المثال أعلاه ، قمت بدمج قيم count معين مباشرة في وظيفة handleAlertClick . لن يؤذينا هذا الاستبدال "العقلي" ، حيث لا يمكن تغيير count الثابت داخل عرض معين. أولاً ، إنه ثابت ، وثانيًا ، إنه رقم. من الآمن القول أنه يمكن للمرء أن يفكر أيضًا في معاني أخرى ، مثل الكائنات ، ولكن فقط إذا قبلنا كقاعدة بعدم إجراء تغييرات (الطفرات) في الدولة. في الوقت نفسه ، نحن راضون عن الدعوة إلى setSomething(newObj) لكائن جديد بدلاً من تغيير الكائن الحالي ، حيث أنه من خلال هذا النهج لم يتم المساس بالحالة التي تنتمي إلى العرض السابق.

كل تقديم له آثاره الخاصة.


هذه المادة ، كما تعلمون ، مخصصة للآثار ، لكننا لم نتحدث عنها حتى الآن. الآن سوف نصلحها. كما اتضح ، فإن العمل مع التأثيرات لا يختلف بشكل خاص عما توصلنا إليه بالفعل.

تأمل مثالاً من الوثائق ، وهو يشبه إلى حد كبير المثال الذي قمنا بتحليله بالفعل:

 function Counter() { const [count, setCount] = useState(0); useEffect(() => {   document.title = `You clicked ${count} times`; }); return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>   </div> ); } 

الآن لدي سؤال لك. كيف يقرأ التأثير أحدث قيمة count ؟

ربما يتم استخدام بعض "ربط البيانات" هنا ، أو "كائن مراقب" يقوم بتحديث قيمة count داخل وظيفة التأثير؟ ربما يكون count متغيرًا قابلاً للتغيير الذي تحدد قيمته React داخل مكوننا ، ونتيجة لذلك يرى التأثير دائمًا أحدث إصدار له؟

لا.

نحن نعلم بالفعل أنه في تقديم عنصر معين ، count ثابتًا. حتى معالجات الأحداث "يرون" قيمة count من التجسيد الذي "ينتمون إليه" نظرًا لحقيقة أن count ثابت في نطاق معين. وينطبق الشيء نفسه بالنسبة للآثار!

وتجدر الإشارة إلى أن هذا ليس هو count المتغيرات count والذي يتغير بطريقة ما داخل تأثير "لم يتغير". أمامنا هي وظيفة التأثير نفسه ، والتي تختلف في كل عملية تقديم.

كل إصدار "يرى" قيمة count من التجسيد الذي ينتمي إليه:

 //     function Counter() { // ... useEffect(   //        () => {     document.title = `You clicked ${0} times`;   } ); // ... } //       function Counter() { // ... useEffect(   //        () => {     document.title = `You clicked ${1} times`;   } ); // ... } //        function Counter() { // ... useEffect(   //        () => {     document.title = `You clicked ${2} times`;   } ); // .. } 

React , DOM .

, ( ), , , , «» , «».

, , .

, ( , ). , , , .

, , :

React:

  • , 0.

:

  • : <p>You clicked 0 times</p> .
  • , , : () => { document.title = 'You clicked 0 times' } .

React:

  • بالطبع . , , - DOM.

:

  • , .

React:

  • , , .
  • () => { document.title = 'You clicked 0 times' } .

, . , , - :

:

  • , React, 1.

React:

  • , 1.

:

  • : <p>You clicked 1 times</p> .
  • , , : () => { document.title = 'You clicked 1 times' } .

React:

  • بالطبع . , , - DOM.

:

  • , .

React:

  • , , .
  • () => { document.title = 'You clicked 1 times' } .


, , , , «» .

. :

 function Counter() { const [count, setCount] = useState(0); useEffect(() => {   setTimeout(() => {     console.log(`You clicked ${count} times`);   }, 3000); }); return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>   </div> ); } 

, ?

, . , , , . ! , , , , , count . .




: «, ! ?».

, , this.setState , , . , , , , , :

   componentDidUpdate() {   setTimeout(() => {     console.log(`You clicked ${this.state.count} times`);   }, 3000); } 

, this.state.count count , , . , , , 5 , 5 .




, JavaScript-, , , , , setTimeout , . , (React this.state , ), .

— , , «» , . , , , . , , . , , , , , , .


, ( , , - API ) , .

:

 function Example(props) { useEffect(() => {   setTimeout(() => {     console.log(props.counter);   }, 1000); }); // ... } function Example(props) { const counter = props.counter; useEffect(() => {   setTimeout(() => {     console.log(counter);   }, 1000); }); // ... } 

, «» . ! . , .

, , - , , , , . , ref , .

, , , , , . , ( ), «» React-. , , . , .

, , , :

 function Example() { const [count, setCount] = useState(0); const latestCount = useRef(count); useEffect(() => {   //        count   latestCount.current = count;   setTimeout(() => {     //            console.log(`You clicked ${latestCount.current} times`);   }, 3000); }); // ... 




- React . React this.state . , , latestCount.current . , . , , , .

?


, . , , «» .

:

 useEffect(() => {   ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);   return () => {     ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);   }; }); 

, props{id: 10} , {id: 20} — . , :

  • React {id: 10} .
  • React {id: 20} .
  • React {id: 20} .

( , , .)

, «» - , , «» - , . — , , , . .

React , . , . . . :

  • React {id: 20} .
  • . {id: 20} .
  • React {id: 10} .
  • React {id: 20} .

, «» props , {id: 10} , , props {id: 20} .

, …


— ?

: « ( , , - API ) , ».

! « » , . , , :

 //  ,  props  {id: 10} function Example() { // ... useEffect(   //       () => {     ChatAPI.subscribeToFriendStatus(10, handleStatusChange);     //           return () => {       ChatAPI.unsubscribeFromFriendStatus(10, handleStatusChange);     };   } ); // ... } //  ,  props  {id: 20} function Example() { // ... useEffect(   //       () => {     ChatAPI.subscribeToFriendStatus(20, handleStatusChange);     //           return () => {       ChatAPI.unsubscribeFromFriendStatus(20, handleStatusChange);     };   } ); // ... } 

, , … , «» , -, {id: 10} .

React . , , . props , .

,


React , . .

, :

 function Greeting({ name }) { return (   <h1 className="Greeting">     Hello, {name}   </h1> ); } 

, <Greeting name="Dan" /> , — <Greeting name="Yuzhi" /> , <Greeting name="Yuzhi" /> . Hello, Yuzhi .

, , . React, . , , . $.addClass $.removeClass jQuery- ( — , «»), , CSS- React ( — , «»).

React DOM , . «» «».

. useEffect , React, .

 function Greeting({ name }) { useEffect(() => {   document.title = 'Hello, ' + name; }); return (   <h1 className="Greeting">     Hello, {name}   </h1> ); } 

useEffect , , . , - , ! , «», «».

, A , B , — C , , C . (, - ), .

, , , . ( ).

?

React


React DOM. DOM , React DOM, - .

, :

 <h1 className="Greeting"> Hello, Dan </h1> 

:

 <h1 className="Greeting"> Hello, Yuzhi </h1> 

React :

 const oldProps = {className: 'Greeting', children: 'Hello, Dan'}; const newProps = {className: 'Greeting', children: 'Hello, Yuzhi'}; 

React , children , DOM. , className . :

 domNode.innerText = 'Hello, Yuzhi'; // domNode.className    

- ? , , .

, , - :

 function Greeting({ name }) { const [counter, setCounter] = useState(0); useEffect(() => {   document.title = 'Hello, ' + name; }); return (   <h1 className="Greeting">     Hello, {name}     <button onClick={() => setCounter(counter + 1)}>       Increment     </button>   </h1> ); } 

counter . document.title name , name . document.title counter , .

React … ?

 let oldEffect = () => { document.title = 'Hello, Dan'; }; let newEffect = () => { document.title = 'Hello, Dan'; }; //   React  ,        ? 

— . React , , . ( . name .)

, , ( deps ), useEffect :

   useEffect(() => {   document.title = 'Hello, ' + name; }, [name]); //   

, React: «, , , , name ».

, , React :

 const oldEffect = () => { document.title = 'Hello, Dan'; }; const oldDeps = ['Dan']; const newEffect = () => { document.title = 'Hello, Dan'; }; const newDeps = ['Dan']; // React     ,     . //      ,     . 

, , ! - - .

React


React — . , , , , useEffect , , , . ( !)

 function SearchResults() { async function fetchData() {   // ... } useEffect(() => {   fetchData(); }, []); //   ?  .      . // ... } 

FAQ , . .

« !», — . : , , . , , , — , .

, , . , , , , . , . .

, , .

, React


, , React , .

   useEffect(() => {   document.title = 'Hello, ' + name; }, [name]); 




, , , [] , , , , :

   useEffect(() => {   document.title = 'Hello, ' + name; }, []); // :    name 




. , «» , , .

, , , . , : « setInterval clearInterval ». . , , , useEffect , , , [] . - , ?

 function Counter() { const [count, setCount] = useState(0); useEffect(() => {   const id = setInterval(() => {     setCount(count + 1);   }, 1000);   return () => clearInterval(id); }, []); return <h1>{count}</h1>; } 

, , .

, « , », . , , , setInterval , . , ?

, — React , , . , count , React , , , . — .

count 0. setCount(count + 1) setCount(0 + 1) . , — [] , setCount(0 + 1) :

 //  ,   0 function Counter() { // ... useEffect(   //       () => {     const id = setInterval(() => {       setCount(0 + 1); //  setCount(1)     }, 1000);     return () => clearInterval(id);   },   [] //    ); // ... } //       1 function Counter() { // ... useEffect(   //     - ,    //   React  ,   .   () => {     const id = setInterval(() => {       setCount(1 + 1);     }, 1000);     return () => clearInterval(id);   },   [] ); // ... } 

React, , , — .

count — , ( ):

   const count = // ... useEffect(() => {   const id = setInterval(() => {     setCount(count + 1);   }, 1000);   return () => clearInterval(id); }, []); 

. React .


,

. , , React , , . — - .


React , . , , , .

, , , . count :

 useEffect(() => { const id = setInterval(() => {   setCount(count + 1); }, 1000); return () => clearInterval(id); }, [count]); 

. , , — , . count , count , setCount(count + 1) :

 //  ,   0 function Counter() { // ... useEffect(   //       () => {     const id = setInterval(() => {       setCount(0 + 1); // setCount(count + 1)     }, 1000);     return () => clearInterval(id);   },   [0] // [count] ); // ... } //  ,   1 function Counter() { // ... useEffect(   //       () => {     const id = setInterval(() => {       setCount(1 + 1); // setCount(count + 1)     }, 1000);     return () => clearInterval(id);   },   [1] // [count] ); // ... } 

, setInterval , count , . , .


,

, , , . — , .

.


, count .

 useEffect(() => {   const id = setInterval(() => {     setCount(count + 1);   }, 1000);   return () => clearInterval(id); }, [count]); 

, , count . , count setCount . , , count . , , setState :

   useEffect(() => {   const id = setInterval(() => {     setCount(c => c + 1);   }, 1000);   return () => clearInterval(id); }, []); 

« ». , count - , setCount(count + 1) . count - , count + 1 «» React. React count . , React — , , , .

setCount(c => c + 1) . « React », , . « » , , .

, , , . React. count :


,

.

, setInterval , , c => c + 1 . count . React .

Google Docs


, , — ? , , «», , . , Google Docs . . , .

, . . , setCount(c => c + 1) , , setCount(count + 1) , «» count . , ( — «»). « React» — . .

( ) , Google Docs . — , React . , , ( , , ) .

, setCount(c => c + 1) , . , . , , , , , . setCount(c => c + 1) . useReducer .


, : count step . setInterval , step :

 function Counter() { const [count, setCount] = useState(0); const [step, setStep] = useState(1); useEffect(() => {   const id = setInterval(() => {     setCount(c => c + step);   }, 1000);   return () => clearInterval(id); }, [step]); return (   <>     <h1>{count}</h1>     <input value={step} onChange={e => setStep(Number(e.target.value))} />   </> ); } 

.

, React . step , . .

: step setIntervalstep . , , , ! , , , , , .

, , , setInterval , step . step ?

, , useReducer .

, setSomething(something => ...) , , . «», , , .

step dispatch :

 const [state, dispatch] = useReducer(reducer, initialState); const { count, step } = state; useEffect(() => { const id = setInterval(() => {   dispatch({ type: 'tick' }); //  setCount(c => c + step); }, 1000); return () => clearInterval(id); }, [dispatch]); 

.

: « , ?». , React , dispatch . .

!

( dispatch setstate useRef , React , . — .)

, , , , . step . , . , . :

 const initialState = { count: 0, step: 1, }; function reducer(state, action) { const { count, step } = state; if (action.type === 'tick') {   return { count: count + step, step }; } else if (action.type === 'step') {   return { count, step: action.step }; } else {   throw new Error(); } } 

, , , .

useReducer — -


, , , . , , ? , , API <Counter step={1} /> . , props.step ?

, ! , :

 function Counter({ step }) { const [count, dispatch] = useReducer(reducer, 0); function reducer(state, action) {   if (action.type === 'tick') {     return state + step;   } else {     throw new Error();   } } useEffect(() => {   const id = setInterval(() => {     dispatch({ type: 'tick' });   }, 1000);   return () => clearInterval(id); }, [dispatch]); return <h1>{count}</h1>; } 

, . , , , , . .

dispatch . , , . .

, , . «» , , ? , dispatch , React . . .

useReducer «-» . , . , , , , .


, - , .

, , , :

 function SearchResults() { const [data, setData] = useState({ hits: [] }); async function fetchData() {   const result = await axios(     'https://hn.algolia.com/api/v1/search?query=react',   );   setData(result.data); } useEffect(() => {   fetchData(); }, []); //   ? // ... 

, .

, , . , , , , , , , , .

, , , , :

 function SearchResults() { // ,       function getFetchUrl() {   return 'https://hn.algolia.com/api/v1/search?query=react'; } // ,        async function fetchData() {   const result = await axios(getFetchUrl());   setData(result.data); } useEffect(() => {   fetchData(); }, []); // ... } 

, , :

 function SearchResults() { const [query, setQuery] = useState('react'); // ,       function getFetchUrl() {   return 'https://hn.algolia.com/api/v1/search?query=' + query; } // ,        async function fetchData() {   const result = await axios(getFetchUrl());   setData(result.data); } useEffect(() => {   fetchData(); }, []); // ... } 

, (, ), . .

, . , :

 function SearchResults() { // ... useEffect(() => {   //      !   function getFetchUrl() {     return 'https://hn.algolia.com/api/v1/search?query=react';   }   async function fetchData() {     const result = await axios(getFetchUrl());     setData(result.data);   }   fetchData(); }, []); //    . // ... } 

.

? , « ». React, - .

getFetchUrl , query , , , , . — , query :

 function SearchResults() { const [query, setQuery] = useState('react'); useEffect(() => {   function getFetchUrl() {     return 'https://hn.algolia.com/api/v1/search?query=' + query;   }   async function fetchData() {     const result = await axios(getFetchUrl());     setData(result.data);   }   fetchData(); }, [query]); //    . // ... } 

.

, « React». query . , , , , . , , .

exhaustive-deps eslint-plugin-react-hooks , . , , .




.

, ?


. , , . , , .

? , . : React . . , « ». , , . , , , !

, , . , getFetchUrl :

 function SearchResults() { function getFetchUrl(query) {   return 'https://hn.algolia.com/api/v1/search?query=' + query; } useEffect(() => {   const url = getFetchUrl('react');   // ...    -   ... }, []); //  : getFetchUrl useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, []); //  : getFetchUrl // ... } 

getFetchUrl — , .

, «» , . getFetchUrl (, , ), :

 function SearchResults() { //       function getFetchUrl(query) {   return 'https://hn.algolia.com/api/v1/search?query=' + query; } useEffect(() => {   const url = getFetchUrl('react');   // ...    -    ... }, [getFetchUrl]); //   ,     . useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, [getFetchUrl]); //   ,     . // ... } 

, getFetchUrl . , — . - , , . , , , .

— .

, , :

 //        function getFetchUrl(query) { return 'https://hn.algolia.com/api/v1/search?query=' + query; } function SearchResults() { useEffect(() => {   const url = getFetchUrl('react');   // ...    -   ... }, []); //     . useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, []); //     . // ... } 

, . , , .

. , useCallback :

 function SearchResults() { //    ,   const getFetchUrl = useCallback((query) => {   return 'https://hn.algolia.com/api/v1/search?query=' + query; }, []);  //      . useEffect(() => {   const url = getFetchUrl('react');   // ...    -   ... }, [getFetchUrl]); //      . useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, [getFetchUrl]); //       // ... } 

useCallback . : , -, , , .

, . ( 'react' 'redux' ). , , , query . , , query , getFetchUrl .

, query useCallback :

 function SearchResults() { const [query, setQuery] = useState('react'); const getFetchUrl = useCallback(() => { //   query   return 'https://hn.algolia.com/api/v1/search?query=' + query; }, []); //  : query // ... } 

useCallback query , , getFetchUrl , query :

 function SearchResults() { const [query, setQuery] = useState('react'); //      query const getFetchUrl = useCallback(() => {   return 'https://hn.algolia.com/api/v1/search?query=' + query; }, [query]);  //    . useEffect(() => {   const url = getFetchUrl();   // ...    -   ... }, [getFetchUrl]); //    . // ... } 

useCallback , query , getFetchUrl , , . query , getFetchUrl , . Excel: - , , , .

— , . , :

 function Parent() { const [query, setQuery] = useState('react'); //      query const fetchData = useCallback(() => {   const url = 'https://hn.algolia.com/api/v1/search?query=' + query;   // ...      ... }, [query]);  //       return <Child fetchData={fetchData} /> } function Child({ fetchData }) { let [data, setData] = useState(null); useEffect(() => {   fetchData().then(setData); }, [fetchData]); //       // ... } 

fetchData Parent query , Child , .

?


, , , , . , , , , :

 class Parent extends Component { state = {   query: 'react' }; fetchData = () => {   const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;   // ...    -   ... }; render() {   return <Child fetchData={this.fetchData} />; } } class Child extends Component { state = {   data: null }; componentDidMount() {   this.props.fetchData(); } render() {   // ... } } 

, : « , , , useEffectcomponentDidMount componentDidUpdate . !». componentDidUpdate :

 class Child extends Component { state = {   data: null }; componentDidMount() {   this.props.fetchData(); } componentDidUpdate(prevProps) {   //         if (this.props.fetchData !== prevProps.fetchData) {     this.props.fetchData();   } } render() {   // ... } } 

, fetchData — ! (, , , .) - , . this.props.fetchData prevProps.fetchData . , , ?

   componentDidUpdate(prevProps) {   this.props.fetchData(); } 

. . ( .) , fetchData this.state.query ?

   render() {   return <Child fetchData={this.fetchData.bind(this, this.state.query)} />; } 

this.props.fetchData !== prevProps.fetchData true , , query ! .

, , , query Child . , , query , query :

 class Parent extends Component { state = {   query: 'react' }; fetchData = () => {   const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;   // ...    -    ... }; render() {   return <Child fetchData={this.fetchData} query={this.state.query} />; } } class Child extends Component { state = {   data: null }; componentDidMount() {   this.props.fetchData(); } componentDidUpdate(prevProps) {   if (this.props.query !== prevProps.query) {     this.props.fetchData();   } } render() {   // ... } } 

, , - , , .

, , . this , . , , , , - . , this.props.fetchData , , , , , .

- useCallback . , , , . , . useCallback props.fetchData .

, useMemo :

 function ColorPicker() { //         Child, //       . const [color, setColor] = useState('pink'); const style = useMemo(() => ({ color }), [color]); return <Child style={style} />; } 

, useCallback , - . « », , , . , . , .

, fetchData ( ), . , , . (« props.onComplete , ?») , .


, :

 class Article extends Component { state = {   article: null }; componentDidMount() {   this.fetchData(this.props.id); } async fetchData(id) {   const article = await API.fetchArticle(id);   this.setState({ article }); } // ... } 

, , , . . — , :

 class Article extends Component { state = {   article: null }; componentDidMount() {   this.fetchData(this.props.id); } componentDidUpdate(prevProps) {   if (prevProps.id !== this.props.id) {     this.fetchData(this.props.id);   } } async fetchData(id) {   const article = await API.fetchArticle(id);   this.setState({ article }); } // ... } 

, , , . , . , {id: 10} , {id: 20} , , . , , , . وهذا خطأ.

, , . — , , async/await ( , - ) , ( , ).

, async -. (, , , , .)

, , ! .

, :

 function Article({ id }) { const [article, setArticle] = useState(null); useEffect(() => {   let didCancel = false;   async function fetchData() {     const article = await API.fetchArticle(id);     if (!didCancel) {       setArticle(article);     }   }   fetchData();   return () => {     didCancel = true;   }; }, [id]); // ... } 

, , , . , .


, , , , , . , , , . . — .

useEffect , , , . React. , useEffect .

, , « », . . , , , , «» , .

, useEffect , . — . — , , — , . , , , , API.

, , useFetch , , useTheme , . , , useEffect . , , , .

, , useEffect . — , . , . ?

Suspense React , , - ( : , , ) .

Suspense , , useEffect , , , - . , , , . , , , , .

النتائج


, . , , - , , , .

Source: https://habr.com/ru/post/ar445276/


All Articles