vue3+composition API+ts 项目实战

4,731 阅读2分钟

vue3+typescript实战项目中,vue3相关内容

createApp(App).component 失效

报错:Component provided template option but runtime compilation is not supported

[Vue warn]: Component provided template option but runtime compilation is not supported in this build of Vue. Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js". 
  at <Test message=1 > 
  at <App>

解决方案:

方案一:index.html模板,此类可使用component注册全局组件。(不推荐)

//main.ts中
import { createApp } from 'vue'
import App from './App.vue'
app.component('Test', {
    template: '#test', //注意此行
})
app.mount('#app')

在html中有id为test的可用模板

<div id="test">   <!-- 此处模板生效 -->test123</div>
<div id="app"></div>

方案二:alias

vue.config.js,alias,vue换一种模式;我使用的webpack启动vue3. 因为使用vite经常热启动会挂

module.exports={
    configureWebpack: config=>{
        config.resolve = {
            alias:{
                vue:"vue/dist/vue.esm-bundler.js" // 这个加上,重新启动就可以使用了
            }
        }
    }
}

main.ts文件

import { createApp } from 'vue'
import App from './App.vue'
// import store from './store'

const app = createApp(App)
app.component('button-counter', {
    template: `<button>You clicked me times.</button>`
})
app.mount('#app')

App.vue文件

<template>
   <button-counter></button-counter>
</template>

原理大概是vue3的compiler 模式和runtime模式的区别吧! 但是我的store用不了了。

方案三:app.component

main.ts

const app = createApp(App);
import Title from './components/Title.vue'
app.component('newtitle',Title)

Title.vue

<template>
    <div>内容标题</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
    name: 'Title',
})
</script>

ref,reactive 针对数组的用法

const componentList: Record<string, any> = ref([])
componentList = res.data.componentList  //错误用法
componentList.value = res.data.componentList // 使用.value改变其值 才有效

directive 【vue3有修改】

  • app.directive 注册方式有所变化
  • bind被移除,可使用mounted, unbind 对应 unmounted

vue2 v-clickoutside demo

/**
 * 点击元素外部,元素关闭
 * v-clickoutside绑定
 */
export function clickoutside(){
    return {
        //初始化指令
        bind(el,binding,vnode){
            function documentHandler(e){
                //这里判断点击的元素是否是本身,是本身,则返回
                if(el.contains(e.target)){
                    return false;
                }
                //判断指令中是否绑定了函数
                if(binding.expression){
                    //如果绑定了函数,则调用那个函数,此处binding.value就是handleClose方法   
                    binding.value(e);   
                }
            }
            //给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
            el.__vueClickOutside__ = documentHandler;
            document.addEventListener("click",documentHandler);
        },
        update(){},
        unbind(el,binding){
            //解除事件监听
            document.removeEventListener("click",el.__vueClickOutside__);
            delete el.__vueClickOutside__;
        }
    }
}

vue3 v-clickoutside demo

export function clickoutside() {
    return {
        //初始化指令
        mounted(el: any, binding: any) {
            function documentHandler(e: any) {
                //这里判断点击的元素是否是本身,是本身,则返回
                if (el.contains(e.target)) {
                    return false;
                }
                //判断指令中是否绑定了函数
                if (binding.value) {
                    //如果绑定了函数,则调用那个函数,此处binding.value就是handleClose方法   
                    binding.value(e);
                }
            }
            //给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
            el.__vueClickOutside__ = documentHandler;
            document.addEventListener("click", el.__vueClickOutside__);
        },
        unmounted(el: any) {
            //解除事件监听
            document.removeEventListener("click", el.__vueClickOutside__);
            delete el.__vueClickOutside__;
        }
    }
}
//引用方式
import { clickoutside, copy } from '@/common/directive'
const clickoutsideObj: Record<string, any> = clickoutside()
app.directive('clickoutside', clickoutsideObj)

$refs 【vue3有修改】

<div :ref="el => { divs[i] = el }></div>

