Vue 3 - The Composition API - Reusability (Part 2)
Vue's Options API constrains the reusability of logic across multiple components. Patterns involving mixins and higher-order components (HOCs) have been established to create reusable blocks of code and consolidate repeated state and functionality. However, each pattern has a drawback: With Vue 3's Composition API , component logic can be extracted, shared and reused amongst multiple components. Exposing Vue's core capabilities (reactivity and lifecycle hooks) as standalone, globally available functions allows developers to create custom hooks that are decoupled from any UI, but yet, can be consumed by any component. This can greatly improve the maintainability and flexibility of a large application composed of hundreds of components. Let's walkthrough an example demo to demonstrate the simplicity of writing reusable code with the Composition API. In this CodeSandbox demo , clicking the "Open Modal" button pops open a basic accessible modal that displays a list of links to sections in the official Vue.js documentation site. The <Modal /> component instance contains a setup option that houses all of its data, methods, lifecycle hooks, etc. ( src/components/Modal.vue ) Here's a brief overview of the code within the setup option: In a web application, a modal acts as a subordinate window to the browser window. Because modals can be used to communicate information to a user, they can be categorized as dialogs . Another component that behaves similarly to modals is toast notifications , which briefly notify users of certain events before disappearing. If you are not familiar with toast notifications, then have a look at an easy-to-use toast notifications library, toastr . Using the Composition API, we can implement a toast notification component that uses the same accessibility and closing logic as our modal component. Let's refactor this logic into a separate hook that can be used in both components. First, create a new directory, hooks , within the src directory. Then, create a new file useDialog.js within this new directory. Inside of src/hooks/useDialog.js , create a function called useDialog that declares a ref, isDialogOpened , and has two methods responsible for updating this ref's value, openDialog and closeDialog . To allow a component to access these functions and this ref, return them in an object. ( src/hooks/useDialog.js ) The useDialog function is created outside of the context of components as a standalone piece of functionality. Notice how this logic resembles the logic within the setup option of the <App /> component: Let's refactor this logic by replacing it with a call to the useDialog function. ( src/App.vue ) Since isDialogOpened is reactive, any updates to this value will be reflected wherever it is used in the component template. Examining at the <Modal /> component, we should extract out the methods and lifecycle hooks for re-use in a <ToastNotification /> component. Because the close method relies on the context object, the handleKeydown method relies on the modalRef ref and the onBeforeMount lifecycle hook relies on the prevRef ref, all of these values need to passed to the useDialog method when we migrate these methods and lifecycle hooks. ( src/hooks/useDialog.js ) Because the call to the useDialog function within the <App /> component does not need to pass prevRef , dialogRef (formerly modalRef ) and context , we should isolate this logic within a function initDialog , which will be exposed to the <Modal /> component and accept these arguments when called. ( src/hooks/useDialog.js ) We will also need to slightly adjust the <Modal /> component's template to call emitClose when the <button class="modal__close-btn" /> element is clicked. ( src/components/Modal.vue ) Because modalRef and prevRef are reactive, their updated values will be available to the methods and lifecycle hooks within useDialog whenever they are called. Try out these changes! When you click on the "Open Modal" button, the modal pops open, and focus is trapped within it. Once the modal is closed, the focus resumes back to the "Open Modal" button. Let's create a simple toast notification component. Add a new file to the src/components directory named ToastNotification.vue . Plug-in the same logic that is now used in the setup option of the <Modal /> component. ( src/components/ToastNotification.vue ) Let's add an example toast notification and a button that pops open this notification when clicked to the <App /> component. ( src/App.vue ) And with little effort (after the initial refactoring of functionality to useDialog ), we have successfully introduced a new component to our application. Additionally, these same steps can be repeated for other dialog-like components. If you would like to view the final version of this CodeSandbox demo, then click here . Composing and isolating functionality within a separate function makes it incredibly easy to integrate and share this logic amongst existing/new components. The Composition API's flexibility and compositional nature allows us to structure and group our logic based on high-level component features rather than low-level component details (in the case of the Options API). Try rewriting React functional components that use hooks with Vue 3's Composition API. If you want to learn more about Vue 3, then check out Fullstack Vue :