阅读 108

TagDown 扩展程序开发—— Vue 组件间数据传递

TagDown 是一款开源的书签管理插件, 您可以使用扩展程序浏览新增修改书签,它也支持以不同方式导出书签。

除了常见的书签管理功能,还具有以下特点:

  • 支持 ???? 新增书签,并附加额外的信息,例如 tagsgroups

  • 支持 ???? 导出任意书签为 json 文档

  • 以 ???? 树图的形式浏览层级结构的书签数据

  • 一键打开多个书签,支持在 ???? 标签组内打开书签


弹出页面开发

在进行弹出页面开发中,主要需要解决的是 Vue 组件间数据传递问题。

为了便于代码的复用和维护,将页面分为多个 Vue 组件,例如在弹出页面的浏览状态中,将网页的父级组件 BrowserPage 中主要有 4 大子组件:

  • BrowserHeader.vue

  • BrowserMenu.vueBrowserMenuPin.vue

  • BrowserGrid.vueBrowserTree.vue,以及 BrowserPin.vue

  • BrowserFooter

tagdown-popup-components.png

父子组件间数据传递

在 Vue 中最常见的组件之间的数据传递方式是使用 propemit 实现父子组件间传值:

  • 通过 prop 向子组件传递数据。prop 是在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。

  • 通过 emit 向父组件传递数据。当组件需要修改父层控管的数据(这些数据通过 props 传递进来)时,基于单向数据流原则不能在组件内进行修改,而是需要通过 $emit('eventName', value) 向外抛出自定义的事件,通知父层进行数据修改。

???? 对于输入型的组件,例如在项目中组件 BrowserFooter 包含一个 <input> 元素,可以允许用户输入标签组的名称。可以在组件上使用 v-model,以在父子组件传值的同时实现数据的双向绑定,即数据可以从父层传递给子组件(一般作为变量的初始值),同时可以接收子组件返回的值,以修改绑定的变量。

在父层使用组件时,通过 v-model 实现数据的双向绑定。

<!-- 使用组件,为了简洁的演示,省略显示其他属性的绑定 --> <BrowserFooter   v-model:multi-on-group="multiOnGroup"   v-model:new-group-name="newGroupName" /> 复制代码

在子组件的表单元素中,绑定相应的属性,同时监听相应的事件:

  • 对于 <input type="checkbox"> 复选框,绑定的属性是 checked,需要监听的事件是 change

  • 对于 <input type="text"> 文本输入框,绑定的属性是 value,需要监听的事件是 input

当属性值变更时,需要抛出事件的格式以 update: 为前缀,而属性值通过读取事件对象的相应属性 $event.target 获得

