【山竹记账前端-vue】9.使用 v-model 实现 Tabs 组件
大纲链接 §
[toc]
1. 新增 item 相关路由 ⇧
@/router/routes.ts
|
|
2. 制作 MainLayout 组件,重构 StartAccountsView,完成 ItemsView 框架 ⇧
抽离相似的顶部导航部分,封装布局组件,使用插槽的形式传入不同部分的内容:
@/components/layout/MainLayout.tsx
|
|
重构到
@/views/StartAccountsView.tsx
|
|
@/views/ItemsView.tsx
|
|
完成 ItemsView 框架
@/views/ItemList.tsx
|
|
注意这里写法格式有一个坑:
1 2 3 4 5 6 7 8 9 10 11 12 13export const ItemList = defineComponent({ name: 'ItemList', setup(/* _props, _ctx */) { return () => ( <MainLayout> {{ title: () => '记一笔', icon: () => <SvgIcon name="back-arrow" class="w-2em h-2em" />, default: () => <div>main</div>, }} </MainLayout> ) }, })如果组件标签
<MainLayout> {{/* ... */}}之间有空格,编译会被认为是两个子节点的数组,一个空格文本节点 加上一个对象,插槽不接受数组导致页面显示内容显示为空,见 路由命名视图
导致警告报错:
[VUE_ROUTER_R0103] The route named "item" has a child without a name, an empty path, and no children.Using that name won't render the empty path child, so this is probably a mistake.
仅在
JSX/TSX组件中出现,区别于SFC组件中的{{}}模板插值语法
3. 设计 Tabs 接口 ⇧
交互:
- 当点击支出(默认显示),下方内容为支出相关记账项目
- 当点击收入,下方内容为收入相关记账项目
- 最下方的键盘输入区域则不随tab切换,作为表单输入
ItemList.tsx
|
|
- 需要保证
SwitchTabs子组件必须是ItemTab,否则报错 ItemTab接受默认插槽内容,作为 tab 对应的内容显示
@/components/tab/SwitchTabs.tsx
|
|
@/components/tab/ItemTab.tsx
|
|
SwitchTabs通过_ctx.slots?.default?.()获取子组件,相当于react的children- 插槽内容可能为多个
VNode元素的数组 - 打印出查看
- 插槽内容可能为多个
- 可以对比
_ctx.slots?.default?.()?.[0]?.type === ItemTab来判断子组件类型是否为ItemTab - 遍历判断数组中所有
VNode元素的type是否为ItemTab,如果不是则抛出异常 - 注意插件
_ctx.slots?.default?.()必须写在setup的返回函数中,否则会报警告:installHook.js:1 [Vue warn]: Slot "default" invoked outside of the render function: this will not track dependencies used in the slot.Invoke the slot function inside the render function instead.
@/components/tab/SwitchTabs.tsx
|
|
4. 实现 Tabs 功能 ⇧
SwitchTabs通过默认插槽获取子组件ItemTab,并通过props传入selected属性,来控制当前选中的 tabprops.selected和 遍历的slot.props?.tabName进行比较,来判断当前 tab 是否被选中,添加selected样式- 添加
onUpdateSelected事件处理函数,更新selected的值,来实现 tab 切换功能- 通过
props中的onUpdateSelected回调函数,执行时传入当前 tab 的名称,来更新父组件的状态
- 通过
@/components/tab/SwitchTabs.tsx
|
|
@/views/ItemList.tsx
|
|
参考
5. 在 TSX 里使用 v-model ⇧
使用
v-model语法糖,替换”将父组件状态变更函数传入子组件,再由子组件执行该函数并传参”的写法:@/views/ItemList.tsx
- 父组件 改为
<SwitchTabs selected={kind.value} v-model:selected={kind.value}>,并取出多余的onUpdateSeleted 子组件
_ctx.emit('update:selected', slot?.props?.tabName)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24// import ... export const ItemList = defineComponent({ name: 'ItemList', setup(/* _props, _ctx */) { const kind = ref('支出') return () => ( <MainLayout> {{ title: () => '记一笔', icon: () => <SvgIcon name="back-arrow" class="w-2em h-2em" />, default: () => ( <> <SwitchTabs selected={kind.value} v-model:selected={kind.value}> <ItemTab tabId="expenses" tabName="支出">支出 icon 列表</ItemTab> <ItemTab tabId="incomes" tabName="收入">收入 icon 列表</ItemTab> </SwitchTabs> </> ), }} </MainLayout> ) }, })
@/components/tab/SwitchTabs.tsx
|
|
参考
6. 实现渲染tab内容,完成 Tabs 组件 ⇧
让
SwitchTabs显示插槽中子组件的默认插槽内容,且只显示当前选中的 tab 内容
- 查看
SwitchTabs中默认插槽VNode数组每一项的children.default,即ItemTab的默认插槽内容 - 直接渲染对应的
VNode即可defaultSlotsArray.find(slot => slot.props?.tabName === _props.selected)
@/components/tab/SwitchTabs.tsx
|
|
完善样式
@/assets/styles/vars.scss
|
|
@/components/tab/SwitchTabs.tsx
|
|
- 使用
unocss的transformerVariantGroup配置 来表示after:()伪元素语法中多个样式 - 给选中的 tab 添加下划线样式
after:(content-[""] block absolute bottom-0 left-0 w-full h-4px bg-[var(--tabs-indicator-bg)])
参考文章
相关文章
- 无
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名