Vue3入门到精通教程

2022-05-24 09:54:49
2024-03-26 07:47:43

起初 Vue3.0 暴露变量必须 return 出来,template中才能使用;Vue3.2 中 只需要在 script 标签上加上setup 属性,组件在编译的过程中代码运行的上下文是在setup()函数中,无需return,template可直接使用。都上了Vue3就直接学setup语法糖吧

一、文件结构

<template>
  // Vue2中,template标签中只能有一个根元素,在Vue3中没有此限制 
  // ...
  <div class="box">hello linge</div>
</template>

<script setup>
import { ref } from "vue";
const color = ref("red");
// ...
</script>

<style lang="scss" scoped>
// 支持CSS变量注入v-bind(color)
.box {
  width: 100px;
  height: 100px;
  color: v-bind(color);
}
</style>

二、data

<script setup>
import { reactive, ref, toRefs } from "vue";

// ref声明响应式数据,用于声明基本数据类型
const name = ref("小白龙");
// 修改
name.value = "小龙龙";

// reactive声明响应式数据,用于声明引用数据类型
const state = reactive({
  name: "小白龙",
  sex: "男",
});
// 修改
state.name = "小龙龙";

// 使用toRefs解构
const { name, sex } = toRefs(state);
// template可直接使用{{name}}、{{sex}}

// toRef 是对定义的响应对象的某个属性进行引用,使用一个函数返回一个响应式对象,依旧保持响应式
const nameRef = toRef(state, 'name')

</script>

获取dom元素

 <div ref="myRef">获取单个DOM元素</div>

 <div>获取多个DOM元素</div>
   <ul>
     <li v-for="(item, index) in 3" :key="index" :ref="setRef">
       {{ item }}
     </li>
 </ul>

//获取单个DOM元素
const myRef = ref(null);

//获取多个DOM元素
// 存储dom数组
const myRefs = ref([]);

const setRef = (el) => {
   myRefs.value.push(el);
};

三、method

<template>
  // 调用方法
  <button @click="changeName">按钮</button>
</template>

<script setup>
import { reactive } from 'vue'

const state = reactive({
  name: '小白龙'
})

// 声明method方法
const changeName = () => {
  state.name = '小龙龙'
}  
</script>

四、computed

<script setup>
import { computed, ref } from 'vue'

const count = ref(1)

// 通过computed获得doubleCount
const doubleCount = computed(() => {
  return count.value * 2
})
</script>

五、watch

属性

  • watch 具备一定的惰性
  • 可以拿到原始值和当前值
  • 可以侦听多个数据的变换,用一个侦听器承载
  • 默认开启深度监听

传参

  • 第一个参数,需要传递一个需要监听的 function 、ref 、reactive object
  • 第二个参数,用来接受数据原始值和当前值
  • 第三个参数,传递 watch 的高级配置项 例如 immediate: true
<script setup>
import { watch, reactive } from 'vue'

### 监听基础类型
const count = ref(0)
watch(count, (newValue, oldValue) => {
  console.log('watch' + newValue, oldValue)
})



### 监听复杂类型
const boy = reactive({
  name: '小白龙',
  friend: {
    friend1 : '小小龙',
    friend2 : '小小小龙'
  }
})

#### 监听整个对象
watch(boy, (newValue, oldValue) => {
 console.log('boy发生了变化')
 console.log(newValue);
 console.log(newValue.friend);
})

boy.name = 'Little children'
boy.friend.friend3 = '小王'
> 第一个参数传入我们要监听的对象,当监听的对象里面的任意一个属性发生变化,watch 方法便会触发。

#### 监听对象中的某个属性

// 如果我们直接写 boy.name
watch(boy.name, (newValue, oldValue) => {
 console.log('boy发生了变化')
 console.log(newValue)
})

// vue会提示我们,监听的对象需要是一个 function 或者 ref 或者是一个 reactive object

// 正确的写法是:
watch(() => boy.name, (newValue, oldValue) => {
 console.log('boy发生了变化')
 console.log(newValue)
})

boy.name = 'Little children'



#### 监听对象的所有属性
watch(() => boy, (newValue, oldValue) => {
 console.log('boy发生了变化')
 console.log(newValue)
}, {
 immediate: true//立即执行
})

boy.name = 'Little children'

#### 监听多个数据

- watch 里面可以接收一个数组
- 无论数组里面的哪一个数据发生变化,都会执行侦听器


watch([()=> boy.name, count], ([newName, newCount], [oldName, oldCount]) => {
 console.log(newName + '---' + oldName)
 console.log(newCount + '---' + oldCount)
})

