Vue - vue 前端框架开发技术
vue 介绍
vue 是一种提供一套声明式的,组件化的编程模型来构建前端界面的js前端框架。 vue 目前主流采用vue3,提供组合式API和选项式api,vue2 在2023年12月份以后不再进行维护,它以选项式的api进行应用开发。
基本使用
- 创建应用
import { createApp } from 'vue'
import { createPinia } from 'pinia'
// 导入根组件
import App from './App.vue'
// 传入根组件创建应用
const app = createApp(App);
// 注册pinia插件
app.use(createPinia())
// 将应用挂载到容器上
app.mount('#app')
- 创建组件
- 模版语法
<template>
<!-- 文本插值 -->
<span>Message: {{ msg }}</span>
<!-- 属性绑定 -->
<div :id="dynamicId"></div>
<!-- 一次绑定多个属性 -->
<div v-bind="objectOfAttrs" :isDisabled="isDisabled"></div>
<!-- 绑定class-->
<div :class="{ active: isActive }"></div>
<div :class="[activeClass, errorClass]"></div>
<!-- 绑定内联样式-->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<div :style="styleObject"></div>
<div :style="[baseStyles, overridingStyles]"></div>
<!-- 条件渲染指令-->
<div v-if="type === 'A'"> A </div>
<div v-else-if="type === 'B'"> B </div>
<div v-else> Not A/B </div>
<!-- 列表渲染 -->
<li v-for="(item, index) in items">
{{ index }} - {{ item.message }}
</li>
<!-- 渲染对象 -->
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
<!-- 事件处理 -->
<button @click="greet">Greet</button>
<!-- 事件处理修饰符 -->
<a @click.once="greet"></a>
<!-- 表单输入双向绑定 -->
<input v-model="message" placeholder="edit me" />
<textarea v-model="message" placeholder="add multiple lines"></textarea>
<!-- 复选框 -->
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<!-- 单选框 -->
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
<!-- 下拉选择 -->
<select v-model="selected">
<option v-for="option in options" :value="option.value">
{{ option.text }}
</option>
</select>
<!-- 修饰符lazy, input事件变为change事件触发-->
<input v-model.lazy="msg" />
<!-- 修饰符number, 输入内容自动转位数字-->
<input v-model.number="age" />
<!-- 修饰符trim, 自动去除输入内容两端的空格-->
<input v-model.trim="msg" />
<!-- 访问模版引用-->
<input ref="input" />
</template>
- js 语法
<script setup>
import { ref, computed, watch, watchEffect, watchPostEffect, onMounted } from 'vue'
/** 定义响应式属性 */
// 通过ref定义响应式变量
const msg = ref('this is a message');
// 动态属性变量定义
const dynamicId = ref(1);
// 一次绑定多个属性
const objectOfAttrs = ref({
id: 'container',
class: 'wrapper'
});
// 使用计算属性
const isDisabled = computed(() => {
if(dynamicId.value > 1) return true;
else return false;
});
/** 绑定样式属性 */
// 定义样式属性
const activeClass = ref('active');
const errorClass = ref('text-danger');
// 定义内联样式属性值
const activeColor = ref('red');
const fontSize = ref(30);
const styleObject = ref({
color: 'red',
fontSize: '13px'
})
// 条件渲染
const type = ref('A');
// 列表渲染数据
const items = ref([{ message: 'Foo' }, { message: 'Bar' }]);
// 对象渲染数据
const myObject = ref({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
});
// 事件处理器
function greet(event) {
// `event` 是 DOM 原生事件
if (event) {
alert(event.target.tagName)
}
}
/** 表单数据双向绑定 */
// 复选框
const checkedNames = ref([]);
// 单选
const picked = ref('One');
// 下拉选择
const selected = ref('A')
const options = ref([
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
])
/** 注册生命周期钩子 */
// 初始化完成时执行
onMounted(() => { })
// 更新组件时执行
onUpdated(() => {});
// 组件卸载时执行
onUnmounted(() => {});
/** 侦听数据源, 执行副作用 */
const x = ref(0);
watch(x, (newX) => {
console.log(`x is ${newX}`)
});
// 立即执行,且当 `source` 改变时再次执行
watch(
source,
(newValue, oldValue) => {
},
{ immediate: true }
);
// 自动跟踪响应式依赖,不需要列举
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
// vue组件更新后访问dom
watchPostEffect(() => { })
/** 访问模版引用 */
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
父组件传递数据到子组件 (Props, Attributes)
- props
- 父组件
<template>
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
</template>
<script setup>
const posts = ref([
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
])
</script>
- 子组件
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>
<template>
<h4>{{ title }}</h4>
</template>
- Attributes (一般用于class,style等属性的传递)
- 透传到子组件的根元素上
// 父组件
<MyButton class="large" />
// 子组件
<!-- <MyButton> 的模板 -->
<button class="btn">click me</button>
// 最后渲染的结果
<button class="btn large">click me</button>
- 透传到子组件非根元素 (defineOptions 宏方法)
/** 父组件 */
<MyButton class="large" />
/** 子组件 */
<script setup>
defineOptions({
inheritAttrs: false
});
</script>
<template>
<div class="btn-wrapper">
<button class="btn" v-bind="$attrs">click me</button>
</div>
</template>
父组件传递模版内容到子组件 (插槽机制)
- 子组件
<div class="container">
<header>
<slot name="header" message="hello"></slot>
</header>
<main>
<slot name="body"></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
- 父组件
<BaseLayout>
<template #header="{message}">
<h1>Here might be a page title</h1>
<p>{{message}}</p>
</template>
<template #body>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
子组件执行父组件中的方法 (触发事件)
- 父组件
<template>
<div :style="{ fontSize: postFontSize + 'em' }">
<BlogPost
...
@enlarge-text="enlargeText"
/>
</div>
</template>
<script setup>
const postFontSize = ref(1);
function enlargeText(){
postFontSize.value += 0.1;
};
</script>
- 子组件
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="enlargeText">Enlarge text</button>
</div>
</template>
<script setup>
const emit = defineEmits(['enlarge-text'])
const enlargeText = () => emit('enlarge-text');
</script>
父组件执行子组件中的方法 (defineExpose宏方法)
- 父组件
<template>
<Child ref="child" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'
const child = ref(null)
onMounted(() => {
// child.value 是 <Child /> 组件的实例
child.value.add(1);
})
</script>
- 子组件
<script setup>
import { ref } from 'vue'
const a = ref(1);
// 通过defineExpose 暴露数据和方法给父组件
defineExpose({
a,
b: (number) => {
return a.value + number;
}
})
</script>
父子组件之间的数据双向传递 (defineModel宏方法)
- 父组件
<MyComponent v-model:title="bookTitle" />
- 子组件
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title');
</script>
<template>
<input type="text" v-model="title" />
</template>
祖先组件为后代组件传递数据 (依赖注入)
- 祖先组件提供数据
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
- 后代组件注入数据
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
组件之间共享数据的方式
- pinia
- 在全局注册pinia
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
- 定义store
import { defineStore } from 'pinia'
export const useTodos = defineStore('todos', {
// 类似于data
state: () => ({
todos: [],
}),
// 类似于computed计算属性
getters: {
finishedTodos(state) {
return state.todos.filter((todo) => todo.isFinished)
}
},
// 类似于vue2中的methods
actions: {
addTodo(text) {
this.todos.push({ text })
},
},
})
- 使用store中的数据和方法
<script setup>
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量
const store = useCounterStore()
</script>
多个组件之间进行切换(动态组件)
<template>
<component :is="tabs[currentTab]"></component>
</template>
<script setup>
import com1 from './components/com1.vue'
import com2 from './components/com2.vue'
import com3 from './components/com3.vue'
const tabs = {
'tab1': com1,
'tab2': com2,
'tab3': com3,
};
const currentTab = ref('tab1');
</script>
动态异步组件
<script setup>
import { defineAsyncComponent } from 'vue';
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
</script>
<template>
<AsyncComp />
</template>
vue中代码重用的方式
- 单文件组件 (*.vue)
可以将项目中多处具有相同UI以及处理逻辑的业务模块,封装成一个单文件组件(SFC)
<template>
<button @click="count++">Count is: {{ count }}</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<style scoped lang="scss">
button {
font-weight: bold;
}
</style>
- 组合式函数
组合式函数是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数, 在vue应用开发中,
可以将单文件组件中, 重复的状态逻辑处理提取为组合式函数,可以在多个单文件组件中进行重用。
/** 创建组合式函数 */
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
}
/** 在组件中使用组合式函数 */
<script setup>
import { useMouse } from './mouse.js'
const { x, y } = useMouse()
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
- 自定义指令
自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑,一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。
在组件中定义指令
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
在应用层全局注册指令
// 在全局注册指令
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})
// 在组件中使用
<div v-color="color"></div>
编写插件(增强vue全局功能)
- 编写插件代码
// plugins/i18n.js
export default {
install: (app, options) => {
// 将options提供给整个应用可以进行注入访问
app.provide('i18n', options);
// 注入一个全局可用的 $translate() 方法
app.config.globalProperties.$translate = (key) => {
// 获取 `options` 对象的深层属性
// 使用 `key` 作为索引
return key.split('.').reduce((o, i) => {
if (o) return o[i]
}, options)
}
}
}
- 全局注册插件
import { createApp } from 'vue'
import i18nPlugin from './plugins/i18n'
const app = createApp({})
app.use(i18nPlugin, {
greetings: {
hello: 'Bonjour!'
}
});
app.mount('#app');
- 在组件中使用
<template>
<h1>{{ $translate('greetings.hello') }}</h1>
</template>
<script setup>
import { inject } from 'vue'
const i18n = inject('i18n');
console.log(i18n.greetings.hello)
</script>