🍛
解决开发时跨域问题
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/dev-api': {
target: 'http://47.97.154.202:1091',
changeOrigin: true,
pathRewrite: { '^/dev-api': '' }
}
}
}
}
静态文件转为 Base64 格式引入
下面是将字体文件,转为 base64 的配置
问题来自 element-ui IE11 部分版本图标不显示
但是转为 base64 就好了
// vue.config.js
module.exports = {
chainWebpack: config => {
const fontsRule = config.module.rule('fonts')
fontsRule.uses.clear()
fontsRule.test(/\.(woff|eot|ttf|otf)(\?.*)?$/i)
fontsRule.use('file-loader')
.loader('url-loader')
.options({
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
})
}
}
Vue3 学习
setup 方法
触发时机位于 beforeCreated 和 created 之间
无法获取 this,也没有必要获取 this
Vue3 相比于 Vue2 新增了 setup 方法,作为初始化方法
将 data、computed、methods、watch、生命周期函数全都整合到这一个方法中定义
但这不是必须的,继续使用 Vue2 的定义方式也是可以的。
import { ref, toRefs } from 'vue';
export default {
props: {
title: {
type: String,
default: '默认值'
}
},
/**
* props 是 "响应式数据",不能使用 ES6 解构,它会消除 prop 的响应性。
* 若非要解构,则需要使用 toRefs 方法将 props 转为普通对象(对象内部的值为 ref 对象)
**/
setup (props, { attrs, slots, emit }) {
const { title } = toRefs(props)
const num = ref(10)
/**
* 只有通过 return 暴露出去的属性和方法才可以在 template 中使用
* 所以 Vue3 开发时感觉最常见的错误应该就是 "忘记写 return 了"
* 不过应该可以通过统一定义到一个对象中,然后在 return 中已解构的方式,一劳永逸
* 待测试开发友好度~~
**/
return { num }
}
}
响应式数据定义
通过 ref 方法或 reactive 方法定义 “响应式数据”
import { ref, reactive } from 'vue';
export default {
setup (props, { attrs, slots, emit }) {
const num = ref(0)
const state = reactive({ num: 0 })
return { num, state }
}
}
/**
* ref 方法用来定义 "简单" 的响应式数据,参数不限制类型
* ref 会将传入的数据,套一层对象,并将数据作为对象的 value 属性
* ref 方法会将内部的引用类型全部转为 "响应式数据"
* ref 内部其实也是用的 reactive
*
* 使用 attr.value 的方式获取值
* 使用 attr.value = any 的方式改变值
*
* shallowRef 与 ref 类似
* 区别是不会将值内部的引用类型的值转为 "响应式数据"
* const data = ref({ obj: { count: 0 } })
* data.value.obj.count = 10
* // => data.value.obj 是响应式数据,obj 中的值改了,页面就会自动刷新
*
* const data = shallowRef({ obj: { count: 0 } })
* data.value.obj.count = 10
* // => data.value.obj 不是响应式数据,obj 中的值虽然改了,但是页面不会自动刷新
**/
const num = ref(0)
/**
* reactive 方法用来定义 "复杂" 的响应式数据
* 参数必须是对象或者数组,通常为对象(对象中再定义数组)
* 比较好的思想是 "抽象" 一个功能,将相关的数据都封装在一起。
* 一旦定义之后,无法改变自身,可以改变内部的属性值。
*
* reactive 不会像 ref 一样套一层 value
* 因为 reactive 的参数只可传入引用类型
* 直接使用 attr.key 的方式获取值
* 使用 attr.key = any 的方式改变值
*
* reactive 和 shallowReactive 的区别
* 区别是不会将值内部的引用类型的值转为 "响应式数据"
* const data = reactive({ obj: { count: 0 } })
* data.value.obj.count = 10
* // => data.value.obj 是响应式数据,obj 中的值改了,页面就会自动刷新
*
* const data = shallowReactive({ obj: { count: 0 } })
* data.value.obj.count = 10
* // => data.value.obj 不是响应式数据,obj 中的值虽然改了,但是页面不会自动刷新
**/
const state = reactive({ num: 0 })
export default {
data () {
return {
num: 0,
state: { num: 0 }
}
}
}
计算属性
概念上没啥大变化。
import { computed } from 'vue';
export default {
setup (props, { attrs, slots, emit }) {
const name = ref('jason')
const fullName = computed(() => {
return `------ ${ name.value } ------`
})
// 可以将计算属性写在 ref 或者 reactive 定义的对象类型 "响应式数据" 中
const state = reactive({
fullName: computed(() => {
return `====== ${ name.value } ======`
})
})
console.log(name.value)
// => jason
console.log(fullName.value)
// => ------ jason ------
console.log(state.fullName)
// => ====== jason ======
return { name, fullName, state }
}
}
/**
* computed 用来定义计算属性
* 和 vue2 的没有任何的不同,都是基于 "响应式数据" 来实时计算结果。
* 也可以传入一个对象,同时设置 get 和 set。
* 注意的是 computed 将返回一个 ref 对象
* 也可以将 computed 写在 ref 和 reactive 定义的中 "响应式数据" 对象中
**/
export default {
data () {
return {
name: 'jason'
}
},
computed: {
fullName () {
return `------ ${ this.name } ------`
}
}
}
方法定义
没啥大变化
import { computed } from 'vue';
export default {
setup (props, { attrs, slots, emit }) {
const submit = () => {
console.log('submit')
}
return { submit }
}
}
/**
* methods 应该是最容易理解的了
* 写在 setup 中,内部的话直接调用即可
* return 出去,就可以在 template 中调用了
**/
export default {
methods: {
submit () {
console.log('submit')
}
}
}
监听器
- 可以监听到数组直接赋值子项的情况了(vue2 中 this.arr[0] = 10 将不会触发 watch)
- 可以同时监听多个 “响应式数据” 了。
- 新增了 onInvalidate 方法,用来在函数即将重新执行时、侦听器被停止时清除异步代码带来的影响。
- 增加了 watchEffect 方法,不用指定需要监听的 “响应式数据”,通过定义的函数内部自动依赖监听项。
- watch 方法的第一个参数,需要理解后使用,否则总会出问题。
import { watch, watchEffect } from 'vue';
export default {
setup (props, { attrs, slots, emit }) {
const num = ref(0)
const state = { num: 0 }
const arr = ref([ 1, 2 ])
// 若监听某 "响应式数据" 的替换型改变(ref.value = any)时,直接传入 ref 就行。
watch(num, (newValue, oldValue, onInvalidate) => {})
// 若 "响应式数据" 为对象,并且想监听对象内部值变化时
// 需要传入一个函数,返回这个对象,并且配置 deep: true
// 注意:此时 oldValue = newValue
watch(() => state, (newValue, oldValue, onInvalidate) => {
// ...
}, { deep: true })
// 若 "响应式数据" 为对象,并且想监听固定某内部值的变化时
// 则需要传入一个函数,返回指定的字段
// 注意:必须深拷贝对象,才能保证 oldValue 值正确
watch(() => _.cloneDeep(state), (newValue, oldValue, onInvalidate) => {})
// 若 "响应式数据" 为对象,并且想监听固定某内部值的变化时
// 则需要传入一个函数,返回指定的字段
watch(() => state.num, (newValue, oldValue, onInvalidate) => {})
// 若想监听的值为数组,则需要传入一个函数,函数中返回数组的克隆副本
// 若数组中含有引用类型,则还是需要深拷贝,否则 oldValue 值错误
watch(() => [ ...arr ], (newValue, oldValue, onInvalidate) => {})
watch(() => _.cloneDeep(arr), (newValue, oldValue, onInvalidate) => {})
// 不需要指定监听的 "响应式数据",内部自动依赖
// 值改变后,重新执行函数
watchEffect((onInvalidate) => {})
}
}
/**
* watch 方法用来监听 "响应式数据" 的变化
* 当指定的 "响应式数据" 变化时,触发回调
*
* 需要传入两个必要参数和一个可选参数
* 1. 指定需要监听的 "响应式数据",可以为数组,代表同时监听多个 "响应式数据"
* 2. 回调函数,调用时将传入 newValue、oldValue、onInvalidate
* 3. 配置项,{ deep: false, immediate: true }
*
* 复杂点在第一个参数上
* - 若监听某 "响应式数据" 的替换型改变(ref.value = any)时,直接传入 ref 就行。
* - 若 "响应式数据" 为对象,并且想监听对象内部值变化时,需要传入一个函数,返回这个对象,并且配置 deep: true
* - 若 "响应式数据" 为对象,并且想监听固定某内部值的变化时,则需要传入一个函数,返回指定的字段
* - 若想监听的值为数组,则需要传入一个函数,函数中返回数组的克隆副本
*
* oldValue
* 若监听值为对象或者数组中包含引用类型,想正确获取 oldValue 值
* 需要在传入的函数中返回深拷贝的副本
* lodash _.cloneDeep 方法可以帮助做到
* 若使用了深拷贝,则不需要 deep: true
*
* onInvalidate
* 函数执行时,将接收 onInvalidate 函数作为入参,
* 需要自行实现
* 在函数即将重新执行时、侦听器被停止时调用
* 主要用来清除异步代码带来的 "副作用"
**/
export default {
data () {
return {
num: 0,
arr: [ 1, 2, 3 ],
state: { num: 0 }
}
},
watch: {
num (newValue, oldValue) {},
state: {
deep: true,
handler (newValue, oldValue) {}
},
'state.num' (newValue, oldValue) {},
// 无法监听 this.arr[0] = 10 的这种情况
arr (newValue, oldValue) {},
}
}
template ref
<template>
<!--
注意:
- ref 不需要动态绑定的写法
- 若元素/组件未渲染,btn = null
-->
<comp-button ref="btn"/>
</template>
export default {
setup (props, { attrs, slots, emit }) {
const btn = ref(null)
return { btn }
}
}
若为数组,动态循环渲染的情况暂时未找到方案…
<template>
<comp-button ref="btn"/>
</template>
export default {
mounted () {
console.log(this.$refs.btn)
}
}
emits
这是 Vue3 新增的 “声明属性”,
类似于 props, 在子组件中提前声明内部向父组件发出的事件列表,
并且还可以校验 emit 时参数是否正确,若不正确 Vue 将发出警告,
不一定非要定义,向 Vue2 一样,不定义,也可以 emit。
export default {
emits: {
// emit('submit', { name: 'jason' })
submit: function (payload) {
return typeof payload.name === 'string'
},
// 当然可以不指定校验
quit: null
}
}
v-model
可以定义多个 v-model 了,弃用了 sync 修饰符语法糖
<template>
<div>
<!--
在 comp-dialog 组件内部
使用 emit('update:title', newValue) 的方式传递事件
其中,默认的 v-model prop 名字为 value
-->
<comp-dialog
v-model="show"
v-model:title="text"
v-model:content="content"/>
</div>
</template>
export default {
setup (props, { attrs, slots, emit }) {
const show = ref(false)
const text = ref('我是标题')
const content = ref('我是内容')
return { show, text, content }
}
}
太简单了,就是 sync 修饰符换成了统一的 v-model 指令写法,
并且好像之前的 sync 串用不了向 trim 这种修饰符,而 v-model 可以,
算是增强功能了。
<template>
<div>
<!--
在 comp-dialog 组件内部
使用 this.$emit('update:title', newValue) 的方式传递事件
其中,默认的 v-model prop 名字为 value
-->
<comp-dialog
v-model="show"
:title.sync="text"
:content.sync="content"/>
</div>
</template>
export default {
data () {
return {
show: false,
text: '我是标题',
content: '我是内容'
}
}
}
v-slot
统一使用 v-slot 指令来定义插槽,统一了 slot 和 slot-scope
同时提供了 v-slot 的简写形式(使用了 # 符),就像 v-bind 和 v-on
<!-- comp-dialog -->
<template>
<div>
<slot name="header" text="header"/>
<slot name="default" text="default"/>
<slot name="footer" text="footer"/>
</div>
</template>
<template>
<!-- 简写 v-slot -->
<comp-dialog>
<template #header="{ text }"></template>
<template #default="scope"></template>
<template #footer="{ text }"></template>
</comp-dialog>
<!-- 正常写 v-slot -->
<comp-dialog>
<template v-slot:header="{ text }"></template>
<template v-slot:default="scope"></template>
<template v-slot:footer="{ text }"></template>
</comp-dialog>
</template>
v-slot 指令在 Vue 2.6 就已经有了
但是在 Vue 2.6 之后还可以继续使用 slot 和 slot-scope()
但是在 Vue3 中 slot 和 slot-scope 已经被移除了
<!-- comp-dialog -->
<template>
<div>
<slot name="header" text="header"/>
<slot name="default" text="default"/>
<slot name="footer" text="footer"/>
</div>
</template>
<template>
<comp-dialog>
<template slot="header" slot-scope="{ name }"></template>
<template slot="default" slot-scope="scope"></template>
<template slot="footer" slot-scope="{ name }"></template>
</comp-dialog>
</template>