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 execution
yarn dev
Start 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
- Install and use
yarn add pinia@next
- Create integration into vUE
const store = createPinia();
app.use(store)
Copy the code
- 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
- 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
- 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
- use
$patch
Batch 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">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…