When Lifting State Creates a Shallow Interface
ხშირად კომპონენტის ინტერფეისი ზედმეტად shallow არის, რომ სივრცე დარჩეს მშობლის მიერ მისი კონფიგურაციის. სწორედ ამ მიზით ისეთი ლოგიკის/prop-ის დელეგაცია ხდება მშობელზე რაც რეალურად მშობელი კომპონენტის პასუხისმგებლობა არ არის. (shallow inteface არის ტერმინი წიგნიდან A Philosophy of Software Design)
function SearchPage() {
const [disabled, setDisabled] = useState(false);
let someBigComputation = 0;
for (let i = 0; i < 50_000_000; i++) {
someBigComputation += i;
}
return (
<>
<p>score: {someBigComputation}</p>
<Input disabled={disabled} />
<button onClick={() => setDisabled((d) => !d)}>Toggle Disabled</button>
</>
);
}
function Input({ disabled }) {
// some primitive input logic
// some primitive input styles
return <input disabled={disabled} />;
}
რა პლიუსები აქვს ამ დიზაინს? პირველ ყოვლისა ისა, რომ არ ვართ შეზღუდული ლეიაუთში, შეგვიძლია გვქონდეს ნებისმიერი ორდერი:
// order A — score → input → controls
<>
<p>score: {someBigComputation}</p>
<Input disabled={disabled} />
<button onClick={() => setDisabled((d) => !d)}>Toggle Disabled</button>
</>
// order B — controls → input → score
<>
<button onClick={() => setDisabled((d) => !d)}>Toggle Disabled</button>
<Input disabled={disabled} />
<p>score: {someBigComputation}</p>
</>
ასევე პოტენციურად მომავალში SearchPage-ზე დაემატება ახალი კომპონენტები და პოტენციურად შეიძლება დასჭირდეთ input-ის disabled სთეითი:
function SearchPage() {
const [disabled, setDisabled] = useState(false);
return (
<>
<Input disabled={disabled} />
<SearchPageSuggestions disabled={disabled} />
<SearchPageSubmit disabled={disabled} />
<button onClick={() => setDisabled((d) => !d)}>Toggle Disabled</button>
</>
);
}
მაგრამ "პოტენციურად შეიძლება ყველაფერი მოხდეს" ამიტომ ამაზე დიზაინის გათვლა, თუ ეს პოტენციური მომავალი ძალიან მკაფიო არ არის არის, არასწორია.
ეხლა განვიხილოთ ის შემთხვევა რომელიც პოტენციურ მომავალზე ფიქრს შეგვაწყვეტინებს რადგან კოდის ეს დიზაინი უკვე ქმნის პრობლემას:
function SearchPage() {
const [disabled, setDisabled] = useState(false);
let someBigComputation = 0;
for (let i = 0; i < 50_000_000; i++) {
someBigComputation += i;
}
return (
<>
<p>score: {someBigComputation}</p>
<Input disabled={disabled} />
<SearchPageSuggestions disabled={disabled} />
<SearchPageSubmit disabled={disabled} />
<button onClick={() => setDisabled((d) => !d)}>Toggle Disabled</button>
</>
);
}
რა ხდება ასეთ დროს: მხოლოდ იმიტომ რომ Input-ის ქასთომიზაცია შევძლოთ, გვჭირდება მთლიანი SearchPage-ის რერენდერი შესაბამისად someBigComputation-ის რეკალკულაციაც. რომ არაფერი ვთქვათ იმაზე, რომ ყველა სხვა შვილის რერენდერიც ხდება და არა მხოლოდ Input-ის, როცა მხოლოდ მისი რერენდერიც საკმარისი იქნებოდა.
ასევე გვაქვს კოდის დიზაინის პრობლემა:
ინტუიციურადაც და ლოგიკურადაც Input და მისი სთეითი ჯობია ერთად იყოს. ეს მოგვცემს იმის შესაძლებლობას, რომ ისინი იზოლაციაში განვაახლოთ, ისე რომ საერთოდ არ ვიყოთ დამოკიდებული იმაზე, თუ სად არის გამოყენებული.
function SearchPage() {
let someBigComputation = 0;
for (let i = 0; i < 50_000_000; i++) {
someBigComputation += i;
}
return (
<>
<p>score: {someBigComputation}</p>
<Input />
</>
);
}
function Input() {
const [disabled, setDisabled] = useState(false);
// some primitive input logic
// some primitive input styles
return (
<>
<input disabled={disabled} />
<button onClick={() => setDisabled((d) => !d)}>Toggle Disabled</button>
</>
);
}
disabled-სთეითის ფუნქციონალი Input-თან ერთად ქმნის ახალ კომპონენტს, რასაც SearchPage არენდერებს
მათი გაერთიანების გამო:
- აღარ ხდება მთლიანი
SearchPage-ის რერენდერი როდესაც მხოლოდinput-ისთვის საჭიროdisabledსთეითი იცვლება - მეტად deep გახდება კომპონენტი, პოტენციურად მისი სხვაგან გამოყენება უფრო მარტივია.
caller-ს აღარ დასჭირდება, რომinputდაdisabled-ის ჰენდლერი ერთად გამოიყენოს, რისი დამახსოვრებაც რთულია და სავარაუდოდ დროთა განმავლობაში მივიღებთ შემთხვევებს, როცაinput-ს ზოგან აქვსdisabledსთეითი, ზოგან — არა - deep მოდული თავიდან აგვარიდებს კოდის დუბლიკაციას
- შეგვიძლია
Inputიზოლაციაში გავტესტოთ
ან, იმის მიხედვით რამდენად პრიმიტიულია Input, შეგვიძლია ახალი კომპონენტიც შევქმნათ.
მაგალითად:
function SearchPage() {
let someBigComputation = 0;
for (let i = 0; i < 50_000_000; i++) {
someBigComputation += i;
}
return (
<>
<p>score: {someBigComputation}</p>
<SearchField />
</>
);
}
function SearchField() {
const [disabled, setDisabled] = useState(false);
return (
<>
<Input disabled={disabled} />
<button onClick={() => setDisabled((d) => !d)}>Toggle Disabled</button>
</>
);
}
function Input({ disabled }) {
return <input disabled={disabled} />;
}
Input რჩება პრიმიტივი. ახალი კომპონენტი კი ერთამენთან აკავშირებს ამ Input-ს და disabled-ის ცლვილების ლოგიკას.
Overview
როცა კომპონენტი და მისი ლოგიკა/სთეითი ერთმანეთზეა დამოკიდებული და ცალკე გამოყენების ალბათობა პრაქტიკულად არ არსებობს, ჯობია ერთად იყვნენ (ერთ კომპონენტში ან ახალ wrapper-ში).