npm create vite@latest
npm run dev 开始预览时 使用模块 是 es module
打包生产环境 使用rollup
webpack基于node
vite 基于 原生 module和 rollup
根目录下的vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
host: '0.0.0.0',
port: 3000,
open: true,
proxy: {
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
}
}
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'@c': fileURLToPath(new URL('./src/components', import.meta.url)),
'@a': fileURLToPath(new URL('./src/api', import.meta.url)),
'@v': fileURLToPath(new URL('./src/views', import.meta.url))
}
}
})
注意:
路径别名 如果vite 使用ts 还需要在 ts.config.json配置该路径
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@a/*": ["./src/api/*"],
"@c/*": ["./src/components/*"],
"@v/*": ["./src/views/*"]
},
"ignoreDeprecations": "5.0"
},
"references": [
{
"path": "./tsconfig.node.json"
}
]
}
interface Type1 {
name: string
}
interface Type2 {
aaa: number
}
interface Type3 {
bbb: string
}
// 导出时 加上type关键字
export type {
Type1,
Type2,
Type3
}
导入type
import type { xxx, xx } from '路径'
import type Xxx from '路径'
支持 vue2 选项式api options api
{
data(){
return {}
},
methods: {},
watch: {},
computed: {}
}
组合式api composition Api是vue3新增语法, 函数式编程
1 组合式api 入口函数式 setup 组件一切业务要定义在setup函数中
(数据、方法、生命周期钩子、watch/计算属性)
2 触发在 实例创建完成之前 这里不能通过 this 访问实例
3 返回对象,对象里面属性和方法 暴露给模板, setup中定义变量或者函数,模板想要中 return出去
{
setup(){
const msg = '你好世界';
const fn = () => {
}
return {
msg,
fn
}
}
}
{
props: {
title: {
type: String,
required: true
}
},
// setup第一个参数就是props
setup(props){}
}
setup(props, context) {
// 透传 Attributes(非响应式的对象,等价于 $attrs)
console.log(context.attrs)
// 插槽(非响应式的对象,等价于 $slots)
console.log(context.slots)
// 触发事件(函数,等价于 $emit) 记住emit即可 这是触发自定义事件的方法
console.log(context.emit)
// 暴露公共属性(函数)
console.log(context.expose)
}
}
export default {
setup () {
/*
原理:
定义一个包装对象,将 值 定义到包装对象的 value属性中
利用Object.defineProperty 劫持 value属性
注意:
在js中使用 一定要加value 模板中可以省略value
*/
const msg = ref('你好vue3');
const changeMsg = () => {
msg.value = '值改变了'
}
return {
msg,
changeMsg
}
}
}
import { ref, onMounted } from 'vue'
export default {
setup () {
// 定义容器 存储需要获取dom
const btnRef = ref(null);
onMounted(() => {
console.log(btnRef.value);
})
return {
btnRef
}
}
}
<button ref="btnRef">按钮</button>
缺点:
使用包装对象,将值定义在value,某个值就是对象, 包装一层访问层级太深
使用场景:
定义 基本数据类型 和 数组 (非对象的数据)
使用场景:
1 适用于 定义 对象这个 响应式数据
import { reactive, toRefs } from 'vue'
export default {
setup () {
/*
利用Proxy直接代理 对象 返回代理对象
*/
const data = reactive({
a: 10,
b: 20
})
const changeData = () => {
data.a = 100
}
return {
data
}
}
}
{{data.a}}
利用 ref方法将 reactive 的响应式对象某个属性 转换成 响应式数据(利用ref,在js中使用 a.value)
import { reactive, toRef } from 'vue'
export default {
setup () {
/*
利用Proxy直接代理 对象 返回代理对象
*/
const data = reactive({
a: 10,
b: 20
})
// a是包装对象 值在value中, js中访问需要加 .value 利用Object.defineProperty劫持
const a = toRef(data, 'a')
const changeData = () => {
a.value = 100
}
return {
data,
a
}
}
}
{{a}}
import { reactive, toRefs } from 'vue'
export default {
setup () {
/*
利用Proxy直接代理 对象 返回代理对象
*/
const data = reactive({
a: 10,
b: 20
})
const changeData = () => {
data.a = 100
}
return {
...toRefs(data)
}
}
}
{{a}}
{{b}}
注意:
reactive 不能重新赋值 重新赋值会变成普通对象
import { ref, watch } from 'vue'
export default {
setup () {
const msg = ref('');
// 01基础侦听 ref 不需要加 value
watch(msg, (newVal) => {
console.log('msg改变了新值是', newVal);
})
return {
msg
}
}
}
import { ref, watch } from 'vue'
export default {
setup () {
const num1 = ref(2);
const num2 = ref(3);
// 参数1定义getter函数 侦听是getter函数返回值
// 侦听是getter函数返回值, 侦听器回调 val也是 getter返回表达式的值
watch(() => num1.value+num2.value, (val) => {
console.log('num1+num2改变了值是', val);
})
return {
num1,
num2,
}
}
}
import { ref, watch, reactive } from 'vue'
export default {
setup () {
const num1 = ref(2);
const num2 = ref(3);
// 同时侦听多个值
watch([num1, num2], ([val1, val2]) => {
console.log('num1或者num2改变了值是', val1, val2);
})
return {
num1,
num2,
}
}
}
import { ref, watch, reactive } from 'vue'
export default {
setup () {
// 侦听响应式对象 reactive 默认就是深度侦听
const data = reactive({
a: 10,
b: 20
})
// 默认就是深度侦听 data任意属性改变都会触发
watch(data, (val) => {
console.log('data改变了值是', val);
})
return {
data
}
}
}
const arr = ref([1, 2, 3, 4]);
watch(arr, (val) => {
console.log('arr改变了', val);
}, {
deep: true
})
watch(侦听源, callback[, options])
watch(obj, () => {}, {
deep: true, // 深度侦听
immediate: true,
flush: 'post'
})
export default {
setup (prop) {
const num1 = ref(10);
const num2 = ref(20);
/*
1 初始化立即执行一次回调
2 将 在 回调中使用的数据当成依赖,依赖改变 回调再次触发
*/
watchEffect(() => {
consolelog(num1.value+ num2.value)
})
return {
num1,
num2
}
}
}
watch vs. watchEffect?
watch 和 watchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:
watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。
import { ref, computed } from 'vue'
export default {
setup () {
const msg = ref('hello vue3');
const reverseMsg = computed(() => {
// 经过计算返回结果即可
return msg.value.split('').reverse().join('')
})
console.log(reverseMsg.value);
// 计算属性 setter
const num = ref(10);
const doubleNum = computed({
get(){
return num.value * 2
},
set(val) {
console.log('setter触发', val);
num.value = val / 2;
}
})
const changeDoubleNum = ()=> {
doubleNum.value = 100;
}
return {
msg,
reverseMsg,
num,
doubleNum,
changeDoubleNum
}
}
}
注意:
在js中访问 需要加 value属性
本质上计算属性是 ref 包装响应式数据
去除了 beforeCreate个created (被setup替代了)
其他钩子前面加 on
onBeforeMount()
onMounted()
onBeforeUpdate()
onUpated()
onBeforeUnmount()
onUnmounted()
onActivated()
onDeactivated()
注意:
初始化发送请求 也是在 onMounted中触发
hook函数,钩子函数 (必须在特定地方调用,获取特定的东西或者在特定时间点 触发函数)
hook函数: 大部分 都是以 use开头
vue hook函数 一定要 在setup函数中调用 在其他函数中调用无效
import { nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'
基础语法
<script setup>
console.log('hello script setup')
</script>
<script setup>
// 变量
const msg = 'Hello!'
// 函数
function log() {
console.log(msg)
}
</script>
<template>
<button @click="log">{{ msg }}</button>
</template>
<script setup>
import MyComponent from './MyComponent.vue'
</script>
<template>
<MyComponent />
</template>
<script setup>
const vMyDirective = {
beforeMount: (el) => {
// 在元素上做些操作
}
}
</script>
<template>
<h1 v-my-directive>This is a Heading</h1>
</template>
<template>
<div>
<h2>我是子组件</h2>
{{ title }}
{{ num }}
</div>
</template>
<script setup>
const props = defineProps({
title: String,
num: {
type: Number,
required: true
}
})
</script>
<template>
<div>
<h2>我是子组件</h2>
{{ title }}
{{ num }}
<button @click="fn">子向父通信</button>
</div>
</template>
<script setup>
const props = defineProps({
title: String,
num: {
type: Number,
required: true
}
})
const emit = defineEmits(['biubiu', 'piupiu']);
const fn = () => {
emit('biubiu', '携带的数据')
}
</script>
<script setup lang="ts">
interface PropType {
title: string,
num?: number
}
const props = defineProps<PropType>();
</script>
<script setup lang="ts">
interface PropType {
title: string,
num?: number
}
// 一定是可选prop定义默认值
const props = withDefaults(defineProps<PropType>(), {
num: 100
})
</script>
const emit = defineEmits<{
(e: 'biubiu', msg: string): void;
(e: 'piupiu', title: string): void
}>();
// 只能触发两个事件 分别是 biubiu piupiu 携带参数格式都是string
面试题?
vue2 和vue3区别
const msg = ref<string>('你好世界')
interface PersonType {
name: string;
age: number;
gender: string
}
const data: PersonType = reactive({
name: 'xxx',
age: 18,
gender: '男'
})
const doubleNum = computed<number>(() => {
return xxxx
})
对比 Vuex 3.x/4.x
Vuex 3.x 只适配 Vue 2,而 Vuex 4.x 是适配 Vue 3 的。
Pinia API 与 Vuex(<=4) 也有很多不同,即:
mutation 已被弃用。它们经常被认为是极其冗余的。它们初衷是带来 devtools 的集成方案,但这已不再是一个问题了。
无需要创建自定义的复杂包装器来支持 TypeScript,一切都可标注类型,API 的设计方式是尽可能地利用 TS 类型推理。
无过多的魔法字符串注入,只需要导入函数并调用它们,然后享受自动补全的乐趣就好。
无需要动态添加 Store,它们默认都是动态的,甚至你可能都不会注意到这点。注意,你仍然可以在任何时候手动使用一个 Store 来注册它,但因为它是自动的,所以你不需要担心它。
不再有嵌套结构的模块。你仍然可以通过导入和使用另一个 Store 来隐含地嵌套 stores 空间。虽然 Pinia 从设计上提供的是一个扁平的结构,但仍然能够在 Store 之间进行交叉组合。你甚至可以让 Stores 有循环依赖关系。
不再有可命名的模块。考虑到 Store 的扁平架构,Store 的命名取决于它们的定义方式,你甚至可以说所有 Store 都应该命名。
import { createPinia } from 'pinia'
const pinia = createPinia();
app.use(pinia);
import { defineStore } from "pinia";
const useUserStore = defineStore('user', {
// 存储这个仓库公共状态
state: () => ({
num: 10,
num2: 1000
}),
getters: {
doubleNum(state){
return state.num * 2
}
},
// 类比为methods中的方法
actions: {
changeNum(n: number){
this.num += n
}
}
});
export default useUserStore
调用 hook 获取仓库实例
import useUserStore from 'xxxx'
const userStore = useUserStore(); // 被Proxy代理后的对象
userStore.状态名 // 即可获取state
修改state
1 直接修改 (不建议)
userStore.state名 = 值
2 使用 $patch方法 优点:同时批量修改多个
userStore.$patch({
num: 值,
num2: 值2
})
3 在action中修改 状态 推荐方案
actions中方法 也是直接 挂载到 仓库实例,action内部直接 通过this即可 修改state
userStore.changeNum(值)
{
state: () => {
return {
num: 10
}
},
actions: {
changeNum(n){
this.num += n
}
}
}
直接在action 发送异步请求成功给 state 赋值
import { defineStore } from "pinia";
import axios from 'axios'
const useUserStore = defineStore('user', {
state: () => ({
cates: []
}),
actions: {
fetchCates(params={}){
axios.get('https://api.it120.cc/conner/cms/category/list', {params}).then(res => {
if (res.data.code === 0) {
this.cates = res.data.data
}
})
}
}
});
export default useUserStore
类似于 toRefs功能,将 仓库中 多个state 解构成 多个 利用 Object.definePerperty 代理 响应式数据
解决:
直接解构赋值 store 状态 视图不刷新的问题
import useUserStore from '@/stores/user'
import { storeToRefs } from 'pinia';
const userStore = useUserStore();
const {num, num2, cates} = storeToRefs(userStore);
const changeNum = () => {
userStore.num = 567890;
}
// num num2 cates 都变成响应式数据了