0、版本讲解
注意:本文主要在以下三个vue版本里讲解,其实大部分人只需要看vue3.0或者3.2的写法就行,‘@vue/composition-api’ 写法只针对于在vue2.x项目里使用vue3.0组合式api的库,在本文中没有标注版本的地方,默认为vue3.x的写法(‘@vue/composition-api’ 适用于服务器node版本低或者工作里都是vue2.x的项目无法直接用vue3的框架)
0.1、@vue/composition-api:(尝鲜版,少数人使用)
import { ref, defineComponent } from '@vue/composition-api'
export default defineComponent({
setup() {
const str = ref<string>('hello lindadayo')
return () => (
<div>{ str.value }</div>
)
}
})
0.2、vue3.0(标准版,多数人使用)
import { ref, defineComponent } from 'vue'
export default defineComponent({
setup() {
const str = ref<string>('hello lindadayo')
return () => (
<div>{ str.value }</div>
)
}
})
0.3、vue3.2(标准版,多数人使用)
<script lang="ts" setup>
import { ref } from 'vue'
const str = ref<string>('hello lindadayo')
return () => (
<div>{ str.value }</div>
)
</script>
1、setup
@vue/composition-api结构
export default defineComponent({
props: {
age: {
type: Number
},
onToggleClick: {
type: Function as PropType<() => void>
}
}
setup(props, {slots, emit, attrs, listeners, root}) {
// slots 插槽使用在下面第6类中介绍
// emit('xxx', xxx)
// attrs 跟vue2的 this.$attrs类似
// listeners 跟vue2的 this.$listeners 类似
// root vue实例
// 其他的还有一些,比如 parent, children
}
})
vue3.0结构:
export default defineComponent({
props: {
age: {
type: Number
},
onToggleClick: {
type: Function as PropType<() => void>
}
}
setup(props, {slots, emit, expose, attrs, listeners, root}) {
// slots 插槽使用在下面第6类中介绍
// emit('xxx', xxx)
// expose 子组件中属性或者方法抛出
// attrs 跟vue2的 this.$attrs类似
// listeners 跟vue2的 this.$listeners 类似
// root vue实例
// 其他的还有一些,比如 parent, children
}
})
vue3.2
<script lang="ts" setup>
// emit用法 先定义emit,执行时候和vue3.0方式一样
const emit = defineEmits<{
(e: 'clickBtn', data: any): void
}>()
// props用法
interface Props {
labelWidth?: string | number //标签的宽度
}
const props = withDefaults(defineProps<Props>(), {
labelWidth: '120px'
})
// 父组件想要调用子组件方法,首先在子组件将方法名抛出去
defineExpose({ getFormValue })
<script>
2、ref,reactive,toRef,toRefs
重点:
ref:
ref
本质也是reactive
,ref(obj)
等价于reactive({value: obj})
- 在
vue
中使用ref
的值,不用通过.value
获取 - 在
js
中使用ref
的值,必须通过.value
获取
reactive:
- reactive的参数必须是一个对象,包括json数据和数组都可以,否则不具有响应式
toRef,toRefs:
常用于解构props,保持响应式特征
区别是toRef只能转换一个值,toRefs转换所有值
3、watch
3.1、监听基础类型
const nums = ref(0)
watch(nums, (newValue, oldValue) => {
})
3.2、监听对象
const react = reactive({
name: 'xxx',
prop: {
xxx: xxx
}
})
// 监听整个对象
watch(react, (newValue, oldValue) => {
})
// 监听对象某个属性(最常用)
watch(() => react.name, (newValue, oldValue) => {
})
// 监听对象的第一级子属性
watch(() => ({ ...react }), (newValue, oldValue) => {
})
// 监听对象的所有属性(最常用)
watch(() => react, (newValue, oldValue) => {
}, { deep: true })
3.3、监听数组
ref定义的数组,下面这种写法是完全没有问题的,新旧数据都是正确的
const react = ref([])
// 监听整个数组
watch(() => [...react.value], (newValue, oldValue) => {
})
reactive定义的数组,不管怎么样,新数据是正确的,旧数据和新数据一直都是一致,这就有问题le, 所以解决方式就只有将数组嵌套再对象里面,然后监听数组的长度
const react = reactive([])
// 新数据正确,旧数据有问题
watch(react, (newValue, oldValue) => {
})
// 新数据正确,旧数据有问题
watch(react, (newValue, oldValue) => {
}, {deep: true})
// 不管加不加deep,都无法运行
watch(() => react.length, (newValue, oldValue) => {
})
const react = reactive({
arr: []
})
// 新旧数据正确
watch(() => react.arr.length, (newValue, oldValue) => {
})
3.4、watchEffect
其实和watch的区别,我的理解哈,watchEffect类似于immediat为true,开局立即启动了,将watchEffect赋值给一个变量,再调用的话,就是解除监听了,这一点要看你喜欢用watch还是watchEffect,当然那些副作用啥的概念我也没去研究,就暂时不讲解那些了
// 监听
const handle = watchEffect(() => {
/* ... */
})
// 解除监听
handle()
4、computed
其实就和vue2的computed类似
const xxx = ref(0)
// 只get
computed(() => xxx.value)
// set get
computed({
set(val) {
},
get() {
}
})
5、css
当然我建议将css提出来,这样tsx里面就只有逻辑层和渲染层了,再很一点的话,将逻辑层也提出来,用一个单独的ts文件存放,tsx就只放渲染层的代码
css文件名: index.module.scss
方式一:直接import的话,就是用的全局样式
index.module.scss:
:global{
.top-container{
width: 100px;
}
}
import 'index.module.scss';
export default defineComponent({
return () => (
<div class='top-container'></div>
)
})
方式二:import Styles from 'index.module.scss' 的话,就是组件scoped方式,不过里面样式要写成小驼峰形式(建议使用)
index.module.scss:
.topContainer{
width: 100px;
}
import Styles from 'index.module.scss';
export default defineComponent({
return () => (
<div class={styles.topContainer}></div>
)
})
6、tsx
6.1、花括号和小括号使用
花括号一般用于js逻辑处理的时候和属性、事件赋值时候,类似于插值表达式(主要是逻辑层处理)
export default defineComponent({
return () => (
<div>
{
showLogo.value && <Logo collapse={isCollapse.value} />
}
</div>
)
})
小括号一般用于最外层 包裹组件的时候 或者 包裹一段单独的逻辑时候(主要是渲染层处理)
export default defineComponent({
return () => (
<div></div>
)
})
6.2、事件绑定
tsx目前还没有事件修饰符的写法,所以想要加修饰符,就两种方式
1、使用 @vue/babel-preset-jsx 依赖的时候,使用 vOn:click_stop 类似的方式
2、自行实现修饰符的功能,比如最简单的两个
.prevent, 写的时候就是 e,preventDefault()
.stop 写的时候就是e.stopPropagation()
但是当常规开发时, 使用 短横线或者小驼峰形式都是可以的
export default defineComponent({
return () => (
<div>
<div on-toggle-click={xxx}></div>
<div onToggleClick={xxx}></div>
</div>
)
})
6.3、动态class,动态style
class:下面是主流的几种class写法
举个例子:class.headerSearch的classModule 是 模块化class写法定义变量,一般的话你可以不用管它,直接类似下面第3,4,5写法就行了
import classModule from './index.module.scss'
export default defineComponent({
return () => (
<div>
<div class={[classModule.headerSearch, { [classModule.show]: show.value }]></div>
<div class={[classModule.avatarContainer, classModule.rightMenuItem]}></div>
<div class='back-to to-top'></div>
<div class={['back-to', { 'show': show.value }]}></div>
<div class={[show.value ? 'active' : '', 'tags-view-item']}></div>
</div>
)
})
style: 下面是主流的几种style写法
export default defineComponent({
return () => (
<div>
<div style={{ 'top': props.buttonTop + 'px', 'background-color': theme.value }}></div>
<div style={{ display: visible.value ? 'block' : 'none' }}></div>
<div style='padding: 8px 10px;'></div>
</div>
)
})
6.4、v-for数组
有童鞋要问了,那我想使用filter筛选怎么办呢? 既然用了tsx,就灵活点呗,tsx里面是允许写逻辑的,直接在里面用filter就可以了
export default defineComponent({
return () => (
<div>
{
array.value.map(route => {
return <SideBarItem
item={route}
basePath={route.path} />
})
}
</div>
)
})
6.5、v-if条件判断
逻辑与运算符:
export default defineComponent({
return () => (
<div>
{
showLogo.value && <Logo collapse={isCollapse.value} />
}
</div>
)
})
三目运算符:
export default defineComponent({
return () => (
<div>
{
showLogo.value ? <Logo collapse={isCollapse.value} /> : null
}
</div>
)
})
6.6、v-show、v-model
vue3 + tsx 默认支持这两个用法喔~和常规写法一致,不需要像其他tsx转换写法
6.7、slot插槽
6.7.1、下面是vue2关于插槽的讲解链接
6.7.2、下面是 vue2.x + tsx(尝鲜版)关于插槽的使用方式
child.tsx: 子组件
export default defineComponent({
setup(props, { slots }) {
const { proxy } = getCurrentInstance()
return () => (
<fragment>
<div>{ slots.header?.() }</div>
<div>{ slots.default?.() }</div>
</fragment>
)}
})
parent.tsx: 父组件
// 方式一:更偏向于v2的slot用法
export default defineComponent({
setup() {
return () => (
<child>
<div slot='header'>这才是标题</div>
<div>默认插槽</div>
</child >
);
}
})
// 方式二:更偏向于v3的jsx用法
export default defineComponent({
setup() {
return () => (
<child
scopedSlots={{
header: () => {
return <div>这才是标题</div>
},
default: () => {
return <div>默认插槽</div>
}
}}
>
</child >
);
}
})
6.7.3、下面是vue3 + tsx(标准版) 关于 插槽的使用方式
scopedSlot:作用域插槽,其意旨在子组件里操作父组件的数据,下面一般都有默认插槽和具名插槽,举个例子: el-table 的使用
export default defineComponent({
return () => (
<div>
<el-table
data={errorLogs.value}
border>
<el-table-column
label='Message'
scopedSlots={{
default: scope => {
return (
<div>
<div>
<span class='message-title'>Msg:</span>
<el-tag type='danger'>
{scope.row.err.message}
</el-tag>
</div>
</div>
)
}
}}
>
</el-table-column>
</el-table>
</div>
)
})
v-slot:在tsx里应修改为v-slots
child.tsx 子组件
// 针对常规自定义子组件
export default defineComponent({
const { proxy } = getCurrentInstance()
setup(props, {slots}) {
return () => (
<>
<div>{ slots.header?.() }</div>
<div>{ slots.default?.() }</div>
</>
)
}
})
// 针对element-plus组件
<el-dialog
title={props.title}
width={props.width}
v-model={dialogVisible.value}
modal={props.modal}
append-to-body={props.appendToBody}
draggable={props.draggable}
z-index={props.zIndex}
custom-class={styles.lttDialog}
before-close={methods.close}
>
{{
header: () => slots.header?.(),
default: () => slots.default?.(),
footer: () => slots.footer?.(),
}}
</el-dialog>
parent.tsx 父组件
// 方式一:
export default defineComponent({
setup() {
const slots = {
header: () => <span>具名插槽喔</span>,
}
return () => (
<child v-slots={slots}>
<div>这才是标题</div>
</child >
);
}
})
// 方式二:
export default defineComponent({
setup() {
return () => (
<child>
{{
default: () => <div>这才是标题</div>,
header: () => <span>具名插槽喔</span>,
}}
</child >
);
}
})
6.7.4、@vue/composition-api + jsx 调用自定义vue2插件使用
// Child.vue (子组件为 vue2 + 模板写法的自定义插件)
<template>
<div>
<slot name="more">具名插槽</slot>
<slot />
</div>
</template>
// 父组件为 @vue/composition-api + tsx
export default defineComponent({
name: 'Parent',
setup() {
return () => (
<Child>
{
<div slot='more'>具名插槽实例化</div>
<div>默认插槽实例化</div>
}
</child>
)
}
})
6.8、attrs
// 顶级组件
export default defineComponent({
name: 'Root',
setup(props, { attrs }) {
return () => (
<Parent
{
...{
attrs: {
mode: mode.value
}
}
}
</Parent>
)
}
})
// 中间组件 继续使用attrs保持响应式
export default defineComponent({
name: 'Parent',
inheritAttrs: false,
setup(props, { attrs }) {
return () => (
<Child
{
...{
attrs: {
mode: mode.value
}
}
}
</Child>
)
}
})
// 子组件
export default defineComponent({
name: 'Child',
setup(props, { attrs }) {
return () => (
<div>{ attrs.mode }</div>
)
}
})
7、xxx.d.ts 使用
注意点:
1、项目内命名为 xxx.d.ts的文件,不需要在main.ts内引入,因为只要是d.ts结尾的,都被视为同级
2、在 d.ts 声明文件中,任何的 declare 默认就是 global 的了,所以你在 d.ts 文件中是不用出现 declare global
的。只有在具体ts文件中的定义,如果想要全局就使用 declare global
vue3 + ts 下的 d.ts 推荐写法
import { ComponentRenderProxy } from '@vue/composition-api';
declare namespace JSX {
type Element = VNode
type ElementClass = ComponentRenderProxy
interface ElementAttributesProperty {
$props: any; // specify the property name to use
}
interface IntrinsicElements {
[elem: string]: BasicVueElement;
}
interface IntrinsicClassAttributes {
class?: any;
id?: string;
ref?: string;
style?: any;
key?: any;
props?: any;
scopedSlots?: object;
}
}
interface BasicVueElement {
[param: string]: any;
props?: {
[key: string]: any;
};
ref?: any;
class?: any | any[];
onClick?: (e: MouseEvent) => void;
onMouseUp?: (e: MouseEvent) => void;
onKeyUp?: (e: KeyboardEvent) => void;
onKeyDown?: (e: KeyboardEvent) => void;
'v-model'?: any;
vModel?: any;
}
8、 h函数
1、概念
返回一个“虚拟节点” ,通常缩写为 VNode: 一个普通对象,其中包含向 Vue 描述它应该在页面上呈现哪种节点的信息,包括对任何子节点的描述。用于手动编写render。是createElement 的简写
2、参数
包含三个参数
- type 元素的类型
- propsOrChildren 数据对象, 这里主要表示(props, attrs, dom props, class 和 style)
- children 子节点
// type only
h('div')
// type + props
h('div', {})
// type + omit props + children
// Omit props does NOT support named slots
h('div', []) // array
h('div', 'foo') // text
h('div', h('br')) // vnode
h(Component, () => {}) // default slot
// type + props + children
h('div', {}, []) // array
h('div', {}, 'foo') // text
h('div', {}, h('br')) // vnode
h(Component, {}, () => {}) // default slot
h(Component, {}, {}) // named slots
// named slots without props requires explicit `null` to avoid ambiguity
h(Component, null, {})
3、props内部的使用
{
// vue2/3 :class
'class': {
},
// vue2/3 :style
style: {
},
// vue2 attr
attrs: {
},
// vue2/3 props
props: {
text: ''
},
// vue2 v-html
domProps: {
innerHTML: 'baz'
},
// 事件监听器
on: {
click: (data) => {
methods.xxx(data)
}
},
// 自定义指令
directives: [
{
name: 'xxx',
value: 'xxx',
arg: 'xxx'
}
],
// 作用域插槽
scopedSlots: {
default: () => h('div', {})
},
// 插槽名
slot: 'xxx'
}
4、h函数配合异步组件使用
// 异步组件定义
export const ArticleTitle = defineAsyncComponent({
loader: () => import('../main/modules/articleTitle'),
loadingComponent: () => '<div>加载中...</div>',
delay: 200, // 延迟 200 毫秒显示
timeout: 3000 // 超时 3 秒
});
// h + defineAsyncComponent
setup(props) {
const data = ref<any>({})
watch(() => props.data, val => {
data.value = val
}, {
deep: true,
immediate: true
})
return () => (
<fragment>
{
(() => {
switch (data.value.type) {
case 'ArticleTitle':
return h(ArticleTitle, {
props: {
data: data.value
}
})
}
})()
}
</fragment>
)
}
9、provide && inject
通常用于从顶级节点往下层节点传数据,避免使用props一层一层传值。
1、常规用法:自上而下传值
当然可以传原对象,也可以传代理对象,实现值的动态变化
// 顶级组件
export default defineComponent({
setup(_props, { emit }) {
const tree = reactive({xxx: xxx})
provide('tree', tree)
return () => <Child
mode={'main'}
/>
}
})
// 下层组件 Child
export default defineComponent({
setup(props, { emit }) {
const tree = inject('tree')
// 使用默认值会导致其他错误,将在疑难解答中指出
const tree1 = inject('tree', '默认值')
return () => (
<div class={styles.previewControl}>
</div>
)
}
})
2、子组件如何操作顶级组件的方法?
// 顶级组件
const previewPdf = {
handler: () => {
emit('previewPdf')
}
}
provide('previewPdf', previewPdf)
// 子组件
const previewPdfs: any = inject('previewPdf')
previewPdfs.handler() // 调用顶级组件方法
3、子组件 自下而上 改变父组件的值(一般不建议,因为违反vue单向数据流)
并且在子组件中定义了inject,父组件不用provide就会报错,所以给inject定义默认值就不会报错了
// 顶级组件
export default defineComponent({
setup(_props, { emit }) {
const count= ref(0)
provide('count', count)
return () => <Child
/>
}
})
// 下层组件 Child
export default defineComponent({
setup(props, { emit }) {
const count= inject('count', {
value: 0
})
const addCount = () => {
count.value++
}
return () => (
<div class={styles.previewControl} onClick={addCount}>
</div>
)
}
})
10、疑难解答
1、vue3 下 tsx 有几种渲染方式?
两种,一种是setup外面 render,一种是setup里面return
render:有this,和antd vue这种组件库写到tsx中,还要再加this,要改的地方比较多(不推荐)
export default defineComponent({
setup(){
interface Item {
[T: string]: any
}
const num = ref<number>(0)
const arr = reactive<Array<Item>>([])
return {
num,
arr
}
},
render() {
return (
<>
<div>{this.num}</div>
<div>hello world</div>
{this.arr.map((item: any, index: number) => {
return (
<div key={index}>
{item.name}:{item.value}
</div>
)
})}
</>
)
}
})
return: (推荐)
export default defineComponent({
setup(){
interface Item {
[T: string]: any
}
const num = ref<number>(0)
const arr = reactive<Array<Item>>([])
return () => (
<>
<div>{num.value}</div>
<div>hello world</div>
{arr.map((item: any, index: number) => {
return (
<div key={index}>
{item.name}:{item.value}
</div>
)
})}
</>
)
}
})
2、如何解决tsx下多节点问题?
vue3及以上 新增fragment特性,可以直接写多个根节点,vue2想要实现多个根节点,安装依赖 vue-fragment
注意:vue-fragment 是在vue2项目里用的!
// main.ts
import Fragment from 'vue-fragment'
Vue.use(Fragment.Plugin)
使用方式如下,其实就是绕过vue的内核编译,然后在渲染时,会将最外层fragment节点删除掉,就不会有多出一层div的结构了,在react里也有类似的结构, React.Fragment 或者 <></>语法糖
vue2 + tsx方式:
export default defineComponent({
name: 'MenuItem',
props: {
icon: {
type: String,
default: ''
}
},
setup(props) {
const { icon } = toRefs(props)
return () => (
<fragment>
{icon.value && (() => {
if (icon.value.includes('el-icon')) {
return <i class={[icon.value, 'sub-el-icon']} style={{ color: 'currentColor', width: '1em', height: '1em' }} />
} else if (icon.value.includes('yxp_nav')) {
return <i class={[icon.value, 'yxp_nav']} />
} else {
return <svg-icon icon-class={icon.value}/>
}
})()}
</fragment>
)
}
})
vue3 + tsx: 默认支持fragment,不需要额外写进去
import { defineComponent, ref, reactive } from 'vue';
export default defineComponent({
setup(){
interface Item {
[T: string]: any
}
const num = ref<number>(0)
const arr = reactive<Array<Item>>([])
return () => (
<>
<div>{num.value}</div>
<div>hello world</div>
{arr.map((item: any, index: number) => {
return (
<div key={index}>
{item.name}:{item.value}
</div>
)
})}
</>
)
}
})
react方式:
class Columns extends React.Component {
render() {
return (
<React.Fragment>
<div>Hello</div>
</React.Fragment>
);
}
}
或者
class Columns extends React.Component {
render() {
return (
<>
<div>Hello</div>
</>
);
}
}
3、 vue3 + tsx 下的el-form表单使用?
下面我以填写手机号和验证码的表单来举例
export default defineComponent({
setup(){
const formData = reactive<{mobile: string, verifyCode: string}>({
mobile: '',
verifyCode: ''
})
const checkPhone = (rule, value, callback) => {
const reg = /^1[3456789]\d{9}$/
if (reg.test(value)) {
return callback();
}
callback('请输入正确格式的手机号码!');
}
const rules = reactive({
mobile: [
{ required: true, validator: checkPhone, message: '请输入正确格式的手机号码', trigger: 'blur' }
],
verifyCode: [
{ required: true, message: '请输入短信验证码!', trigger: 'blur' }
]
})
const form = ref(null)
return () => (
<el-form
ref={form}
rules={rules}
props={{
model: formData
}}
>
<el-form-item prop='mobile'>
<el-input
v-model={formData.mobile}
placeholder='请输入手机号'
>
<i slot='prefix' class={['el-input__icon']} />
</el-input>
</el-form-item>
<el-form-item prop='verifyCode'>
<el-input
v-model={formData.verifyCode}
placeholder='请输入短信验证码'
>
<i slot='prefix' class={['el-input__icon']} />
</el-input>
{
verShow.value ? <el-button
获取短信验证码
</el-button> : <el-button>
<span>{timer.value}</span>秒后重新获取
</el-button>
}
</el-form-item>
<el-form-item>
<el-button
>确定</el-button>
<el-button
>取消</el-button>
</el-form-item>
</el-form>
)
}
})
那么这里其实有两个不容易解决的点:
1、Invalid handler for event "input": got undefined
解决方法如下:
<el-form
ref={form}
rules={rules}
props={{
model: formData
}}
>
</el-form>
2、Cannot add property isRootInsert, object is not extensible
出现这个问题的原因是v-show不能同时出现在同一级上,应该跟v-if的方式一样,使用三目运算符,解决方法如下:
{
verShow.value ? <el-button>
获取短信验证码
</el-button> : <el-button>
<span>{timer.value}</span>秒后重新获取
</el-button>
}
4、el-form 自定义校验规则的ts写法
vue3情况下: XXX.d.ts: 定义在d.ts后缀的都被认为是声明全局
@vue/composition-api情况下: 主动声明为global 才会被ts校验过,在外部tsx文件使用global里面的interface
interface CallbackType {
(message?: string | Error | undefined): Error | void
}
declare global {
interface ValidateType {
(_rule: any, value: string, callback: CallbackType): void
}
}
使用方式:
xxx.tsx
const checkPhone: ValidateType = (rule, value, callback) => {
const reg = /^1[3456789]\d{9}$/
if (reg.test(value)) {
return callback();
}
callback('请输入正确格式的手机号码!');
}
5、props里是对象或者数组的时候如何类型处理?
'@vue/composition-api' 写法和vue3.x标准写法都和下面例子一致:
type: Object as PropType<ObjType> 或者 type: Array as PropType<ArrType[]>
interface ObjType{
type?: string;
}
interface ArrType{
type?: string;
}
export default defineComponent({
name: 'MenuItem',
props: {
obj: {
type: Object as PropType<ObjType>,
default: () => {
return {
type: 'default'
}
}
},
arr: {
type: Array as PropType<ArrType[]>,
default: () => {
return [
{
type: 'drop'
}
]
}
}
},
setup(props) {
const { icon } = toRefs(props)
return () => (
<div>
{props.obj.type}
{
props.arr.map(item => {
return <div>{item.type}</div>
})
}
</div>
)
}
})
6、 jsx里想使用switch该怎么写呢?
jsx 里先写上 (() => {})(),然后在大括号里写上自己想要的逻辑,其根本就是自启动函数,里面可以写任何你想要的逻辑,只需要返回出来的值符合jsx写法即可
setup() {
const type = ref<number>(1)
return () => (
<div>
{
(() => {
switch (type.value) {
case 1:
return <span>类型1</span>
default:
return <span>类型其他</span>
}
})()
}
</div>
)
}
7、jsx里如何使用directives自定义指令呢?
此处我举了个例子,表格的懒加载指令
普通模板写法
<template>
<el-table
v-load-more:{x:xx}='xxx'
>
</el-table>
</template>
jsx是如下写法,其实和我们平时用的有一些区别,需要你先注册全局会更好一些
setup() {
const type = ref<number>(1)
return () => (
<el-table
{...{ directives: [{ name: 'load-more', value: xxx, arg: { x: xx} }] }}
>
</el-table>
)
}
8、jsx 里v-html,v-text如何写?
domPropsInnerHTML 代替 v-html
domPropsInnerText 代替 v-text
setup() {
return () => (
<div
domPropsInnerHTML={xxx}
>
)
}
9、jsx中遍历数组可以使用filter吗?
答案是不能!
首先先来了解下基本概念:
filter过滤数组,不会对原始值做改变,map不会过滤,可以对原数组改变,我们在jsx里面会根据逻辑来返回对应标签,但最后渲染的还是dom。用filter的话,返回的就是数组数据,jsx无法根据这些数据来进行渲染喔
10、vue3中extends和mixin区别联系?
联系:extends属性值直接和常规一致,类似于mixin
区别:mixin不会覆盖方法和生命周期钩子,extends全覆盖
11、defineAsyncComponent 刷新TypeError: Cannot read properties of undefined (reading 'props')
错误原因:
在组件加载完成之前,父组件可能会访问子组件的 props
属性,此时 props
属性还没有赋值
解决方法:
const loadingConfig = {
props: {},
setup() {
return () => h('div', {}, '加载中...')
}
}
// 异步组件
const ArticleTitle = defineAsyncComponent({
loader: () => import('@/main/modules/xxx'),
loadingComponent: loadingConfig,
delay: 50, // 延迟 50 毫秒显示
timeout: 3000 // 超时 3 秒
});
12、inject 赋默认参数会覆盖provide的值
在使用 inject API 时,如果为其指定了默认值,Vue.js 实际上会优先考虑默认值,而不是通过 provide 提供的实际值
解决方法:
<script>
export default {
setup() {
const message = inject('message');
const defaultMessage = 'Default Message';
return {
message: message ?? defaultMessage,
};
},
};
</script>
13、Injection "Symbol()" not found
在使用 inject
API 时,你指定的依赖项 key 值存在拼写错误或其他语法错误,导致 Vue.js 无法正确地解析它。
那啥时候会出现这种问题呢?下面是例子,provide时候第一参数为字符串previewPdf,但是inject为Symbol类型,导致没有匹配上,因为暂时没有想到更好解决方法,所以就只有按照我例子中的来写
// child.tsx
export default defineComponent({
setup() {
interface ControlType {
handler?: Function;
}
// 有问题
const previewPdf: InjectionKey<ControlType> = Symbol()
// 无问题
const previewPdfs: any = inject('previewPdf')
},
});
// App.tsx
export default defineComponent({
setup() {
const previewPdf = {
handler: () => {
emit('previewPdf')
}
}
provide('previewPdf', previewPdf)
},
});
14、 vue-fragment+html2canvas在@vue/composition-api 下使用有问题?
在@vue/composition-api环境下开发,想使用fragment就得使用vue-fragment插件,但是搭配上html2canvas导出canvas会出现bug,就是只会渲染第一个fragment标签的dom,其他的都不会渲染,目前还是建议在@vue/composition-api环境下不使用fragment
15、子组件方法如何抛出,父组件才能来调用?
在@vue/composition-api环境
// 父组件
export default defineComponent({
setup(_props, { emit }) {
const comp = ref();
const methods = {
use() {
child.value.start();
}
}
return () => <Child
ref={child}
/>
}
})
// 子组件 Child
export default defineComponent({
setup(props, { emit }) {
const { proxy } = getCurrentInstance();
const methods = {
start() {}
}
proxy.start = methods.start;
return () => (
<div class={styles.previewControl}>
</div>
)
}
})
在vue3.2环境下
父组件调用方式一致,子组件通过defineExpose({xxx: xxx})方式来暴露
11、有趣知识科普
1、@vue/composition-api 下的h函数和vue3下的h函数
vue3框架里有四种写法:
1、模板template(常规,不讲解了)
2、h函数 写法(高难,这里着重讲解)
3、jsx写法(中难,这篇博客上面的写法都是这种jsx的, jsx是h的语法糖,更简洁好用)
4、函数式组件写法(稀有,太少用了,我都没咋写过)
@vue/composition-api 下h函数写法, 注意在return时,不能当做一个组件 <ele />来用噢~
export default defineComponent({
setup() {
const ele = h('div', null, 'hello world')
return () => (
{
ele
}
)
}
})
vue3.x 下h函数写法(h函数的核心就是createVNode方法,包裹着一层)
<script lang="ts">
export default defineComponent({
return () => createVNode(
'div', null, 'hello world'
)
})
</script>
// 或者
<script lang="ts">
import { defineComponent, h } from 'vue'
export default defineComponent({
return () => h(
'div', null, 'hello world'
)
})
</script>
2、@vue/composition-api 和vue3.x下的父组件调用子组件方法
1、vue3.2 + template
<!-- 子组件 -->
<script setup>
const methods = () => {}
// 暴露子组件方法
defineExpose({ methods })
</script>
<!-- 父组件 -->
<script setup>
import { ref } from 'vue'
import child from './child.vue'
const child = ref()
const clickChild= () => {
child.value.methods()
}
</script>
<template>
<child ref="child"></child>
<button @click="clickChild">调用子组件方法</button>
</template>
2、vue3.0 + template
<!-- 子组件 -->
<script>
setup() {
const methods = () => {}
return {
methods
}
}
</script>
<!-- 父组件 -->
<script setup>
import { ref } from 'vue'
import child from './child.vue'
const child = ref()
const clickChild= () => {
child.value.methods()
}
</script>
<template>
<child ref="child"></child>
<button @click="clickChild">调用子组件方法</button>
</template>
3、@vue/composition-api + tsx
<!-- 子组件 -->
export default defineComponent({
setup() {
const { proxy } = getCurrentInstance()
const methods = () => {}
proxy.methods = methods;
}
})
<!-- 父组件 -->
export default defineComponent({
setup() {
return () => (
<div>
<Child ref={child} />
<button onClick={() => child.value.methods()}>点击子组件</button>
</div>
)
}
})
3、@vue/composition-api + tsx下的provide/inject使用
类似于vuex的自定义注入用法,不过你能使用vuex就使用vuex噢,不能使用的话,就使用本方法~
第一个红框是各个变量的单独文件,类似于vuex的模块化,第二个红框是入口文件,以下是源码
/context/index.ts
import { useGeneratePdfDataProvide, useGeneratePdfDataInject } from './useGeneratePdfData';
import { useGobalClickProvide, useGobalClickInject } from './useGobalClick';
import { useModuleInfoInject, useModuleInfoProvide } from './useModuleInfo';
import { useImageCalcInject, useImageCalcProvide } from './useImageCalc';
import { usePinyinInject, usePinyinProvide } from './usePinyin';
export {
usePinyinInject,
useModuleInfoInject,
useImageCalcInject,
useGobalClickInject,
useGeneratePdfDataInject
};
export const useProvider = () => {
usePinyinProvide();
useModuleInfoProvide();
useImageCalcProvide();
useGobalClickProvide();
useGeneratePdfDataProvide();
};
/context/useGobalClick/index.ts 全局点击状态变量
/** 全局点击状态 */
const GobalClickSymbol = Symbol('globalClick');
type GobalClick = {
GobalClickStatus: Ref<boolean>;
setGobalClick: (flag: boolean) => void;
}
export function useGobalClickProvide() {
const GobalClickStatus = ref<boolean>(true)
const setGobalClick = (flag: boolean) => {
GobalClickStatus.value = flag
}
provide(GobalClickSymbol, {
GobalClickStatus,
setGobalClick
})
}
export function useGobalClickInject() {
const GobalClickContext = inject<GobalClick>(GobalClickSymbol);
return GobalClickContext;
}
使用方法是:入口文件的useProvider()直接在全局顶层文件(App.tsx/App.vue) 实例化,这里就相当于在全局初始化了这些变量,然后在你需要使用的地方inject,比如我在某个文件中就可以这么做,使用 useGobalClickInject()将状态值和set方法返回出来,就能使用了~
const {
GobalClickStatus
setGobalClick
} = useGobalClickInject()
---有问题可以随时评论噢~喜欢的请点赞收藏啦---