This is the first day of my participation in the More text Challenge. For details, see more text Challenge

The paper

Vue3 and Vite have been out for a while, and I’m going to use a simple Todolist demo to try out some new features. It just involves some normal operations, but there are no particularly advanced features yet. The main purpose is to try out the pit

Environment set up

Project creation

  • Create project command:

create @vitejs/app

  • Select the Vue-TS template
  • Enter project executionyarn devStart the

Add the dependent

Component library:

 "ant-design-vue": 2.1.2 ""."element-plus": 46 "" ^ 1.0.2 - beta..Copy the code

Store management:

   "pinia": 19 "" ^ 2.0.0 - alpha..Copy the code

Vite plug-in installation and configuration modification

Load on demand:

You wanted to use one of them to load both component libraries on demand, but there was a problem

The questions are as follows:

  • “Ite -plugin-importer”: “^0.2.1”, ANTD-vue loading error with element style
  • “Ite -plugin-style-import”: “^0.10.0”, Element-plus is easy to use, antd will load full, always a warning component loaded full

Solution: Add the corresponding configuration to the plugins referenced separately in the plugins of vt.config. ts:

import styleImport from "vite-plugin-style-import";
import usePluginImport from "vite-plugin-importer";
export default defineConfig({
    plugins: [
    usePluginImport({
      libraryName: "ant-design-vue".libraryDirectory: "es".style: "css",
    }),
    styleImport({
      libs: [{libraryName: "element-plus".esModule: true.ensureStyleFile: true.resolveStyle: name= > {
            return `element-plus/lib/theme-chalk/${name}.css`;
          },
          resolveComponent: name= > {
            return `element-plus/lib/${name}`; },},],}),],})Copy the code

Support JSX/TSX:

"@vitejs/plugin-vue-jsx": "^ 1.1.4." ".Copy the code

Modify vte.config. ts add

import vueJsx from "@vitejs/plugin-vue-jsx";
export default defineConfig({
  plugins: [
    vueJsx(),
  ]
})
Copy the code

Global alias definition:

As previously used, you can directly use something like Component: () => import(“@/views/ lem-tpl.vue “), and modify the vite.config.ts addition

import path from "path";
export default defineConfig({  
    resolve: {
    alias: [{find: "@".replacement: path.resolve(__dirname, "src"),},],},})Copy the code

Import {useTodoStore} from “@/store/modules/todo”; import {useTodoStore} from “@/store/modules/todo”; The error message is as follows:

Cannot find module '@/store' or its Corresponding type declarations.Vetur(2307)Copy the code

Solution: You need to add the following two properties to tsconfig.json

"baseUrl": "src"."paths": {
  "@ / *": [". / *"]},Copy the code

Inject the ANTD theme color variable globally for all less files:

To facilitate reference to antD’s preset color system in your project, for example

  &.checked {
    color: @success-color;
  }
  &.unchecked {
    color: @primary-color;
  }
Copy the code

Instead of having to use @import (URL) in every less file to introduce modifications to the vite.config.ts addition

import { generateModifyVars } from "./build/style/generateModifyVars";
export default defineConfig({ 
    css: {
    preprocessorOptions: {
      less: {
        modifyVars: generateModifyVars(),
        javascriptEnabled: true,},},},})Copy the code

Create a new file. / build/style/generateModifyVars ts for import and inject less overall implementation principle: use less hack properties for injection Such as injection can be as follows:

 less: {
   globalVars: {
     hack: `true; @import '~@/assets/less/variables.less'; `}}Copy the code

Since ANTD already has a ready-made method and already has a hack property, to use its preset color variable, I will directly export the variable code returned by getThemeVariables as follows:

import { getThemeVariables } from "ant-design-vue/dist/theme";
export function generateModifyVars(dark = false) {
  const modifyVars = getThemeVariables({ dark });
  return {
    ...modifyVars,
  };
}
Copy the code

The prettier configuration is added

Create an prettier.conf.js for the root directory

module.exports = {
  printWidth: 80.// Line length (default: 80)
  tabWidth: 2.// How many Spaces does each TAB correspond to (default 2)
  useTabs: false.// Whether to use TAB to indent (default false)
  singleQuote: false.// Use single quotation marks (false by default)
  semi: true.// End declarations with a semicolon (true by default)
  trailingComma: 'es5'.// Use a trailing comma for multiple lines (default: None)
  bracketSpacing: true.// Use Spaces between braces for object literals (default true)
  jsxBracketSameLine: false.// In multi-line JSX, > is placed at the end of the last line, not at the beginning (default false)
  arrowParens: "avoid".// If an arrow function with only one argument has arguments with parentheses (default avoid)
};
Copy the code

Development experience