boy.name = 'Little children'


</script>

watchEffect

属性

  • 立即执行的,没有惰性
  • 不需要传递要侦听的内容,会自动感知代码依赖,不需要传递很多参数,只需要一个回调函数
  • 不能获取之前数据的值

监听基础类型

const count = ref(0)

watchEffect(() => {
  console.log(count.value)
})

count.value ++
const count = ref(0)

watchEffect(() => {
  console.log(count.value)
})

count.value ++

六、props父传子

子组件

<template>
  <span>{{ props.name }}</span>
  // 可省略【props.】
  <span>{{ name }}</span>
</template>

<script setup>
// import { defineProps } from 'vue'
// defineProps在<script setup>中自动可用,无需导入
// 需在.eslintrc.js文件中【globals】下配置【defineProps: true】

// 声明props
const props = defineProps({
  name: {
    type: String,
    default: ''
  }
})  
</script>

父组件

<template>
  <child name="Jerry" />
</template>

<script setup>
// 引入子组件(组件自动注册)
import child from './child.vue'
</script>

七、emit子传父

子组件

<template>
  <span>{{ props.name }}</span>
  // 可省略【props.】
  <span>{{ name }}</span>
  <button @click="changeName">更名</button>
</template>

<script setup>
// import { defineEmits, defineProps } from 'vue'
// defineEmits和defineProps在<script setup>中自动可用,无需导入
// 需在.eslintrc.js文件中【globals】下配置【defineEmits: true】、【defineProps: true】

// 声明props
const props = defineProps({
  name: {
    type: String,
    default: ''
  }
})
// 声明事件
const emit = defineEmits(['updateName'])
//ts写法
//const emit = defineEmits<{
//    (e: 'updateName', name: string): void
//  }>()

const changeName = () => {
  // 执行
  emit('updateName', '小龙龙')
}
</script>

父组件

<template>
  <child :name="state.name" @updateName="updateName" />
</template>

<script setup>
import { reactive } from 'vue'
// 引入子组件
import child from './child.vue'

const state = reactive({
  name: '小白龙'
})

// 接收子组件触发的方法
const updateName = (name) => {
  state.name = name
}
</script>

八、v-model

子组件

<template>
  <span @click="changeInfo">我叫{{ modelValue }},今年{{ age }}岁</span>
</template>

<script setup>
// import { defineEmits, defineProps } from 'vue'
// defineEmits和defineProps在<script setup>中自动可用,无需导入
// 需在.eslintrc.js文件中【globals】下配置【defineEmits: true】、【defineProps: true】

defineProps({
  modelValue: String,
  age: Number
})

const emit = defineEmits(['update:modelValue', 'update:age'])
const changeInfo = () => {
  // 触发父组件值更新
  emit('update:modelValue', '小龙龙')
  emit('update:age', 30)
}
</script>

父组件

<template>
  // v-model:modelValue简写为v-model
  // 可绑定多个v-model
  <child v-model="state.name" v-model:age="state.age" />
</template>

<script setup>
import { reactive } from 'vue'
// 引入子组件
import child from './child.vue'

const state = reactive({
  name: '小白龙',
  age: 20
})
</script>

九、nextTick

<script setup>
import { nextTick } from 'vue'

nextTick(() => {
  // ...
})
</script>

十、子组件ref变量和defineExpose

  • 在标准组件写法里,子组件的数据都是默认隐式暴露给父组件的,但在 script-setup 模式下,所有数据只是默认 return 给 template 使用,不会暴露到组件外,所以父组件是无法直接通过挂载 ref 变量获取子组件的数据。
  • 如果要调用子组件的数据,需要先在子组件显示的暴露出来,才能够正确的拿到,这个操作,就是由 defineExpose 来完成。

子组件

<template>
  <span>{{ state.name }}</span>
</template>

<script setup>
import { reactive, toRefs } from 'vue'
// defineExpose无需引入
// import { defineExpose, reactive, toRefs } from 'vue'

// 声明state
const state = reactive({
  name: 'Jerry'
})

// 将方法、变量暴露给父组件使用,父组件才可通过ref API拿到子组件暴露的数据
defineExpose({
  // 解构state
  ...toRefs(state),
  // 声明方法
  changeName() {
    state.name = '小白龙'
  }
})
</script>

父组件

<template>
  <child ref="childRef" />
</template>

<script setup>
import { ref, nextTick } from 'vue'
// 引入子组件
import child from './child.vue'

// 子组件ref
const childRef = ref('childRef')

