欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Vue3 文档学习笔记

程序员文章站 2022-05-17 21:03:42
...

vue3学习笔记

setup()

生命周期的与vue2的不同点在:beforeCreatedcreated都在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

可以通过toRefreactive响应源数据(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里面其他所有的程序。

以后慢慢消化SideEffect

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,还有postsync。具体解释看下面

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

只有reactivereadonly创建的对象才能被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 LifeCycleComposition 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()代替了beforeCreatecreate钩子,之前要写在他们里的东西现在只需写在setup()里面。

建议在onMounted里请求数据。

组件卸载时可在onbeforeUnmountonUnmounted里处理一些事件。

onErrorCaptured里捕获子孙组件的异常错误,也就是可以在父组件看到子组件的错误。

onRenderTracked函数里写debugger即可进入调试模式(组件第一次render时)。

onRenderTriggered函数里写debugger即可进入调试模式(rerender时)。

provide/inject (option API)

通常,父子组件传值我们用props。但想象一种情况,当有嵌套比较深的子组件时,比如是个孙组件,那么我们传值必须通过父 -> 子 -> 孙,这样非常麻烦。

为了解决这个情况,我们可以使用provideinject

举个例子:

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}`
    }
}