Jak działa przypisanie kontekstu w metodach klas

0

Hej,

mam jedno pytanie dotyczące działania this w Reacie.

Przeglądam sobie kurs Samuraja Programowania z Udemy i w jednym z pierwszych filmów napisał przykładowy skrypt, który polega na tym, że po kliknięciu na przycisk "pokaż" pojawi nam się na stronie tekst, jeżeli klikniemy ponownie, to zostanie ukryty. Poniżej umieszczam link do skryptu:

https://jsfiddle.net/db7f96Le/

Chciałbym się zapytać, dlaczego było konieczne zbindowanie metody handleMessageButton w konstruktorze? Znaczy się wiem, że zrobiono to ponieważ zostało zgubione wiązanie this w momencie wywołania metody po kliknięciu na przycisk, za pomocą onClicka:

<button onClick={this.handleMessageButton}>
    {this.state.messageIsActive ? 'Ukryj' : 'Pokaż'}
</button>

Tylko dlaczego to zostało zgubione? Przecież metoda handleMessageButton znajduje się w klasie Message, więc chyba this użyte w tej metodzie powinno chyba i tak wskazywać na obiekt, który jest instancją klasy Message?

Zacząłem coś teraz czytać na necie i przeczytałem, że w React (czy ogólnie w JS) this w metodzie będzie na 100% wskazywać na obiekt, jeżeli metoda zostanie wywołana w kontekście obiekt.metoda() i podobno kluczowe właśnie jest, żeby po nazwie metody był nawias. Czyli jak mam np poniższy kod w czystym JS:

class user {
    constructor(name, surname, age) { //funkcja konstruktora
        this.name = name;
        this.surname = surname;
        this.age = age;
    }

    hello() {
        console.log(`Użytkownik ma na imię ${this.name} ${this.surname} i ma ${this.age} lat. This w funkcji hello:`);
        console.log(this);
    }

    secondFunction(){
        console.log(`Funkcja secondFunction. Na co wskazuje this:`);
        console.log(this);
        console.log(`Wywołanie w funkcji secondFunction funkcji hello (na co wskazuje this?)`);
        this.hello();
    }
}

const newPerson = new user("Jan", "Lewandowski", 24);
newPerson.hello();
newPerson.secondFunction();

To this użyte w metodzie hello wskazuje na obiekt newPerson, bo wywołaliśmy to jako newPerson.hello(). Tak samo jak w metodzie secondFunction() wywołamy metodę hello(), to this i tak będzie wskazywać na ten obiekt, żadne wiązanie nie zostanie zgubione.

Przeczytałem też w poniższej dokumentacji Reacta,

https://pl.reactjs.org/docs/handling-events.html

że "Należy zwrócić szczególną uwagę na znaczenie this funkcjach zwrotnych (ang. callbacks) używanych w JSX. W JavaScripcie metody klasy nie są domyślnie dowiązane do instancji. Jeśli zapomnisz dowiązać metodę this.handleClick i przekażesz ją jako atrybut onClick, to this przy wywołaniu będzie miało wartość undefined.

To zachowanie nie jest specyficzne dla Reacta; tak właśnie działają funkcje w JavaScripcie. Generalnie, jeśli odwołujesz się do metody bez () po nazwie, jak na przykład onClick={this.handleClick}, pamiętaj, aby zawsze dowiązywać ją do instancji."

Czyli mam rozumieć, że w kodzie z JSfiddle konieczne było zbindowanie metoda, gdyż w buttonie napisaliśmy this.handleMessageButton, BEZ nawiasów, więc wiązanie this zostało zgubione. Tylko teraz mam jeszcze pytanie, dlaczego w tym miejscu nie można napisać this.handleMessageButton(), czyli Z nawiasami? Jak próbowałem tak zrobić, to wywala mi błąd, a jak kiedyś programowałem w czystym JS, to mogłem bez problemu za pomocą onClicka wywoływać funkcję z nawiasami?

4
kario97 napisał(a):

Tylko dlaczego to zostało zgubione? Przecież metoda handleMessageButton znajduje się w klasie Message, więc chyba this użyte w tej metodzie powinno chyba i tak wskazywać na obiekt, który jest instancją klasy Message?

w innych językach tak może być, w JS nie.

W JS jak weźmiesz samą funkcją z obiektu, to this się utraci (zakładając, że nie jest to funkcja specjalnie zbindowana):

"use strict"; // włączenie strict mode (bez tego zachowanie będzie trochę inne)
const foo = {
  someMethod() { console.log("this", this); } // pokaże undefined
};
const bar = foo.someMethod;
bar(); 

Czyli mam rozumieć, że w kodzie z JSfiddle konieczne było zbindowanie metoda, gdyż w buttonie napisaliśmy this.handleMessageButton, BEZ nawiasów, więc wiązanie this zostało zgubione. Tylko teraz mam jeszcze pytanie, dlaczego w tym miejscu nie można napisać this.handleMessageButton(), czyli Z nawiasami? Jak próbowałem tak zrobić, to wywala mi błąd, a jak kiedyś programowałem w czystym JS, to mogłem bez problemu za pomocą onClicka wywoływać funkcję z nawiasami?

Piszesz tak, jakby można było wywołać funkcję bez nawiasów a raz z nawiasami.

