In this paper, the author tries to use Vite combined with VUe3 and TSX to complete the mental process of the Todo List demonstration demo step by step, hoping to give beginners guidance on the relevant pit stepping
A demonstration of the relevant functionality that has been implemented
- typescript
- Most of vue3 syntax examples
- Vite scaffold configuration
- TSX development mode
- less
- router
- vuex
- Element-plus
I think the above functions combined with Axios are sufficient for most of the small project requirements. If you are interested, please welcome to fork the code and experience it, and also welcome to submit an issue for exchange.
Why did the VUE project abandon SFC writing and try JSX/TSX coding?
I think there’s a blog post that summarizes this issue pretty well, so I won’t bore you with the details. Here’s the link:
Why do I recommend using JSX to develop Vue3
Of course, this is also a matter of opinion. It is normal for everyone to have their own preferences and prejudices, but one more way of thinking and one more scheme can also be a choice for the later business implementation.
Why did I write this demo?
At present, I like the two-way data binding mechanism of VUE and the flexibility of react JSX syntax in the mainstream front-end frameworks. However, the two have not been combined perfectly in the era of VUE2, so I have been regrettably.
Until after vue3 introduction, more and more open source UI component library to implement the related components in the use of TSX way business, ecology is becoming more and more mature, so take advantage of this company’s business was still not heavy to experience, experience after it is very comfortable and I also like to find a perfect frame encoding (of course this perfect just for me, After all, everyone’s favorite framework is coded differently).
I think a considerable number of people are interested in this coding mode at present, because there are many articles in this community. However, I haven’t found a complete example demo so far from reading these days, so I have to search and read each part separately. Therefore, based on this situation, I have also summarized a simple example article for interested partners to learn and read, hoping to help you.
I will explain the sequence of project creation and each functional module one by one
Demonstration analysis
The main dependencies involved:
Vue @ ^ 3.0.5
Vite @ ^ 2.3.5
[email protected]
[email protected]
The typescript @ ^ 4.1.3
Less @ ^ 4.4.1
@ vitejs/plugin - vue - JSX @ ^ 1.1.5
Element - plus @ ^ 1.0.2 - beta. 46
Project creation
The preparatory work
- Ensure that installation
yarn
$ npm install yarn -g
Copy the code
- Ensure that installation
vite
The scaffold
$ npm install -g create-vite-app
# or
$ yarn add -g create-vite-app
Copy the code
create
$ npm init @vitejs/app
# or
$ yarn create @vitejs/app
Copy the code
Then enter the name of the project you want and press Enter to select the template default:
As you can see, there are a lot of presets. The official website lists the currently supported preset templates:
vanilla
vanilla-ts
vue
vue-ts
react
react-ts
preact
preact-ts
lit-element
lit-element-ts
svelte
svelte-ts
Here we choose vUE
Then let’s go ahead and select VUE-TS, press Enter and the scaffolding builds the project for us.
Here is the completed directory structure:
│ ├ ─ the publicStatic resource directory│ │ ├─ SRC │ │ favicon. Ico │ │ ├─ SRC │ ├.vue# Entry vue file│ │ │ main. Ts# import file│ │ │ shims - vue. Which s# vue file module declaration file│ │ │ vite - env. Which s# vite environment variable declaration file│ │ ├─assets in │ │ ├─assetsResource file directory│ │ │ logo. PNG │ │ │ │ │ └ ─ componentsComponent file directory│ │ helloWorld.vue │ │ │ gitignore │ │ index# Vite project entry file│ │ package.json │ │ readme. md │ tsconfig.json# tsconfig config file│ │ vite. Config. Ts# vite configuration file
Copy the code
After installing dependencies, run YARN Dev to see what the project looks like in action. Of course, such a configuration is far from what we need, so from here on to the transformation.
Project reform
Configuration eslint
Added esLint to normalize Typescript and Vue code by installing dependencies first:
yarn add eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
Copy the code
The functions of these three dependencies are as follows:
- Eslint: The core code for ESLint
- Eslint-plugin-vue: A plugin for ESLint to detect vUE code specifications
- @typescript-eslint/parser: A parser for ESLint that parses typescript to check and standardize typescript code
- @typescript-eslint/eslint-plugin: This is an ESLint plugin that contains various specifications for checking typescript code
Prettier combined with Eslint
yarn add prettier eslint-config-prettier eslint-plugin-prettier -D
Copy the code
Among them:
- Prettier: The core code for the prettier plug-in
- Eslint-config-prettier: Resolves the conflict between the style specification in ESLint and the style specification in Prettier by taking the style specification of Prettier as the standard
- Eslint-plugin-prettier: The use of prettier as the ESLint specification
Once the dependencies are installed, you can start to configure the related files by adding.eslintrc.js files in the project root directory
Eslint-config-prettier extends the ‘prettier/ @typescript-esLint ‘configuration does not need to add ‘prettier/@typescript-eslint’ if eslint-config-prettier is older than 8.0.0. Otherwise running ESLint will report an error
module.exports = {
parser: 'vue-eslint-parser'.parserOptions: {
parser: '@typescript-eslint/parser'.// Specifies the ESLint parser
ecmaVersion: 2020.// Allows for the parsing of modern ECMAScript features
sourceType: 'module'.// Allows for the use of imports
ecmaFeatures: {
// Allows for the parsing of JSX
jsx: true}},extends: [
'plugin:vue/vue3-recommended'.'plugin:@typescript-eslint/recommended'.'plugin:prettier/recommended'].rules: {}}Copy the code
Next, add the prettier configuration and add the. Prettierrc.js file in the root directory of the project
/ / specific configuration can refer to https://prettier.io/docs/en/options.html
module.exports = {
printWidth: 100.tabWidth: 2.useTabs: false.semi: false.// Open comma
vueIndentScriptAndStyle: true.singleQuote: true./ / single quotation marks
quoteProps: 'as-needed'.bracketSpacing: true.trailingComma: 'none'.// Final semicolon
jsxBracketSameLine: false.jsxSingleQuote: false.arrowParens: 'always'.insertPragma: false.requirePragma: false.proseWrap: 'never'.htmlWhitespaceSensitivity: 'strict'.endOfLine: 'lf'
};
Copy the code
With this done, ESLint is configured except for script configuration. Now you just need to configure script commands in package.json to complete esLint configuration.
{..."scripts": {
"dev": "vite"."build": "vue-tsc --noEmit --skipLibCheck && vite build".// Add skipLibCheck to skip the ts check imported into the library
"serve": "vite preview"."lint": "eslint src"."lint:fix": "eslint src --fix --ext .ts,.tsx"}},Copy the code
Now let’s try the result of esLint testing:
You can see that ESLint has successfully performed a code check, and then we’ll fix it automatically:
Eslint’s auto-fix was also successful, and esLint’s configuration is now complete.
TSX composite support
First need to install the official maintenance of vite plug-in @vitejs/plugin-vue-jsx, this plug-in is actually the core of @vue/babel-plugin-jsx, but in this plug-in encapsulated a layer for vite plug-in call. Therefore, the JSX syntax specification for Vue can be directly referred to @vue/babel-plugin-jsx. The document link is as follows. It is recommended that you read the syntax specification first. The official writing is more detailed, and I will also explain the usage of most of the specifications based on the actual situation, VUE JSX syntax specifications.
$ npm install @vitejs/plugin-vue-jsx -D
# or
$ yarn add @vitejs/plugin-vue-jsx -D
Copy the code
After installation, use the plugin in vite.config.ts, the code is as follows:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
export default defineConfig({
plugins: [
vue(),
vueJsx() // Plugins are used]});Copy the code
Vue, HelloWorld. Vue, and shims.vue.d.ts files can be deleted from the directory, because we only need to write TSX files.
Add app. TSX file to SRC directory and write the following code:
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
return () = > <div>hello world</div> // Write a hello world to heaven}})Copy the code
Then rerun Yarn Dev to see the familiar Hello World on the page. Yes, you read it right. It’s as simple as that.
At this time, I encountered a small problem, that is, port 3000 is occupied, how to configure the port configuration of vite at this time, the actual vite official website is quite clear, because the vite.config.ts also has related type prompt, so the problem was solved quickly. Add a serve object in vite.config.ts and set the port as follows:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
export default defineConfig({
plugins: [
vue(),
vueJsx() // Plugins are used].server: {
port: 8888}});Copy the code
A small tip
It is also troublesome to write TSX templates for vue3 mode every time. Here we suggest that you can add a custom code fragment if you use vscode, which is the template I use daily:
{
"Print to console": {
"prefix": "vuetsx"."body": [
"import { defineComponent } from 'vue'\n"."export default defineComponent({"." props: {},"." emits: [],"." components: {},"." setup(props, ctx) {"." return () =>
"."}"."})",]."description": "Create vue template"}}Copy the code
Configuring a Path Alias
The path alias also needs to be configured in vite.config.ts.
import { resolve } from "path"; // Install node/path dependencies if there is an error
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
export default defineConfig({
plugins: [vue(), vueJsx()],
server: {
port: 8888
},
resolve: {
alias: {
"@": resolve(__dirname, "/src"),}}});Copy the code
At this point in the project can directly use the new path alias, use vscode may be there is no path, this time only need in jsconfig. Json/tsconfig json configuration paths and baseUrl path will light up to alert the, specific as follows:
{
"compilerOptions": {
// ...
"baseUrl": "src"."paths": {
"@ / *": ["*"],}},// ...
}
Copy the code
Less configuration
Vite provides built-in support for.scss,.sass,.less,.styl, and.stylus files. Therefore, there is no need to install a specific Vite plug-in for them, but the appropriate preprocessor dependencies must be installed so that the project can parse the LESS files directly.
$ npm install less less-loader -D
# or
$ yarn add less less-loader -D
Copy the code
“Less” and “less-loader” need to be written to devDependencies, otherwise an error will be reported.
The router configuration
The installation
Please note that routing must be installed at least 4.0.0. It is better to install the current version directly.
Viewing the vue-router version:
$ npm info vue-router versions
Copy the code
Install the latest vue-router directly:
$NPM install [email protected]# or$yarn add [email protected]Copy the code
Create the following directory structure under the SRC directory:
- src
|- router
| index.ts
|- views
| 404.tsx
| login.tsx
| home.tsx
Copy the code
configuration
The new version of the routing configuration is very similar to the previous version, with only minor differences. The new version of the routing API is all introduced functionally, along with ts type hints, allowing us to complete configuration without documentation.
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
// Route configuration is the same as before
const routes: RouteRecordRaw[] = [
{
path: "/".redirect: "/login"}, {path: "/home".name: "home".meta: {
type: "home",},component: () = > import("@/views/home"),}, {path: "/login".name: "login".meta: {
type: "login",},component: () = > import("@/views/login"),}, {path: "/:pathMatch(.*)*".// Note that the 404 page matching rule is not the same as before
name: "404".component: () = > import("@/views/404"),},];// Change the mode from [new VueRouter] to [createRouter]
const router = createRouter({
history: createWebHashHistory(), // The routing mode is configured using API calls instead of hash routing in the previous string
routes,
});
export default router;
Copy the code
Adding a Route Guard
// The route guard is implemented in the same way as before
router.beforeEach(
(
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext
) = > {
// Obtain the userToken. The value can be obtained from localStorage or cookie based on the service scenario
const user = localStorage.getItem("user");
// Route guard judge
if (to.meta.type === "login" && user) {
next({ name: "home" });
return;
}
if (to.meta.type === "home" && !user) {
next({ name: "login" });
return;
}
next();
});
Copy the code
Now a vue3 base route is configured and imported via vue as a plug-in in the main.ts entry file
import App from './App'
import router from "@/router"
import { createApp } from 'vue'
createApp(App).use(router).mount("#app");
Copy the code
At this point, when starting the project, you can see that the address bar already uses hash routing links, but at this time, there is still the last step to realize route jump, which requires router-view. This part is implemented in the same way as vue2, and I use import to achieve it.
# App.tsx
import "@/assets/base.less"
import { defineComponent } from "vue";
import { RouterView } from "vue-router"; // Importing the RouterView component from the Vue Router can actually be used without importing it
export default defineComponent({
setup() {
return () = > <RouterView />; }});Copy the code
Vuex configuration
The installation
Please note that vuex also needs to be installed in version 4.0.0 and above, so it is best to install the current version directly.
View the VUEX version:
$ npm info vuex versions
Copy the code
Install the latest vue-router directly:
$NPM install [email protected]# or$yarn add [email protected]Copy the code
Create the following directory structure under the SRC directory:
- src
|- store
| | index.ts
| |- home
| | | index.ts
| | | actionType.ts
| |- login
| | | index.ts
| | | actionType.ts
Copy the code
configuration
The configuration of Vuex in VUe3 is basically the same as that of VUe2, so I’ll create it in a modular way for demonstration purposes.
Configure the main entrance first
# store/index.ts
import { createStore } from "vuex"
import home from "./home";
import login from "./login"
// This is similar to router
const store = createStore({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {
home,
login
}
})
export default store
Copy the code
I will only take the login module repository to do the relevant code demonstration
# store/login/index.ts
import { Module } from "vuex";
import { SET_USER } from "./actionType";
export type IUser = Record<"name" | "password".string>;
export interface ILoginState {
user: IUser;
}
// Module can pass two generic variables: the first is the object interface type of the current Module state and the second is the object interface type of the main repository state
const LoginStore: Module<ILoginState, {}> = {
namespaced: true.state: {
user: {
name: "".password: "",}},getters: {},
mutations: {
[SET_USER](state, payload: IUser) { state.user = payload; }},actions: {
[SET_USER]({ commit }, payload: IUser) { commit(SET_USER, payload); ,}}};export default LoginStore;
Copy the code
It can be seen from the code and vue2 basically no difference, let me more speechless is upgraded vuex support for ts is still not very awesome, declaration file in a lot of any type, basically can’t use the advantage of ts type hinting, this can see late official optimization, of course the community now has the relevant solutions, For those interested, here’s a solution to make Vuex support typescript better
Now that the state store is created, import stores just as you import routes
import App from './App'
import store from "@/store"
import router from "@/router"
import { createApp } from 'vue'
createApp(App).use(router).use(store).mount("#app");
Copy the code
I will demonstrate how vuEX can be used in components based on the code in the components.
Element – plus introduction
In fact, the introduction of Element-Plus has been described in detail on the official website, so here is a demonstration of the introduction on demand.
The installation
$ npm install element-plus
# or
$ yarn add element-plus
Copy the code
Then install the Vite style import plug-in
$ npm install vite-plugin-style-import -D
# or
$ yarn add vite-plugin-style-import -D
Copy the code
configuration
Then, in vite.config.ts, do the following
import { resolve } from "path";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import styleImport from "vite-plugin-style-import";
export default ({ mode }) => // Environment variables in the vite configuration file can be retrieved as follows
defineConfig({
plugins: [
vue(),
vueJsx(),
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}`; },},],},base: mode === "development" ? "/" : ". /".// In this case, configure the environment packaging path to avoid blank screen in production environment packaging
server: {
port: 8888,},resolve: {
alias: {
"@": resolve(__dirname, "/src"),}}});Copy the code
Then style files are introduced in main.ts, after which you can use Element-Plus directly in your components on demand
// main.ts
import App from "./App";
import store from "./store";
import router from "./router";
import { createApp } from "vue";
import "element-plus/lib/theme-chalk/index.css";
createApp(App).use(router).use(store).mount("#app");
Copy the code
Project analysis
JSX/TSX syntax specification
If you have experience developing React, you will notice that there are several new concepts that are unique to Vue: With the exception of slot, directive, emit, etc., most of the JSX syntax specifications supporting VUE are the same as those supporting React. I won’t go into details about the same syntax.
Fragment
The vue3 template syntax supports parsing multiple root nodes, such as:
<template>
<div></div>
<div></div>
<div></div>
</template>
Copy the code
JSX does not support this method, and only one root node is required. We can do the same with react by adding a virtual node:
const App = () = > (
<>
<span>I'm</span>
<span>Fragment</span>
</>
);
Copy the code
instruction
Vue /babel-plugin-jsx: vue/ babel-jsx: vue/ babel-jsx: vue/ babel-jsx: vue/ babel-jsx: vue/ babel-jsx: vue/ babel-jsx: vue/ babel-jsx: vue/ babel-jsx: vue/ babel-jsx
- v-bind
import { defineComponent, ref } from "vue";
const App = defineComponent({
setup(){
const size = ref<"large" | "medium" | "small" | "mini"> ("mini")
return () = >
<Button size={size.value}></Button> // The JSX template syntax is the same as v-bind}});Copy the code
- v-if
Use a conditional statement to implement the V-if function, as in React.
const App = () = > (
<>
{
condition ? <span>A</span> : <span>B</span>
}
</>
);
Copy the code
- v-for
As in React, the map loop is used
import { defineComponent, ref } from "vue";
const App = defineComponent({
setup(){
const list = ref<string[] > ([])return () = > {
list.value.map((data,index) = > <p key={index}>{data}</p>)}}});Copy the code
- Custom instruction
Start by creating a custom directive
import { ObjectDirective } from "vue";
const foucsDirective: ObjectDirective<HTMLElement, any> = {
mounted(el) {
switch (el.tagName) {
case "INPUT":
el.focus();
break;
default:
const input = el.querySelector("input"); input? .focus();break; }}};export default foucsDirective;
Copy the code
Introduced the global
import App from "./App";
import store from "./store";
import router from "./router";
import { createApp } from "vue";
import foucsDirective from "@/directive/focus";
import "element-plus/lib/theme-chalk/index.css";
const app = createApp(App);
// Global mount command
app.directive("focus",foucsDirective);
app.use(router).use(store).mount("#app");
Copy the code
Local introduction
import { defineComponent, ref } from "vue";
import foucsDirective from "@/directive/focus";
const App = defineComponent({
directives: { focus: foucsDirective },
setup(){
const value = ref<string> ("")
return () = > <input type="text" v-focus v-model={value.value}/>}});Copy the code
slot
Unlike React, component comes with a children props, and the nested custom components of vue depend entirely on slots, so JSX has a very different way of implementing slots in Vue.
import { defineComponent } from "vue";
/ / child component
const Child = defineComponent({
setup(props, { slots }) {
return () = > (
<>Default slot: {slots.default && slots.default()}<br />Named slot: {slots.prefix && slots.prefix()}<br />Suffix ({name: "This is a demonstration of the slot.suffix"})}</>); }});/ / the parent component
const Father = defineComponent({
setup() {
return () = > (
<Child
v-slots={{
prefix: <i class="el-icon-star-on"></i>, // named socket suffix: (props: Record<"name", string>) =><span>{props.name}</span>, // props for slot scope}} > This is an example of the default slot</Child>); }});export default Father
Copy the code
From the simple example above, it is easy to summarize the use of the default slot, named slot, and scoped slot in vue, which renders the following:
There is a pit where components from V-slots that pass in a defineComponent package directly will not perform rendering
const Test1 = defineComponent({
setup() {
return <i class="el-icon-star-on"></i>; }});// Error This component will not be successfully rendered as a slot passed child
const Test2 = () = > <i class="el-icon-star-on"></i> // Correctly this component will be rendered successfully as a slot passed child
Copy the code
emit
In general, vUE neutrons send emit values to the parent in the way of emit. This is roughly similar in VUe3, but there is an additional step to define emit, which is also to prepare for the subsequent type destruction.
import { defineComponent } from "vue";
/ / child component
const Child = defineComponent({
emits: ["click"].setup(props ,{ emit }) {
return () = > (
<button onClick={()= >{emit("click")}}> I trigger emit</button>); }});/ / the parent component
const Father = defineComponent({
setup() {
return () = > (
<Child onClick={()= >{console.log("emit triggered ")}}/>); }});Copy the code
This is fine, but in TSX there is no type declaration for the related EMIT event in the sub-component props, so an error is reported
But the actual function can be triggered, and there is only an exception in type detection. When you run into a library that does not support TSX notation (e.g. elemental-plus = =) and you do not want a red error, you can do this:
import { defineComponent } from "vue";
/ / child component
const Child = defineComponent({
emits: ["click"].setup(props ,{ emit }) {
return () = > (
<button onClick={()= >{emit("click")}}> I trigger emit</button>); }});/ / the parent component
const Father = defineComponent({
setup() {
return () = > (
<Child {.{// Avoid errors due to type detection this method can be applied to any non-existentpropsParameter to a type declarationonClick:() = >{console.log("emit triggered ")}}}/>); }});Copy the code
But this is actually a curve to save the country’s plan, so if you have a development library or usually plan to use VUE3 combined with TSX to write the project, it is best to use the following way, do a compatible processing:
import { defineComponent, PropType } from "vue";
/ / child component
const Child = defineComponent({
emits: ["click"].// The traditional template method
props: {
onClick: Function as PropType<(event:MouseEvent) = > void> // Compatible with TSX writing, so that events have type declarations
},
setup(props ,{ emit }) {
return () = > (
<button onClick={(event:MouseEvent)= >{emit("click",event)}}> I trigger emit</button>); }});/ / the parent component
const Father = defineComponent({
setup() {
return () = > (
<Child onClick={(event:MouseEvent)= >{// There is no type error and a good type message console.log("emit triggered ")}}/>); }});Copy the code
TSX Render way
TSX also supports render writing, which is currently the writing method of most open source UI libraries. Personally, I recommend this writing method, which separates the logical layer from the template layer for easier maintenance
import { ref, renderSlot, onUnmounted, defineComponent } from "vue";
// Component with render function advantage: logical area can be separated from template
export const RenderComponent = defineComponent({
props: {
title: String,},/ / logic layer
setup() {
const count = ref<number> (1);
const timer = setInterval(() = > {
count.value++;
}, 2000);
onUnmounted(() = > {
clearInterval(timer);
});
return {
count,
};
},
/ / rendering layer
render() {
// The render function automatically fires when reactive data changes (similar to react)
const { count, $slots, title } = this;
return (
<div class="render-component">
{renderSlot($slots, "prefix")} {count}
<br />This is props: {title}<br />
{renderSlot($slots, "default")}
</div>); }});Copy the code
Used in Router and VUEX projects
Use in router projects
import { defineComponent } from "vue";
import { useRouter, useRoute, RouterView } from "vue-router";
const App = defineComponent({
setup(){
const router = useRouter();
const route = useRoute();
function go(pathName:string){
// Redirect route
router.push({
name: pathName,
query: {
value: "Route parameter Transfer"}})// Params is the same as params
const { query } = route;
console.log(query)
}
return () = > <>
<button onClick={()= >{go (' home ')}} > jump home</button>
<button onClick={()= >{go (' login ')}} > jump login</button>
<RouterView />
</>}});Copy the code
Use in VUEX projects
import { useStore } from "vuex";
import { SET_USER } from "@/store/login/actionType";
import { defineComponent, computed, readonly } from "vue";
const App = defineComponent({
setup(){
// Expose state and dispatch
const { state, dispatch } = useStore();
// It is best to wrap the exposed state with readonly to make it read-only to avoid direct modification
const loginState = computed(() = > readonly(store.state.login));
function modifyUserInfo(){
// Call dispatch directly as in vue2
dispatch(`login/${SET_USER}`}, {})return () = > <>
<button onClick={modifyUserInfo}>Change the state</button>
<div>{loginState.user} {loginState.password}</div>
</>}});Copy the code
conclusion
At this point the relevant example analysis is all over, this article sample source code in this warehouse vite-vue3-tsx interested welcome fork down to run a run, also can be more familiar with vue3 related syntax.
I write this article is just to help beginners vuE3 and TSX students to establish a more perfect framework, the article must also have my own cognition is not very clear, welcome you to exchange, mutual progress!