- context
1). expose
如果我们的 setUp 直接返回了一个 jsx ,这时我么还想同时返回一个方法让父组件使用我们就可以使用 expose,不需要 return 直接可以暴露给父组件使用
- 子组件
import { h, ref } from 'vue'
export default {
setup(props, { expose }) {
const count = ref(0)
const increment = () => ++count.value
expose({
increment
})
return () => h('div', count.value)
}
}
- 父组件
const testRef = ref()
const onClick = () => {
testRef.value.increment?.()
}
return (
<div onClick={onClick}>
<Parent ref={testRef}/>
</div>
)
2). context.emit
使用 context.emit 触发更新的时候要更新的对应的 props 必须是可以直接被赋值的一个属性,(也就是说不能是最外层的 reactive,可是是 reactive 里的某个属性值,也可以是 ref) 否则不会更新 ui ,因为 emit 触发更新传出一个新的值的本质是在父组件拿到这个新的value,然后直接赋值给对应的属性 a = newValue 这种
比如:
- 直接使用 reactive 最外层不会更新 ui
const Parent = defineComponent({
setup() {
const fileList = reactive([])
oMounted(() => {
Object.assign(fileList, ["a", "b"])
})
return () => (
<Child v-model:fileList={fileList}/>
)
}
// 触发 emit 后相当于 fileList = newValue,因为这里的 reactive 是最外层的只能通过 Object.assign 来修改,所以失败不会更新 ui
export const Child = defineComponent({
props: {
fileList: {
type: Array as PropType<string[]>,
default: () => []
}
},
emits: ["update:fileList"],
setup(props, context) {
const handleClick = () => {
console.log(123)
context.emit("update:fileList", ["c", "d", "e"])
}
return () => (
<>
<button onClick={handleClick}>点我</button>
<div>
{props.fileList.map(list => (
<span>{list}</span>
))}
</div>
</>
)
}
})
- ui 可以更新
我们把上面的 reactive 改成一个 ref 或者里面再加一个属性就可以
方式1:使用 ref
const Parent = defineComponent({
setup() {
const fileList = ref([]) // 这里可以直接被赋值,所以可以更新 ui
oMounted(() => {
fileList.value = ["a", "b"]
})
return () => (
<Child v-model:fileList={fileList.value}/>
)
}
// 触发 emit 后相当于 fileList.value = newValue
方式2:reactive 里加一个属性
const Parent = defineComponent({
setup() {
const fileList = reactive({list: []})
oMounted(() => {
fileList.list = ["a", "b"]
})
return () => (
<Child v-model:fileList={fileList.list}/>
)
}
// 触发 emit 后相当于 fileList.list = newValue
- Provide / Inject
Provide 提供一个属性或者方法给其他子孙组件用(要确保Provide 传递的数据是只读的),其他组件可以通过 Inject 使用,注意:Provide 只能在父组件中提供方法,然后inject 在子组件中注入
- symbol.ts
import { InjectionKey, Ref } from "vue"
export const PersonInfoKey: InjectionKey<Ref<PersonId>> = Symbol("PersonId")
- Parent.tsx
import { provide, reactive, readonly, ref } from 'vue'
import {PersonInfoKey} from "./symbol"
setup() {
const personIdRef = ref({personId: ""})
provide(PersonInfoKey, personIdRef)
onMounted(() => personIdRef.value.personId = "111")
return () => (<Child />)
}
- Child.tsx
import { inject } from 'vue'
import {PersonInfoKey} from "./symbol"
setup() {
const personIdRef = inject(PersonInfokey)
console.log(personIdRef.value)
}
注意 provide 和 inject 都要在setup 内部直接使用,不能放在声明周期和异步里使用,
- watchEffect
只有回调里使用了的ref变了才会执行
const testRef = ref(0)
const xRef = ref(0)
watchEffect(() => {
console.log(xRef.value, 'iii')
})
// 等价于
useEffect(() => {}, [xRef.value])
上面的代码只有 xRef.value 变了才会执行
- watch
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// 直接侦听ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
// 侦听多个
const firstName = ref('')
const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues)
})
firstName.value = 'John' // logs: ["John", ""] ["", ""]
lastName.value = 'Smith' // logs: ["John", "Smith"] ["John", ""]
默认情况下 watch 初始化页面的时候不会执行,如果想要 watch 一开始就执行,需要设置 immediate: true
watch(
a,
newValue => {
console.log(newValue, "ddd")
},
{
immediate: true
}
)
- reactive
对象类型的 ref,可以直接通过变量.属性访问不需要通过 value
const testRef = reactive({number: 1})
console.log(restRef.number)
- setup
setup 对于普通表达式和语句如果不是在 return 里只会执行一次,setup 就相当于 created 声明周期
比如:
setup: (props, context) => {
const current = ref(0)
const array = [
{name: 'lifa'},
{name: 'hh'}
]
// 这里因为我们的value只是一个普通的变量所以只会在初始化的时候执行一次
//也就是初始值 lifa,所以下面我们点击切换的时候,即使点击 hh,value 值也不会变化
const value = array[current.value].name
const onClick = (index) => {
current.value = index
}
// 上面这部分代码只会在初始化的时候执行一次
// return 里面的代码每次重新 render 都会重新执行
return () => (
<div>
<div style={{marginBottom: '20px'}}>{array.map((item, index) => <div onClick={() => onClick(index)} style={{marginBottom: '10px', fontSize: '16px'}}>{item.name}</div>)}</div>
<div>{value}</div>
</div>
)
}
如果想要我们的value变化有两种方式
1). 写在 return 里(不推荐)
setup: (props, context) => {
const current = ref(0)
const array = [
{name: 'lifa'},
{name: 'hh'}
]
const onClick = (index) => {
current.value = index
}
return () => {
const value = array[current.value].name
return (
<div>
<div style={{marginBottom: '20px'}}>{array.map((item, index) => <div onClick={() => onClick(index)} style={{marginBottom: '10px', fontSize: '16px'}}>{item.name}</div>)}</div>
<div>{value}</div>
</div>
)
}
}
2). 使用 computed
setup: (props, context) => {
const current = ref(0)
const array = [
{name: 'lifa'},
{name: 'hh'}
]
const value = computed(() => {
return array[current.value].name
})
const onClick = (index) => {
current.value = index
}
return () => (
<div>
<div>{array.map((item, index) => <div onClick={() => onClick(index)}>{item.name}</div>)}</div>
<!--注意这里得用.value-->
<div>{value.value}</div>
</div>
)
}
- toRaw
将 proxy 数据转化为原始数据
const formData = reactive({
name: '',
sign: '',
})
const onSubmit = (event: Event) => {
console.log(formData, 'fff')
event.preventDefault()
}
上面不加 toRaw 结果如下
加 toRaw 就是原始对象
console.log(toRaw(formData), 'fff') // {name: '', sign: ''}
- v-model 的类型声明
vue3 默认的v-model 绑定的是modelValue 属性,也就是我们需要再props里定义一个 modelValue
exoprt const Test = defineComponent({
props: {
modelValue: string as PropType<String>
},
setup: (props, context) => {
cons onChange = () => {
context.emit('update:modelValue')
}
}
})
// 父组件中使用
<Test v-modle={test.value} />
不使用 v-model 语法糖的形式
exoprt const Test = defineComponent({
props: {
modelValue: string as PropType<String>
},
// 在 emits 里写的update方法可以直接在父组件中通过onUpdate:属性的方法来监听
emits: ['update:modelValue']
setup: (props, context) => {
cons onChange = () => {
context.emit('update:modelValue')
}
}
})
// 父组件使用
<Test modelValue={test.value} onUpdate:modelValue={(value) => test.value = value}
onUpdate:modelValue和 v-model 可以一起使用
useHooks
用来代替 vue2 的 mixin
适用场景:有重复的js代码,然后里面用了vue的相关api,我们可以把这些重复的代码封装成 hooksshallowReactive
相对于 reactive 来说它只是浅层的响应式代理,也就是只有第一层对象在我们改变时是响应式的,其他情况下都不会重新渲染 ui,可以提升页面性能
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
state.foo = 2 // 响应式
state.nested.bar = 3 // 非响应式,数据变了但是 ui 不会变
这里需要注意如果在修改深层的同时紧接着改了其他可以响应式的数据,那么这个深层数据也会跟着在 ui 上变化
const handleClick = () => {
state.foo = 2
state.nested.bar = 3
}
<div>{state.foo} {state.nested.bar}</div>
<div onClick={handleClick}>按钮</div>
上面的代码当我们触发 click 事件后页面上展示的state.foo 变成了2,state.nested.bar 也变成了 3
- 路由懒加载
使用路由懒加载必须导出一个默认的组件,所以我们不能直接 export const,而要使用 export default
- Home/index.tsx
const Home = defineComponent({})
export default Home
- router.ts
{
path: "/",
name: "Dashboard",
meta: {
title: "首页",
icon: "Home"
},
component: () => import(/* webpackChunkName: "about" */ "@/views/Home")
},
- Teleport 传送门
将元素添加到指定dom下
<Teleport to="body">
<div>aaaa</div>
</Teleport>
上面的代码我们的Teleport 里的内容就会出现在 body 下面,to 后面还可以跟类名、id
- 自定义指令
注意如果你使用tsx写法只能使用全局指令
- directive/index.ts
import { App, DirectiveBinding } from "vue";
import { ElLoading } from "element-plus";
type loadingOption = ReturnType<typeof ElLoading.service>
let loadingInstance: loadingOption = {} as loadingOption
export default {
install(app: App) {
app.directive(
"loading",
(
el: HTMLElement,
binding: DirectiveBinding<boolean>,
vnode: VNode<unknown, HTMLElement>
) => {
const { oldValue, value } = vnode.dirs![0]
if (oldValue !== value) {
if (binding.value) {
loadingInstance = ElLoading.service({ target: el })
} else {
loadingInstance.close?.()
}
}
}
)
}
}
- main.ts
import directives from '../src/directive'
app.use(directives)
- 使用
const loading = ref(false)
onMounted(() => {
setTimeout(() => {
loading.value = true
}, 6000)
setTimeout(() => {
loading.value = false
}, 8000)
})
<div v-loading={loading.value} >
aaa
</div>
- 使用扩展运算符解构props
const newProps = computed(() => {
const {age, ...reset} = props
return reset
})
return () => (
<div>
<div>{props.age}</div>
<Child {...newProps.value}></Child>
</div>
);
- KeepAlive 的使用
注意如果有多层路由嵌套 router-view,必须得在最底层使用 KeepAlive
- App.tsx
<RouterView>
{({
Component,
route,
}: {
Component: VNode
route: RouteLocationNormalizedLoaded
}) => {
return (
<>
<KeepAlive>{route.meta.keepAlive ? Component : null}</KeepAlive>
{!route.meta.keepAlive ? Component : null}
</>
)
}}
</RouterView>
注意 KeepAlive 组件移动要自始至终渲染,错误写法如下
<RouterView>
{({
Component,
route
}: {
Component: VNode
route: RouteLocationNormalizedLoaded
}) => {
console.log(route.meta.keepAlive, "kekke")
if (route.meta.keepAlive) {
return <KeepAlive>{Component}</KeepAlive>
} else {
return Component
}
}}
</RouterView>
- router.ts
import { RouteRecordRaw } from "vue-router"
import Home from "@/views/Home"
// import { Child } from "@/views/Home/Child";
import { Test } from "@/views/Test"
import About from "@/views/About"
// import { TestChild } from "@/views/Home/TestChild";
export const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "home",
component: Home,
meta: {
keepAlive: true,
},
beforeEnter: (to, from) => {
if (from.name === "test" || from.name === undefined) {
to.meta.keepAlive = true
} else {
to.meta.keepAlive = false
}
},
},
{
path: "/test",
component: Test,
name: "test",
meta: {
keepAlive: false,
},
},
{
path: "/about",
component: About,
name: "about",
meta: {},
},
]
- 使用 slot 插槽
import { defineComponent } from 'vue';
import s from './First.module.scss';
export const First = defineComponent({
setup: (props, {slots}) => {
return () => (
<div class={s.wrapper}>
<div class={s.card}>
{slots.icon?.()}
{slots.title?.()}
</div>
<div class={s.actions}>
{slots.buttons?.()}
</div>
</div>
)
}
})
// demo
import { WelcomeLayout } from './WelcomeLayout';
export const First = defineComponent({
setup: (props, context) => {
const slots = {
icon: () => <span>icon</span>,
title: () => 'hi',
buttons: () => <><button>+1</button></>
}
return () => (
<WelcomeLayout v-slots={slots} />
)
}
})
或者
export const First = defineComponent({
setup: (props, context) => {
return () => (
<WelcomeLayout>
{{
icon: () => <span>icon</span>,
title: () => 'hi',
buttons: () => <><button>+1</button></>
}}
</WelcomeLayout>
)
}
})
- refs 的使用
import { ComponentPublicInstance, defineComponent, onMounted, reactive, ref } from 'vue';
export const Test = defineComponent({
setup: (props, context) => {
const arr = reactive([{
name: "立发",
age: "19"
}, {
name: "帅哥",
age: "18"
}])
const refs = ref<Record<string, Element | ComponentPublicInstance | null>>({})
onMounted(() => {
const a = refs.value["18"] as Element
a.scrollIntoView()
})
return () => (
<>
{
arr.map((item, index) => (
<div key={item.name} ref={el => refs.value[item.age] = el}>{item.name}</div>
))
}
</>
)
}
});
- 在 tsx 中使用 v-if 和 v-show 必须在普通元素标签上,不能在组件上使用
// 正确
const visible = ref(false)
<div v-show={visible.value}>aaa</div>
// 错误
<Child v-show={visible.value} />