<template>   <!-- 省略其他模板内容 -->   <!-- 单个复选框 -->   <!-- 为了简洁的演示,省略显示其他属性的绑定 -->   <input     id="group"     type="checkbox"     :checked="multiOnGroup"     @change="$emit('update:multiOnGroup', $event.target.checked)"   >   <label     for="group"     class="text-xs text-gray-500"   >在组内打开</label>   <!-- 省略其他模板内容 -->   <!-- 文本输入框 -->   <!-- 为了简洁的演示,省略显示其他属性的绑定 -->   <input     type="text"     placeholder="输入 group 名称"     :value="newGroupName"     @input="$emit('update:newGroupName', $event.target.value)"   >   <!-- 省略其他模板内容 --> </template> <script> export default {   props: {     // ...     multiOnGroup: Boolean,     newGroupName: {       type: String,       default: 'new',     },   },   emits: [     // ...     'update:multiOnGroup',     'update:newGroupName',   ],   // ... } </script> 复制代码

祖孙组件间数据传递

provideinject 实现从祖先组件向其子孙后代组件传值:

  • 父组件用 provide 选项或(在组合式 API 中)方法来提供数据,可以提供响应式数据,例如 ref 对象

  • 子组件用 inject 选项或(在组合式 API 中)方法来接收这个数据

在项目中多个组件中都需要使用 IndexedDB 数据库,在弹出页的 Vue 入口文件 src/popup/main.js 中使用框架 Dexie.js 创建一个数据库实例,再通过 provide 的方式供其他子组件使用。

import Dexie from 'dexie'; import { createApp } from 'vue'; import App from './App.vue'; import '@/styles/tailwind.css'; const app = createApp(App); const db = new Dexie('tagdown'); db.version(1).stores({   bookmark: 'id, *tags, *groups',   share: 'id, share',   star: 'id, star', }); db.open().then((database) => {   app.provide('db', database);   app.mount('#app'); }).catch((err) => {   // Error occurred   console.log(err); }); 复制代码

然后在组件,例如 BrowserPage.vue 中通过 const db = inject('db'); 来接收该数据库实例。

???? 为了便于代码维护,当使用响应式 provide / inject 值时,建议尽可能将对响应式 property 的修改限制在定义 provide 的组件内。如果真的需要在注入数据的组件内部更新 inject 的数据,建议同时 provide 一个方法来负责改变值,这样可以将数据值的设定和数据修改的逻辑代码都依然集中放在祖父组件中,便于后续跟踪和维护。

在祖先组件 src/popup/App.vue 中根据响应式变量 Page 控制弹出页面的状态是 "browser" 还是 "edit",而控制该变量切换的按钮是在后代组件 BrowserFooter.vue 中.

因此在祖先组件提供修改该变量的方法

<script> import { ref, provide } from 'vue'; // ...      export default {   // ...   // provide page state and change the page state function   provide('page', page);   const changePage = (value) => {     page.value = value;   };   provide('changePage', changePage);   // ... } </script> 复制代码

在后代组件接收该方法,并绑定到按钮上

<template>   <!-- 省略其他模板代码 -->   <!-- 为了简洁的演示,省略显示其他属性的绑定 -->   <button     title="add bookmark"     @click="changePage('edit')"   >   <!-- 省略其他模板代码 --> </template> <script> import { ref, watch, inject } from 'vue'; // ... export default {   // ...   const changePage = inject('changePage');    // ...   return {     // ...     changePage   } } </script> 复制代码

兄弟组件间信息传递

对于在层级结构中处于「平行」关系,如果需要进行数据传递,可以考虑使用 Vue 官方推荐的数据状态管理插件 Vuex。

在项目中有一个需求是通过点击组件 BrowserMenu 的按钮,控制兄弟组件 BrowserGrid 的文件夹展开/折叠。

tagdown-toggle-folder.gif

把兄弟组件的父组件作为信息传递的「桥梁」,在父组件监听其中一个子组件抛出的事件,然后接收到自定义事件时,通过模板引用来调用另一个子组件的方法,从而实现兄弟组件信息传递。

将父组件 BrowserPage 作为「桥梁」,连接两个「平行」关系子组件。

<template>   <!-- 省略其他模板代码 -->   <!-- menu -->   <!-- 为了简洁的演示,省略显示其他属性的绑定 -->   <div>     <component       :is="browserMenuComponent"       @unfold-all="unfoldAllHandler"       @fold-all="foldAllHandler"     />   </div>   <!-- 省略其他模板代码 -->   <!-- 为了简洁的演示,省略显示其他属性的绑定 -->   <BrowserGrid     ref="grid"   /> </template> <script> import {   ref, computed, watch, inject, } from 'vue';      export default {   // ...   setup() {     // unfold or fold all folder     const grid = ref(null);  // 引用 BrowserGrid 组件     const unfoldAllHandler = () => {       // ...       grid.value.unfoldAll(); // 调用组件中的方法 unfoldAll()       // ...     };     const foldAllHandler = () => {       // ...       grid.value.foldAll(); // 调用组件中的方法 foldAll()       // ...     };        // ...     return {       // ...       grid,       unfoldAllHandler,       foldAllHandler,     }   } } </script>


作者:Benbinbin
链接:https://juejin.cn/post/7018540193040826382

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