Vue3 TSX Mobile Terminal Project Based on Vue Cli4 (IV)

In the previous chapter, the framework of this project was basically completed. This article is not about framework level changes, but about using the Vue3 Components API to package Hooks that are often used on mobile and comparing TSX and single Vue file writing in Vue3.

Encapsulating common Hooks

In our business, four Hooks are essential to daily mobile development:

  1. Interface request
  2. WeChat share
  3. File upload
  4. Data buried point reporting

Here, wechat sharing, file uploading and interface request are relatively universal, while the buried point reporting of user operation records is too much business, so it will not be described again.

UseRequest interface request

Here I borrowed umi’s useRequest and removed some of the functions that I didn’t have a scenario for. The main implementation of this Hooks is as follows

  1. Return the response variabledata.loadingConvenient page display
  2. returnrunMethod that allows the business page to manually trigger the request. At the same timerunIs aPromiseThis is triggered when the request is successfulPromise, convenient some need to do chain request business.
  3. Automatic/manual triggering of requests can be configured
  4. Request to offer
  5. swrData cache
  6. Description The encapsulation back-end interface is abnormal

Create a new SRC /hooks folder for hooks and create userequest.ts. The specific code is as follows, here I have made detailed comments, the code logic is very simple.

import { ref } from '@vue/reactivity';
import { AxiosPromise } from 'axios';
import { Toast } from 'vant';
import { errorParse } from '@/utils/errorParse';
interface IRequestConfig {
  // Whether to request manuallymanual? :boolean;// The number of millisecondsdebounce? :number;// SWR cache keycacheKey? :string;// There is an error in whether to close ToastcloseToast? :boolean; }/ / cache
const cache:Record<string, any> = {};
/ / image stabilization
let timer:number | undefined;

/ * * *@description Perform anti-shake *@author Wynne
 * @date The 2021-03-16 *@param {IRequestConfig} [config]
 */
function runDebounce(config? :IRequestConfig) {
  return new Promise((resolve, reject) = > {
    clearTimeout(timer);
    // Whether anti-shake is configured
    if(config? .debounce && config.debounce >0) {
      timer = setTimeout(() = > {
        resolve(true);
      }, config.debounce);
    } else {
      resolve(true); }}); }/ * * *@description useRequest Hooks
 * @author Wynne
 * @date The 2021-03-16 *@param {() => AxiosPromise<T>} request
 * @param {IRequestConfig} [config]
 */
export function useRequest<T> (request:() => AxiosPromise
       
        , config? :IRequestConfig
       ) {
  // Whether the file is being loaded
  const loading = ref(false);
  // Return data
  const data = ref<T>();
  // Execute the request method
  const run = ():AxiosPromise<T> => new Promise((resolve, reject) = > {
    // If the cache is configured and a match is made, the request is returned to the cache first
    if(config? .cacheKey && cache) { data.value = cache[config.cacheKey]; } loading.value =true;
    runDebounce(config).then(() = > {
    	/** The request argument is a specific request method * The method I'm thinking of here can take two forms: * 1. Pass the request method and parameter separately * 2. Wrap the request method one more layer, forming a closure similar to: (param) => () => AxiosPromise
      
        * I use the second form. The main reason is that the request method will not only have passed parameters, but also some headers and so on. I don't like methods with more than three parameters. * As to whether there is a better way, I have not yet thought of, also hope there are big men can give advice */
      
      request().then((res) = > {
        loading.value = false;
        if(config? .cacheKey) { cache[config?.cacheKey] = res.data; }if (/^2\d/.test(res.status.toString())) {
          data.value = res.data;
        } else if(! config? .closeToast) {// If it does not start with 200, the generic errorCode prompt is displayed
          Toast(errorParse((res.data as any).error_code));
        }
        resolve(res);
      }).catch((err) = > {
        reject(err);
        loading.value = false;
       	// If you do not want to display the error dialog box, you can close the error dialog box when the business code needs to handle it itself
        if(! config? .closeToast) {ErrorParse (err. Error_code) returns an error code in exchange for a copyToast(errorParse(err.error_code)); }}); }).catch((e) = > console.log(e));
  });
  // If no immediate execution is required
  if(! config? .manual) { run(); }return { loading, data, run };
}
Copy the code

So I’m going to briefly show you how to use this Hooks

// src/apis/wechat.ts
import Http from 'axios';
const config = window.CUSTOMCONFIG; // Configuration files for different environments of the project
export const Api = new Http(config.api);
// Obtain the wechat Token API interface
export const fetchWechatToken = ():AxiosPromise<IWechatToken> => Api.get('/getWechatToken');

// src/views/Test.tsx
 const { data, loading, run } = useRequest(fetchAccountLogin({
   openId: 'xxxxxxxxxx', {})manual: true.// Enable manual triggering
   debounce: 500.// Enable buffeting for 500ms
   cacheKey: 'WechatToken'.// Set the cache key
   closeToast: false.// Enable the default prompt
 });
// Manually trigger the request
run().then((res) = > {
  console.log('Request successful', res);
}).catch((err) = > {
  console.log('Request failed', err);
});
Copy the code

UseWechatShare Share on wechat

– JSSDK has been checked too many times, so I included some regex parameters in the package that contains this Hooks, so as to prevent future users from passing unstandardized parameters that would cause the Settings to fail.

First we add wechat JSSDK to public/index.html

<script src="/ / res.wx.qq.com/open/js/jweixin-1.6.0.js" async></script>
Copy the code

Then I put the rules of regex.ts in utils/regex.ts, and put the related methods of wechat JSSDK in utils/wechat. This will unify the regex in subsequent projects and facilitate rule changes later.

utils/regex.ts

// The phone number is regular
export const mobileReg = /^1[3-9]\d{9}/;
// Integer regular
export const integerReg = /^\d{6}/;
/ / url regular
export const urlReg = /(https?) :\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;] +[-A-Za-z0-9+&@#/%=~_|]/;
// Check whether the browser is wechat
export constisWechat = !! navigator.userAgent.toLocaleLowerCase().match(/micromessenger/i);
// Whether it is IOS
export constisIOS = !! navigator.userAgent.match(/ipad|iphone/i);
Copy the code

utils/wechat.ts

interface IWechatConfig {
  debug:boolean; // If debug mode is enabled, the return value of all API calls will be displayed in the client alert. If you want to view the parameters passed in, you can open it in the PC, and the parameter information will be printed in the log.
  appId:string; // Mandatory, the unique identifier of the public account
  timestamp:number; // Mandatory to generate the timestamp of the signature
  nonceStr:string; // Mandatory to generate a random string of signatures
  signature:string; // Mandatory, signature
  jsApiList:string[]; // Mandatory, a list of JS interfaces to be used
}

export interface IWechatSharedConfig {
  title:string; // Share the title
  desc:string; // Share the description
  link:string; // Share link, the link domain name or path must be the same as the current page corresponding public number JS security domain name
  imgUrl:string; // Share ICONS
}

/ * * *@description Wechat share to friends configuration *@author Wynne
 * @date The 2021-03-16 *@param {IWechatSharedConfig} [config]
 */
const wechatAppMessageShared = (config:IWechatSharedConfig) = > new Promise((resolve, reject) = > {
  window.wx.updateAppMessageShareData({
    title: config.title,
    desc: config.desc,
    link: config.link,
    imgUrl: config.imgUrl,
    success: function() {
      resolve(true); }}); });/ * * *@description Wechat share moments configuration *@author Wynne
 * @date The 2021-03-16 *@param {IWechatSharedConfig} [config]
 */
const wechatTimelineShared = (config:IWechatSharedConfig) = > new Promise((resolve, reject) = > {
  window.wx.updateTimelineShareData({
    title: config.title,
    desc: config.desc,
    link: config.link,
    imgUrl: config.imgUrl,
    success: function() {
      resolve(true); }}); });/ * * *@description Wechat initialization *@author Wynne
 * @date The 2021-03-16 *@param {IWechatConfig} [config]
 */
export const wechatInit = (config:IWechatConfig) = > new Promise((resolve, reject) = > {
  window.wx.config(config);
  window.wx.ready(() = > {
    resolve(true);
  });
  window.wx.error((res:any) = > {
    reject(res);
  });
});

/ * * *@description Wechat share initialization *@author Wynne
 * @date The 2021-03-16 *@param {IWechatSharedConfig} [config]
 */
export const wechatSharedInit = async(config:IWechatSharedConfig) => Promise.all([wechatAppMessageShared(config), wechatTimelineShared(config)])

Copy the code

Continue to add the usewechatShare. ts file to the hooks folder

import { fetchWechatJSSDK } from '@/apis/wechat'
import { isWechat, urlReg } from '@/utils/regex'
import { IWechatSharedConfig, wechatInit, wechatSharedInit } from '@/utils/wechat'
const config = window.CUSTOMCONFIG
/ * * *@description Wechat share configures Hooks *@author Wynne
 * @date The 2021-03-16 *@param {IWechatSharedConfig} [config]
 */
export const useWechatShare = (wechatSharedConfig:IWechatSharedConfig, isDebug = false) = > {
  // Verify that shared links do not conform to URL rules
  if(! urlReg.test(wechatSharedConfig.link)) {throw new Error('Link format is incorrect!! ')}// Verify that the shared image does not comply with url rules
  if(! urlReg.test(wechatSharedConfig.imgUrl)) {throw new Error('imgUrl format is not correct!! ')}// If the browser is not wechat, do not perform subsequent operations
  if(! isWechat)return
  // You need to get the signature from the backend. The specific method can be written according to your own business
  fetchWechatJSSDK({
    url: location.href.split(The '#') [0].js_api_list: ['updateAppMessageShareData'.'updateTimelineShareData'].appid: config.wechat.appId
  }).then((res) = > {
    const { data } = res
    // Initialize the wechat JSSDK
    wechatInit({
      debug: isDebug,
      appId: data.appId,
      nonceStr: data.nonceStr,
      signature: data.signature,
      timestamp: data.timestamp,
      jsApiList: data.jsApiList
    }).then((res) = > {
      // Set up wechat sharing copywriting
      wechatSharedInit(wechatSharedConfig)
    }).catch((err) = > {
      console.log(err)
    })
  }).catch((err) = > console.log(err))
}

Copy the code

When we need to do wechat sharing style Settings, only need to use this

useWechat({
  title: "Turn on the time machine, surprise and gift you win.".desc: 'Participating in the event will get 100% chance to win Xiaomi notebook, iPad, floor sweeping robot... '.link: location.origin,
  imgUrl: 'https://source-static.xxxxx.cn/wx_shared.png'});Copy the code

UseUpload file upload

Import {CdnUpload} from ‘@/utils/upload’; Hooks: uploads number of files, file types, file sizes, etc. The specific ali cloud OSS upload method is no longer described, you can check baidu or their own use of cloud service provider documents to see.

import { ref } from '@vue/reactivity';
import { Toast } from 'vant';
import { CdnUpload } from '@/utils/upload';

// Upload status
export enum UploadStatusEnum {
  WAIT, / / not start
  UPLOADING, / / on the cross
  SUCCESS, / / success
  FAIL, / / fail
}

// Upload the configuration
export interface IUploadConfig {
  // Upload type, for example, image/*accept? :string;// Maximum size limit, unit KmaxSize? :number;// Maximum number of uploadsmaxCount? :number;// Custom file name. If not, a GUID will be generated automaticallyfileName? :string;// Upload path, do not upload default stored in the root directorypath? :string; }// File information
export interface IFileInfo {
  // File name
  fileOriginName:string;
  // The custom name of the file
  fileName:string;
  // File size in K
  fileSize:number;
  // The URL of the uploaded filefileUrl? :string;// File upload progress
  progress:number;
  // Bucket name, only for poster uploadsbucket? :string; }/ * * *@description Generated GUID *@author Wynne
 * @date The 2021-03-24 *@return {*}* /
function generateGUID() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g.(c) = > {
    const r = Math.random() * 16 | 0;
    const v = c === 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}

// Upload the input instance
let fileInputEle:HTMLInputElement | undefined;

/ * * *@description Generated GUID *@author Wynne
 * @date The 2021-03-24 *@param {function} FileUrl successfully callback *@param {IUploadConfig} Config Upload the configuration */
export const useUpload = (config? :IUploadConfig) = > {
  if(config? .path && !/[0-9|a-zA-Z|\-|_]*\/$/.test(config.path)) {
    throw new Error('Path only supports Chinese/English/digits/hyphens/underscores');
  }
  // Upload progress in percentage scale
  const files = ref<IFileInfo[]>([]);
  // Upload status
  const status = ref<UploadStatusEnum>(UploadStatusEnum.WAIT);

  // Start uploading
  conststart = (uploadFiles? :File[]):Promise<IFileInfo[]> => new Promise((resolve, reject) = > {
    // Progress bar information changed
    const handleProgressChange = (fileName:string, pg:any) = > {
      const item = files.value.find((e) = > e.fileName === fileName);
      if(item) { item.progress = pg.total.percent; }};// Verify that the upload is complete
    const checkUploadComplete = () = > {
      if(fileInputEle? .files && fileInputEle.files.length === files.value.filter((e) = > e.fileUrl).length) {
        fileInputEle.value = ' '; status.value = UploadStatusEnum.SUCCESS; resolve(files.value); }};// File upload
    const fileUpload = (file:File) = > {
      // Rename the file
      const fileExt = file.name.split('. ').pop();
      const fileName = `${(config && config.path) || ""}${generateGUID()}.${fileExt}`;
      const newFile = new File([file], fileName, {
        type: file.type,
      });
      // Add to upload file record summary
      files.value.push({
        fileName: fileName,
        fileOriginName: file.name,
        fileSize: Math.floor(file.size / 1024),
        progress: 0});// Regular uploads
      CdnUpload(newFile, (progress) = > {
        handleProgressChange(fileName, progress);
      }).then((url) = > {
        const item = files.value.find((e) = > e.fileName === fileName);
        if (item) {
          item.fileUrl = url;
        }
        // Verify whether the upload is complete
        checkUploadComplete();
      }).catch((error) = > {
        status.value = UploadStatusEnum.FAIL;
        if (error.code === 'ECONNABORTED') {
          Toast('Poor network environment, upload failed, please try again later ~');
        } else {
          Toast('Upload failed, please try again ~');
        }
        console.log('Upload failed:', error);
        reject(error);
      });
    };

    // input change event
    const handleInputChange = () = > {
      constuploadFileList = uploadFiles || fileInputEle? .files;console.log(uploadFileList);
      // Determine that the file is empty
      if(! uploadFileList || uploadFileList.length ===0) {
        Toast('Please select upload file first');
        return;
      }
      // Determine the maximum number of uploads exceeded
      if(config? .maxCount && uploadFileList.length > config? .maxCount) { Toast('Exceed the maximum number of uploaded files oh, maximum upload${config.maxCount}A file `);
        return;
      }
      // Determine that the maximum file size is exceeded
      if(config? .maxSize) {for (let index = 0; index < uploadFileList.length; index++) {
          const file = uploadFileList[index];
          if (file.size / 1024 > config.maxSize) {
            Toast('Over upload too large oh, maximum upload${config.maxSize}K file ');
            return; }}}// Check whether it matches the file type
      if(config? .accept) {let acceptArr = config.accept.split(', ');
        acceptArr = acceptArr.map((e) = > {
          e = e.replaceAll('/'.'/ /');
          e = e.replaceAll(The '*'.'*');
          return ` (${e}) `;
        });
        const reg = new RegExp(acceptArr.join('|'));
        for (let index = 0; index < uploadFileList.length; index++) {
          const file = uploadFileList[index];
          if(! reg.test(file.type)) { Toast('File format error, upload only supported${config.accept}File `);
            return;
          }
        }
      }
      status.value = UploadStatusEnum.UPLOADING;
      for (const file ofuploadFileList) { fileUpload(file); }};// Create an input File instance if there is no input instance
    if(! fileInputEle) { fileInputEle =document.createElement('input');
      fileInputEle.type = 'file'; fileInputEle.multiple = config? .maxCount ! = =1;
      if(config? .accept) { fileInputEle.accept = config.accept; } fileInputEle.addEventListener('change', handleInputChange);
    }
    files.value = [];
    // If you customize the upload file, the input upload is not triggered
    if (uploadFiles && uploadFiles.length > 0) {
      handleInputChange();
    } else {
      fileInputEle?.click();
    }
  });

  return {
    start,
    files,
    status,
  };
};
Copy the code

Here is another example of how this HOOKS can be used:

 const { start, files, status } = useUpload({
   accept: 'image/*,video/mp4'.maxCount: 2.maxSize: 50 * 1024.path: 'test/'
 });

start().then((res) = > {
  console.log(res[0]);
}).catch((err) = > { console.log(err); });
Copy the code

TSX vs Vue

Vue3 static upgrade

Vue3 has a static enhancement for static DOM, but this optimization is currently only available with template. You can’t enjoy writing TSX. Here’s an example I tested:

Vue file template

Here I use the simplest page presentation for static improvement testing

<template> <h1>h1</h1> <h1>h2</h1> <h1>{{ msg }}</h1> </template> <script> import { defineComponent, ref } from "@vue/runtime-core"; export default defineComponent({ name: "App", setup() { const msg = ref("hello world"); return { msg, }; }}); </script>Copy the code

Then we package and look at the converted source code

From the code above we can see that the two static DOM’s above have been optimized for static promotion. So let’s try to write TSX.

TSX file writing method

So let me rewrite the DOM structure in TSX notation

import { defineComponent, ref } from "@vue/runtime-core";
export default defineComponent({
  name: "App".setup() {
    const msg = ref("hello world");
    return () = >(
      <div>
        <h1>h1</h1>
        <h1>h2</h1>
        <h1>{msg.value}</h1>
      </div>); }});Copy the code

Then we take a look at the TSX writing package after the source

By testing the above two sets of code, we can see that the TSX writing of Vue3 does not enjoy static improvement. After all, Vue was never created for TSX in the first place, so the official recommendation is to use Template instead of TSX. Therefore, in daily business development, it is still recommended to use template for development. In addition to optimization, template has better support. So I personally prefer to write primarily with template, and then use TSX when developing flexible components, a mix of the two.

Common syntax in Vue is written in TSX

V-show Hide/show

The template method

<template>
  <h1 v-show="visible">Hello world</h1>
</template>

<script lang="ts">
import { defineComponent, ref } from '@vue/runtime-core'

export default defineComponent({
  setup () {
    const visible = ref(true)
    return { visible }
  }
})
</script>

Copy the code

TSX writing

import { defineComponent, ref } from 'vue'

export default defineComponent({
  name: 'Home',
  setup () {
    const visible = ref(false)
    return () = > (
      <div>
        <h1 v-show={visible.value}>Hello world1</h1>
        <h1 vShow={visible.value}>Hello world2</h1>
      </div>)}})Copy the code

The original H5 tag does not declare vShow, vHtml, vModel, etc., so this will cause an error. There is no better way to solve this problem.

V-if whether to render

The template method

<template>
  <h1 v-if="visible">Hello world</h1>
</template>

<script lang="ts">
import { defineComponent, ref } from '@vue/runtime-core'

export default defineComponent({
  setup () {
    const visible = ref(true)
    return { visible }
  }
})
</script>
Copy the code

TSX writing

import { defineComponent, ref } from 'vue'
export default defineComponent({
  name: 'Home',
  setup () {
    const visible = ref(false)
    return () = > (
      visible.value
        ? <div>
          <h1>Hello world</h1>
        </div>
        : null)}})Copy the code

V-if in TSX, we can directly use javascript ternary expressions to determine

V – a for loop

The template method

<template>
  <h1 v-for="i in list" :key="i">
    Hello world
  </h1>
</template>

<script lang="ts">
import { defineComponent, ref } from '@vue/runtime-core'

export default defineComponent({
  setup () {
    const list = ref([1, 2, 3, 4, 5])
    return { list }
  }
})
</script>

Copy the code

TSX writing

import { defineComponent, ref } from 'vue'

export default defineComponent({
  name: 'Home',
  setup () {
    const list = ref([1.2.3.4.5])
    return () = > (
      <div>
        {
          list.value.map((e) => <h1>Hello world</h1>)}</div>)}})Copy the code

V-for in TSX we use javascript loops directly

Ref The original DOM reference

The template method

<template> <h1 ref="h1Ref">Hello world</h1> </template> <script lang="ts"> import { defineComponent, onMounted, ref } from '@vue/runtime-core' export default defineComponent({ setup () { const h1Ref = ref<HTMLElement>() onMounted(()  => { console.log(h1Ref.value) }) return { h1Ref } } }) </script>Copy the code

TSX writing

import { defineComponent, onMounted, ref } from 'vue'

export default defineComponent({
  name: 'Home',
  setup () {
    const h1Ref = ref<HTMLElement>()
    onMounted(() = > {
      console.log(h1Ref.value)
    })

    return () = > (
      <div>
        <h1 ref={h1Ref}>Hello world</h1>
      </div>)}})Copy the code
Slots slot

The template method

/ / subcomponents < template > < div > < p > < slot / > < / p > < h1 > < slot name = "strong" / > < / h1 > < / div > < / template > < script lang = "ts" > import { defineComponent } from 'vue' export default defineComponent({ name: 'HelloWorld'}) </script> // Parent <template> <div> <HelloWorld> <span>default</span> <template #strong> strong </template> </HelloWorld> </div> </template> <script lang="ts"> import { defineComponent } from '@vue/runtime-core' import { HelloWorld } from '@/components/HelloWorld' export default defineComponent({ components: { HelloWorld }, setup () { return {} } }) </script>Copy the code

TSX writing

/ / child component
import { defineComponent, renderSlot } from 'vue'
export const HelloWorld = defineComponent({
  name: 'HelloWorld',
  setup (props, ctx) {
    console.log(ctx.slots)
    return () = >
      <div>
        <p>{renderSlot(ctx.slots, 'default')}</p>
        <h1>{renderSlot(ctx.slots, 'strong')}</h1>
      </div>}})/ / the parent component
import { defineComponent } from 'vue'
import { HelloWorld } from '@/components/HelloWorld'
export default defineComponent({
  name: 'Home',
  setup () {
    return () = > (
      <HelloWorld vSlots={{
        default:() = > [<span>default</span>],
        strong: () => [<span>Strong</span>]
      }}>
      </HelloWorld>)}})Copy the code
V-model bidirectional binding

The template method

<template>
  <div>
    <input v-model="form" type="text"/>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch } from '@vue/runtime-core'

export default defineComponent({
  setup () {
    const form = ref('')
    watch(
      () => form.value,
      () => console.log(form.value)
    )
    return {
      form
    }
  }
})
</script>
Copy the code

TSX writing

import { defineComponent, ref, watch } from 'vue'
export default defineComponent({
  name: 'Home',
  setup () {
    const form = ref(' ')
    watch(
      () = > form.value,
      () = > console.log(form.value)
    )
    return () = > (
      <div>
        <input v-model={form.value} type="text" />
        <input vModel={form.value} type="text" />
      </div>)}})Copy the code

The same problem with vShow is that using vModel TS on native DOM will cause an error

The sync modifier

Child components

import { defineComponent } from 'vue'
export const HelloWorld = defineComponent({
  name: 'HelloWorld'.props: ['visible'],
  setup (props, ctx) {
    const updateStatus = () = > {
      ctx.emit('update:visible', !props.visible)
    }
    return () = >
      <button onClick={updateStatus}>Click on the change</button>}})Copy the code

The template method

<template>
  <HelloWorld v-model:visible="visible" />
</template>

<script lang="ts">
import { defineComponent, ref, watch } from '@vue/runtime-core'
import { HelloWorld } from '@/components/HelloWorld'
export default defineComponent({
  components: { HelloWorld },
  setup () {
    const visible = ref(false)
    watch(
      () => visible.value,
      () => console.log(visible.value)
    )
    return {
      visible
    }
  }
})
</script>
Copy the code

TSX writing

import { defineComponent, ref, watch } from 'vue'
import { HelloWorld } from '@/components/HelloWorld'
export default defineComponent({
  name: 'Home',
  setup () {
    const visible = ref(false)
    watch(
      () = > visible.value,
      () = > console.log(visible.value)
    )
    return () = > (
      <div>
        <HelloWorld vModel={[visible.value, 'visible']} / >
      </div>)}})Copy the code

You can feel that Vue3’s TSX support is far worse than React’s. Therefore, it is recommended to use template for daily business development and TSX for flexible components. This will also allow your project to enjoy a wider range of static improvements

TSX style modular

We know that we can use scoped in vue files to implement style modularity, so in TSX vue Cli4 also helps us to build CSS modules. We just need to change the style file name to something like *.module. SCSS when we import styles

First we need to add a declaration to the shims-vue.d.ts file to avoid introducing SCSS errors

// Resolve SCSS file error
declare module '*.scss'{
	const sass:any
	export default sass
}
Copy the code

Introduce SCSS in the TSX file and use style to assign classes

import { defineComponent, ref, watch } from 'vue'
import style from './home.module.scss'
export default defineComponent({
  name: 'Home',
  setup () {
    return () = > (
      <div class={style.blue}>
        <div class={style.red}></div>
      </div>)}})Copy the code

This enables modularity of CSS in THE TSX

Error: Custom components onClick, vModel, vSlots, etc

When importing a third-party component library or one of our own, we find that when we listen for the onClick event, TS will tell us that the property is undefined,

This is where Vue3’s TSX support is not perfect. I looked through the literature and found a solution mentioned in Elementui-Plus. Add a new ele. D. ts declaration file to the Typings folder to extend @vue/ Run-time core module

import { VNodeChild } from '@vue/runtime-core';
import { HTMLAttributes } from '@vue/runtime-dom';

export type JsxNode = VNodeChild | JSX.Element;

export interface SlotDirective {
  [name: string] :() = > JsxNode;
}

typeJsxComponentCustomProps = { vModel? : unknown; vModels? : unknown[]; vCustom? : unknown[]; vShow? :boolean; vHtml? : JsxNode; vSlots? : SlotDirective; } & Omit<HTMLAttributes,'innerHTML'> & { innerHTML? : JsxNode; };declare module '@vue/runtime-core' {
  interface ComponentCustomProps extendsJsxComponentCustomProps { onClick? :() = > any; vSlots? : { [eleName:string]: JSX.Element;
    };
    [eleName: string] :any; }}Copy the code