飞道的博客

vue2向vue3进阶(tsx)

1084人阅读  评论(0)

1、setup

vue3.0结构:


  
  1. import { PropType } from 'vue' 或者 '@vue/composition-api'
  2. export default defineComponent({
  3. props: {
  4. age: {
  5. type: Number
  6. },
  7. onToggleClick: {
  8. type: Function as PropType< () => void>
  9. }
  10. }
  11. setup( props, {slots, emit, expose, attrs, listeners, root}) {
  12. // slots 插槽使用在下面第6类中介绍
  13. // emit('xxx', xxx)
  14. // expose 有可能是被废弃,当你使用内核为'@vue/composition-api'时是肯定被废弃的
  15. // attrs 跟vue2的 this.$attrs类似
  16. // listeners 跟vue2的 this.$listeners 类似
  17. // root vue实例
  18. // 其他的还有一些,比如 parent, children
  19. }
  20. })

 vue3.2


  
  1. <script lang= "ts" setup>
  2. // emit用法 先定义emit,执行时候和vue3.0方式一样
  3. const emit = defineEmits<{
  4. ( e: 'clickBtn', data: any): void
  5. }>()
  6. // props用法
  7. interface Props {
  8. labelWidth?: string | number //标签的宽度
  9. }
  10. const props = withDefaults(defineProps< Props>(), {
  11. labelWidth: '120px'
  12. })
  13. // 父组件想要调用子组件方法,首先在子组件将方法名抛出去
  14. defineExpose({ getFormValue })
  15. <script>

2、ref,reactive,toRef,toRefs

重点:

ref:

ref本质也是reactiveref(obj)等价于reactive({value: obj})

  • vue中使用ref的值,不用通过.value获取
  • js中使用ref的值,必须通过.value获取

reactive:

  • reactive的参数必须是一个对象,包括json数据和数组都可以,否则不具有响应式

toRef,toRefs

常用于解构props,保持响应式特征

区别是toRef只能转换一个值,toRefs转换所有值

3、watch

3.1、监听基础类型


  
  1. const nums = ref( 0)
  2. watch(nums, (newValue, oldValue) => {
  3. })

3.2、监听对象 


  
  1. const react = reactive({
  2. name: 'xxx',
  3. prop: {
  4. xxx: xxx
  5. }
  6. })
  7. // 监听整个对象
  8. watch(react, (newValue, oldValue) => {
  9. })
  10. // 监听对象某个属性(最常用)
  11. watch( () => react. name, (newValue, oldValue) => {
  12. })
  13. // 监听对象的第一级子属性
  14. watch( () => ({ ...react }), (newValue, oldValue) => {
  15. })
  16. // 监听对象的所有属性(最常用)
  17. watch( () => react, (newValue, oldValue) => {
  18. }, { deep: true })

 3.3、监听数组

ref定义的数组,下面这种写法是完全没有问题的,新旧数据都是正确的


  
  1. const react = ref([])
  2. // 监听整个数组
  3. watch( () => [...react. value], (newValue, oldValue) => {
  4. })

reactive定义的数组,不管怎么样,新数据是正确的,旧数据和新数据一直都是一致,这就有问题le, 所以解决方式就只有将数组嵌套再对象里面,然后监听数组的长度


  
  1. const react = reactive([])
  2. // 新数据正确,旧数据有问题
  3. watch(react, (newValue, oldValue) => {
  4. })
  5. // 新数据正确,旧数据有问题
  6. watch(react, (newValue, oldValue) => {
  7. }, { deep: true})
  8. // 不管加不加deep,都无法运行
  9. watch( () => react. length, (newValue, oldValue) => {
  10. })
  11. const react = reactive({
  12. arr: []
  13. })
  14. // 新旧数据正确
  15. watch( () => react. arr. length, (newValue, oldValue) => {
  16. })

3.4、watchEffect

其实和watch的区别,我的理解哈,watchEffect类似于immediat为true,开局立即启动了,将watchEffect赋值给一个变量,再调用的话,就是解除监听了,这一点要看你喜欢用watch还是watchEffect,当然那些副作用啥的概念我也没去研究,就暂时不讲解那些了


  
  1. // 监听
  2. const handle = watchEffect( () => {
  3. /* ... */
  4. })
  5. // 解除监听
  6. handle()

4、computed

其实就和vue2的computed类似


  
  1. const xxx = ref( 0)
  2. // 只get
  3. computed( () => xxx. value)
  4. // set get
  5. computed({
  6. set( val) {
  7. },
  8. get( ) {
  9. }
  10. })

