1、setup
vue3.0结构:
-
import {
PropType }
from
'vue' 或者
'@vue/composition-api'
-
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 有可能是被废弃,当你使用内核为'@vue/composition-api'时是肯定被废弃的
-
// 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插槽
下面是vue2关于插槽的讲解链接
下面是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({
-
return
() => (
-
<>
-
<div>{ slots.header?.() }
</div>
-
<div>{ slots.default ? slots.default() : '默认插槽' }
</div>
-
</>
-
)
-
})
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 >
-
);
-
}
-
})
7、疑难解答
7.1、vue3 下 tsx 有几种渲染方式?
两种,一种是setup外面 render,一种是setup里面return
render:有this,和antd vue这种组件库写到tsx中,还要再加this,要改的地方比较多(不推荐)
-
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 {
-
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: (推荐)
-
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>
-
)
-
})}
-
</>
-
)
-
}
-
})
7.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方式:
-
import { defineComponent, toRefs }
from
'@vue/composition-api';
-
-
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>
-
</>
-
);
-
}
-
}
7.3、 vue3 + tsx 下的el-form表单使用?
下面我以填写手机号和验证码的表单来举例
-
import { defineComponent, ref, reactive }
from
'vue';
-
-
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>
-
}
转载:https://blog.csdn.net/qq_39404437/article/details/128562320