State management – integrated use of pinia (maybe like vuex5)

Pinia use

  1. Install and useyarn add pinia@next
  2. Create integration into vUE
const store = createPinia();
app.use(store)
Copy the code
  1. Define store, define state
import { defineStore } from "pinia";
export const useTodoStore = defineStore({
  id: "todo".state: (a) :TodoState= > {
    return {
      total: [].testName: "Test it out."}; }});Copy the code
  1. piniaDelete themutationDefinition, just leaveactionTo define the action
actions: {
   deleteItem(item: DataItem) {
     const arr = this.total;
     const index = arr.findIndex((item2: DataItem) = > item2.id == item.id);
     if(index ! = -1) {
       arr.splice(index, 1); }},clearData() {
     this.$reset(); }},Copy the code

By default, this refers to the current instance. You can access state directly via this. Total, or you can call this. The application can be used directly by calling the usexxxStore in the Composition API, as follows

export function useTodo() { 
  const todoStore = useTodoStore();
  todoStore.restoreData();
  const data: UnwrapRef<DataItem[]> = todoStore.total;
  
  watch(todoStore.total, total= > {// You can listen to changes in the store
    localStorage.setItem(StoreKey.TODO_LIST, JSON.stringify(total));
  });
  
  return {
    total: data,// There is a response
  };
}
Copy the code
  1. Note that deconstruction cannot be used in setup
    // ❌ This won't work because it breaks reactivity
    // it's the same as destructuring from `props`
    const { name, doubleCount } = store
Copy the code

It also cannot be defined outside of the route guard

// ❌ Depending on where you do this it will fail
const store = useStore()
router.beforeEach((to, from, next) = > {
  if (store.isLoggedIn) next()
  else next('/login')})Copy the code
  1. use$patchBatch update state
cartStore.$patch((state) = > {
  state.items.push({ name: 'shoes'.quantity: 1 })
  state.hasChanged = true
})
Copy the code

Script setup to use

  • The setup TAB sets the setup property
<script setup lang="ts">
<script lang="tsx" setup>// TSX needs to set lang= 'TSX', otherwise TSX syntax cannot be parsed
Copy the code
  • Methods or properties that are introduced are automatically bound without having to return an object to include them, for example, and can be called directly by the page
const { finishes, unfinish, add, total,clearAll } = useTodo();
Copy the code
  • Script setup using TSX requires an export instead of a return, similar to the following
export default() = ><>
           <Button type="danger" onClick={()= >ClearAll ()}> Clear everything</Button>
         </>
Copy the code

Hooks encapsulate generic logic

Create an hooks folder. Use compositionApi to implement common logic. Export the use method. Encapsulate all the form rules, form status, and submission methods that are required for form submission, so that the user only cares about what they’re using and they can bind it to that method, and they don’t have to worry about the internal implementation, okay

import { DataItem } from "@/types/model";
import { onMounted, reactive, ref, Ref, toRaw, UnwrapRef } from "vue";
import { useTodo } from "./useTodo";
export function useForm() {
  const formRef = ref(); // Reference the form instance of the page
  // const { add } = useTodo();
  let formState: UnwrapRef<DataItem> = reactive({
    title: "".id: "".finish: false});const rules = {
    title: [{required: true.message: "Please fill in the title of todo",}]};type SubmitType = () = > Promise<DataItem>;
  const onSubmit: SubmitType = () = > {
    return formRef.value.validate().then(() = > {
      return toRaw(formState);
    });
  };
  const reset = () = > {
    formRef.value.resetFields();
  };
  return {
    formRef,
    formState,
    rules,
    reset,
    onSubmit,
  };
}
Copy the code

Note: How to obtain the form instance has changed: $refs. FormRef () = const formRef (); $refs. FormRef () = const formRef ();

TSX related applications

Reference github.com/vuejs/jsx-n…

Use of slots

  • Using V-slots, the key value is the slot name and the value is the render function
<List.Item
  v-slots={{
    actions: () = > renderActions(item)
  }}
>
</List.Item>
Copy the code
  • Define object literals directly, with key being the slot name
