组合式函数
在vue3中,不同的组件有时候会存在一些相同的逻辑和,比如一些特定的字符串转换操作,数据的清洗。这个时候我们通常会将这一部分的逻辑封装成一个函数或者工具类,以便后续开发的对齐进行一个复用。
以下是一个简单的日期格式化函数示例
1 2 3 4 5
| export function formatDate(date, format) { const options = {year: 'numeric', month: '2-digit', day: '2-digit'}; return new Date(date).toLocaleDateString(undefined, options); }
|
在vue组件中使用这个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| //vue
<template> <div>{{formattedDate}}</div> </template>
<script> import {formatDate} from './dateFormatter';
export default {
data() { return { date: new Date(), }; }, computed: { formattedDate() { return formatDate(this.date, 'YYYY-MM-DD'); }, }, }; </script>
|
理解无状态逻辑与有状态逻辑
- 无状态逻辑:formatDate 函数是无状态的,因为它不依赖于外部状态,只根据传入的日期和格式返回格式化后的日期字符串。它的行为是确定的,给定相同的输入,总是会返回相同的输出。
- 有状态逻辑:如果函数或组件需要管理和维护随时间变化的状态(例如,跟踪用户输入、管理组件的内部状态等),则为有状态逻辑。
vue3能更好的复用有状态的逻辑
- 响应式核心api的抽离,诸如ref(),computed(),watch(),生命周期钩子等核心api的抽离,对于复用有状态的逻辑变得更加的容易。
- 组合式api允许我们在组合函数中使用生命周期钩子。
这种方式赋予了组件更加细粒度的控制。而在vue2里面要达到这样的效果是比较麻烦的,而且代码难以去维护。
分析与实践
通过对以下效果的分析,基于vue3组织代码逻辑并且编码
左边与右边把它们分别看成独立的组件,因为呈现的布局与样式都不一样,但是都是基于同一份数据进行显示的。
启动项目
采用基于 vite 的脚手架 crate vue ,这里我使用的包管理工具是 yarn
- 删除无用的静态资源文件与模板组件,并且创建对应的文件,整体的目录目录如下
1 2 3 4 5 6 7 8
| src ├── App.vue ├── components │ ├── Bar1.vue │ └── Bar2.vue ├── main.js └── compositions └── useGdp.js
|
相关文件内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| <template> <h1>2024 GDP Top 5</h1> <div class="container"> <Bar1 :gdp="gdp"/> <Bar2 :gdp="gdp"/> </div> <div class="controls"> <div class="item" v-for="item in gdp" :key="item.country"> <label>{{ item.country }}</label> <input type="number" step="0.001" min="0" v-model="item.value"/> </div> </div> </template>
<script> import {ref} from "vue"; import Bar1 from "@/components/Bar1.vue"; import Bar2 from "@/components/Bar2.vue";
export default { components: {Bar2, Bar1}, setup() { const gdp = ref([])
async function fetchGDP() { gdp.value = await fetch("/api/gdp.json").then(res => res.json()) console.log(gdp.value) }
fetchGDP() return { gdp } }, } </script>
<style scoped> .container { display: flex; justify-content: center; flex-wrap: wrap; }
.controls { margin: 1em; display: flex; justify-content: center; flex-wrap: wrap; }
.item { margin: 1em; }
.item label { margin: 0 1em; }
.item input { height: 26px; font-size: 14px; }
h1 { text-align: center; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| <template> <div class="bar1"> <div class="item" v-for="item in bars" :key="item.country"> <label>{{ item.country }}</label> <div class="bar" :style="{background: item.color,width: item.size + 'px'}"></div> <div class="value">{{ item.value }}万亿</div> </div> </div> </template>
<script> import useGdp from "@/composition/useGdp.js"; import {computed} from "vue";
export default { props: ["gdp"], setup(props) { const gpd = computed(() => props.gdp) return { ...useGdp(gpd, 400) } } }
</script>
<style scoped> .bar1 { width: 500px; box-sizing: border-box; margin: 3em; border-left: 1px solid #333; }
.item { display: flex; height: 35px; line-height: 35px; margin: 1em 0; position: relative; }
.bar { width: 100px; height: 100%; margin-right: 1em; flex: 0 0 auto; }
.item label { position: absolute; left: -50px; }
.value { flex: 0 0 auto; }
</style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| <template> <div class="bar2"> <div class="item" v-for="item in bars" :key="item.country"> <label>{{ item.country }}</label> <div class="bar" :style="{background: item.color,width: item.size + 'px'}"></div> <div class="value">{{ item.value }}万亿</div> </div> </div> </template>
<script> import useGdp from "@/composition/useGdp.js"; import {computed} from "vue";
export default { props: ["gdp"], setup(props) { const gpd = computed(() => props.gdp) return { ...useGdp(gpd, 400) } } }
</script>
<style scoped> .bar2 { width: 600px; box-sizing: border-box; margin: 3em; position: relative; }
.bar2::before { content: ""; display: block; width: 1px; height: 100%; position: absolute; background: #666; left: 50%; }
.item { display: flex; height: 35px; line-height: 35px; margin: 1em 0; position: relative; justify-content: center; }
.bar { width: 100px; height: 100%; margin-right: 1em; flex: 0 0 auto; }
.item label { transform: translateX(-50%); text-shadow: 0 0 3px rgba(0, 0, 0, 0.5); }
.item .value { color: #2c3e50; } </style>
|
这里使用到了一个第三方库,gsap,用于动画效果的展示。
这里简要说明一下,bars和barsTarget的关系,bars是一个ref,barsTarget是一个computed,当barsTarget变化的时候,我们通过watch监听barsTarget的变化,然后将bars变化到barsTarget,这里使用到了gsap,让数据逐步变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import {computed, ref, watch} from "vue"; import {gsap} from "gsap";
export default function useGdp(gdpRef, maxSize) { const colors = ["#334552", "#B34335", "#6E9FA5", "#A2C3AC", "#C8846C"] const maxValue = computed(() => { if (gdpRef.value.length) { return Math.max(...gdpRef.value.map(it => it.value)) } }) const bars = ref([]) const barsTarget = computed(() => { console.log("computed") return gdpRef.value.map((it, i) => ({ ...it, color: colors[i % colors.length], size: (it.value / maxValue.value) * maxSize })) }) watch(barsTarget, () => { barsTarget.value.forEach((item, i) => { bars.value[i] = { ...barsTarget.value[i], size: 0, value: 0, } gsap.to(bars.value[i], { ...barsTarget.value[i], duration: 1, }) })
}, {deep: true})
return { bars } }
|
1 2 3 4 5
| import {createApp} from 'vue' import App from './App.vue'
createApp(App).mount('#app')
|
代理配置,基于vite的代理配置
通过代理配置,我们可以将请求代理到本地的json文件,这样我们就可以在本地数据的基础上进行开发。对于这部分的知识可以在官方文档中进行了解vite代理配置
本站也有相关的学习记录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue'
export default defineConfig({ plugins: [ vue(), ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, server:{ proxy:{ '/api': { target: 'http://localhost:5173/src/api/', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), }, } } })
|