Vue3

312 阅读3分钟

vue3官网

vue3底层是用typescript写的能更好的支持ts。

Vue2和Vue3核心差异

Vue2:选项式API、Object.defineProperty响应式(仅监听对象属性/数组下标,需重写数组方法)、打包体积大;

Vue3:组合式API、Proxy响应式(监听整个对象/数组,无边界限制)、Tree-shaking按需打包体积-更小,新增Teleport/Suspense等特性。

响应式

Vue2:通过Object.defineProperty劫持对象属性的get/set,初始化递归遍历数据,缺陷:无法监听- 新增/删除属性、数组下标修改;

Vue3:通过Proxy代理整个目标对象,配合Reflect操作属性,天然支持新增/删除属性、数组变化,无-需递归初始化(懒代理)。

Vite

选择创建项目

  • npm create vite@latest
  • npm init vite@latest
  • npm init vue@latest (官网推荐使用)

vite 创建的项目常用配置的地方

Network: use --host to expose
image.png

可以通过下面两种方法的任意一种:

1、修改vite.config.js文件
server: {
    host: '0.0.0.0',
    host: true, // 这是上面的缩写形式
}

2、修改package.json
"dev": "vite --host 0.0.0.0",
"dev": "vite --host", // 这是上面的缩写形式

Vue CLI

  • 卸载旧版本:
    • npm uninstall vue-cli -g
    • yarn global remove vue-cli
  • 安装脚手架:
    • npm install -g @vue/cli
    • yarn global add @vue/cli

ts、tsx

<script lang="ts"></script>
TypeScript 与 JSX `render` 函数]结合起来:
  <script lang="tsx"></script>

全局

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

globalProperties对象
    app.config.globalProperties.$http = () => {}
    app.config.globalProperties.$name = 'xxx'
    在template模板中使用:{{$name}}
    js中使用:
        import { getCurrentInstance } from "vue";
        const GCI = getCurrentInstance();
        console.log(GCI.appContext.config.globalProperties.$name);
        console.log(GCI.proxy.$name);

注册组件:app.component()
指令:app.direction('', {
          created() {},
          // 在绑定元素的父组件挂载之前调用
          beforeMount() {},
          // 在绑定元素的父组件挂载之后调用
          mounted() {},
          // 在包含组件的 VNode 更新之前调用
          beforeUpdate() {},
          // 在包含组件的 VNode 及其子组件的 VNode 更新之后调用
          updated() {},
          // 在绑定元素的父组件卸载之前调用
          beforeUnmount() {},
          // 在绑定元素的父组件卸载之后调用
          unmounted() {}
     })

defineComponent 定义组件

import { defineComponent } from 'vue'
// 只有一个setup函数,可以这样简写
export default defineComponent(() => {
  return { }
})

defineAsyncComponent 定义异步组件、Suspense

// 引入组件
import { defineAsyncComponent } from 'vue';
const Child = defineAsyncComponent(() => import('@/components/Child.vue'))

// 使用需要Suspense内置组件包裹
<Suspense>
    <template #default>
        <Child />
    </template>
    <template #fallback>
        <h3>加载中...</h3>
    </template>
</Suspense>

teleport

<teleport to="#app"></teleport>
<teleport to=".lxh"></teleport>
<teleport to="body"></teleport>

useRouter、useRoute

import { useRouter, useRoute } from "vue-router";
const router = useRouter()
router.push({path: '',query: {}})
const route = useRoute()
onMount(() => {
    console.log(route)
})

ref、reactive

import { ref, reactive } from 'vue';

// 定义数据
let str1 = ref('lxh');
let str2 = ref({name: 'lxh'});
// 修改
str1.value = 'LXH';
str2.value.name = 'LXH';

// 定义复杂数据类型,不能定义基本数据类型
let obj = reactive({name: 'lxh'});
// 修改
obj.name = 'LXH';

setup

// setup在beforeCreate之前执行,所有setup里没有this
/**
 * 接收两个参数
 * props:在props里接收了数据,才能在setup里获取到数据,props是只读属性
 * context:emit、attrs、slots
 */
setup(props, context) {
    return {}
}

computed、watch、watchEffect

import { computed, watch, watchEffect } from 'vue';
const com = computed(() => {})

// 监听ref定义的数据
watch(xxx, (newValue, oldValue) => {}, {immediate: true, deep: true})
watch([xxx, yyy], (newValue, oldValue) => {})

// 监听reactive定义的数据,强制开启了深度监听,所以设置deep无用
watch(xxx, (newValue, oldValue) => {}, {immediate: true})
watch(() => xxx.name, (newValue, oldValue) => {})
watch([() => xxx.name, () => xxx.age], () => {})

// 停止监听的用法
const stopWatch = watch(xxx, (newValue, oldValue) => {
    newValue > 25 ? stopWatch : console.log(newValue)
})
// watch开启了immediate和watchEffect的顺序,谁写在前面就先执行谁

// watchEffect里用到哪个属性,就监听哪个属性,在onBeforeMount之前执行
watchEffect(() => {})

获取组件实例

// 获取当前组件的实例
import { getCurrentInstance } from 'vue'

// 获取子组件的实例3.2版本
<child ref='lxhRef' />

import { ref } from 'vue';
const lxhRef = ref();

console.log(lxhRef.value); // 获取到实例

vue3.2

