Heim >Web-Frontend >js-Tutorial >[Übersetzung] Refactoring von React-Komponenten mithilfe benutzerdefinierter Hooks
Ich höre oft Leute über React-Funktionskomponenten sprechen und erwähnen, dass Funktionskomponenten zwangsläufig größer werden und eine komplexere Logik haben. Schließlich haben wir die Komponente in „einer Funktion“ geschrieben, sodass Sie akzeptieren müssen, dass die Komponente erweitert wird und die Funktion weiterhin erweitert wird. Es wird auch in React-Komponenten erwähnt:
Da Funktionskomponenten immer mehr Dinge tun können, werden die Funktionskomponenten in Ihrer Codebasis insgesamt immer länger. [Verwandte Empfehlungen: Redis-Video-Tutorial, Programmiervideo]
Es wird auch erwähnt, dass wir:
Versuchen sollten, das vorzeitige Hinzufügen von Abstraktionen zu vermeiden
Wenn Sie CodeScene verwenden, stellen Sie möglicherweise fest, dass eine Warnung ausgegeben wird Sie, wenn Ihre Funktion zu lang oder komplex ist. Wenn wir dem folgen, was wir zuvor gesagt haben, können wir darüber nachdenken, ob wir CodeScene-bezogene Warnungen umfassender konfigurieren sollten. Natürlich ist dies möglich, aber ich denke, wir sollten dies nicht tun, und wir sollten uns nicht weigern, dem Code viele Abstraktionen hinzuzufügen. Wir können viele Vorteile daraus ziehen, aber die Kosten sind es meistens nicht hoch. Wir können die Gesundheit unseres Codes weiterhin sehr gut halten!
Wir sollten uns darüber im Klaren sein, dass die Funktionskomponente zwar in „einer Funktion“ geschrieben ist, diese Funktion jedoch wie andere Funktionen auch aus vielen anderen Funktionen bestehen kann. Wie useState
, useEffect
oder andere Hooks sind auch Unterkomponenten selbst Funktionen. Daher können wir natürlich dieselbe Idee verwenden, um mit der Komplexität von Funktionskomponenten umzugehen: useState
,useEffect
,抑或是别的hooks,子组件它们本身也是个函数。因此我们自然可以利用相同的思路来处理函数组件的复杂性问题:通过建立一个新函数,来把即符合公共模式又复杂的代码封装起来。
比较常见的处理复杂组件的方式是把它分解成多个子组件。但是这么做可能会让人觉得不自然或是很难准确的去描述这些子组件。这时候我们就可以借助梳理组件的钩子函数的逻辑来发现新的抽象点。
每当我们在组件内看到由useState
、useEffect
或是其他内置钩子函数组成的长长的列表时,我们就应该去考虑是否可以将它们提取到一个自定义hook中去。自定义hook函数是一种可以在其内部使用其他钩子函数的函数,并且创建一个自定义钩子函数也很简单。
如下所示的组件相当于一个看板,用一个列表展示一个用户仓库的数据(想像成和github类似的)。这个组件并不算是个复杂组件,但是它是展示如何应用自定义hook的一个不错的例子。
function Dashboard() { const [repos, setRepos] = useState<Repo[]>([]); const [isLoadingRepos, setIsLoadingRepos] = useState(true); const [repoError, setRepoError] = useState<string | null>(null); useEffect(() => { fetchRepos() .then((p) => setRepos(p)) .catch((err) => setRepoError(err)) .finally(() => setIsLoadingRepos(false)); }, []); return ( <div className="flex gap-2 mb-8"> {isLoadingRepos && <Spinner />} {repoError && <span>{repoError}</span>} {repos.map((r) => ( <RepoCard key={i.name} item={r} /> ))} </div> ); }
我们要把钩子逻辑提取到一个自定义hook中,我们只需要把这些代码复制到一个以use
开头的函数中(在这里我们将其命名为useRepos
):
/** * 请求所有仓库用户列表的hook函数 */ export function useRepos() { const [repos, setRepos] = useState<Repo[]>([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { fetchRepos() .then((p) => setRepos(p)) .catch((err) => setError(err)) .finally(() => setIsLoading(false)); }, []); return [repos, isLoading, error] as const; }
必须用use
开头的原因是linter
插件可以检测到你当前创建的是个钩子函数而不是普通函数,这样插件就可以检查你的钩子函数是否符合正确的自定义钩子的相关规则。
相比提炼之前,提炼后出现的新东西只有返回语句和as const
。这里的类型提示只是为了确保类型推断是正确的:一个包含3个元素的数组,类型分别是Repo[], boolean, string | null
。当然,你可以从钩子函数返回任何你希望返回的东西。
译者注:这里添加
as const
在ts类型推断的区别主要体现在数字元素的个数。不添加as const
,推断的类型为(string | boolean | Repo[] | null)[]
,添加后的类型推断为readonly [Repo[], boolean, string | null]
。
将自定义钩子useRepos
应用在我们的组件中,代码变成了:
function Dashboard() { const [repos, isLoadingRepos, repoError] = useRepos(); return ( <div className="flex gap-2 mb-8"> {isLoadingRepos && <Spinner />} {repoError && <span>{repoError}</span>} {repos.map((i) => ( <RepoCard key={i.name} item={i} /> ))} </div> ); }
可以发现,我们现在在组件内部无法调用任何的setter
函数,即无法改变状态。在这个组件我们已经不需要包含修改状态的逻辑,这些逻辑都包含在了useRepos
Durch die Erstellung einer neuen Funktion können wir komplexen Code kapseln, der einem gemeinsamen Muster entspricht
Immer wenn wir eine lange Liste von
🎜Die unten gezeigte Komponente entspricht einem Dashboard, das eine Liste verwendet, um die Daten eines Benutzerlagers anzuzeigen (stellen Sie sich das ähnlich wie bei Github vor). Diese Komponente ist keine komplexe Komponente, aber sie ist ein gutes Beispiel für die Anwendung benutzerdefinierter Hooks. 🎜rrreee🎜Wir werden die Hook-Logik in einen benutzerdefinierten Hook extrahieren. Wir müssen diesen Code nur in eine Funktion kopieren, die mituseState
,useEffect
oder anderen integrierten Hook-Funktionen in einer Komponente sehen, sollten wir überlegen, ob wir sie in einen benutzerdefinierten Hook extrahieren können . Eine benutzerdefinierte Hook-Funktion ist eine Funktion, die andere darin enthaltene Hook-Funktionen verwenden kann, und das Erstellen einer benutzerdefinierten Hook-Funktion ist ebenfalls einfach.use
beginnt (hier nennen wir sieuseRepos
). ): 🎜rrreee🎜muss mituse
beginnen, da daslinter
-Plug-in erkennen kann, dass es sich bei dem, was Sie gerade erstellen, um eine Hook-Funktion und nicht um eine gewöhnliche Funktion handelt, sodass die Plug-in Sie können überprüfen, ob Ihre Hook-Funktion dem richtigen benutzerdefinierten Hook entsprichtVerwandte Regeln 🎜. 🎜🎜Im Vergleich zu vor der Verfeinerung waren die einzigen neuen Dinge, die nach der Verfeinerung auftauchten, 🎜Rückgabeanweisungen🎜 undas const
. Der Typhinweis hier dient nur dazu, sicherzustellen, dass die Typinferenz korrekt ist: ein Array mit 3 Elementen, die Typen sindRepo[], boolean, string null
. Natürlich können Sie mit der Hook-Funktion alles zurückgeben, was Sie möchten. 🎜🎜🎜Anmerkung des Übersetzers: Fügen Sie hieras const
hinzu. Der Unterschied in der ts-Typinferenz spiegelt sich hauptsächlich in der Anzahl der numerischen Elemente wider. Ohne das Hinzufügen vonas const
ist der abgeleitete Typ(string | boolean | Repo[] | null)[]
und die hinzugefügte Typinferenz istreadonly [Repo[ ], boolean, string |. null]
. 🎜🎜🎜Wenden Sie den benutzerdefinierten HookuseRepos
auf unsere Komponente an und der Code lautet: 🎜rrreee🎜Sie können feststellen, dass wir jetzt keinensetter
innerhalb der Komponente aufrufen können. Funktionen können nicht geändert werden Zustand. In dieser Komponente müssen wir die Logik zum Ändern des Status nicht mehr einbinden. Diese Logik ist in der Hook-FunktionuseRepos
enthalten. Wenn Sie sie wirklich benötigen, können Sie sie natürlich in der Return-Anweisung der Hook-Funktion verfügbar machen. 🎜🎜Was sind die Vorteile davon? In der Dokumentation von React wird Folgendes erwähnt:🎜🎜🎜Durch das Extrahieren benutzerdefinierter Hook-Funktionen kann die Komponentenlogik wiederverwendet werden🎜
Wir können uns einfach vorstellen, dass, wenn andere Komponenten in dieser Anwendung auch die Benutzerliste im Warehouse anzeigen müssen, diese Komponente lediglich die Hook-Funktion useRepos
importieren muss. Wenn der Hook aktualisiert wird, möglicherweise mithilfe einer Form von Caching oder einer kontinuierlichen Aktualisierung über Abfragen oder einen komplexeren Ansatz, profitieren alle Komponenten, die auf diesen Hook verweisen. useRepos
钩子函数。如果钩子更新了,可能使用某种形式的缓存,或者通过轮询或更复杂的方法进行持续更新,那么引用了这个钩子的所有组件都将受益。
当然,提取自定义钩子除了可以方便复用外,还有别的好处。在我们的例子中,所有的useState
和useEffect
都是为了实现同一个功能——就是获取库用户列表,我们把这个看作一个原子功能,那么在一个组件中,包含很多个这样的原子功能也是很常见的。如果我们把这些原子功能的代码都分别提取到不同的自定义钩子函数中,就更容易发现哪些状态在我们修改代码逻辑时要保持同步更新,不容易出现遗漏的情况。除此之外,这么做的好处还有:
我们已经了解到React的钩子函数并没有多么神秘,也和其他函数一样很容易就可以创建。我们可以创建自己的领域特定的钩子,进而在整个应用程序中重用。也可以在各种博客或“钩子库”中找到很多预先编写好的通用钩子。这些钩子可以想useState
和useEffect
一样很方便的在我们的项目中应用。Dan Abramov的useInterval
钩子就是一个例子,例如你有一个类似于useRepos
的钩子,但是你需要可以轮询更新?那你就可以尝试在你的钩子中使用useInterval
useState
und useEffect
verwendet, um die gleiche Funktion zu erreichen – um die Bibliotheksbenutzerliste zu erhalten. Wir betrachten dies als eine atomare Funktion, die ebenfalls üblich ist dass eine Komponente viele solcher atomaren Funktionen enthält. Wenn wir die Codes dieser atomaren Funktionen in verschiedene benutzerdefinierte Hook-Funktionen extrahieren, ist es einfacher herauszufinden, welche Zustände synchron aktualisiert werden müssen, wenn wir die Codelogik ändern, wodurch die Wahrscheinlichkeit geringer ist, dass sie übersehen werden. Darüber hinaus bietet dies folgende Vorteile:
- Kürzere Funktionen sind leichter zu verstehen
- Die Möglichkeit, atomare Funktionen zu benennen (z. B. useRepo)
Dokumentation bereitstellen natürlicher (die Funktion jeder benutzerdefinierten Hook-Funktion ist zusammenhängender und einzelner, und es ist einfacher, Kommentare für diese Art von Funktion zu schreiben)Endlich
Wir haben gelernt, dass die Hook-Funktion von React nicht so mysteriös ist und wie andere Funktionen erstellt werden kann. Wir können unsere eigenen domänenspezifischen Hooks erstellen und sie in der gesamten Anwendung wiederverwenden. Sie können auch viele vorgefertigte Allzweck-Hooks in verschiedenen Blogs oder „Hook-Bibliotheken“ finden. Diese Hooks können einfach in unseren Projekten verwendet werden, genau wie
useState
unduseEffect
. DeruseInterval
-Hook von Dan Abramov ist ein Beispiel. Sie haben beispielsweise einen Hook, deruseRepos
ähnelt, aber Sie müssen in der Lage sein, Updates abzufragen? Dann können Sie versuchen,useInterval
in Ihrem Hook zu verwenden.
Englische Originaladresse: https://codescene.com/engineering-blog/refactoring-components-in-react-with-custom-hooks
[Empfohlenes Lernen: 🎜Javascript-Video-Tutorial🎜]🎜Das obige ist der detaillierte Inhalt von[Übersetzung] Refactoring von React-Komponenten mithilfe benutzerdefinierter Hooks. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!