setup(){
	const allComP = ref([])
	onUpdated(() => {
		console.log('allComP.value', allComP.value)
	})
	return {allComP}
}

对应文档 v3.vuejs.org/guide/compo…

slot 【vue3有修改】

vue2-slot demo

//父组件
<SonComponent>
    <template slot='toTop'>
        <div class="scrollWrap-toTop fixed-box"></div>
    </template>
</SonComponent>

//SonComponent
<template>
	<slot name='toTop'></slot>
</template>

vue3-slot demo

//父组件
<SonComponent>
	<template v-slot:toTop> //区别
		<div class="scrollWrap-toTop fixed-box"></div>
	</template>
</SonComponent>

//SonComponent内部
<template>
	<slot name="toTop"></slot>
</template>

报错:runtime-core.esm-bundler.js?5c40:2834 Uncaught (in promise) TypeError: Cannot read property 'created' of undefined

  • 我碰到的问题是,自定义指令未注册,但先提前使用了
  • 一般是标签上的属性或绑定事件未提前注册时会报错

模块无法显示,无提示

解决方案可参考 suspense ,但我碰到的情况是 component v-for="item in list" is:item

 async setup(){ //加了async貌似就会有问题。暂无解决方案
}

vue3+typescript实战项目中,ts相关错误提示

Object is possibly 'null'.

解决方案 as 对应类型

const scrollElement = document.querySelector('body') 
scrollElement.scrollTop

//解决方案
const scrollElement = document.querySelector('body') as HTMLElement

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type

解决方案 附加类型

const componentsMap = {
    title: Title,
    abstract: Abstract
}

Object.keys(componentsMap).forEach(val => {
    app.component(val, componentsMap[val]) //componentsMap[val] 提示的错误
})

//解决方案
const componentsMap :any   //方案1
const componentsMap :Record<string, DefineComponent>  //方案2

Property 'xxx' does not exist on type 'Window & typeof globalThis'

解决方案 window.d.ts

interface Window {
    getAccount(): string | number | never
}

//使用
window.getAccount()

出错的可能是由于你更改了d.ts文件但 vscode 未重新编译,可重新编译试一下

全局缩写 --- Record<string, any>加个

type Obj = Record<string, any>

表达Object,对象内部任意成员

interface A{
	[params: string]:any
}

No overload matches this call.Overload 1 of 2, '(...items: ConcatArray[]): never[]', gave the following error.

type Item = {
  time: number;
};

const list = [].concat([{ time: 123 }]); // time会报错

解决方案一:

const list = ([] as Item[]).concat([{ time: 123 }]);

解决方案二:

const initList: Item[] = []
const list = initList.concat([{ time: 123 }]);

Argument of type 'Record<string, any> & { isNew: boolean; }' is not assignable to parameter of type 'never'.

报错场景

const list = ref([]);
const list.value.unshift({}) //报错

解决方案

const allListModule: Record<string,any>[] = []
const allList = ref(allListModule)
const allList.value.unshift({})

Property 'forEach' does not exist on type 'HTMLCollectionOf<Element>'

解决方案

const frameZones = Array.from(document.querySelectorAll('path.frame-zone'));
frameZones.forEach((...) => {});

'this' implicitly has type 'any' because it does not have a type annotation.

vue2.x 及 各类eslint报错汇总

大多数都是eslint报错

1:1 error Parsing error: Unexpected token <

Parsing error: Unexpected character '@'

使用 .eslintrc.js 做eslint的项目 保留 'plugin:vue/essential', 其他注释

module.exports = {
    root: true,
    env: {
        node: true
    },
    'extends': [
        'plugin:vue/essential',  //这行起作用 error  Parsing error: Unexpected token <
        'eslint:recommended',
        '@vue/typescript/recommended' //Parsing error: Unexpected character '@'
    ],
    parserOptions: {
        ecmaVersion: 2020
    },
    rules: {
        'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
        'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
    }
}

No overload matches this call.The last overload gave the following error.