defineProps、defineEmits

<script setup>
const props = defineProps({
  xxx: String
})

在template模板中使用:{{xxx}}
在js中使用:props.xxx

const emit = defineEmits(['yyy'])
使用:emit('yyy', '传给父组件的数据')

</script>
<script setup lang='ts'>
const props = defineProps<{
  foo: string
  bar?: number
}>()

const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

// 使用类型声明时的默认 props 值
interface Props {
  msg?: string
  labels?: string[]
}

// withDefaults
const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

</script>

defineExpose

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

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

useSlots 和 useAttrs

<script setup>
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs() // 相当于vue2中的this.$attrs
</script>

组件通信

父子组件

vue3移除了.sync,用v-model:替代

// 父组件
<Child v-model='msg' v-model:xxx='xxx'></Child>

const msg = ref('parent data1');
const xxx = ref('parent data2');

// 子组件
<div>{{ modelValue }}</div>
<button @click="emit('update:modelValue', 'child data1')">change</button>
<button @click="emit('update:xxx', 'child data2')">change</button>

const props = defineProps({ modelValue: String, xxx: String });
const emit = defineEmits(['update:modelValue', 'update:xxx']);

v-if优先级高于v-for

pinia

vuex的替代者,拥有更简洁的语法,扁平化的代码编排,更符合vue3组合式api

基本使用

安装:npm i pinia
在main.js文件里注册使用:
    import { createPinia } from "pinia"
    const APP = createApp(App)
    APP.use(createPinia())
    
在store/index.js文件,多个可以互相调用
    import { defineStore } from 'pinia'

    export const useStore = defineStore('store', {
        state: () => ({
            count: 0,
        }),
        getters: {
            formatterCount(state) {
                // return `数字是${state.count}`
                return `数字是${this.count}`
            }
        },
        actions: {
            change() {
                this.count++
                
                // this.$patch({
                    // count: this.count + 1
                // })

                // this.$patch((state) => {
                    // state.count = state.count + 1
                    // state.count = this.count + 1
                // })
            }
        },
    })

在组件中使用
    <script setup>
        // storeToRefs解构让值具有响应式
        import { storeToRefs } from "pinia";
        import { useStore } from "../store";

        const store = useStore();
        const { count, formatterCount } = storeToRefs(store);
        
        // 修改单个
        const btn = () => {
            store.count++;
        };
        
        // 修改多个
        const batchBtn = () => {
            // 对象形式
            // store.$patch({
            //   count: store.count + 1,
            // });
            
            // 函数形式
            store.$patch((state) => {
              state.count = state.count + 1;
            });
        };
    </script>
    
    <template>
        <div>{{count}} -- {{ formatterCount }}</div> <!-- 显示 -->
        <button @click='count++'>修改</botton> <!-- 修改 -->
        <button @click='btn'>修改</botton>
        <button @click="batchBtn">批量修改</button>
        <button @click="store.change()">通过actions修改</button>
    </template>

模块化

user.js文件

    import { defineStore } from 'pinia'

    export default defineStore('user', {
        state: () => ({
            obj: {
                name: 'xxx',
                age: 18
            },
        })
    })
    
    
table.js文件

    import { defineStore } from 'pinia'

    export default defineStore('table', {
        state: () => ({
            arr: [1, 2, 3]
        })
    })
    
    
store/index.js文件
    import table from "./modules/table";
    import user from "./modules/user";

    export default () => ({
        user: user(),
        table: table(),
    })


在组件中使用
<script setup>
  import { storeToRefs } from "pinia";
  import store from "../store";

  const { table, user } = store();

  const { obj } = storeToRefs(user);
  const { arr } = storeToRefs(table);

  const btn = () => {
    user.obj.age++;
  };

  const batchBtn = () => {
    table.$patch((state) => {
      state.arr = [...state.arr, 4];
    });
  };
</script>

<template>
  <div>hello world -- {{ obj.name }} -- {{ obj.age }} -- {{ arr }}</div>
  <button @click="obj.name = 'yyy'">修改</button>
  <button @click="obj.age++">修改</button>
  <button @click="btn">修改</button>
  <button @click="batchBtn">批量修改</button>
</template>

<style scoped></style>

持久化

插件pinia-plugin-persistedstate

安装:npm i pinia-plugin-persistedstate

在main.js文件注册使用

    import { createPinia } from "pinia"
    import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

    const APP = createApp(App)
    APP.use(createPinia().use(piniaPluginPersistedstate))
    APP.mount('#app')
    
user.js文件
    import { defineStore } from 'pinia'

    export default defineStore('user', {
        persist: true, // 开启持久化
        state: () => ({obj: {}}),
    })
    
持久化存储插件其他配置
persist: {
    // 修改存储中使用的键名称,默认为当前 Store的 id 如:user
    key: 'pinia',
    // 修改为 sessionStorage,默认为 localStorage 
    storage: window.sessionStorage, 
    // 部分持久化状态的点符号路径数组,[]意味着没有状态被持久化(默认为undefined,持久化整个状态),一般不用
    paths: ['obj'], 
},

vite使用scss

默认没有安装

安装

npm i sass sass-loader -D

配置

在vite.config.js文件里配置

  css: {
    preprocessorOptions: {
      scss: {
        additionalData: "@import  '@/assets/reset.scss';"
      }
    }
  }

vue2

不定期更新