Vue3 文档学习笔记
vue3学习笔记
setup()
生命周期的与vue2的不同点在:beforeCreated
、created
都在setup()
里进行默认调用,其他的都要写在setup()
里面,算做compositionAPI
。
props
由父组件传过来的值会自动被响应式,不用再去reactive包一层(注意不能在子组件里直接修改props的值)。
setup()
的第一个参数是props
(不可进行解构,否则会丢失双向绑定),第二个参数是context
,有3个值 attrs
,slots
,emit
,(可进行解构)
props: {
name: String,
num: Number
},
setup (props, ctx) {
props.name // 获取props的name的值
ctx.emit('add', value) // 在子组件中使用emit像父组件传递值,与vue2中的this.$emit()有较大区别
}
watch()
监听两个回调函数,第一个函数return
你要监听数据,第二个函数有参数new Value
,然后执行你的操作。
reactive, ref
return
出去的ref
数据在template
里不用.value
去调用。
如果将一个对象通过ref
创建,那么会通过reactive
进行创建。
const person = ref({
name: '李四',
info: {
age: 11,
height: 174
}
})
console.log(person.value) // 会打印一个Proxy对象,说明是通过reactive创建的
如果在reactive
里访问/修改ref
,它会自动展开ref
,也就是自动给你.value
const count = ref(0)
const state = reactive({
count
})
console.log(state.count)
如果在reactive
里放的是Array
或者原生集合(比如Map
),他们取值都必须加上.value
,不会自动展开
const arr = reactive([ref(100),2])
console.log(arr[0].value)
unref
如果是ref
对象就返回通过reactive
创建的数据,如果是普通对象,就直接返回
const info = {
name: '李四',
age: 22
}
const info2 = ref({
name: '李四',
age: 22
})
console.log(unref(info)) // {name: '李四', age: '22'}
console.log(unref(info2)) // Proxy{name: '李四', age: '22'}
// unref就等同于 isRef(info) ? info.value : info
toRef
可以通过toRef
把reactive
响应源数据(source property
)里的值单独拿出来使用。转引用一下,用的对方不多。官方示例是对自己写的composition API
进行传值。
const toRef1 = reactive({
name: 'xx',
age: 11
})
const nameRef = toRef(toRef1, 'name')
nameRef.value = '徐忠炜'
console.log(toRef1.name) // 徐忠炜
toRefs
获取源数据所有数据。在template
里直接写属性名就可以使用
<template>
<h1> {{count}} </h1>
</template>
export default {
name: 'App',
setup(props, ctx) {
const state = reactive({
count: 4
})
return {
...toRefs(state)
}
}
}
customRef
用于防抖节流(图片懒加载、边输入边请求接口的场景)。
computed (API)
跟vue2一样的用法
// 这里用的get
const hello = ref('先生,欢迎来到红浪漫会所。')
const xiaogege = computed(() => {
return `徐忠炜${hello.value}`
})
console.log(xiaogege.value) // 徐忠炜先生,欢迎来到红浪漫会所。
readonly
让响应式数据或者普通对象都只读,并且是深层的(DEEP)
const read = reactive({
a:1,
b: {
c: 3,
d: [3,4,5,6]
}
})
const readOnly = readonly(read)
console.log(readOnly.a) // 1
readOnly.a = 3 // Set operation on key "a" failed: target is readonly.
watchEffect
watchEffect
会立即执行(在onBeforeMount之前),并响应式的追踪当前watchEffect
里的依赖(数据),并且只要数据改变了它就重新执行一次。当watchEffect
写在setup()
或生命周期
里时,在该组件被销毁时它会自动停止监听。
watchEffect
还返回一个stop,用于我们手动停止监听。
const stop = watchEffect(() => {
// do something
})
// later
stop()
Side Effect Invalidation(副作用失效时)
在watchEffect
即将重新执行/stop时会执行一次onInvalidate
方法,watchEffect
接收一个onInvalidate
参数,其实onInvalidate
是一个函数,这个函数的执行顺序是先与watchEffect
里面其他所有的程序。
const count = ref(0)
count.value = 1
const stop = watchEffect((onInvalidate) => {
console.log(count.value)
onInvalidate(() => {
console.log('onInvalidate is triggered')
})
})
// 打印结果
// 0
// onInvalidate is triggered
// 1
2020-12-03 听至 21p https://www.bilibili.com/video/BV1Q54y1k7At?p=21
watchEffect
刷新时间是在所有组件刷新之前调用。也就是执行顺序基本在最前面。
watchEffect
的第二个对象(OPTIONS),默认是pre
,还有post
,sync
。具体解释看下面
watchEffect(() => {
console.log(count.value)
}, {
flush: 'pre' // 呼应上面的话,pre是默认在所有组件刷新之前调用
flush: 'post' // post是默认在所有组件刷新之后调用,第一次在onBeforeMount之后调用(onMounted之前),之后改变在onbeforeUpdate之后调用(onUpdate之前)
flush: 'sync' // 同步执行
})
watchEffect
第一次执行时是在mounted
之前,如果你要操作DOM
或者Template refs
请把watchEffect
放在onMounted
生命周期里
<template>
<h1 ref='myRef'></h1>
</template>
export default {
name: 'App',
setup(props, ctx) {
const myRef = ref(null)
watchEffect(() => {
console.log(myRef.value)
})
return {
myRef
}
}
}
//打印结果
// null
// <h1></h1> (dom)
// 如果放在onMounted里面
onMounted(() => {
watchEffect(() => {
console.log(myRef.value)
})
})
// 打印结果
// <h1></h1> (dom)
watch
watch
必须监听一个数据,还得有一个回调函数去处理数据
watch(() => {
return count.value
}, (newValue, oldValue) => {
// 源数据改变才执行(懒执行)
})
watch监听单一数据有两种写法
// watching a getter
const state = reactive({count: 0})
watch(
() => state.count,
(newValue, oldValue) => {
// do something!
}
)
// watch a ref
const count = ref(0)
watch(count, (newValue,oldValue) => {
// do something!
})
watch监听多个源数据
const foo = ref(1)
const bar = ref(2)
setTimeout(() => {
foo.value = 11
bar.value = 22
}, 2000)
// 监听要加上.value
watch(() => [foo.value, bar.value], ([newFoo, newBar], [oldFoo, oldBar]) => {
// do something!
})
watch
的执行顺序和watchEffect
一样,配置参数也一样,详情往前翻。
isProxy
只有reactive
和readonly
创建的对象才能被isProxy
验证为true
即使new Proxy
创建的也为false*
isReactive
一个readonly
对象用isReactive
验证为false
const plain = readonly({
name: 'Mary'
})
console.log(isReactive(plain)) // false
如果将一个reactive
对象用readonly
再包一下,那么验证为true
const state = reactive({
name: 'John'
})
const stateCopy = readonly(state)
console.log(isReactive(stateCopy)) // true
shallowReactive
很简单,就是浅的响应式,说明嵌套的对象不会被代理,也就是没有响应式
markRaw
生命周期
生命周期函数只能用在setup()
里,并且是同步的。
Options API LifeCycle
与 Composition API LifeCycle
的映射关系
Options API LifeCycle | Composition API LifeCycle |
---|---|
beforeCreate | setup() |
created | setup() |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
update | onUpdate |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
setup()
代替了beforeCreate
和create
钩子,之前要写在他们里的东西现在只需写在setup()
里面。
建议在onMounted
里请求数据。
组件卸载时可在onbeforeUnmount
和onUnmounted
里处理一些事件。
在onErrorCaptured
里捕获子孙组件的异常错误,也就是可以在父组件看到子组件的错误。
onRenderTracked
函数里写debugger
即可进入调试模式(组件第一次render时)。
onRenderTriggered
函数里写debugger
即可进入调试模式(rerender时)。
provide/inject (option API)
通常,父子组件传值我们用props。但想象一种情况,当有嵌套比较深的子组件时,比如是个孙组件,那么我们传值必须通过父 -> 子 -> 孙,这样非常麻烦。
为了解决这个情况,我们可以使用provide
和inject
。
举个例子:
Root
TodoList
TodoItem
TodoListFooter
ClearTodoButton
TodoListStatistics
如果我们想把数据从TodoList
传到TodoListStatistics
,需经过TodoList
->TodoListFooter
->TodoListStatistics
,但使用provide/inject,我们可以这样做:
// 祖先组件
export default {
name: 'TodoList',
provide: {
name: '徐忠炜'
}
}
// 孙组件
<template>
{{ name }}
</template>
export default {
name: 'TodoListStatistics',
inject: ['name']
}
!如果我们尝试传递一些组件的实例(绑定在this上),那么它是不会工作的。
如果实在要传实例,那么可以这样写(其实也更推荐这样写,就跟Vue2的data
一样)
provide() {
return {
name: this.name
}
}
之前这么做,它的数据是不会有响应式的。因为绑定的
provide/inject
默认是不被响应的。在Vue2中我们把数据用computed包起来就可以让它变成响应式
provide() {
return {
name: Vue.computed(() => '张三')
}
}
provide/inject (composition API)
普通用法与optionAPI
的一样
在setup()里使用provide
provide
允许你定义两个参数,第一个是属性名(<string>
),第二个是属性值
setup() {
// 可定义多个
provide('name', '张三')
provide('age', 22)
provide('lover', {
name: '陈诗诗',
xiongwei: '70D'
})
}
在setup()里使用inject
inject
允许你填两个参数,第一个是属性名,第二个是默认值(可选)。使用值时要加.value
setup() {
const name = inject('name')
const age = inject('age', 18)
const lover = inject('lover')
return {
name,
age,
lover
}
}
自带响应式,不用像Vue2里面那样去解决响应式的问题
如果要在inject
的那个子组件去改变传递的数据,最好的解决方式是在父组件provide
一个方法,方法里面去改变那个值
setup() {
const lover = ref('nobody')
const updateLover = () => {
lover.value = null
}
provide('updateLover', updateLover)
}
// 子组件还是一样的方法去获取。完美!
还有,如果你为了杜绝在子组件不小心修改了传递的数据,那么你provide数据的时候给数据加个readonly
provide('name',readonly(name))
2020-12-09学习到 refs https://www.bilibili.com/video/BV1Q54y1k7At?p=36
refs (composition API)
在setup()
里声明一个refs
,然后导出,在视图里进行绑定。
// Child.vue
<template>
<h1> {{ name }} </h1>
<button @click='changeName'>changeName</button>
</template>
setup() {
const child = ref(null)
const name = ref('张三')
const changeName = () => {
console.log(child.value) // 打印的dom
child.value.innerText = '李四'
}
return {
child,
changeName
}
}
// 如果想在父组件调用子组件的方法或数据
// Father.vue
<template>
<child ref='child'></child>
</template>
setup() {
const child = ref(null)
console.log(child.value.name) // 张三
}
遍历生成refs
<div v-for='(item, i) in list' :ref='el => { if (el) divs[i] = el }'></div>
setup() {
const divs = ref([])
onMounted(() => {
console.log(divs.value) // 打印出经过代理的dom节点
})
}
Application Config
Application Config
const app = Vue.createApp({})
app.config = {
...
}
errorHandler
// 基本用不到
app.config.errorHandler = (err, vm, info) => {
// handle error
// 'info' 是Vue的特别的错误信息,比如生命周期呀。
}
warnHandler
// 基本用不到
app.config.warnHandler = (msg, vm, trace) => {
// 在运行时产生的一些警告
// 'trace'
}
global properties
添加一个全局属性,在任何组件实例里都能引用
app.config.globalProperties.foo = 'bar'
app.component('child-component', {
mounted() {
console.log(this.foo) // 'bar'
}
})
就相当于在Vue2.x中,我们往Vue实例上绑东西是这样的
Vue.prototype.$axios = axios
在Vue3中,应该这样做
const app = Vue.createApp({})
app.config.globalProperties.$axios = axios
在组件中调用全局属性
// main.js
app.config.globalProperties.token = 'aofhbioasdlfbnislfbsdkl'
// App.vue
// 获取globalProperties
const instace = getCurrentInstance()
console.log(`全局的token:${instace.ctx.token}`)
isCustomElement
使用方法,不报错的。
// vue.config.js
module.exports = {
vueCompilerOptions: {
isCustomElement: tag => /^jsjiajia-/.test(tag)
}
}
Application API
const app = Vue.createApp({})
component 全局注册组件
app.component('componentName', component)
directive 自定义指令
实例一:不使用自定义指令,实现tab栏切换
<template>
<div class='tab' @click='handleTabClick($event)'>
<button data-index='0' :class='['tab-btn', {'active': curIndex === 0}]'></button>
<button data-index='1' :class='['tab-btn', {'active': curIndex === 1}]'></button>
<button data-index='2' :class='['tab-btn', {'active': curIndex === 2}]'></button>
</div>
</template>
export default {
setup() {
const curIndex = ref(0)
const handleTabClick = e => {
const tar = e.target
const className = tar.className
const index = parseInt(tar.dataset.index)
if (className === 'tab-btn') {
curIndex.value = index
}
}
return {
curIndex,
handleTabClick
}
}
}
实例二:使用自定义指令
<template>
<div class='tab' @click='handleTabClick($event)' v-tab="{ className: 'tab-btn', activeClass: 'active', curIndex}">
<button data-index='0' class='tab-btn'></button>
<button data-index='1' class='tab-btn'></button>
<button data-index='2' class='tab-btn'></button>
</div>
</template>
<script>
import tab from './tab.js'
export default {
directive: {
tab
},
setup() {
const curIndex = ref(0)
const handleTabClick = e => {
const tar = e.target
const className = tar.className
const index = parseInt(tar.dataset.index)
if (className === 'tab-btn') {
curIndex.value = index
}
}
return {
curIndex,
handleTabClick
}
}
}
</script>
// tab.js
export default {
mounted(el,binding) {
// el是指令挂载的对象
// binding对象的value包含了传过来的值 binding.value.activeClass
const { className, activeClass, curIndex } = binding.value
const btns = el.getElementsByClassName(className)
btns[curIndex].className += ` ${activeClass}`
},
update(el,binding) {
const { className, activeClass, curIndex } = binding.value
const { oldIndex } = binding.oldValue
const btns = el.getElementsByClassName(className)
btns[oldIndex].className = className
btns[curIndex].className += ` ${activeClass}`
}
}
上一篇: 断点续传,分片上传
下一篇: 在vue使用Examples