vue3复习-搭建/基础/常用方法/响应式/组件化/动画

150 阅读5分钟

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 };
}

优点

  1. 可以把逻辑拆分
  2. 拆分的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 {   
    colorv-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({  
modelValueNumber,  
})   
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 {  
  opacity0;  
}

列表元素动画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 {  
  opacity0;  
  transformtranslateX(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>

参考

玩转Vue 3