Bootstrap

vue-vue3学习笔记

Vite 工具

npm install -g create-vite-app

创建项目

通过vite创建项目

create-vite-app [name]
vue create [name]

2.1 vue3 + ts + tsx

git clone https://github.com/justwiner/vue3-tsx.git
# 如果你正在使用 npm 7
$ npm init @vitejs/app my-vue-app -- --template vue 

# 如果你正在使用 npm 6
$ npm init @vitejs/app my-vue-app --template vue

# 如果你是一个偏向于使用 yarn 的开发者
$ yarn create @vitejs/app my-vue-app --template vue

Vue setup语法糖

<template>
 <div class='container'>
  content {{ state.num }}
 </div>
</template>

<script setup>
import { defineEmit, defineProps, onBeforeMount, onMounted, reactive } from 'vue';
import { useStore } from 'vuex';
import { useRoute, useRouter } from 'vue-router';
// props
const props = defineProps({})
const emit = defineEmit([])
// state
const state = reactive({
 num: 1
})
const store = useStore();
const route = useRoute();
const router = useRouter();
// methods
onBeforeMount(() => {})
onMounted(() => {})
</script>

<style lang='less' scoped></style>

运行

npm install
npm run dev

Vue2 + ts

index.vue
<script lang="ts">
import { Component, Provide, Vue } from "vue-property-decorator";
import { api } from '@/api/api';
import MyTemplate from '@/components/trmplate.vue';

// 注册组件
@Component({
    components: {
        MyTemplate
    }
})

// 子类和父类首字母大写
export default class Demo extends Vue {
    // data
    @Provide() msg: string = 'hello world'
    @Provide() obj: Object = {}
    @Provide() times: number = 0
    
    // 生命周期
    mounted() {
        api.login(data).then(res => {
            console.log(res.data)
        })
    }
    
    // methods
    addTimes(a: number): number {
        console.log(this.obj)
        this.times = this.times + a
        return this.times
    }
    
    // computed
    get comMsg() {
        return 'typescript' + this.msg
    }
}
</script>
装饰器
<script lang="ts">
import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from 'vue-property-decorator';
// 组件
@Component({
    components: {
        MyTemplate
    }
})

// props
@Prop({ default: 0 }) num: number = 1
@prop([String, Boolean]) bool: string|boolean
    
// watch
@Watch('str') onChange(newV: string, oldV: string) {
    console.log(newV, oldV)
}
</script>

Vue3 + ts

shims-vue.d.ts
declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "@/router";
import store from "@/store";

// 静态资源
import "./assets/css/reset.css";

const app = createApp(App);
app.use(store);
app.use(router);

app.mount("#app");
demo.vue
<template>
  <div class="index">
    <p>{{ msg }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent ({
  setup() {
    let msg: String = "index";

    return {
      msg,
    };
  }
});
</script>

<style lang="less" scoped>
@import url("../../style/main/index.less");
</style>
index.vue

通过ref绑定数据

<template>
  <div class="login">
    <p><input type="text" v-model="user.username"></p>
    <p><input type="text" v-model="user.password"></p>
    <button @click="login">login</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue';
import router from '@/router';
export default defineComponent ({
  setup() {
    const state = reactive({
      user: {
        username: "15653116166",
        password: "maruiqq100",
      },
    });

    function login() {
      LoginRequest(state.user).then(res => {
        console.log(res);
      });
    }

    return {
      ...toRefs(state),
      login,
    };
  }
});
</script>

<style lang="less" scoped></style>
axios / http
import axios, { AxiosResponse } from 'axios';
import Qs from 'qs';

// 请求拦截
axios.interceptors.request.use(
  (config: any) => {
    return config;
  },
  (error: any) => {
    Promise.reject(error);
  }
);

// 响应拦截
axios.interceptors.response.use(
  (response: any) => {
    return Promise.resolve(response);
  },
  (error: any) => {
    return Promise.reject(error);
  }
);

// get
export const getResponse = function<T, U, E> (url: string, param: T): Promise<AxiosResponse<U>> {
  return new Promise<AxiosResponse<U>>((resolve, reject) => {
    axios.get(url, param)
      .then((data: AxiosResponse<U>) => {
        resolve(data);
      })
      .catch((err: E | any) => {
        reject(err);
      });
  });
};

// post
export const postResponse = function<T, U, E> (url: string, param: T): Promise<AxiosResponse<U>> {
  return new Promise<AxiosResponse<U>>((resolve, reject) => {
    // post参数处理
    let params = Qs.stringify(param);
    axios.post(url, params)
      .then((data: AxiosResponse<U>) => {
        resolve(data);
      })
      .catch((err: E | any) => {
        reject(err);
      });
  });
};
axios - api
import { getResponse, postResponse } from './index';

// 请求地址
const api = "apis";

function filterApi(url: String){
  switch (url){
    case "login": return "/api/login";
  }
}

// post
export const post = (url: string ,params: any, response: Function):void => {
  const postApi =  filterApi(url);
  postResponse(api + postApi, params)
    .then((res: any) => {
      response(res.data);
    })
    .catch((err: any) => {
      console.log("error:", err);
    });
};

// get
export const get = (url: string ,params: any, response: Function):void => {
  const getApi =  filterApi(url);
  getResponse(api + getApi, params)
    .then((res: any) => {
      response(res.data);
    })
    .catch((err: any) => {
      console.log("error:", err);
    });
};
router
npm install vue-router@4 -D
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";

const routes: Array<RouteRecordRaw> = [
  {
    path: "/",
    name: "index",
    component: () => import("../views/main/index.vue")
  },
  {
    path: "/login",
    name: "login",
    component: () => import("../views/login/index.vue"),
  },
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
});

