npm install -g create-vite-app
通过vite创建项目
create-vite-app [name]
vue create [name]
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
<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
<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>
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
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");
<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>
通过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>
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);
});
});
};
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);
});
};
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;
<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>
即希望继续在组件内部使用
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>
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" }
}
];
安装
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>
<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);
}
)
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
同一组件使用多个路由,并给路由添加不用名称
基础使用
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>
在
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 }
}
});
import { reactive } from '@vue/reactivity'
// 使用 reactive() 函数定义响应式数据
const state = reactive({
text: 'hello'
})
console.log(state.text)
当响应式数据变化之后,会导致副作用函数重新执行
import { effect, reactive } from '@vue/reactivity'
// 使用 reactive() 函数定义响应式数据
const obj = reactive({ text: 'hello' })
// 使用 effect() 函数定义副作用函数
effect(() => {
document.body.innerText = obj.text
})
// 一秒后修改响应式数据,这会触发副作用函数重新执行
setTimeout(() => {
obj.text += ' world'
}, 1000)
import { effect, reactive, stop } from '@vue/reactivity'
const obj = reactive({ text: 'hello' })
let run = effect(() => {
document.body.innerText = obj.text
})
// 停止一个副作用
stop(run)
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
}
}
类似于 const
import { readonly } from 'vue';
const read = readonly({
num: 10
})
// read.num 只允许使用,不允许修改
深层次的数据可以被修改,但不会渲染页面
import { shallowReadonly } from 'vue';
const read = shallowReadonly({
obj: {
num: 10
}
})
function add1() {
read.obj.num = read.obj.num++ // ok
read.obj = { num: 11 } // warn
}
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 浅只读数据
})
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 浅只读数据
})
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 浅只读数据对象
})
标记后的数据不能被监听,数据更改,但是页面不会变化
带有 __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) // 这里修改了,但页面不会修改
}
源数据非响应式数据,为原始数据,修改源数据不会更新页面
import { reactive, toRaw } from 'vue';
// state
const state = reactive({
num: 10
})
const data = toRaw(state) // 获取源数据
// methods
function add() {
data.num++
console.log(data.num) // 源数据修改这里可更新,页面不更新
}
需要单独安装 @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)
watchEffect
不需要指定被监听的属性,只要响应式的数据改变就会执行watch
可以拿到新值和旧值,watchEffect
拿不到watchEffect
在组件初始化的时候会执行一次,watch
不会
import { reactive, watchEffect } from 'vue';
// state
const state = reactive({
num: 10
})
watchEffect(()=>{
console.log(state.num)
})
// methods
function add() {
state.num++
}
/* 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-plus
<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;
}