watchEffect
,它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
换句话说:watchEffect
相当于将watch
的依赖源和回调函数合并,当任何你有用到的响应式依赖更新时,该回调函数便会重新执行。不同于 watch
,watchEffect
的回调函数会被立即执行(即 { immediate: true }
)
watchEffect
的回调函数就是一个副作用函数,因为我们使用watchEffect
就是侦听到依赖的变化后执行某些操作。
import { watchEffect, ref } from 'vue'const count = ref(0)
watchEffect((onInvalidate) => {console.log(count.value)onInvalidate(() => {console.log('执行了onInvalidate')})
})setTimeout(()=> {count.value++
}, 1000)
定义一个定时器,或者监听某个事件,我们需要在mounted
生命周期钩子函数内定义或者注册,然后组件销毁之前在beforeUnmount
钩子函数里清除定时器或取消监听。这样做我们的逻辑被分散在两个生命周期,不利于维护和阅读。
如果我利用watchEffect
,创造和销毁逻辑放在了一起
// 定时器注册和销毁
watchEffect((onInvalidate) => {const timer = setInterval(()=> {// ...}, 1000)onInvalidate(() => clearInterval(timer))
})const handleClick = () => {// ...
}
// dom的监听和取消监听
onMounted(()=>{watchEffect((onInvalidate) => {document.querySelector('.btn').addEventListener('click', handleClick, false)onInvalidate(() => document.querySelector('.btn').removeEventListener('click', handleClick))})
})
利用watchEffect做一个防抖节流
const id = ref(13)
watchEffect(onInvalidate => {// 异步请求const token = performAsyncOperation(id.value)// 如果id频繁改变,会触发失效函数,取消之前的接口请求onInvalidate(() => {// id has changed or watcher is stopped.// invalidate previously pending async operationtoken.cancel()})
})
可以使用 Vue.js 的三元运算符在 {{ }}
中展示不同的状态,具体代码如下:
<el-table-column label="状态" min-width="100">{{row.status === 1 ? '已完成' : row.status === 2 ? '进行中' : '未开始'}}
</el-table-column>
在上面的代码中,我们使用了两个嵌套的三元运算符来实现根据 row.status
的值显示不同的状态。如果 row.status
的值为 1
,则显示 "已完成"
,如果值为 2
,则显示 "进行中"
,否则显示 "未开始"
。
在 Vue 3 中,可以使用 v-slot
的缩写语法(#
)来指定插槽的位置。对于 el-table-column
组件中的插槽,您可以将模板语法放置在 <template v-slot:default>
或 <template #default>
标签内。
下面是根据您提供的代码示例修改后的 Vue 3 代码,用于在 el-table-column
组件中显示 sysUser.active
字段的值:
<el-table-column label="状态" min-width="100"><template #default="{ row }">{{ row.active }}</template>
</el-table-column>
在上面的代码中,我们使用了 #default
缩写语法来定义插槽,并使用对象解构来获取 row
参数。然后,我们在模板中使用 row.active
来显示 active
字段的值。
el-table-column
组件没有绑定数据源:请确保你的表格组件已经正确地绑定了数据源,例如使用:data
属性绑定数据源。.active
字段不存在或为空值:请确保.active
字段存在且有值。如果该字段不存在或为空,您的模板代码将无法正确地解析。样式问题:请检查您的样式是否正确,以确保表格列宽度足够容纳内容,并且字体颜色不会与背景颜色混淆。
要根据条件隐藏<el-form-item>
,可以使用Vue的条件渲染指令v-if或v-show。
使用v-if指令时,只有当条件为真时,元素才会被渲染到页面上。如果条件变为假,则该元素从DOM中删除。
例如, 如果您想要在表单项不为空时才显示密码字段,您可以将v-if指令添加到包装表单项的
元素中:
<div v-if="formData.name !== ''"><el-form-item label="密码" prop="name"><el-inputclass="ls-input"v-model="formData.passwd"placeholder="请输入密码"clearable/></el-form-item>
</div>
在这个例子中,<el-form-item>
只有在formData.name
不为空时才会被渲染出来。
另一种选择是使用v-show指令。与v-if不同的是,当条件为假时,元素仍然存在于DOM中,只是样式设置为display:none。
例如,如果您希望在表单项不为空时显示密码字段,您可以将v-show指令添加到元素中:
<el-form-item label="密码" prop="name" v-show="formData.name !== ''"><el-inputclass="ls-input"v-model="formData.passwd"placeholder="请输入密码"clearable/>
</el-form-item>
在这个例子中,当formData.name
不为空时,<el-form-item>
将显示出来,否则它将以display:none的方式隐藏起来。
data | 展示数据 |empty-text | 内容为空的时候展示的文本 |node-key | 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 |render-after-expand | 是否在第一次展开某个树节点后才渲染其子节点 |load 加载子树数据的方法,仅当lazy属性为true时生效render-content 树节点的内容区的渲染Functionhighlight-current 是否高亮当前选中节点,默认值是falsedefault-expand-all 是否默认展开所有节点expand-on-click-node
是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。
这段代码使用了 Vue 3 Composition API 中的 defineProps
函数来定义一个名为 props
的响应式属性对象,并通过 withDefaults
函数设置了默认值。
首先,defineProps
函数是用于定义组件接收的 props 属性的函数。<TreeFilterProps>
泛型表示这个组件接收的 props 类型,即传递给组件的数据类型。这个函数返回一个 props 对象,其中每个属性都对应着组件中接收的 prop 属性。在这个例子中,这个 props 对象是空的,也就是说这个组件没有接收任何 props 属性。
接下来,withDefaults
函数是一个辅助函数,用于设置默认值。此函数接收两个参数:第一个是需要设置默认值的对象,第二个是一个包含默认值的对象。在这个例子中,我们将 defineProps<TreeFilterProps>()
返回的空对象作为第一个参数传入,表示我们需要给这个对象设置默认值;然后,我们将一个包含默认值的对象作为第二个参数传入,其中 id: 'id'
表示默认的 id
属性是 'id'
,label: 'label'
表示默认的 label
属性是 'label'
,multiple: false
表示默认的 multiple
属性是 false
。
因此,这段代码的作用是定义一个响应式的 props
属性对象,并为其设置默认值。如果在父组件中没有传递对应的 props 属性,则会使用默认值。
defineProps<TreeFilterProps>()
是一个函数调用,它将 props
对象的预期类型定义为 TreeFilterProps
。它返回一个所有属性均设置为 undefined
的对象。
withDefaults()
是一个函数,它接受两个参数:一个具有可选属性的对象(在这种情况下是 props
),以及一个具有默认值的对象(在这种情况下是 { id: 'id', label: 'label', multiple: false }
)。它将这两个对象合并在一起,如果有任何冲突,则优先使用默认值。
生成的 props
对象将具有与 TreeFilterProps
相同的形状,还包括额外的属性 id
、label
和 multiple
,如果未提供,则将设置为它们的默认值。
defaultProps
是另一个对象,用于设置 children
和 label
属性的默认值。它使用与 props
相同的 label
值,除非提供了不同的值。此对象的目的是为可能未由组件的调用者指定的 props 提供回退值。
ref<T>
是一个函数,它创建对类型为 T
的值的响应式引用。常量 treeRef
被赋予一个 ElTree
类型实例的引用,该类是由 Element-Plus UI 库提供给 Vue.js 的组件之一。此引用可用于访问 ElTree
实例的属性和方法。
treeData
和 treeAllData
常量也是使用 ref
创建的。它们都被赋予了一个对象数组的空数组,其中包含键值对,其中键的类型为 string
,值的类型为 any
。这些引用可用于存储将由 ElTree
组件动态呈现的数据。
在 setSelected
函数中,首先检查了 props.multiple
参数是否为 true,如果是的话则判断 props.defaultValue
是否为数组,如果是则将其赋值给 selected.value
变量,否则将其包装成一个数组并赋值给 selected.value
变量。
接着,如果 props.multiple
参数不为 true,则判断 props.defaultValue
是否为字符串类型,如果是的话直接将其赋值给 selected.value
变量,否则将 selected.value
变量赋值为空字符串。
后端项目打包部署
在控制台中执行mvn clean package
命令把项目打成一个jar包,在控制台日志中看到如下信息表明打包成功
使用XShell6 ssh客户端连接软件登录自己的Linux云服务器,执行cd /usr/local
命令进入/usr/local目录
执行mkdir logs命令创建日志文件夹
给项目启动和关闭bash脚本文件授予读、写和执行权限
chmod 775 startup.sh stop.sh
执行vim ./conf/nginx.conf
命令修改nginx.conf配置文件
$ npm install --save vue3-eventbus
import eventBus from 'vue3-eventbus'
app.use(eventBus)
// Button.vue
import bus from 'vue3-eventbus'
export default {setup() {bus.emit('foo', { a: 'b' })}
}
mitt
export default class EventBus {constructor() {this.event = {}}
}
组合式API介绍
Vue3提供两种组织代码逻辑的写法:
通过data、methods、watch 等配置选项组织代码逻辑是
选项式(Options) API
写法
<template><p>计数器:{{ count }}</p><button @click="increment">累加</button>
</template><script>
import { ref } fro 'vue'
export default {setup () {// 打印undefinedconsole.log(this)// 定义数据和函数const count = ref(0)const increment = () => {count.value++}// 返回给模板使用return { count , increment}}
}
</script>
在
setup
中通过Vue
提供的内置函数组合,实现代码功能,就是Composition API
写法。Composition API
有什么好处?可复用,可维护。setup
函数是Vue3
特有的选项,作为组Composition API
的起点。函数中
this
不是组件实例,是undefined
。如果数据或者函数在模板中使用,需要在
setup
返回。在Vue3的
Composition API
项目中几乎用不到this
, 所有的东西通过函数获取。
<template><p>计数器:{{ count }}</p><button @click="increment">累加</button>
</template><script setup>
import { ref } from 'vue'
// 定义数据和函数
const count = ref(0)
const increment = () => {count.value++
}
</script>
在
script setup
中声明的变量都可以在模板使用,数据,函数,组件。不需要export default。
不需要return。
Vue3 的 setup
中无法使用 this
这个上下文对象,但是如果我想使用 this
上的属性和方法应该怎么办呢。虽然不推荐这样使用,但依然可以通过 getCurrentInstance
方法获取上下文对象:
注意
ctx
只能在开发环境使用,生成环境为 undefined 。 推荐使用 proxy
,在开发环境和生产环境都可以使用。
<script setup>
import { getCurrentInstance } from 'vue'
// 以下两种方法都可以获取到上下文对象
const { ctx } = getCurrentInstance()
const { proxy } = getCurrentInstance()
</script>
<template><p>计数器:{{ count }}</p><button @click="increment">累加</button>
</template><script setup>
import { ref } from 'vue'
// 创建响应式数据对象
const count = ref(0)
const increment = () => {// 在JS中使用的时候需.valuecount.value++
}
</script>
ref
可以把简单数据或者复杂数据转换成响应式数据,注意
JS中使用加上
.value
,template模板中使用省略
.value
reactive
函数通常定义复杂类型的响应式数据,不能使用简单的数据类型。
<template><div><p>姓名:{{state.name}}</p><p>年龄:{{state.age}}</p><button @click="addAgeHandle">+1</button></div>
</template><script>
import { reactive } from "vue";
// 创建响应式数据对象
const state = reactive({ name: 'xxx', age: 25 })
const addAgeHandle = () => {state.age++
}
</script>
<template><div><p>姓名:{{state.name}}</p><p>年龄:{{state.age}}</p><button @click="addAgeHandle1">+1</button><button @click="addAgeHandle2">+1</button></div>
</template><script>
import { reactive, ref } from "vue";
// 创建响应式数据对象
let user1 = reactive({ name: 'xxx', age: 25 })
let user2 = ref({ name: 'xxx', age: 25 })
const addAgeHandle = () => {state.age++
}// 下面是使用的对比
// 💥💥 重新赋值会导致响应式丢失
user1 = {name:'xxx',age: 123
}// 👍👍 重新赋值不会导致响应式丢失
user2.value = {name:'xxx',age: 123
}
</script>
import { ref, reactive, isRef } from 'vue'
const count = ref(1)
const userInfo = reactive({name:'xxx',age: 18
})
const plusOne = computed(() => count.value + 1)
const plusUserInfo = computed(() => userInfo)
console.log(plusOne.value) // 2
// 也就是说,即使返回值写的是reactive对象,最后也会被转换为响应式的ref对象
console.log(isRef(plusUserInfo)) // true
plusOne.value++ // 错误
const count = ref(1)
const plusOne = computed({get: () => count.value + 1,set: (val) => {count.value = val - 1}
})plusOne.value = 1
console.log(count.value) // 0
作用:与Vue2相同
语法:
computed( () => 返回一个新的值 )
注意:💥回调函数一定要有返回值
watch(基本数据,(newValue, oldValue) => { })
watch(复杂数据,(newValue, oldValue) => { }, {deep: true, immediate: true})
watch( [数据1, 数据2], (newValue, oldValue) => { })
watch( () => 复杂数据.属性名, (newValue, oldValue) => { } )
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
const count = ref(0)
const user = reactive({name:'chengang',age: 28
})
// 监听基本数据类型
watch(() => count, (newval, oldval) => {console.log(newval, oldval)
})
watch(() => count.value, (newval, oldval) => {console.log(newval, oldval)
})
watch(count, (newval, oldval) => {console.log(newval, oldval)
})
// 监听复杂数据类型
watch(user, (newval, oldval) => {console.log(newval, oldval)
}, { deep: true, immediate: true })
// 监听多个数据
watch([count, user], (newval, oldval) => {console.log(newval, oldval)
}, { deep: true})
// 监听对象的属性
watch(() => user.age, (newval, oldval) => {console.log(newval, oldval)
})
</script>
选项式API下的生命周期函数使用 | 组合式API下的生命周期函数使用 |
---|---|
beforeCreate | 不需要(直接写到setup函数中) |
created | 不需要(直接写到setup函数中) |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroyed | onBeforeUnmount |
destroyed | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
创建 ref:
const xxxRef = ref()
绑定ref属性到标签上:
ref=“xxxRef”
通过
xxxRef.value
访问dom
<template><!-- 2. 绑定到标签ref属性上,注意:名字要和script定义的保持一致 --><input type="text" ref="inputRef" />
</template><script setup lang="ts">
import { ref, onMounted } from 'vue'
// 1. 调用ref函数
const inputRef = ref(null)
const focusHandle = () => {inputRef.value.focus()
}
onMounted(() => {focusHandle()
})
</script>
子组件内, 调用
defineExpose()
暴露数据和方法,defineExpose方法无需导入即可使用
<template><Child ref="childRef"/><button @click="handleClick">点击</button>
</template><script lang="ts" setup>
import Child from './components/child1.vue'
import { ref } from 'vue'
const childRef = ref()
const handleClick = () => {childRef.value?.countAddHandle()
}
</script>
父传子-defineProps函数
<template><Child :msg="msg"/>
</template><script lang="ts" setup>
import Child from './components/child1.vue'
import { ref } from 'vue'
const msg = ref('hello world')
</script>
<script lang="ts" setup>
import { computed } from 'vue'
// 使用Vue提供的方法增加对类型的声明
import type { PropType } from 'vue'
type User = {username: string,age: number,address: string
}
// 第一种写法
const props = defineProps({msg: {type: String,default:''},userList: {type: Array as PropType<User[]>,default: () => []},userInfo: {type: Object as PropType<User>,default: () => {}}
})
// 第二种写法withDefaults可以赋予prop默认值,但是有一点需要注意的是,类型的声明不能使用d.ts,此为Vue3尚未解决的issur
interface ChildProps {foo: stringuserList: User[],userInfo: User
}
const props = withDefaults(defineProps<ChildProps>(), {foo: '1',// 复杂数据类型的默认值需要是函数类型userList: () => [{ username:'admin', age: 18, address:'湖北'}],userInfo: () => {return { username:'', age: 19, address:''}}
})
</script>
子传父-defineEmits函数
使用步骤:
子组件通过
defineEmits
获取emit
函数(因为没有this)子组件通过
emit
触发事件,并且传递数据父组件,监听自定义事件
语法:
const emit = defineEmits(["自定义事件名1", "自定义事件名2"])
emit("自定义事件名", 值)
<template><Child :count="count" @add-count="count++" @reduce-count="count--" />
</template><script lang="ts" setup>
import Child from './components/child1.vue'
import { ref } from 'vue'
const count = ref(0)
</script><template><p>{{ count }}</p><button @click="addHandle">+1</button><button @click="reduceHandle">+1</button>
</template><script lang="ts" setup>
// 使用defineEmits的方式1
const emits = defineEmits<{// 向上传递参数(e:'add-count', value?: number): void,(e:'reduce-count'): void
}>()
// 使用defineEmits的方式二
const emits = defineEmits(['add-count', 'reduce-count'])
defineProps({count: {type: Number,default:0}
})
// 增加
const addHandle = () => {emits('add-count')
}
// 减少
const reduceHandle = () => {emits('reduce-count')
}
</script>
<template><p>{{ count }}</p><button @click="addHandle">+1</button><button @click="reduceHandle">+1</button>
</template><script lang="ts" setup>
// 使用defineEmits的方式1
const emits = defineEmits<{// 向上传递参数(e:'add-count', value?: number): void,(e:'reduce-count'): void
}>()
// 使用defineEmits的方式二
const emits = defineEmits(['add-count', 'reduce-count'])
defineProps({count: {type: Number,default:0}
})
// 增加
const addHandle = () => {emits('add-count')
}
// 减少
const reduceHandle = () => {emits('reduce-count')
}
</script>
保持响应式-toRef/toRefs函数
toRef/toRefs用于解构响应式数据,因为如果直接解构响应式数据会使其失去响应性。
toRef返回的值是否具有响应性取决于被解构的对象本身是否具有响应性。响应式数据经过toRef返回的值仍具有响应性,非响应式数据经过toRef返回的值仍没有响应性。
将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用
toRef()
创建的。toRef
基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。toRefs
亦然。
<template><p>{{ name }}</p><p>{{ password }}</p><p>{{ user }}</p><button @click="changeHandle">修改name属性</button>
</template><script lang="ts" setup>
import { reactive, toRef, toRefs } from 'vue'
const user = reactive({name: 'chengang',password: 'admin123'
})
// 每次拿出一个
const name = toRef(user, 'name')
// 每次解构对象上全部的属性,可以只取部分
const { password } = toRefs(user)
// 这里特别需要注意:通过toRef/toRefs解构出来的新的响应式对象,值的改变会影响原来的响应式对象,换言之,他俩相互影响
const changeHandle = () => {password.value = 'xxxxxxx'name.value = 'this is changed'
}
</script>
unref语法糖
如果参数是 ref,则返回内部值,否则返回参数本身。
在Vue3中,由于认为$on
, $off
,$emit
这三个方法不应该由Vue提供,因此被移除掉了,需要使用类似功能的话推荐下面的库。
// utils/mitt.js
import mitt from 'mitt'
import type { Emitter } from 'mitt'
type Events ={// 事件名称:传递的参数类型change: string
}
const emitter: Emitter<Events> = mitt()
export default emitter
// utils/mitt.ts
import mitt, { Emitter } from 'mitt'type Events = { // 事件名称:传递的参数类型 change: string
}const emitter: Emitter<Events> = mitt()
export default emitter
<template><div class="app"><Child1 /><Child2 /></div>
</template><script lang="ts" setup>
import Child1 from './components/child1.vue'
import Child2 from './components/child2.vue'
</script><template><button @click="changeHandle">触发修改组件2的msg</button>
</template><script lang="ts" setup>
import mitt from '@/utils/mitt'
const changeHandle = () => {mitt.emit('change', '这个是组件2需要修改的内容')
}
</script><template><p>{{ msg }}</p>
</template><script lang="ts" setup>
import mitt from '@/utils/mitt'
import { ref } from 'vue'
const msg = ref('hello world')
mitt.on('change', (value) => {msg.value = value
})
</script>
v-model语法糖
Vue2中的
v-model
语法糖 完整写法原生标签,
:value="count"
和@input="count=$event.target.value"
组件标签,
:value="count"
和@input="..."
Vue3中的
v-model
语法糖 完整写法原生标签,
:value="count"
和@input="count=$event.target.value"
组件标签,
:modelValue="count"
和@update:modelValue="..."
Vue2->Vue3为什么要这么改变?
Vue3 中组件标签上,可以使用多个
v-model
语法糖Vue3中已废弃使用
.sync
语法,一律使用v-model
<input v-model="msg" type="text">
<!-- 等价 -->
<input :value="msg" @input="msg=$event.target.value" type="text"><Demo v-model="count"></Demo>
<!-- 等价 -->
<Demo :modelValue="count" @update:modelValue="count=$event"></Demo><Demo v-model:count1="count1" v-model:count2="count2"></Demo>
<!-- 等价 -->
<Demo :count1="count" @update:count1="count1=$event" :count2="count" @update:count2="count2=$event"></Demo>
useAttrs的使用
透传属性,所有未经props定义的属性都归纳到此处。使用useAttrs在setup语法中访问透传的属性。
<template><div class="app"><Child name="name" msg="hello world" demo="123" /></div>
</template><script lang="ts" setup>
import Child from './components/child.vue'
</script><!-- 同一个.vue文件中可以同时书写两个script,一个用来书写setup语法的代码,一个用来使用Options API的属性来增加属性 -->
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({// 关闭透传inheritAttrs: false
})
// export default {
// inheritAttrs: false
// }
</script>
child.vue
<template><!-- 在模板中使用通过$attrs即可 --><h1 v-bind="$attrs">透传属性$attrs</h1>
</template><script lang="ts" setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
// 在setup中使用
console.log(attrs, '在setup中使用')
// 通过v-bind="attrs"将父祖组件传递过来的未经props定义的属性赋给元素或者组件
</script>
Vue3的slot
具名插槽
<template><div><slot name="header"><h1>这里是header的默认内容</h1></slot><!-- default插槽可以写name="default",也可以不用写,默认都会归纳到default插槽 --><slot name="default"></slot><slot name="footer"></slot></div>
</template>
<template><div class="app"><Child><!-- 具名插槽使用的时候,名称要作用到template上,除非是default --><template #header><p>header的内容</p></template><!-- #和v-slot写法都可以 --><template #default><p>默认的内容</p></template><!-- 或者default插槽直接放内容也可以 --><!-- <p>默认插槽的内容</p> --><!-- #和v-slot写法都可以 --><template v-slot:footer><p>footer的内容</p></template></Child></div>
</template>
动态插槽
<base-layout><template v-slot:[dynamicSlotName]>...</template><!-- 缩写为 --><template #[dynamicSlotName]>...</template>
</base-layout>
<template><div class="app"><!-- 这里需要注意一下的是,Vue2的作用域插槽是通过slot-scope="scope"来取值的,但是在Vue3中,是通过v-slot --><!-- 如果是取具名插槽的作用域值,需要使用v-slot:插槽名称="变量名称",默认插槽是v-slot="变量名称" --><Child6 v-for="item in userList" :user="item" :key="item.username"><template v-slot="{ age, username, address }">{{ age }} - {{ username }} - {{ address }}</template></Child6></div>
</template><script lang="ts" setup>
import Child6 from './components/child6.vue'
import { ref } from 'vue'
type User = {username: string,age: number,address: string
}
const userList = ref<User[]>([{ username: '用户1', age: 24, address: '地址1' },{ username: '用户2', age: 45, address: '地址2' },{ username: '用户3', age: 27, address: '地址3' },
])
</script>
<template><div class="child"><slot :username="user.username" :age="user.age" :address="user.address"></slot><!-- 全部绑定所有的数据,使用下面的方法 --><!-- <slot v-bind="user"></slot> --></div>
</template><script lang="ts" setup>
import type { PropType } from 'vue'
type User = {username: string,age: number,address: string
}
defineProps({user: {type: Object as PropType<User>,default: () => {}}
})
</script>
移除了
$children
属性,官方建议访问子组件的时候使用$refs
,需要注意的是,在setup语法中使用子组件的数据需要defineExpose导出才能使用。Vue3移除了
.sync
语法,一律使用v-model
pinia 中只有 state、getter、action,抛弃了 Vuex 中的 Mutation,Vuex 中 mutation 一直都不太受小伙伴们的待见,pinia 直接抛弃它了,这无疑减少了我们工作量。
pinia 中 action 支持同步和异步,Vuex 不支持
良好的 Typescript 支持,毕竟我们 Vue3 都推荐使用 TS 来编写,这个时候使用 pinia 就非常合适了
无需再创建各个模块嵌套了,Vuex 中如果数据过多,我们通常分模块来进行管理,稍显麻烦,而 pinia 中每个 store 都是独立的,互相不影响。
体积非常小,只有 1KB 左右。
pinia 支持插件来扩展自身功能。
支持服务端渲染。
安装 Pinia
shell
npm install pinia -S
在 main.js 中引入 Pinia
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';const app = createApp(App);app.use(createPinia());
app.mount('#app');
使用 defineStore() 定义一个 Store 。defineStore() 第一个参数是 storeId,第二个参数是一个选项对象:
// src/stores/counter.js
import { defineStore } from 'pinia';export const useCounterStore = defineStore('counter', {state: () => ({ count: 0 }),getters: {doubleCount: (state) => state.count * 2,},actions: {increment() {this.count++;},},
});
defineStore() 第一个参数是 storeId ,第二个参数传入一个函数来定义 Store :
// src/stores/counter.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', () => {const count = ref(0)const doubleCount = computed(() => count.value * 2)function increment() {count.value++}return { count, doubleCount, increment }
})
在组件中使用store
<script setup>
import { useCounterStore } from '@/stores/counter'const counterStore = useCounterStore()
// 以下三种方式都会被 devtools 跟踪
counterStore.count++
counterStore.$patch({ count: counterStore.count + 1 })
counterStore.increment()
</script><template><div>{{ counterStore.count }}</div><div>{{ counterStore.doubleCount }}</div>
</template>
prettier用来美化代码的结构,eslint用来检测代码的质量,但是二者一起使用会产生冲突,因此需要安装下面的这些依赖。
npm install prettier eslint eslint-plugin-prettier eslint-config-prettier eslint-define-config --save-dev
创建.prettierignore文件
忽略对指定文件的代码格式化
/dist/*
.local
.output.js
/node_modules/****/*.svg
**/*.sh/public/*
创建.eslintignore文件
忽略对指定文件的代码质量的检测
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile
创建.eslintrc.js
文件
// @ts-check
const { defineConfig } = require('eslint-define-config');module.exports = defineConfig({root: true,env: {browser: true,node: true,es6: true,},parser: 'vue-eslint-parser',parserOptions: {parser: '@typescript-eslint/parser',ecmaVersion: 2020,sourceType: 'module',jsxPragma: 'React',ecmaFeatures: {jsx: true,},},extends: ["eslint:recommended","plugin:vue/vue3-essential","plugin:@typescript-eslint/recommended","prettier",],rules: {'vue/script-setup-uses-vars': 'error','@typescript-eslint/ban-ts-ignore': 'off','@typescript-eslint/explicit-function-return-type': 'off','@typescript-eslint/no-explicit-any': 'off','@typescript-eslint/no-var-requires': 'off','@typescript-eslint/no-empty-function': 'off','vue/custom-event-name-casing': 'off','no-use-before-define': 'off','@typescript-eslint/no-use-before-define': 'off','@typescript-eslint/ban-ts-comment': 'off','@typescript-eslint/ban-types': 'off','@typescript-eslint/no-non-null-assertion': 'off','@typescript-eslint/explicit-module-boundary-types': 'off','@typescript-eslint/no-unused-vars': ['error',{argsIgnorePattern: '^_',varsIgnorePattern: '^_',},],'no-unused-vars': ['error',{argsIgnorePattern: '^_',varsIgnorePattern: '^_',},],'space-before-function-paren': 'off','vue/attributes-order': 'off','vue/v-on-event-hyphenation': 'off','vue/multi-word-component-names': 'off','vue/one-component-per-file': 'off','vue/html-closing-bracket-newline': 'off','vue/max-attributes-per-line': 'off','vue/multiline-html-element-content-newline': 'off','vue/singleline-html-element-content-newline': 'off','vue/attribute-hyphenation': 'off','vue/require-default-prop': 'off','vue/html-self-closing': ['error',{html: {void: 'always',normal: 'never',component: 'always',},svg: 'always',math: 'always',},],},
});
创建prettier.config.cjs文件(在vite的项目里面,注意是cjs后缀)
特别需要注意的是,由于vite设置了"type": "module"
后你的所有js文件默认使用ESM模块规范,不支持commonjs规范,所以必须显式的声明成xxx.cjs才能标识这个是用commonjs规范的,因此需要把你的配置文件都改成.cjs后缀,例如prettier的配置文件。
// prettier.config.js
module.exports = {// 一行最多多少个字符printWidth: 120,// 指定每个缩进级别的空格数tabWidth: 2,// 使用制表符而不是空格缩进行useTabs: true,// 在语句末尾是否需要分号semi: false,// 是否使用单引号singleQuote: true,// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"quoteProps: 'as-needed',// 在JSX中使用单引号而不是双引号jsxSingleQuote: false,// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认nonetrailingComma: 'none',// 在对象文字中的括号之间打印空格bracketSpacing: true,// jsx 标签的反尖括号需要换行jsxBracketSameLine: false,// 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => xarrowParens: 'always',// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码rangeStart: 0,rangeEnd: Infinity,// 指定要使用的解析器,不需要写文件开头的 @prettierrequirePragma: false,// 不需要自动在文件开头插入 @prettierinsertPragma: false,// 使用默认的折行标准 always\never\preserveproseWrap: 'preserve',// 指定HTML文件的全局空格敏感度 css\strict\ignorehtmlWhitespaceSensitivity: 'css',// Vue文件脚本和样式标签缩进vueIndentScriptAndStyle: false,//在 windows 操作系统中换行符通常是回车 (CR) 加换行分隔符 (LF),也就是回车换行(CRLF),//然而在 Linux 和 Unix 中只使用简单的换行分隔符 (LF)。//对应的控制字符为 "\n" (LF) 和 "\r\n"(CRLF)。auto意为保持现有的行尾// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"endOfLine: 'auto'
}
在package.json中配置一个scripts:
"prettier": "prettier --write ."
执行下面的命令,将当前所有的文件按照prettier进行格式化
npm run prettier
Mac系统中使用Command + Shift + P
打开
版本较低的浏览器不支持ES6的语法和新API,而Babel默认只转换新的JavaScript句法,不转换新的API,比如Proxy、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法都不会转码。
安装插件:npm i @vitejs/plugin-legacy -D
在vite.config.js中配置
// vite.config.js
import legacy from '@vitejs/plugin-legacy'export default {plugins: [legacy({// 需要兼容的目标列表,可以设置多个targets: ['defaults', 'ie >= 11', 'chrome >= 52'], additionalLegacyPolyfills: ['regenerator-runtime/runtime'],renderLegacyChunks:true,// 下面的数组可以自定义添加低版本转换的方法polyfills:['es.symbol','es.array.filter','es.promise','es.promise.finally','es/map','es/set','es.array.for-each','es.object.define-properties','es.object.define-property','es.object.get-own-property-descriptor','es.object.get-own-property-descriptors','es.object.keys','es.object.to-string','web.dom-collections.for-each','esnext.global-this','esnext.string.match-all']})]
}
打包过程中使用@babel/preset-env转换浏览器不支持的语法和新API,为包中的每个块生成相应的转化块;
生成一个包含 SystemJS 运行时的 polyfill 块;
在打包文件中生成带有legacy名的文件,每个js脚本文件都有一个与其对应的转化版本;
html文件中新增了一些script-nomodule脚本,这些脚本根据浏览器的支持程度来动态的引入正常版本文件还是带有legacy字样的转化版本文件
加群联系作者vx:xiaoda0423
仓库地址:https://github.com/webVueBlog/WebGuideInterview