Vite + TS has become the standard paradigm for vuE3 project development. The new syntax experience vue Composition API is combined with Script Setup. Who knows, Vite is the next generation of building tools for development and construction. With ES6 Module + ESbuild support for native development, speed and efficiency will take off in one word

preface

Vite as a build tool, we currently understand how to use it, the tool is good enough, the default for many functions have done support (CSS Module, LESS, SCSS), and as the father of VUE especially great works, for VUE also has good support, the current utilization rate has been very high, Nuxt and other large projects are supported, combined with documentation and community, the current use is enough, there is no need to worry about problems ~, get on the bus ~

Vue3, the biggest change is the full embrace of functional programming, the composition API now makes it really easy to manage complex business code, get rid of this and this a lot of unfriendly mixins, use hooks now, Logic reuse, function module split is simply too convenient, syntax, API use is also elegant and convenient, it is worth a try

Another highlight is vue3’s excellent support for TS. Now projects can fully embrace TS writing, and combine it with Setup and the next few tools I recommend

For TS, the first thing to do is to define the type, which is different from the traditional writing JS, but this step is necessary and worthwhile, which is good for your future work or for the future of this project

For example, interfacing with a back-end interface:

In the early stage, we got the interface document, defined the corresponding TS type according to the format and type, and combined with Mock to write the interface and business logic. When using TS, we could efficiently complete the code development and avoid mistakes greatly, which greatly guaranteed the later maintenance iteration

import.meta

Using Vite as a building tool, you can obtain the corresponding method through import.meta to facilitate and fast processing of business

Environment variable acquisition

  • inpackage.jsonIs defined as a command line ("dev:development": "vite --mode development")
  • in.envIs defined as a configuration file (VITE_APP_ENV = dev)

When the. Env. development file is configured as follows:

VITE_APP_ENV = dev VITE_APP_TITLE = I am titleCopy the code

Note:

When reading variables configured in.env.development, make sure that the mode parameter of vite –mode development is development

invite.config.tsIn the

You can read the mode argument in a functional way (if you want to read configuration information in.env.development from this file, you can do so in conjunction with the Dotenv library).

import { UserConfig, ConfigEnv } from 'vite';

