1.Vue3 项目搭建
开发环境
- 编辑器: VSCode
- 插件: Volar
- 浏览器: Chrome
- 开发者工具: Vue DevTools 插件
项目构建
vue-cli创建
以vite构建
构建一个以vite为构建工具的vue3项目, 注意node的版本要在15以上
npm create vite@latest
安装常用工具
- 安装路由
npm install vue-router@next
项目结构
src/
│
├── api/ # API 请求封装
├── pages/ # 页面组件
├── components/ # 公共组件
├── router/ # 路由管理
├── store/ # Vuex 状态管理
├── utils/ # 工具函数
./router/index
import {
createRouter,
createWebHashHistory,
} from 'vue-router'
import Home from '../pages/home.vue'
import About from '../pages/about.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
createApp(App).use(router).mount('#app')
2.常用方法
Composition API
use 函数
使用Composition API,我们可以通过use函数创建可复用的逻辑块。
例子
import { ref } from 'vue';
function useCounter() {
const count = ref(0);
function increment() {
count.value++;
}
return { count, increment };
}
优点
- 可以把逻辑拆分
- 拆分的hook依然支持响应式
script & setup
普通写法
<script >
import { ref } from "vue";
export default {
setup() {
let count = ref(1)
return {
count
}
}
}
</script>
简化代码
- 不需要导出,把setup内定义的变量自动导出
<script setup>
import { ref } from "vue";
let count = ref(1)
</script>
style
<style scope>
局部样式定义
v-bind定义样式
<template>
<button class="active" @click="colorChange">切换颜色</button>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
let color = ref('red')
function colorChange(){
console.log("xxx")
color.value = 'green'
}
</script>
<style scoped>
.active {
color: v-bind(color);
}
</style>
3.响应式
响应式原理
当数据a变化时候,关联的数据doubleA也会跟着改变
let a = 1
let doubleA = a *2;
a++
doubleA = a *2;
每次都要主动设置一下 a* 2的逻辑
- 响应式解决的就是自动处理doubleA = a * 2的逻辑
vue3中3种响应式处理
1. Object.definepropty的get和set方法
let a = 1
let doubleA = 1
let obj = {a:1}
Object.defineProperty(obj,"a",{
get(){
return doubleA
},
set(val){
a = val
doubleA = a * 2
}
})
console.log(a)
console.log(doubleA)
obj.a = 2
console.log(a)
console.log(doubleA)
- 缺点
- 不支持delete关键字
- 数据组场景的push pop等等都不支持
- vue2中的实现
2. proxy (reactive)
```js
let doubleA = 1
let obj = {a:1}
let proxy = new Proxy(obj,{
get(target,prop){
return target[prop]
},
set(target,prop,val){
target[prop] = val
if(prop == 'a') {
doubleA = val * 2
}
},
deleteProperty(target,prop){
delete target[prop]
if(prop == 'a') {
doubleA = 0
}
},
})
proxy.a = 2 //调用和赋值必须使用proxy对象处理,不能再使用原来的对象
console.log(doubleA)
proxy.a = 4
console.log(doubleA)
delete proxy.a
console.log(doubleA)
```
- vue3中reactive实现
3. value setter (ref)
- vue3中ref的实现,嵌套的数据用的是reactive
let doubleA = 1 let obj = { _a:1, get a(){ return this._a; }, set a(val){ this._a = val doubleA = val * 2 } } obj.a = 2 console.log("xx",obj.a) console.log("xx",doubleA) obj.a = 4 console.log("xx",obj.a) console.log("xx",doubleA)
封装成本地存储
- useStorage
import { ref, computed ,watchEffect} from 'vue'
export default function(name,nowList = []) {
let data = ref( nowList || JSON.parse(localStorage.getItem(name)||'[]'))
watchEffect( () => {
localStorage.setItem(name,JSON.stringify(data.value))
})
return data
}
使用
let myList = useStorage('todoList' )
useFaivcon
import { ref, computed ,watch} from 'vue'
export default function(url){
let favicon = ref(url)
let updateIcon = (url) => {
document.querySelectorAll("link[rel*=icon]").forEach((o) => o.href = url)
}
watch(favicon,(o) => {
updateIcon(o)
})
return favicon
}
使用
import useFavicon from './useFavicon.js'
let fa = useFavicon()
function test(){
fa.value = 'http://www.baidu.com/img/flexible/logo/pc/result.png'
}
- 第三方-Vueuse 工具库
npm install @vueuse/core
useFullScreen
<template>
<button @click="toggle">全屏/还原</button>
<div ref="el" style="width: 200px;height: 200px; background-color: red;" ></div>
</template>
<script setup lang="ts">
const el = ref(null)
const { isFullscreen, enter, exit, toggle } = useFullscreen(el)
</script>
4.组件化
组件分类
- 通用组件
- 按钮
- 表单
- 弹框
- 业务组件
- 购物车
- 登录页面
- 注册
评分组件
简单字符版
```js
<template>
<div>
{{rate}}
</div>
</template>
<script setup lang="ts">
import { defineProps,computed} from 'vue'
let props = defineProps({
value:Number
})
let rate = computed( () => {
return "★★★★★☆☆☆☆☆".slice(5-props.value,10-props.value)
//由于选择占用前5个,5-选中 ,减越多,起点月底,可现实越多
//没选中占用后5个, 10-选中,减越少,可现实越多
})
</script>
<style scoped>
</style>
```
使用
<Rate value="1"></Rate> <Rate value="2"></Rate>
import Rate from './Rate.vue'
复杂版
- emits 定义和调用
//子组件定义
let emits = defineEmits('rateUpdate') //定义
emits('rateUpdate',num) //调用
//父组件设置
<Rate2 @rateUpdate="rateUpdate" value="1"></Rate2>
- v-model 传值
//子组件
let props = defineProps({
modelValue: Number,
})
let emits = defineEmits('update:modelValue',)
//父组件
<Rate2 v-model="myVal"></Rate2>
const myVal = ref(1)
具体代码
父组件
<script setup>
import { ref } from 'vue'
import Rate from '../components/Rate.vue'
const rateList = ref([
{
score: 1,
name: 'xxxx',
},
{
score: 3,
theme: 'red',
name: 'yyy',
},
{
score: 3.5,
theme: 'blue',
name: 'zzzz',
},
])
</script>
<template>
<Rate v-for="rate in rateList" :key="rate" v-model:score="rate.score" :theme="rate.theme">
{{ rate.name }}
</Rate>
</template>
子组件
<script setup>
import { computed, ref } from 'vue'
const props = defineProps({
score: {
type: Number,
default: 0,
},
theme: {
type: String,
default: 'orange',
},
})
let fontStyle = computed(() => `color:${props.theme};`)
let width = ref(props.score)
const mouseOver = (num) => {
width.value = num
}
const mouseOut = () => {
width.value = props.score
}
let fontWidth = computed(() => `width:${width.value}em;`)
const emits = defineEmits(['update:score'])
const updateScore = (num) => {
emits('update:score', num)
}
</script>
<template>
<div :style="fontStyle">
<slot />
<div class="rate" @mouseout="mouseOut">
<span v-for="num in 5" :key="num" @mouseover="mouseOver(num)">☆</span>
<span class="solid" :style="fontWidth">
<span v-for="num in 5" :key="num" @click="updateScore(num)" @mouseover="mouseOver(num)">★</span>
</span>
</div>
</div>
</template>
<style scoped>
.rate {
position: relative;
display: inline-block;
}
.rate > .solid {
position: absolute;
display: inline-block;
overflow: hidden;
left: 0;
top: 0;
}
</style>
5.vue中动画
纯改变宽度
<template>
<div class="box" :style="{width:width+'px'}"></div>
<button @click="change">click</button>
</template>
<script setup>
import {ref} from 'vue'
let width= ref(100)
function change(){
width.value += 100
}
</script>
<style>
.box{
background:red;
height:100px;
}
</style>
过渡动画animation
.box{
background:#d88986;
height:100px;
transition: width 1s linear; //加入动效
}
vue3-<transition>标签
<transition name="fade">
<h1 v-if="showTitle">test</h1>
</transition>
4个状态方法
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s linear;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
列表元素动画transition-group
.flip-list-move {
transition: transform 0.8s ease;
}
.flip-list-enter-active,
.flip-list-leave-active {
transition: all 1s ease;
}
.flip-list-enter-from,
.flip-list-leave-to {
opacity: 0;
transform: translateX(30px);
}
组件间切换效果,以此使用transition
<router-view v-slot="{ Component }">
<transition name="route" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
路由切换动画
src/components/Dustbin.vue
<script setup>
const props = defineProps({
x: {
type: Number,
required: true,
},
y: {
type: Number,
required: true,
},
state: {
type: Boolean,
required: true,
},
})
const emits = defineEmits(['update:state'])
function onBeforeEnter(el) {
el.style.transform = `translate(${props.x}px, ${props.y}px)`
}
function onEnter(el, done) {
el.offsetWidth
el.style.transform = `translate(${window.innerWidth - 46}px, ${window.innerHeight - 60}px)`
el.addEventListener('transitionend', done)
}
function onAfterEnter(el) {
emits('update:state', false)
el.style.display = 'none'
}
</script>
<template>
<span class="dustbin">🗑</span>
<Transition @before-enter="onBeforeEnter" @enter="onEnter" @after-enter="onAfterEnter">
<span v-show="state" class="document">📄</span>
</Transition>
</template>
<style scoped>
.dustbin {
font-size: 2em;
position: fixed;
bottom: 20px;
right: 20px;
}
.document {
font-size: 1.2em;
position: fixed;
left: 0px;
top: 0px;
transition: all 0.8s linear;
}
</style>
使用
<script setup>
import { computed, ref } from 'vue'
import { useToggle, usePointer } from '@vueuse/core'
import Dustbin from '@/components/Dustbin.vue'
const showInfo = ref(false)
const toggleInfo = useToggle(showInfo)
let title = ref('')
let todoList = ref([
{
title: '学习学习',
done: false,
},
])
const addTodo = () => {
if (!title.value.trim()) {
toggleInfo()
setTimeout(() => {
toggleInfo()
}, 1500)
} else {
todoList.value.push({
title: title.value.trim(),
done: false,
})
title.value = ''
}
}
const deleteTodo = (index) => {
todoList.value.splice(index, 1)
dustbinShow.value = true
}
const clear = () => {
todoList.value = todoList.value.filter((todoItem) => !todoItem.done)
dustbinShow.value = true
}
let undoneCount = computed(() => {
return todoList.value.filter((todoItem) => !todoItem.done).length
})
let all = computed(() => todoList.value.length)
let allDone = computed({
get: function () {
return undoneCount.value === 0
},
set: function (value) {
todoList.value.forEach((todoItem) => (todoItem.done = value))
},
})
const { x, y } = usePointer()
const dustbinShow = ref(false)
</script>
<template>
<input v-model="title" type="text" @keydown.enter="addTodo" />
<button v-if="undoneCount < all" @click="clear">清空完成选项</button>
<template v-if="todoList.length">
<TransitionGroup name="list" tag="ul">
<li v-for="(todoItem, index) in todoList" :key="todoItem.title">
<input v-model="todoItem.done" type="checkbox" />
<span :class="{ done: todoList.done }">{{ todoItem.title }}</span>
<span @click="deleteTodo(index)"> x </span>
</li>
</TransitionGroup>
<div>全部完成<input v-model="allDone" type="checkbox" /></div>
<div>完成情况{{ all - undoneCount }} / {{ all }}</div>
</template>
<div v-else>暂无数据</div>
<Transition name="info">
<div v-if="showInfo" class="infoWrapper">
<div class="info">输入为空</div>
</div>
</Transition>
<Dustbin v-model:state="dustbinShow" :x="x" :y="y"></Dustbin>
</template>
<style scoped>
.infoWrapper {
position: fixed;
top: 12px;
width: 100%;
}
.infoWrapper > .info {
color: red;
text-align: center;
}
.info-enter-from,
.info-leave-to {
opacity: 0;
transform: translateY(-60px);
}
.info-enter-active,
.info-leave-active {
transition: all 0.3s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
.list-move,
.list-enter-active,
.list-leave-active {
transition: all ease 0.3s;
}
.list-leave-active {
position: absolute;
}
</style>