// nextTick
nextTick(() => {
  // 获取子组件name
  console.log(childRef.value.name)
  // 执行子组件方法
  childRef.value.changeName()
})
</script>

十、插槽slot

子组件

<template>
  // 匿名插槽
  <slot/>
  // 具名插槽
  <slot name='title'/>
  // 作用域插槽
  <slot name="footer" :scope="state" />
</template>

<script setup>
  import { useSlots, reactive } from 'vue'
  const state = reactive({
    name: '小白龙',
    age: '18岁'
  })
  
  const slots = useSlots()
  // 匿名插槽使用情况
  const defaultSlot = reactive(slots.default && slots.default().length)
  console.log(defaultSlot) // 1
  // 具名插槽使用情况
  const titleSlot = reactive(slots.title && slots.title().length)
  console.log(titleSlot) // 3
</script>

父组件

<template>
  <child>
    // 匿名插槽
    <span>我是默认插槽</span>
    // 具名插槽
    <template #title>
      <h1>我是具名插槽</h1>
      <h1>我是具名插槽</h1>
      <h1>我是具名插槽</h1>
    </template>
    // 作用域插槽
    <template #footer="{ scope }">
      <footer>作用域插槽——姓名:{{ scope.name }},年龄{{ scope.age }}</footer>
    </template>
  </child> 
</template>

<script setup>
  // 引入子组件
  import child from './child.vue'
</script>

将某个.vue组件挂载到根节点

  <teleport to="#app">
    <ChannelProgress :show="isShow" @updateShow="updateShow" />
  </teleport>

KeepAlive

<KeepAlive>是一个内置组件,允许我们在多个组件之间动态切换时,有条件地缓存组件实例。

<KeepAlive>
  <component :is="activeComponent" />
</KeepAlive>

属性

  • include 排查

include="a,b"
:include="/a|b/"
:include="['a', 'b']"

  • max 最大缓存实例数

:max="10"

  • 缓存实例的生命周期
//创建
 activated() {

  },
//销毁
  deactivated() {
    // called when removed from the DOM into the cache
    // and also when unmounted
  }

vue全局方法/变量(不推荐这样子使用)

只要创建一个createApp(App),然后用app变量接受就好
main.ts

const app = createApp(App);

//全局方法
app.config.globalProperties.$name1 = '小白龙';

使用

  const { proxy } = getCurrentInstance() as any;
  console.log(111, proxy.$name1);//小白龙

十二、路由useRoute和useRouter

<script setup>
  import { useRoute, useRouter } from 'vue-router'
	
  // 必须先声明调用
  const route = useRoute()
  const router = useRouter()
	
  // 路由信息
  console.log(route.query)

  // 路由跳转
  router.push('/newPage')
</script>

十三、路由导航守卫

<script setup>
  import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
	
  // 添加一个导航守卫,在当前组件将要离开时触发。
  onBeforeRouteLeave((to, from, next) => {
    next()
  })

  // 添加一个导航守卫,在当前组件更新时触发。
  // 在当前路由改变,但是该组件被复用时调用。
  onBeforeRouteUpdate((to, from, next) => {
    next()
  })
</script>

十四、store

*Vue3 中的Vuex不再提供辅助函数写法

<script setup>
  import { useStore } from 'vuex'
  import { key } from '../store/index'

  // 必须先声明调用
  const store = useStore(key)
	
  // 获取Vuex的state
  store.state.xxx

  // 触发mutations的方法
  store.commit('fnName')

  // 触发actions的方法
  store.dispatch('fnName')

  // 获取Getters
  store.getters.xxx
</script>

十五、生命周期

通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。

下表包含如何在 Option API 和 setup() 内部调用生命周期钩子

Option API setup中
beforeCreate 不需要
created 不需要
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
activated onActivated
deactivated onDeactivated

十六、CSS变量注入

<template>
  <span>Jerry</span>  
</template>

<script setup>
  import { reactive } from 'vue'

  const state = reactive({
    color: 'red'
  })
</script>
  
<style scoped>
  span {
    // 使用v-bind绑定state中的变量
    color: v-bind('state.color');
  }  
</style>

十七、原型绑定与组件内使用

main.js

import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)

// 获取原型
const prototype = app.config.globalProperties

// 绑定参数
prototype.name = 'Jerry'

组件内使用

<script setup>
  import { getCurrentInstance } from 'vue'

  // 获取原型
  const { proxy } = getCurrentInstance()
  
  // 输出
  console.log(proxy.name)
</script>

十八、对 await 的支持

不必再配合 async 就可以直接使用 await 了,这种情况下,组件的 setup 会自动变成 async setup 。