export default ({ command, mode }: ConfigEnv): UserConfig= > {
  console.log(command, mode);

  return {
    // ...}}Copy the code

In the component

Console. log(‘my config env: ‘, import.meta.env) prints the following

// console.log(import.meta.env)
{
  BASE_URL: "/"
  DEV: false
  MODE: "development"
  PROD: true
  SSR: false
  VITE_APP_ENV: "dev"
  VITE_APP_TITLE: "I am the title."
}
Copy the code

Note:

Cooperate. Env/env. Development /. Env. The production and other documents to set up the environment variables, when using variable Key should VITE_ as prefix

{
  "script": {"dev": "vite --mode development"}}Copy the code

To prevent accidental leakage of env variables to clients, only variables prefixed with VITE_ are exposed to Vite code. Only import.meta.env.vite_some_key is exposed to your client source VITE_SOME_KEY, but DB_PASSWORD is not.

Batch file processing

import.meta.globEager

// Read all.ts files in the current directory
const modules = import.meta.globEager('./**/*.ts')
Copy the code

Ref and reactive

Can be used to define reactive data

ref

It is used to define basic types, which need to be read or modified through.value

Basic types: Excluding Object, including String, Number, Boolean, NULL, and undefined

The console prints the data structure RefImpl

// ref
const count = ref(0)

count.value++
console.log(count.value)
Copy the code

When defining basic types, the responsivity principle is object.defineProperty (), similar to vue2.x, which reads and modifies data via get and set

However, a REF can also define reference type data, and note that when a reference type is defined, its internal implementation is reactive

You can print data to view the structure of RefImpl and Proxy on the console

reactive

Only reference types can be defined, that is, Object, including: Object, Array, Date, function

When used, read and write directly from properties

// reactive
const state = reactive({count:0})

state.count++
console.log(state.value)
Copy the code

Reactive defaults to all properties within an object and enables deep listening

The responsiveness capability is realized by ES6 Proxy, which can add and delete the monitoring of attributes, solve the defect of defineProperty, and has good support for nested attributes, and can easily realize the responsiveness update of A.B.C.D =xx

Proxy and Reflect are both ES6 grammars. generally they are used together, so they can make hijacking updates to attributes in a safe and elegant way

summary

The tempalte template is automatically unpacked and does not need to be used in the template

Ref (obj) is reactive({value: obj}). Ref (obj) is reactive({value: obj})

As you can see, reactive is actually a hijacking of attributes

Each layer of data defined by REF and Reactive is reactive

Watch, watchEffect

Listen for changes in reactive data

watch

The basic syntax is similar to vue2, though there are a few different ways to use it

Listen for reactive data defined by ref (base type)

  • The functional way to write it is.value, listen for a change in value
const count = ref(0);
const str = ref('abc');

// 1
// watch can omit.value
watch(count, (val, old) = > console.log({ val, old }));

// 2
watch(
  () = > count.value,
  (val, old) = > console.log({ val, old }),
);

// 3
watch(
  () = > [count.value, str.value],
  (val, old) = > console.log({ val, old }),
);
Copy the code

Listen for reactive data defined by ref (reference type)

  • The thing to understand is,refDefines the reference type that is used internallyreactiveThe implementation, therefore, needs to be passed.valueGet the reactive object, and then do the property listening
const refState = ref({
  count: 0.str: 'abc'});// 1
// => refstate. value Valid
watch(refState, (val, old) = > console.log({ val, old }));
// 2
watch(
  () = > refState.value.count,
  (val, old) = > console.log({ val, old }),
);
Copy the code

Listen for reactive data defined by Reactive

  • You need to listen for attributesstate.count
const state = reactive({
  count: 0.str: 'abc'.a: {
    b: {
        c: 'a-b-c',}}});// 1
// result: val, old
// watch(state, (val, old) => console.log({ val, old }));

// 2
// Result: only specified property changes are triggered
watch(
  () = > state.value.a.b.c, // Only listen for specified attributes
  (val, old) = > console.log({ val, old }),
);
Copy the code

watchEffect

This method automatically takes over the dependencies used inside the function and triggers the function to execute when the dependencies are updated

This function is initialized once by default

watchEffect(() = >{

  if(state.count>1) {// The watchEffect function is executed once whenever count changes
    // If count > 1, do the corresponding behavior}})Copy the code

Summary of Watch and watchEffect

There are many things to consider when using the Watch

Watch is more about results, and Watch Effect is more about process

WatchEffect seems easier to use in terms of usage

ShallowRef and shallowReactive

  • Recursive and non-recursive listening

Both REF and Reactive are recursive listeners, that is, each layer of data is responsive. If the data volume is large, performance will be consumed. Non-recursive listeners only listen to the first layer of data.

Script setup: props, context

When using setup as

However, there are issues with the definition of props, EMIT, and getting the CTX attribute

Vue also provides three new apis for this purpose

  • defineProps
  • defineEmit
  • useContext
// These three apis correspond to the setup properties one by one
setup(props, { emit, ctx}){}
Copy the code

If you want to get the attributes of a child component from a parent component, you need to define the attributes to expose in the child component via defineExpose

// Child component Child
const count = ref(0)
defineExpose({
  count,
});

/ / the parent component
// <Child ref="ChildRef" />
const ChildRef = ref<RefType<{ count: number} > > (0);
const count = ChildRef.value.count
Copy the code

More API see the official documentation, said very detailed, here will not repeat

Props Type Type definition problem

Aside from the basic types that vue defaults to, there are special scenarios where you need to define more complex types and use propTypes in conjunction with them

For example, define menu routing types

props: {
    menuData: {
        type: Array as PropType<MenuDataItem[]>,
        default: () = >[],}.}Copy the code

In this case, it is difficult to meet our requirements according to the conventional Array type (only known as a data, but the shape of the data is not clear), and the primitive type writing method is difficult to accurately deduce the type definition of each attribute

Prop, REF, EMIT data communication

prop

React emphasizes single-element data streams (parent => child), similar to react, mainly used to pass parameters to child components

ref

Two usages:

  • Pointing instances of child components to Ref by reference makes it possible to get all the attributes and methods in the child component in the parent, which can be done with the defineExpose API

  • Used to get DOM elements

// When echarts is used to bind DOM nodes
// <div class="chart-box" ref="chartRef"></div>

const chartRef = ref<HTMLDivElement | null> (null);
echarts.init(unref(chartRef))
Copy the code

emit

It is primarily used by the child component to pass parameters to the parent component and communicate emit (child => parent), which is received via the event method @event

<! -- Parent component --> emit('getMessage'.'I'm the parent! ') <! -- Subcomponent --><child @event="handleMethod">
Copy the code

JSX grammar

JSX has great flexibility with the template syntax. React developers should be familiar with the JSX syntax

However, for VUE, has a unique advantage, as a template syntax itself, through the injection of instance methods, the use of instructions, can be quickly and efficiently developed, in some scenarios, JSX syntax + VUE template syntax has a completely different experience ~

$router.push(‘xx’)” v-auth=”create”>

The Table component

Take the common table component as an example. After encapsulating the reusable paging logic, we split the columns into separate columns and use JSX syntax to encapsulate them. Different configurations are made according to the use of different components, which is more convenient for maintenance

export function columnsConfig(refresh: () => void) {
  / /... Other business logic

  const columns: ColumnProps[] = [
    {
      title: 'IP address and Port '.dataIndex: 'ip'.width: 150.customRender: ({ record }) = > `${record.ip}:${record.port}`}, {title: 'operation'.key: 'action'.width: 200.fixed: 'right'.customRender: ({ record }) = >
        <Space>
          <Button type="primary" onClick={()= >The router. Push (` / app/product/detail / ${record. Id} `)} > for details</Button>
          <Divider type="vertical" />
          {
            record.isSelf && <Popconfirm
              title="Are you sure you want to quit the network?"
              onConfirm={async() = >{ const res = await fetchApi.delete(record.id); If (res) {message.success(' requested to exit network '); // Trigger list refresh? . (); }}} ><Button>delete</Button>
            </Popconfirm>
          }
        </Space>},];return columns;
}
Copy the code

When the business of action operation column is complicated, it needs to communicate with other data frequently. We can also separate the action operation column and process it inside VUE, and then cooperate with the reencapsulation of Table component

Table Component Encapsulation

<template> <a-table :columns="columns"> <! <template #action="{record}"> <template v-for="(record) index) in getActions" :key="`${index}-${action.label}`"> <! --> < a-popConfirm V-if ="action.enable" :title="action? .title" @confirm="action? .onConfirm(record)" @cancel="action? .onCancel(record)" > <a @click.prevent="() => {}" :type="action.type">{{ action.label }}</a> </a-popconfirm> <! <a v-else @click="action? .onClick(record)" :type="action.type">{{ action.label }}</a> <! <a-divider type="vertical" V-if ="index < getActions. Length-1 "/> </template> </template> </a-table> < / template > < script lang = "ts" > / / the action column const getActions = computed (() = > {return (toRaw (props. Actions) | | []) .filter((action) => hasPermission(action.auth)) .map((action) => { const { popConfirm } = action; return { type: 'link', ... action, ... (popConfirm || {}), enable: !! popConfirm, }; }); }); </script>Copy the code

use

// <Table :columns="columns" :actions="tableActions"/>

export const columns = [
  // ...
  {
    title: 'operation'.key: 'action'.width: 120.slots: { customRender: 'action'}},]const tableActions = ref([
  {
    label: 'edit'.auth: AuthEnum.user_update, // Configure button permissions
    onClick: async (row) => {
      modalState.visible = true;
      const res = await store.fetchDetail(row.id);
      if(res) formModel.value = res; }},// ...
]
Copy the code

This is my experience in the actual use of the last project, for the improvement of development efficiency is very obvious, maintenance is also very convenient, more usage is also welcome to exchange and learn, as far as the current experience vue3 is great ~

event bus

Vue3 has removed the behavior of mounting $emit in the instance. If you want to continue using it, you can download the corresponding NPM package separately, such as mitt, which is very light and only 200 bytes

The API is similar to the usage, except that for functional creation, we need to ensure that the Emitter creation of a single operation is unique

import mitt from 'mitt'

const emitter = mitt()

export emitter
Copy the code

conclusion

In fact, this article is equivalent to my own study notes, but also to deepen my impression, I recorded some problems encountered in the process of using, hoping to bring some help to myself and everyone. As far as content is concerned, it belongs to the entry level. Currently, it does not involve deep water area. This article will be updated continuously according to the usage situation