5、css

当然我建议将css提出来,这样tsx里面就只有逻辑层和渲染层了,再很一点的话,将逻辑层也提出来,用一个单独的ts文件存放,tsx就只放渲染层的代码


  
  1. css文件名: index. module. scss
  2. 方式一:直接 import的话,就是用的全局样式
  3. index. module. scss:
  4. : global{
  5. . top-container{
  6. width: 100px;
  7. }
  8. }
  9. import 'index.module.scss';
  10. export default defineComponent({
  11. return () => (
  12. <div class='top-container'></div>
  13. )
  14. })
  15. 方式二: import Styles from 'index.module.scss' 的话,就是组件scoped方式,不过里面样式要写成小驼峰形式(建议使用)
  16. index. module. scss:
  17. . topContainer{
  18. width: 100px;
  19. }
  20. import Styles from 'index.module.scss';
  21. export default defineComponent({
  22. return () => (
  23. <div class={styles.topContainer}></div>
  24. )
  25. })

6、tsx

6.1、花括号和小括号使用

花括号一般用于js逻辑处理的时候和属性、事件赋值时候,类似于插值表达式(主要是逻辑层处理)


  
  1. export default defineComponent({
  2. return () => (
  3. <div>
  4. {
  5. showLogo.value && <Logo collapse={isCollapse.value} />
  6. }
  7. </div>
  8. )
  9. })

小括号一般用于最外层 包裹组件的时候 或者 包裹一段单独的逻辑时候(主要是渲染层处理)


  
  1. export default defineComponent({
  2. return () => (
  3. <div></div>
  4. )
  5. })

6.2、事件绑定

tsx目前还没有事件修饰符的写法,所以想要加修饰符,就两种方式

1、使用 @vue/babel-preset-jsx 依赖的时候,使用 vOn:click_stop 类似的方式

2、自行实现修饰符的功能,比如最简单的两个

.prevent, 写的时候就是 e,preventDefault()

.stop 写的时候就是e.stopPropagation()

但是当常规开发时, 使用 短横线或者小驼峰形式都是可以的


  
  1. export default defineComponent({
  2. return () => (
  3. <div>
  4. <div on-toggle-click={xxx}> </div>
  5. <div onToggleClick={xxx}> </div>
  6. </div>
  7. )
  8. })

6.3、动态class,动态style

class:下面是主流的几种class写法