<List.Item.Meta description={`id=${item.id}`} > {{title: (): JSX.Element => {
            return item.editing ? <Input v-model={[item.title, "value]} "/ > : <div onDblclick={()= > edit(item)}>
                        {`${item.title}`}
                    </div>
        }
  }}
 </List.Item.Meta>
Copy the code

V – the use of the model

For example, antD form, template, and JSX are written as follows:

The template of writing:

<a-input v-model:value="formState.title" />
Copy the code

JSX = JSX

<Input v-model={[this.formState.title, "value"]} / >Copy the code

Frequently Asked Questions (Resolved)

Antdv, menu warning problem, as long as the menu component reference will be reported, warning as follows

reactivity.esm-bundler.js:337 Set operation on key "default" failed: target is readonly.
Copy the code

Solution:

  • Related issue: AntD-vue bug github.com/vueComponen…
  • Upgrade the VUE version to 3.0.10 github.com/vuejs/vue-n…

A query indicates that the problem is caused by ANTD packaging and the version needs to be rolled back

  • Antdv rolls back to 2.1.2
  • The value of Vue is higher than 3.0.10

• The template uses the

component, but when importing, import {divider} from “ant-design-vue”; There is no separate method for registering components, so you have to manually write a method for global registration, as follows:

interfaceComp { displayName? :string; name? :string;
}
type RegisteComp = (comp: Comp & Plugin) = > void;
const registeComp: RegisteComp = comp= > {
  const name = comp.displayName || comp.name;
  if(name && ! registed[name]) {const instance = getCurrentInstance();
    constapp = instance? .appContext.app;if (app) {
      app.use(comp);
      registed[name] = true; }}};Copy the code

In Vue 3, the type of events that the TSX listens to on a custom component is incorrect

If the child component only defines emits, vue3 will not be able to derive the corresponding method error message from the parent component using ts normally:

Solution:

Method 1: Reference: juejin.cn/post/692093…

Parent component: Assigns values to properties using extended operations

<TodoList data={unref(finishes)} {... {/ / this solution reference: https://juejin.cn/post/6920939733983969294
      onDeleteItem: (item: DataItem): void= >{ deleteItem(item); }}} / >Copy the code

Functions: functions: functions: functions: functions: functions: functions: functions: functions: functions

const todoProps = {
  data: {
    type: Array as PropType<DataItem[]>,
  },
  onDeleteItem: {
    type: Function as PropType<(item: DataItem) = > void>}};Copy the code

Method 2: sub-component: define the following: setup props without type limit, and put all render functions in the render method (guess this is related to this)

const todoProps = {
  data: {
    type: Array as PropType<DataItem[]>,
  },
  onDeleteItem: {
    type: Function as PropType<(item: DataItem) = > void>}};const list = defineComponent({
  name: 'TodoListTSX'.props: todoProps,
  emits: ['deleteItem'].setup(props) {
    // The operation logic is written to setup, which connects to the view,
    const toggle = (item: DataItem) = > {
      if (item.finish) {
        item.finish = false;
      } else {
        item.finish = true; }};const { edit, finishEdit, cancelEdit } = useEdit(props.data as DataItem[]);
    return {
      edit, finishEdit, cancelEdit, toggle
    };
  },
  render() {
    // Write the render logic here and import the method from setup
    const { data, finishEdit, cancelEdit, edit, onDeleteItem, toggle } = this}})Copy the code

There will always be a warning for switching routes:

vue-router.esm-bundler.js:72 [Vue Router warn]: Component "default" in record with path "/script-tsx" is a function that does not return a Promise. If you were passing a functional component, make sure to add a "displayName" to the component. This will break in production if not fixed.
Copy the code

The solution is to add a displayName property

  • Issue github.com/vuejs/vue-r…
const list = () = > <></>
list.displayName = 'ScriptTsx'
export default list;
Copy the code

Remark:

  • next.router.vuejs.org/api/#props

As the official document states,

If you want to use a functional component, make sure to add a displayName to it, even if it returns a JSX.

const HomeView = () = > h('div'.'HomePage')
// in TypeScript, you will need to use the FunctionalComponent type
HomeView.displayName = 'HomeView'
const routes = [{ path: '/'.component: HomeView }]
Copy the code

legacy

Script returns the TSX starts changing, switches to another page and cuts back to the list without changing the data,

Repetition steps: When returning to TSX using Script setup, only the route executes the method here the first time. When switching to this page the second time, it is not called. The page does not follow the response change, but in fact the data has changed. The setup method is automatically executed when the route switches to the current page. It is suspected to be a vUE bug, which has not been solved yet

In the TSX component, introducing less style, deep does not take effect

Repetition steps: TSX uses the following methods:

import/ '.TodoList.less'
 font-size: 24px; // Deep does not work with TSX, so this is the only way to use it
  :deep(.anticon) {
    font-size: 24px;
  }
Copy the code

Element PopConfirm style does not take effect

Shown below:

repetition: This may be a bug, issue has been submitted

The bug link

  • Element – plus: github.com/element-plu…
  • Vue3:github.com/vuejs/vue-n…

Special thanks to

  • Excellent open source admin: github.com/anncwb/vue-…

The code address

  • codesandboxTemplate address:Codesandbox. IO/s/vue3 – vite…
  • githubAddress:Github.com/nabaonan/to…