<script setup>
  const post = await fetch('/api').then(() => {})
</script>

十九、定义组件的name

用单独的<script>块来定义

<script>
  export default {
    name: 'ComponentName',
  }
</script>

二十、provide和inject

父组件

<template>
  <child/>
</template>

<script setup>
  import { provide } from 'vue'
  import { ref, watch } from 'vue'
  // 引入子组件
  import child from './child.vue'

  let name = ref('Jerry')
  // 声明provide
  provide('provideState', {
    name,
    changeName: () => {
      name.value = 'Tom'
    }
  })

  // 监听name改变
  watch(name, () => {
    console.log(`name变成了${name}`)
    setTimeout(() => {
      console.log(name.value) // Tom
    }, 1000)
  })
</script>

子组件

<script setup>
  import { inject } from 'vue'
	// 注入
  const provideState = inject('provideState')
  
  // 子组件触发name改变
  provideState.changeName()
</script>

二十一、Vue3中使用echarts

// 安装
npm i echarts --save

// 组件内引入
import * as echarts from 'echarts'

自定义指令实现按钮防抖

监听按钮的点击事件,给点击事件设置防抖,如果特定时间段内多次提交,则每以最后一次重新计算时间。

代码实现

src/directives/preReClick.ts

export default (app) => {
  app.directive('preReClick', {
    mounted(el, binding) {
      el.addEventListener('click', () => {
        if (!el.disabled) {
          el.disabled = true;
          setTimeout(() => {
            el.disabled = false;
          }, binding.value || 2000)
        }
      })
    }
  })
}

在main.js中引入指令文件

import preReClick from '@/directives/preReClick';

const app = createApp(App);
app.use(ElementPlus).use(preReClick).mount('#app');

组件中使用

  <button @click="confirm" v-preReClick>点我点我</button>

全局消息弹框封装

Tips.vue

<template>
    <div class="tips">
        <div class="box" :class="type">
            <svg class="iconpark-icon"><use :href="icontype"></use></svg>
            <span>{{ title }}</span>
        </div>
    </div>
</template>

<script lang="ts" setup>
import { defineProps, computed } from 'vue';

interface Props {
    type: string;
    title: string;
}

const props: Props = defineProps({
    type: {
        type: String,
        default: 'success'
    },
    title: {
        type: String,
        default: ''
    }
});

const icontype = computed(() => {
    const type = {
        success: '#gou',
        warning: '#zu2141',
        error: '#4',
        info: '#gou',
        hint: 'icon-tishi'
    };
    return type[props.type];
});
</script>

<style lang="scss" scoped>
.tips {
    z-index: 9999;
    position: fixed;
    top: 100px;
    left: 50%;
    transform: translateX(-50%);
    animation: tipsTop 1s;
    overflow: hidden;
    box-shadow: 0 1px 20px 0 rgba(153, 153, 153, 0.5);
    .box {
        width: fit-content;
        height: 80px;
        display: flex;
        align-items: center;
        background-color: #ffffff;
        box-shadow: 0 1px 20px 0 rgba(153, 153, 153, 0.5);
        border-radius: 4px;
        padding: 0 42px;
        .iconpark-icon {
            width: 30px;
            height: 30px;
            margin-right: 10px;
        }
        span {
            max-width: 1000px;
            font-size: 18px;
            font-weight: normal;
            font-stretch: normal;
            letter-spacing: 2px;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
        }
    }
    .success {
        color: #0173cd;
    }
    .warning {
        color: #ff9800;
    }
    .error {
        color: #c73741;
    }
    .info {
        color: #777777;
    }
    .hint {
        color: #fd4d4d;
    }
}

// 动画
@keyframes tipsTop {
    0% {
        top: -10px;
    }

    100% {
        top: 100px;
    }
}
</style>

Tips.ts

import { createVNode, render } from 'vue';
import Tips from './Tips.vue';

const div = document.createElement('div');
div.setAttribute('class', 'tips');
document.body.appendChild(div);

let timer: number;

export default ({ type, title }: { type: string; title: string }) => {
    const vnode = createVNode(Tips, { type, title });
    render(vnode, div);
    timer && clearTimeout(timer);
    timer = setTimeout(() => {
        render(null, div);
    }, 2000);
};

使用

import Tips from '@/components/Tips/Tips';
Tips({ type: 'success', title: '新建素材成功' });
目录
暂无评论,欢迎留下你的评论

运营需要亿点资金维持,您的支持,是小白龙创作的动力!!!

昵称
留言
赞赏金额