文章

重构书单分享应用。refactoring book list sharing app

This web app is initially built with svelte without much third party dependencies. Its features include:

  • fetching book list from api
  • apply filtering by regex
  • select/unselect multiple items in list
  • add/remove books to a book list
  • create book list
  • extensive use of loading spinners
  • user login/logout/register
  • client-side routing

System overview before refactoring

Library.svelte component

This component has many child components. It passes many functions down to its children via setContext mechanism provided by Svelte. These functions are used to config the filtering and to control multi-selection state. For example, in the original post I have cataloged the following

A Filter object is shared between Library.svelte component and a Filter.svelte component. The root component listens for changes to this Filter object and update item listing, Filter.svelte updates this object upon user interactions. Multi-selection helpers i.e. select-all/reverse-selection/clear-selection are implemented in a similar fashion.

Furthermore, this Library.svelte component toggles the AddToBookListModal.svelte component when user choose to add all books in current selection to her book list. However, this AddToBookListModal.svelte should be shown when user choose to add one single book to her book list. These two use cases are essential to this simple app, but it’s not clear to see that they are properly implemented because the hierarchy of components obscures the control follow. Programmer must trace from AddToBookListModal.svelte back to its various clients then to some click event handlers, only then she could conclude that these two use cases are implemented. The diagram below illustrates this process.

flowchart

AddToBookListModal.svelte
--> |search for import statement|L[Library.svelte]
--> |"bind:shouldShow={showBatchAdd}"|B1["on:click={() => (showBatchAdd = true)}"]
--> M1[make sure that this button is the right one]

AddToBookListModal.svelte
--> |search for import statement|AP[ActionPanel.svelte]
--> |"follow bind:shouldShow={shouldShowAddToBookListModal}"|B2["bind:checked={shouldShowAddToBookListModal}"]
--> M2[make sure that this checkbox is the right one]

Besides the mental overhead described above, in that example ActionPanel.svelte is actually also a child of Library.svelte, which means that when Library.svelte is rendered there is actually two instances of AddToBookListModal.svelte in the subtree of Library.svelte. This will an an issue if the duplicated components possess some resources e.g. access tokens.

Refactoring proposal

Use redux through redux-toolkit.

Pocketbase frontend design

pocketbase/ui/src/stores

App wide states, e.g. active collection, are stored here as well as states of toasts, confirmations and error notifications.

pocketbase/ui/src/actions

There is only one file implementing a simple tooltip.

本文由作者按照 CC BY 4.0 进行授权