// 全局路由守卫
router.beforeEach((to, form, next) => {
  let state = localStorage.getItem("loginState");
  if(to.name != "login"){
    if(state !== "true"){
      next({ name: "login" });
    } else {
      next();
    }
  } else {
    if(state === "true"){
      next({ name: "index" });
    } else {
      next();
    }
  }
});

export default router;

获取DOM元素

<template>
  <div class="index">
        <!-- ref属性可以获取到dom元素 -->
    <p ref="box">{{ msg }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
export default defineComponent ({
  setup() {
    let msg: String = "index";
    let box = ref();
    
        // 获取dom元素需要在mounted生命周期中,此时元素才挂载完成
    onMounted(() => {
      console.log(box.value.innerText);
    });

    return {
      msg,
      box,   // 对应ref元素
    };
  }
});
</script>

Teleport 瞬移组件,顶层组件

即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中

可以在任意地方使用的组件

index.html

<!DOCTYPE html>
<html lang="zh">
  <head>
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <div id="app"></div>
    <!-- 瞬移组件的顶层容器 -->
    <div id="dialog"></div>
  </body>
</html>

dialog.vue

<template>
  <teleport to="#dialog">
    <div class="dialog">
      <div class="content">dialog</div>
    </div>
  </teleport>
</template>

<script>
export default {};
</script>

<style lang="less" scoped>
</style>

index.vue

<template>
  <div>
        <!-- 使用组件 -->
    <Dialog></Dialog>
  </div>
</template>

<script lang="ts">
// 引入组件
import Dialog from '@/components/dialog.vue';
export default defineComponent ({
    // 注册组件
  components: { 
    Dialog
  },
});
</script>

Suspense 异步组件

App.vue

<template>
  <div id="content">
    <Suspense>
      <!-- 加载完成显示 -->
      <template #default>
        <router-view />
      </template>
      <!-- 加载中显示 -->
      <template #fallback>
        <p>loading...</p>
      </template>
    </Suspense>
  </div>
</template>

index.vue

<template>
  <div class="index">
    <p>{{ msg }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs,  } from 'vue';
export default defineComponent ({
  async setup() {
    const state = reactive({
      msg: "hello world",
    });
    
    await setTimeout(() => {
      state.msg = "hello Vue3";
    }, 3000);

    return {
      ...toRefs(state),
    };
  },
});
</script>

路由传参

传参使用 useRouter

接参使用 useRoute

<script>
import { useRouter, useRoute } from 'vue-router'
export default {
    setup() {
        // 路由跳转
        const router = useRouter()
        // 路由信息
        const route = useRoute()
    }
}
</script>

Vue3 路由重定向

const routes = [
 {
  path: "/",
  name: "index",
  component: () => import("/@/views/main/index.vue"),
 },
 {
  path: "/:pathMatch(.*)*",
  redirect: { name: "index" }
 }
];

Vuex4.0 状态管理

安装

npm install vuex@next --save
yarn add vuex@next --save

store/index.js 定义状态

import { createStore } from 'vuex';

const store = createStore({
  state(){
    return {
      number: 0,
    };
  },

  mutations: {
    add(state){
      state.number += 1;
    },
    sub(state){
      state.number -= 1;
    }
  }
});

export default store;

main.js 引入

import { createApp } from "vue";
import App from "./App.vue";
import store from "@/store";

const app = createApp(App);
app.use(store);
app.mount("#app");

使用

<template>
  <div class="login">
    <p>{{ number }}</p>
    <p><button class="button" @click="add">++</button></p>
    <p><button class="button" @click="sub">--</button></p>
  </div>
</template>

<script>
import { onMounted, reactive, toRefs, computed } from 'vue';
import { useStore } from 'vuex';
export default {
  name: "Login",
  setup() {
    const store = useStore();
    const state = reactive({
      // 使用状态
      number: computed(() => store.state.number),
    });

    function add(){
      store.commit("add");
    }
    function sub(){
      store.commit("sub");
    }

    return {
      ...toRefs(state),
      add, sub
    };
  }
};
</script>

reactive 响应式数据

<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue';
export default defineComponent ({
  setup() {
        // 响应式数据
    const state = reactive({
      username: "15653116166",
      password: "123456"
    });

    return {
      ...toRefs(state), // 导出使用
    };
  }
});
</script>

watch 监听数据

<template>
  <div class="index">
    <p>{{ msg }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, onMounted, reactive, toRefs, watch } from 'vue';
export default defineComponent ({
  setup() {
    const state = reactive({
      msg: 1
    });
        // watch 监听数据变化
    const watchdata = watch(()=>state.msg, 
      (newv, oldv) => {
        console.log(newv, oldv);
      },
            { deep: true }
    );
        // 停止监听
        setTimeout(() => {
      watchdata();
    }, 5000);

    return {
      ...toRefs(state),
    };
  },
});
</script>
// 多个监听器
watch(()=>[value1, value2], 
 ([newV1, newV2], [oldV1, oldV2]) => {
  console.log(newV1, oldV1);
  console.log(newV2, oldV2);
    }
)

computed 计算属性

import { reactive, toRefs, computed } from 'vue';
export default {
 name: "Login",
 setup(){
  const state = reactive({
   msg: "hello world",
            // computed()
            number: computed(() => store.state.number),
  })
        const store = useStore();
  
  return {
   ...toRefs(state),
            store
  }
 }
};

生命周期

选项 API 生命周期选项和组合式 API 之间的映射

  • beforeCreate -> use setup()
  • created -> use setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeUnmount -> onBeforeUnmount
  • unmounted -> onUnmounted
  • errorCaptured -> onErrorCaptured
  • renderTracked -> onRenderTracked
  • renderTriggered -> onRenderTriggered

14. 命名视图

同一组件使用多个路由,并给路由添加不用名称

基础使用 component

命名视图使用 components ,加 s

const routes = [
 {
  path: "/",
  name: "index",
  meta: { title: "首页" },
  components: {
   default: () => import("/@/views/main/index.vue"),
   test1: () => import("/@/views/test1/index.vue"),
   test2: () => import("/@/views/test2/index.vue")
  },
 },
    {
  path: "/test1",
  name: "test1",
  meta: { title: "测试1" },
  components: {
   default: () => import("/@/views/test1/index.vue"),
   test2: () => import("/@/views/test2/index.vue")
  },
 },
 {
  path: "/test2",
  name: "test2",
  meta: { title: "测试2" },
  components: {
   default: () => import("/@/views/test2/index.vue"),
   test1: () => import("/@/views/test1/index.vue"),
  },
 },
];
<template>
 <div id="app">
  <router-view></router-view>
  <router-view name="test1"></router-view>
  <router-view name="test2"></router-view>
 </div>
</template>

15. 路由过渡

meta 里设置 transition 属性

const routes = [
 {
  path: "/",
  name: "index",
  meta: { title: "首页" },
  component: () => import("/@/views/main/index.vue")
 },
 {
  path: "/test1",
  name: "test1",
  meta: { title: "测试1", transition: "slide-left" },
  component: () => import("/@/views/test1/index.vue")
 },
 {
  path: "/test2",
  name: "test2",
  meta: { title: "测试2", transition: "slide-right" },
  component: () => import("/@/views/test2/index.vue")
 },
];
<router-view v-slot="{ Component, route }">
    <transition :name="route.meta.transition || 'fade'">
        <component :is="Component" />
    </transition>
</router-view>

<style lang="less">
/* 动画开始 */
.fade-enter,.fade-leave-to{}
/* 运行时间 */
.fade-enter-active,.fade-leave-active{}
/* 动画结束 */
.fade-enter-to,.fade-leave{}
</style>

路由滚动

/src/router/index.js

每次进入新路由会自动滚动到指定位置

const router = createRouter({
 history: createWebHashHistory(process.env.BASE_URL),
 routes,
 scrollBehavior(to, from){
  return { top: 100 }
 }
});

17. Vue3 Reactivity API

1. reactive() 响应式数据
import { reactive } from '@vue/reactivity'
// 使用 reactive() 函数定义响应式数据
const state = reactive({
    text: 'hello'
})

console.log(state.text)
2. effect() 副作用函数

当响应式数据变化之后,会导致副作用函数重新执行

import { effect, reactive } from '@vue/reactivity'
// 使用 reactive() 函数定义响应式数据
const obj = reactive({ text: 'hello' })
// 使用 effect() 函数定义副作用函数
effect(() => {
     document.body.innerText = obj.text
})

// 一秒后修改响应式数据,这会触发副作用函数重新执行
setTimeout(() => {
  obj.text += ' world'
}, 1000)
stop() 停止一个副作用
import { effect, reactive, stop } from '@vue/reactivity'
const obj = reactive({ text: 'hello' })
let run = effect(() => {
     document.body.innerText = obj.text
})
// 停止一个副作用
stop(run)
3. shallowReactive() 浅响应数据
import { shallowReactive } from 'vue';
const shallow = shallowReactive({
    obj: {
        num: 1
    }
})
// 修改无效
function add1() {
    shallow.obj.num++
}
// 修改有效
function add2() {
    let num = shallow.obj.num
    shallow.obj = {
        num: num+1
    }
}
4. readonly() 只读变量

类似于 const

import { readonly } from 'vue';
const read = readonly({
    num: 10
})
// read.num 只允许使用,不允许修改
5. shallowReadonly() 浅只读数据

深层次的数据可以被修改,但不会渲染页面

import { shallowReadonly } from 'vue';
const read = shallowReadonly({
    obj: {
        num: 10
    }
})

function add1() {
    read.obj.num = read.obj.num++ // ok
    read.obj = { num: 11 }   // warn
}
6. isReactive() 判断数据对象是否是reactive(响应数据)
import { onMounted, reactive, isReactive, shallowReactive, readonly, shallowReadonly } from 'vue';
// state
const state = reactive({
    obj: { num: 1 }
})
const shallowReactive = shallowReactive({
    obj: { num: 1 }
})
const readonly = readonly({
    obj: { num: 1 }
})
const shallowReadonly = shallowReadonly({
    obj: { num: 1 }
})
// methods
onMounted(() => {
    console.log(isReactive(state))    // true  响应数据
    console.log(isReactive(state.obj))   // true  响应数据对象
    console.log(isReactive(state.obj.num))  // false 响应数据值(对象才是true)
    console.log(isReactive(shallowReactive)) // true  浅响应数据
    console.log(isReactive(shallowReactive.obj))// false 浅响应数据对象
    console.log(isReactive(readonly))   // false 只读数据
    console.log(isReactive(shallowReadonly)) // false 浅只读数据
})
7. isReadonly() 判断数据是否是readonly(只读数据)
import { reactive, shallowReactive, readonly, shallowReadonly, isReadonly } from 'vue';
// state
const state = reactive({})
const shallow = shallowReactive({})
const read = readonly({})
const shallowRead = shallowReadonly({})
// methods
onMounted(() => {
    console.log(isReadonly(state))  // false 响应数据
    console.log(isReadonly(shallow)) // false 浅响应数据
    console.log(isReadonly(read))  // true  只读数据
    console.log(isReadonly(shallowRead))// true  浅只读数据
})
8. isProxy() 判断对象是否是代理对象
import { onMounted, reactive, shallowReactive, readonly, shallowReadonly, isProxy } from 'vue';
// state
const state = reactive({obj:{}})
const shallow = shallowReactive({obj:{}})
const read = readonly({obj:{}})
const shallowRead = shallowReadonly({obj:{}})
// methods
onMounted(() => {
    console.log(isProxy(state))     // true  响应数据
    console.log(isProxy(state.obj))    // true  响应数据对象
    console.log(isProxy(shallow))    // true  浅响应数据
    console.log(isProxy(shallow.obj))   // false 浅响应数据对象
    console.log(isProxy(read))     // true  只读数据
    console.log(isProxy(read.obj))    // true  只读数据对象
    console.log(isProxy(shallowRead))   // true  浅只读数据
    console.log(isProxy(shallowRead.obj)) // false 浅只读数据对象
})
9. markRaw() 将数据标记为不可被代理的

标记后的数据不能被监听,数据更改,但是页面不会变化

带有 __v_skip 属性的,值为true 的对象都不会被代理,包括自定义的

import { onBeforeMount, onMounted, reactive, markRaw } from 'vue';
// state
const data = {
    num: 10
}
const data1 = reactive({
    num: 10,
    __v_skip: true
})
markRaw(data)
// methods
function add() {
    data.num++
    console.log(data.num) // 这里修改了,但页面不会修改
}
10. toRaw() 获取源数据

源数据非响应式数据,为原始数据,修改源数据不会更新页面

import { reactive, toRaw } from 'vue';
// state
const state = reactive({
    num: 10
})
const data = toRaw(state) // 获取源数据
// methods
function add() {
    data.num++
    console.log(data.num) // 源数据修改这里可更新,页面不更新
}
11. ReactiveFlags 枚举值

需要单独安装 @vue/reactivity

export const enum ReactiveFlags {
  skip = '__v_skip',
  isReactive = '__v_isReactive',
  isReadonly = '__v_isReadonly',
  raw = '__v_raw',
  reactive = '__v_reactive',
  readonly = '__v_readonly'
}

当需要定义一个不可被代理的对象时,可以使用 ReactiveFlags

import { reactive, isReactive } from 'vue';
import { ReactiveFlags } from '@vue/reactivity'
// state
let obj = {
    [ReactiveFlags.skip]: true // __v_skip 为true
}
const state = reactive(obj)
12. watchEffect(func) 数据监听
  1. watchEffect 不需要指定被监听的属性,只要响应式的数据改变就会执行
  2. watch 可以拿到新值和旧值,watchEffect 拿不到
  3. watchEffect 在组件初始化的时候会执行一次,watch 不会
import { reactive, watchEffect } from 'vue';
// state
const state = reactive({
    num: 10
})
watchEffect(()=>{
    console.log(state.num)
})
// methods
function add() {
    state.num++
}

–. element 样式穿透

/* vue3 新方法 */
&:deep(.class) {
    width: 100%;
}

/* vue2 旧方法 */
/deep/ .class{
    width: 100%;
}

–. 父子组件传值问题

Extraneous non-emits event listeners (submit) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the “emits” option.

外部的非发出事件监听器(提交)被传递给组件,但不能自动继承,因为组件呈现片段或文本根节点。如果侦听器只打算作为组件自定义事件侦听器,则使用"emit "选项声明它。

加上 一行声明 emits: [“自定义方法名”]

<script>
import { onBeforeMount, onMounted, reactive, ref, toRefs, watch } from 'vue';
export default {
 name: "DialogComponent",
 props: {},
    // 使用 emits 自定义事件
 emits: ["<自定义方法名>"],
 setup(props, context){
  const state = reactive({})
        
        function method(){
            context.emit("<自定义方法名>", "value")
        }

  return {
   ...toRefs(state),
  }
 }
};
</script>

–. 运行报错

TypeError: Cannot read property ‘replace’ of undefined

文件引入错误,(比如样式文件引入地址错误)

父组件调用子组件问题

父组件调用子组件,使用 setup 语法糖后无法获取 ref

使用 defineExpose 将需要的属性和方法暴露出去

<script setup>
import { onMounted } from 'vue';

defineExpose({
    state
});
</script>

使用element实现tab走马灯效果

element-plus

<!-- tab栏 -->
<my-tabs :tabs="tabs" :index="tabIndex" @change="changeTab"></my-tabs>

<el-carousel
  ref="carousel"
  height="470px"
  :autoplay="false"
  indicator-position="none"
  arrow="never"
>
  <el-carousel-item v-for="item in tabs.length" :key="item.id">
    <p>{{ item.name }}</p>
  </el-carousel-item>
</el-carousel>
const carousel = ref(null);

// 切换tabs
function changeTab(index) {
  if (state.tabIndex > index) {
    carousel.value.prev();
  } else {
    carousel.value.next();
  }
  state.tabIndex = index;
}
;