export default defineComponent({
    name: 'Title',
    props: {
        propData: {
            title: '', //报错. 基础prop语法使用错误
        },
    },
    setup() {
        console.log(`title`)
    },
})

typescript vue emitter.js 不起效。broadcast 不起效,dispatch不起效

案例代码如下

@Component
export default class HelloWorld extends Vue {
  componentName = 'HelloWorld333'
 
}

问题在于要注册comoponentName需如下写法

@Component({
	componentName:'realComponentName' //这样写才起效
})
export default class HelloWorld extends Vue {
  componentName = 'HelloWorld333'
 
}

原理:class-component.vuejs.org/guide/class…

vue-cli sourceMap无法打断点;debugger无法定位;

如下图:无法打断点,且查找出来的代码块在 webpack-internal:// 开头的代码中

  • 解决方案
//vue.config.js
configureWebpack: {
    devtool: 'source-map',
}

关闭eslint中的warn。停止输出eslint中的warn,配置warn为0。

//.eslintrc.js文件
module.exports = {
    rules: {
        warn: 0
    },
};

文件全局取消eslint关注

  • 文件头部加 /* eslint-disable */ 亲测有效

vue3.x之各类API

computed 有两种注册方式

  1. 只读
import { defineComponent, computed,ref } from 'vue'
export default defineComponent({
	name:'Test',
    setup(){
    	const count = ref(1);
        const plusOne = computed(()=> count.value + 1})
        console.log(plusOne.value)
        plusOne.value++ // 报错
    }
})
  1. 可读可写
const plusOne = computed({
	get:()=> count.value +1,
    set:(val)=>{
    	count.value = val -1
    }
})

vue2.x之各类API

Provide及Inject,vue-property-decorate版

  • 遵循几个规则:
  • provide、inject
  1. father组件注册 provide,son组件可以通过Inject获取对应属性
// father组件
@Provide() provide = 1 

//son组件
@Inject({ from: 'provide', default: 0 }) provide!: number 
  1. 父子组件改变对应值时,是独立的内存单元。互不影响
  • ProvideReactive、InjectReactive
  1. father注册ProvideReactive,son组件可使用 @InjectReactive获取对应属性。
  2. father改变值时,子组件可使用watch捕捉到
  3. 子组件无法更改对应值
//father组件
@ProvideReactive() provideReactive = 1

//son组件
@InjectReactive({ from: 'provideReactive' }) provideReactive!: number

@Watch('provideReactive')
onChangeProvideReactive() {
    console.log(`%c 特殊输出watch provideReactive`, 'color:#ff0000') //可捕捉因
}
test() {
    this.provide = 2	//可修改
    console.log(`%c change provide`, 'color:#ff0000', this.provide) 
    this.provideReactive = 2	//修改后不生效
    console.log(`%c change provideReactive`, 'color:#ff0000', this.provideReactive)
}

vue2 + ts 进阶必备库

vue-class-component

class装饰器 vue-class-component

vue-property-decorator

npm i vue-class-component vue-property-decorator -D

class及其他装饰器,base(vue-class-component):vue-property-decorator

*.vue中支持ts语法,需在script上加lang=ts

vue-cli3 配置感悟

configureWebpack

如果这个值是一个对象,则会通过 webpack-merge 合并到最终的配置中。

总算理解了。无非就是把该在 {配置里的东西} 写到了这个属性中去

chainWebpack

允许对内部的 webpack 配置进行更细粒度的修改。

无非就是配合 vue inspect 去修改对应的配置;

vue.config.js

module.exports = {
  chainWebpack: config => {
    // GraphQL Loader
    config.module
      .rule('graphql')
      .test(/\.graphql$/)
      .use('graphql-tag/loader')
        .loader('graphql-tag/loader')
        .end()
      // 你还可以再添加一个 loader
      .use('other-loader')
        .loader('other-loader')
        .end()
  }
}

各种API地址

sketch图使用中碰到问题汇总

  • sketch中px与百分比切换显示快捷键为 Alt