举个例子:class.headerSearch的classModule 是 模块化class写法定义变量,一般的话你可以不用管它,直接类似下面第3,4,5写法就行了


  
  1. import classModule from './index.module.scss'
  2. export default defineComponent({
  3. return () => (
  4. <div>
  5. <div class={[classModule.headerSearch, { [classModule.show]: show.value }]> </div>
  6. <div class={[classModule.avatarContainer, classModule.rightMenuItem]}> </div>
  7. <div class='back-to to-top'> </div>
  8. <div class={['back-to', { 'show': show.value }]}> </div>
  9. <div class={[show.value ? 'active' : '', 'tags-view-item']}> </div>
  10. </div>
  11. )
  12. })

style: 下面是主流的几种style写法


  
  1. export default defineComponent({
  2. return () => (
  3. <div>
  4. <div style={{ 'top': props.buttonTop + 'px', 'background-color': theme.value }}> </div>
  5. <div style={{ display: visible.value ? 'block' : 'none' }}> </div>
  6. <div style='padding: 8px 10px;'> </div>
  7. </div>
  8. )
  9. })

6.4、v-for数组

有童鞋要问了,那我想使用filter筛选怎么办呢? 既然用了tsx,就灵活点呗,tsx里面是允许写逻辑的,直接在里面用filter就可以了


  
  1. export default defineComponent({
  2. return () => (
  3. <div>
  4. {
  5. array.value.map(route => {
  6. return <SideBarItem
  7. item= {route}
  8. basePath= {route.path} />
  9. })
  10. }
  11. </div>
  12. )
  13. })

6.5、v-if条件判断

逻辑与运算符:


  
  1. export default defineComponent({
  2. return () => (
  3. <div>
  4. {
  5. showLogo.value && <Logo collapse={isCollapse.value} />
  6. }
  7. </div>
  8. )
  9. })

三目运算符: 


  
  1. export default defineComponent({
  2. return () => (
  3. <div>
  4. {
  5. showLogo.value ? <Logo collapse={isCollapse.value} /> : null
  6. }
  7. </div>
  8. )
  9. })

6.6、v-show、v-model 

vue3 + tsx 默认支持这两个用法喔~和常规写法一致,不需要像其他tsx转换写法

6.7、slot插槽

下面是vue2关于插槽的讲解链接 

vue学习(5)vue高阶slot插槽使用

下面是vue3 + tsx 关于 插槽的使用方式 

scopedSlot:作用域插槽,其意旨在子组件里操作父组件的数据,下面一般都有默认插槽和具名插槽,举个例子: el-table 的使用


  
  1. export default defineComponent({
  2. return () => (
  3. <div>
  4. <el-table
  5. data= {errorLogs.value}
  6. border>
  7. <el-table-column
  8. label= 'Message'
  9. scopedSlots= {{
  10. default: scope => {
  11. return (
  12. <div>
  13. <div>
  14. <span class='message-title'>Msg: </span>
  15. <el-tag type='danger'>
  16. {scope.row.err.message}
  17. </el-tag>
  18. </div>
  19. </div>
  20. )
  21. }
  22. }}
  23. >
  24. </el-table-column>
  25. </el-table>
  26. </div>
  27. )
  28. })

v-slot:在tsx里应修改为v-slots

child.tsx  子组件


  
  1. export default defineComponent({
  2. return () => (
  3. <>
  4. <div>{ slots.header?.() } </div>
  5. <div>{ slots.default ? slots.default() : '默认插槽' } </div>
  6. </>
  7. )
  8. })

 parent.tsx 父组件


  
  1. // 方式一:
  2. export default defineComponent({
  3. setup( ) {
  4. const slots = {
  5. header: () => <span>具名插槽喔</span>,
  6. }
  7. return () => (
  8. <child v-slots={slots}>
  9. <div>这才是标题 </div>
  10. </child >
  11. );
  12. }
  13. })

  
  1. // 方式二:
  2. export default defineComponent({
  3. setup( ) {
  4. return () => (
  5. <child>
  6. {{
  7. default: () => <div>这才是标题 </div>,
  8. header: () => <span>具名插槽喔 </span>,
  9. }}
  10. </child >
  11. );
  12. }
  13. })

7、疑难解答

7.1、vue3 下 tsx 有几种渲染方式?

两种,一种是setup外面 render,一种是setup里面return

render:有this,和antd vue这种组件库写到tsx中,还要再加this,要改的地方比较多(不推荐


  
  1. import { defineComponent, ref, reactive } from 'vue';
  2. export default defineComponent({
  3. setup( ){
  4. interface Item {
  5. [ T: string]: any
  6. }
  7. const num = ref< number>( 0)
  8. const arr = reactive< Array< Item>>([])
  9. return {
  10. num,
  11. arr
  12. }
  13. },
  14. render( ) {
  15. return (
  16. <>
  17. <div>{this.num} </div>
  18. <div>hello world </div>
  19. {this.arr.map((item: any, index: number) => {
  20. return (
  21. <div key={index}>
  22. {item.name}:{item.value}
  23. </div>
  24. )
  25. })}
  26. </>
  27. )
  28. }
  29. })

return:推荐


  
  1. import { defineComponent, ref, reactive } from 'vue';
  2. export default defineComponent({
  3. setup( ){
  4. interface Item {
  5. [ T: string]: any
  6. }
  7. const num = ref< number>( 0)
  8. const arr = reactive< Array< Item>>([])
  9. return () => (
  10. <>
  11. <div>{num.value} </div>
  12. <div>hello world </div>
  13. {arr.map((item: any, index: number) => {
  14. return (
  15. <div key={index}>
  16. {item.name}:{item.value}
  17. </div>
  18. )
  19. })}
  20. </>
  21. )
  22. }
  23. })

7.2、如何解决tsx下多节点问题?

vue3及以上 新增fragment特性,可以直接写多个根节点,vue2想要实现多个根节点,安装依赖 vue-fragment

注意:vue-fragment 是在vue2项目里用的! 


  
  1. // main.ts
  2. import Fragment from 'vue-fragment'
  3. Vue. use( Fragment. Plugin)

使用方式如下,其实就是绕过vue的内核编译,然后在渲染时,会将最外层fragment节点删除掉,就不会有多出一层div的结构了,在react里也有类似的结构, React.Fragment 或者 <></>语法糖

vue2 + tsx方式:


  
  1. import { defineComponent, toRefs } from '@vue/composition-api';
  2. export default defineComponent({
  3. name: 'MenuItem',
  4. props: {
  5. icon: {
  6. type: String,
  7. default: ''
  8. }
  9. },
  10. setup( props) {
  11. const { icon } = toRefs(props)
  12. return () => (
  13. <fragment>
  14. {icon.value && (() => {
  15. if (icon.value.includes('el-icon')) {
  16. return <i class={[icon.value, 'sub-el-icon']} style={{ color: 'currentColor', width: '1em', height: '1em' }} />
  17. } else if (icon.value.includes('yxp_nav')) {
  18. return <i class={[icon.value, 'yxp_nav']} />
  19. } else {
  20. return <svg-icon icon-class={icon.value}/>
  21. }
  22. })()}
  23. </fragment>
  24. )
  25. }
  26. })

vue3 + tsx: 默认支持fragment,不需要额外写进去 


  
  1. import { defineComponent, ref, reactive } from 'vue';
  2. export default defineComponent({
  3. setup( ){
  4. interface Item {
  5. [ T: string]: any
  6. }
  7. const num = ref< number>( 0)
  8. const arr = reactive< Array< Item>>([])
  9. return () => (
  10. <>
  11. <div>{num.value} </div>
  12. <div>hello world </div>
  13. {arr.map((item: any, index: number) => {
  14. return (
  15. <div key={index}>
  16. {item.name}:{item.value}
  17. </div>
  18. )
  19. })}
  20. </>
  21. )
  22. }
  23. })

react方式:


  
  1. class Columns extends React.Component {
  2. render( ) {
  3. return (
  4. <React.Fragment>
  5. <div>Hello </div>
  6. </React.Fragment>
  7. );
  8. }
  9. }
  10. 或者
  11. class Columns extends React.Component {
  12. render( ) {
  13. return (
  14. <>
  15. <div>Hello </div>
  16. </>
  17. );
  18. }
  19. }

7.3、 vue3 + tsx 下的el-form表单使用?

下面我以填写手机号和验证码的表单来举例


  
  1. import { defineComponent, ref, reactive } from 'vue';
  2. export default defineComponent({
  3. setup( ){
  4. const formData = reactive<{ mobile: string, verifyCode: string}>({
  5. mobile: '',
  6. verifyCode: ''
  7. })
  8. const checkPhone = ( rule, value, callback) => {
  9. const reg = /^1[3456789]\d{9}$/
  10. if (reg. test(value)) {
  11. return callback();
  12. }
  13. callback( '请输入正确格式的手机号码!');
  14. }
  15. const rules = reactive({
  16. mobile: [
  17. { required: true, validator: checkPhone, message: '请输入正确格式的手机号码', trigger: 'blur' }
  18. ],
  19. verifyCode: [
  20. { required: true, message: '请输入短信验证码!', trigger: 'blur' }
  21. ]
  22. })
  23. const form = ref( null)
  24. return () => (
  25. <el-form
  26. ref={form}
  27. rules={rules}
  28. props={{
  29. model: formData
  30. }}
  31. >
  32. <el-form-item prop='mobile'>
  33. <el-input
  34. v-model= {formData.mobile}
  35. placeholder= '请输入手机号'
  36. >
  37. <i slot='prefix' class={['el-input__icon']} />
  38. </el-input>
  39. </el-form-item>
  40. <el-form-item prop='verifyCode'>
  41. <el-input
  42. v-model= {formData.verifyCode}
  43. placeholder= '请输入短信验证码'
  44. >
  45. <i slot='prefix' class={['el-input__icon']} />
  46. </el-input>
  47. {
  48. verShow.value ? <el-button
  49. 获取短信验证码
  50. </ el-button> : <el-button>
  51. <span>{timer.value} </span>秒后重新获取
  52. </el-button>
  53. }
  54. </el-form-item>
  55. <el-form-item>
  56. <el-button
  57. >确定 </el-button>
  58. <el-button
  59. >取消 </el-button>
  60. </el-form-item>
  61. </el-form>
  62. )
  63. }
  64. })

那么这里其实有两个不容易解决的点:

1、Invalid handler for event "input": got undefined

 解决方法如下:


  
  1. <el-form
  2. ref={form}
  3. rules={rules}
  4. props={{
  5. model: formData
  6. }}
  7. >
  8. </el-form>

2、Cannot add property isRootInsert, object is not extensible

 出现这个问题的原因是v-show不能同时出现在同一级上,应该跟v-if的方式一样,使用三目运算符,解决方法如下:


  
  1. {
  2. verShow. value ? <el-button>
  3. 获取短信验证码
  4. </el-button> : <el-button>
  5. <span>{timer.value} </span>秒后重新获取
  6. </el-button>
  7. }


转载:https://blog.csdn.net/qq_39404437/article/details/128562320
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场