Ogólnie nawiasy są tym, co wywołuje funkcje. Nazwa funkcji bez nawiasów to w JS po prostu referencja do funkcji, coś co można przekazywać dalej.

Np. pierwsza linijka w tym kodzie

const bar = this.foo;
console.log(bar()); // pokaże wynik funkcji (dopiero musimy wywołać)

nie wywołuje funkcji, a tylko ją bierze i przypisuje do zmiennej bar. Tę funkcję potem musimy wywołać w drugiej linijce, żeby mieć wynik.

a tutaj mamy kod, który już w pierwszej linijce wywołuje i przypisuje wynik funkcji do zmiennej bar:

const bar = this.foo(); 
console.log(bar); // pokaże wynik funkcji (nie musimy już wywoływać)

Tylko teraz mam jeszcze pytanie, dlaczego w tym miejscu nie można napisać this.handleMessageButton(), czyli Z nawiasami?

bo to by nie miało sensu. Taki kod:

onClick={this.handleClick()}

wywołałby po prostu od razu funkcję this.handleClick. A ty nie chcesz, żeby się kod od razu wywołał, tylko chcesz przekazać funkcję jako handler zdarzenia onClick (tu masz wytłumaczone, tyle że nie na przykładzie Reacta, ale ogólnie w JS https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function ).

czyli do onClick powinna jakaś funkcja być przekazana. Jak chcesz zachować this to możesz ją zbindować, ale mógłbyś zrobić inaczej i podać do onClick funkcję anonimową, która się odpali i wywoła twoją funkcję

onClick={(e) => {
    this.handleClick(e)
}}

(e) => {...} to tzw. arrow function (funkcja, którą krócej się pisze i która nie ma swojego this, więc widzi this funkcji zewnętrznej, bo taka zwykła funkcja:

onClick={function (e) {
    this.handleClick(e); // to nie jest this komponentu!! Więc nie zadziała!
}}

by nie zadziałała, bo zwykłe funkcje mają swoją własną zmienną this, więc przesłaniają.
(nie wiem, czy nie tłumaczę oczywistości teraz, swoją drogą, ale już wolałem to wyjaśnić)

0

@LukeJL: Hej, bardzo chciałbym Ci podziękować za pomoc i wytłumaczenie wszystkiego, nawet oczywistości w postaci funkcji strzałkowej, chociaż tutaj mam jeszcze jedno pytanie, ale to później. :)

Co do "wywoływania funkcji z nawiasami lub bez" to wiem, że nawiasy są potrzebne do wywołania funkcji, po prostu zastanawiałem się, dlaczego w tym kodzie, który wstawiłem na jsfiddle, w buttonie nie można było napisać:

onClick={this.handleClick()}

Ponieważ kiedyś widziałem skrypty, w których w onClicku funkcja była bez problemu wywoływana, np jest to pokazane w przykładach na w3schools:
https://www.w3schools.com/jsref/event_onclick.asp

Jednak Ty mi napisałeś, że taka instrukcja sprawi, że metoda nie zostanie od razu wywołana (bo nie ma nawiasów), tylko zostanie przekazana jako handler zdarzenia onClick, ponieważ przez brak nawiasów to jest tak naprawdę referencja na funkcję, a nie jej wywołanie. I mam teraz pytanie, bo napisałeś, że "chce przekazać funkcję jako handler zdarzenia onClick". Czy możesz mi wytłumaczyć co to dokładnie jest ten handler? :D

I jeszcze mam pytanie o funkcję strzałkową, którą napisałeś. Masz tam w nawiasie argument "e", ale po co on tam jest, skoro w moim kodzie tego nie ma? Chyba, że jest to tylko taki przykład nie związany z moim kodem z jsFiddle, bo mi się to kojarzy z argumentem "e" w funkcjach zdarzenia, aby uzyskać dostęp do tzw. obiektu zdarzenia, który przechowuje informacje o zdarzeniu, np można dzięki temu uzyskać współrzędne zdarzenia i się do nich później odwołać za pomocą e.clientX lub e.clientY itd.

2
kario97 napisał(a):

Co do "wywoływania funkcji z nawiasami lub bez" to wiem, że nawiasy są potrzebne do wywołania funkcji, po prostu zastanawiałem się, dlaczego w tym kodzie, który wstawiłem na jsfiddle, w buttonie nie można było napisać:

onClick={this.handleClick()}

Ponieważ kiedyś widziałem skrypty, w których w onClicku funkcja była bez problemu wywoływana, np jest to pokazane w przykładach na w3schools:
https://www.w3schools.com/jsref/event_onclick.asp

Pierwszą różnicą pomiędzy tymi przykładami jest to, że na w3schools funkcja jest przekazywana w formie tekstu

<button onClick="handleClick()">
  My button
</button>

Działa to w taki sposób, że ta funkcja nie wykonuje się od razu, tylko dopiero w momencie kliknięcia, tak jak odpowiednik onClick w Reactcie.

Drugą różnicą jest nazwa atrybutu onClick vs onclick przez co są to dwa inne atrybuty o dwóch różnych zastosowaniach.

onClick z którego korzysta React, oraz onclick z którego korzysta standardowy javascript, ale ten zapis jest obecnie przestarzały i lepiej go zastąpić addEventListener (https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)

1 użytkowników online, w tym zalogowanych: 0, gości: 1