Heim  >  Artikel  >  Web-Frontend  >  [Übersetzung] Refactoring von React-Komponenten mithilfe benutzerdefinierter Hooks

[Übersetzung] Refactoring von React-Komponenten mithilfe benutzerdefinierter Hooks

青灯夜游
青灯夜游nach vorne
2023-01-17 20:13:511223Durchsuche

[Ü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!

Umgang mit Komplexität

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: useStateuseEffect ,抑或是别的hooks,子组件它们本身也是个函数。因此我们自然可以利用相同的思路来处理函数组件的复杂性问题:通过建立一个新函数,来把即符合公共模式又复杂的代码封装起来

比较常见的处理复杂组件的方式是把它分解成多个子组件。但是这么做可能会让人觉得不自然或是很难准确的去描述这些子组件。这时候我们就可以借助梳理组件的钩子函数的逻辑来发现新的抽象点。

每当我们在组件内看到由useStateuseEffect 或是其他内置钩子函数组成的长长的列表时,我们就应该去考虑是否可以将它们提取到一个自定义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 函数,即无法改变状态。在这个组件我们已经不需要包含修改状态的逻辑,这些逻辑都包含在了useReposDurch die Erstellung einer neuen Funktion können wir komplexen Code kapseln, der einem gemeinsamen Muster entspricht

.

Der üblichere Weg, mit komplexen Komponenten umzugehen, besteht darin, sie in mehrere Unterkomponenten zu zerlegen. Dies könnte sich jedoch unnatürlich anfühlen oder die genaue Beschreibung dieser Unterkomponenten erschweren. Zu diesem Zeitpunkt können wir neue abstrakte Punkte entdecken, indem wir die Logik der Hook-Funktion der Komponente klären.

Immer wenn wir eine lange Liste von useState, 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.

🎜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 mit use beginnt (hier nennen wir sie useRepos). ): 🎜rrreee🎜muss mit use beginnen, da das linter-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🎜 und as const. Der Typhinweis hier dient nur dazu, sicherzustellen, dass die Typinferenz korrekt ist: ein Array mit 3 Elementen, die Typen sind Repo[], 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 hier as const hinzu. Der Unterschied in der ts-Typinferenz spiegelt sich hauptsächlich in der Anzahl der numerischen Elemente wider. Ohne das Hinzufügen von as const ist der abgeleitete Typ (string | boolean | Repo[] | null)[] und die hinzugefügte Typinferenz ist readonly [Repo[ ], boolean, string |. null]. 🎜🎜🎜Wenden Sie den benutzerdefinierten Hook useRepos auf unsere Komponente an und der Code lautet: 🎜rrreee🎜Sie können feststellen, dass wir jetzt keinen setter 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-Funktion useRepos 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 钩子函数。如果钩子更新了,可能使用某种形式的缓存,或者通过轮询或更复杂的方法进行持续更新,那么引用了这个钩子的所有组件都将受益。

当然,提取自定义钩子除了可以方便复用外,还有别的好处。在我们的例子中,所有的useStateuseEffect 都是为了实现同一个功能——就是获取库用户列表,我们把这个看作一个原子功能,那么在一个组件中,包含很多个这样的原子功能也是很常见的。如果我们把这些原子功能的代码都分别提取到不同的自定义钩子函数中,就更容易发现哪些状态在我们修改代码逻辑时要保持同步更新,不容易出现遗漏的情况。除此之外,这么做的好处还有:

  • 越短小的函数越容易看懂
  • 为原子功能命名的能力(如useRepo)
  • 更自然的提供文档说明(每个自定义钩子函数的功能更加内聚单一,这种函数也很容易去写注释)

最后

我们已经了解到React的钩子函数并没有多么神秘,也和其他函数一样很容易就可以创建。我们可以创建自己的领域特定的钩子,进而在整个应用程序中重用。也可以在各种博客或“钩子库”中找到很多预先编写好的通用钩子。这些钩子可以想useStateuseEffect 一样很方便的在我们的项目中应用。Dan Abramov的useInterval钩子就是一个例子,例如你有一个类似于useRepos 的钩子,但是你需要可以轮询更新?那你就可以尝试在你的钩子中使用useInterval

Natürlich erleichtert das Extrahieren benutzerdefinierter Hooks nicht nur die Wiederverwendung, sondern hat auch andere Vorteile. In unserem Beispiel werden alle 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 und useEffect. Der useInterval-Hook von Dan Abramov ist ein Beispiel. Sie haben beispielsweise einen Hook, der useRepos ä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!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen