Vue3, Vuex, TS project practice

Original text: My github article

Follow the official account: wechat search web stack advanced; Receive more dry goods

1, Opening

  • vue3. The 0 beta version is officially launched. As a new technology lover, the new project will officially use vue3 0 development; Next, summarize (the firm mastery of their own technology) and introduce (share the ape friends in need)
  • The last blog introduced vue3 0 common grammar and development skills; If necessary, please click Vue3.0 advanced, environment construction, use of related API s
  • If you think it's useful for you, github will order a star
  • Project GitHub address: https://github.com/laijinxian/vue3-typescript-template

2, Project introduction (mobile terminal)

  • 1) Technology stack: vue3 + vuex + typescript + webpack + vant UI + Axios + less + postcss pxtorem (REM adaptation)
  • 2) Why the official build tool vite is not used: there are really many vite pits. Sometimes the normal way of writing webpack is OK, and an error is reported on vite; It's useless for vite's github to mention Issues. The maintenance personnel answered casually and turned off my Issues. I'm also drunk;
  • 3) However, I still look forward to vite. Wait for him to mature and use it formally;
  • 4) Points involved: at present, only a few functions in the early stage of the project are posted
    • webpack require automatic registration of routing and asynchronous group price
    • axios request encapsulation (request interception, response interception, request cancellation, unified processing)
    • vuex service modularization and unified processing of takeover requests

3, Project construction

Refer to the previous article Vue3.0 advanced, environment construction, use of related API s

  1. vue cli, vue download the latest version
  2. Execute the command vue create my_app_name
  3. After executing the above command, choose manual configuration (the third one) instead of default configuration. There are many things we can't use. My choice is shown in the figure below:

3, Main functions of the project

1. Webpack requires automatic registration of routing and asynchronous group pricing

// The file is in global. Under utils ts
// Distinguish whether the file is automatically registered as a component, and the vue file defines the isComponents field; Distinguish whether to automatically register as a route definition isRouter field
// The usage methods are in main The method asyncComponent() in TS and the index. In the routing file router TS method vueRouters()

import { defineAsyncComponent } from 'vue'
import { app } from '../main'

// Get all vue files
function getComponent() {
  return require.context('../views', true, /\.vue$/);
}

// Convert initial to uppercase
function letterToUpperCase(str: string) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

// Convert first letter to lowercase
function letterToLowerCase(str: string) {
  return str.charAt(0).toLowerCase() + str.slice(1);
}

export const asyncComponent = function () {

  // Get file global object
  const requireComponents = getComponent();

  requireComponents.keys().forEach((fileSrc: string) => {

    const viewSrc = requireComponents(fileSrc);

    const fileNameSrc = fileSrc.replace(/^\.\//, '')

    const file = viewSrc.default;

    if (viewSrc.default.isComponents) {
      
      // Asynchronous registration component
      let componentRoot = defineAsyncComponent(
        () => import(`@/views/${fileNameSrc}`)
      )
      
      app.component(letterToUpperCase(file.name), componentRoot)
    }
    
  });
};

// Get route file
export const vueRouters = function () {

  const routerList: any = [];

  const requireRouters = getComponent();

  requireRouters.keys().forEach((fileSrc: string) => {

    // Get the file name under the components file
    const viewSrc = requireRouters(fileSrc);
    const file = viewSrc.default;

    // Initial to capital
    const routerName = letterToUpperCase(file.name);

    // Initial to lowercase
    const routerPath = letterToLowerCase(file.name);

    // Set routing path
    const fileNameSrc = fileSrc.replace(/^\.\//, '');

    if (file.isRouter) {
      routerList.push({
        path: `/${routerPath}`,
        name: `${routerName}`,
        component: () => import(`@/views/${fileNameSrc}`)
      });
    }
  });
  return routerList;
};

2. axios request encapsulation (request interception, response interception, cancellation request, unified processing)

import axios, { AxiosRequestConfig, AxiosResponse, Canceler } from 'axios'
import router from '@/router'
import { Toast } from 'vant'

if (process.env.NODE_ENV === 'development') {
  // development environment 
  axios.defaults.baseURL = `https://test-mobileapi.qinlinkeji.com/api/`
} else {
  // Formal environment
  axios.defaults.baseURL = `Official environment address`
}

let sourceAjaxList: Canceler[] = []

export const axionInit = () => {
  axios.interceptors.request.use((config: AxiosRequestConfig) => {
    // Set the cancel token to cancel the request (when a 401 occurs on an interface, cancel the subsequent multiple requests to avoid several error prompts)
    config.cancelToken = new axios.CancelToken(function executor(cancel: Canceler): void {
      sourceAjaxList.push(cancel)
    })

    // sessionId exists. Add sessionId to all requests
    if (localStorage.getItem(`h5_sessionId`) && config.url!.indexOf('/user/login') < 0) config.url += ('sessionId=' + localStorage.getItem(`h5_sessionId`))
    if (!config.data) config.data = {}
    return config
  }, function (error) {
    // Throw error
    return Promise.reject(error)
  })

  axios.interceptors.response.use((response: AxiosResponse) => {
    const { status, data } = response
    if (status === 200) {
      // If no error occurs, output data directly to the callback function
      if (data.code === 0) {
        return data
      } else if (data.code === 401) {
        // If there is no login or login failure, cancel the subsequent request
        sourceAjaxList.length && sourceAjaxList.length > 0 && sourceAjaxList.forEach((ajaxCancel, index) => {
          ajaxCancel() // Cancel request
          delete sourceAjaxList[index]
        })
        Toast({
          message: data.message,
          duration: 2000
        })
        return router.push('/login')
      } else {
        return data
      }
    } else {
      return response
    }
  }, error => {
    const { response } = error
    // Error handling http code or server or background error reporting
    if (!response || response.status === 404 || response.status === 500) {
      if (!response) {
        console.error(`404 error %o ${error}`)
      } else {
        if (response.data && response.data.message) {
          Toast.fail({
            message: 'Request exception, please try again later!',
            duration: 2000
          })
        }
      }
    }
    return Promise.reject(error.message)
  })
}

3. vuex business modularization and unified processing of takeover requests

// See the project store directory for details
import { Module } from 'vuex'
import { IGlobalState } from '../../index'
import * as Types from './types'
import { IHomeState, ICity, IAccessControl, ICommonlyUsedDoor } from './interface'
import axios from 'axios'

const state: IHomeState = {
  cityList: [],
  commonlyUsedDoor: {
    doorControlId: '',
    doorControlName: ''
  },
  accessControlList: []
}

const home: Module<IHomeState, IGlobalState> = {
  namespaced: true,
  state,
  actions: {
    // Get cell list
    async [Types.GET_CITY_LIST]({ commit }) {
      const result = await axios.post(`auth/v2/getApplyListGroupByCommunityH5?`)
      commit(Types.GET_CITY_LIST, result.data)
    },
    // Access control list of community
    async [Types.GET_ACCESS_CONTROL_LIST]({ commit }, data) {
      const result = await axios.post(`doorcontrol/v2/queryUserDoor?`, { ...data })
      commit(Types.GET_ACCESS_CONTROL_LIST, result.data.userDoorDTOS)
      commit(Types.SET_COMMONLY_USERDOOR, result.data.commonlyUsedDoor)
    },
  },
  mutations: {
    // Set cell list
    [Types.GET_CITY_LIST](state, cityList: ICity[]) {
      if (cityList.length !== 0) state.cityList = cityList
    },
    // Set community access control list
    [Types.GET_ACCESS_CONTROL_LIST](state, accessControlList: IAccessControl[]) {
      if (accessControlList.length !== 0) return state.accessControlList = accessControlList
    },
    // Set current cell
    [Types.SET_COMMONLY_USERDOOR](state, commonlyUsedDoor: ICommonlyUsedDoor) {
      state.commonlyUsedDoor = commonlyUsedDoor
    }
  }
}
export default home

4. home file code

<template>
  <div class="home-container">
    <header>
      <Suspense>
        <template #default>
          <HomeSwiper></HomeSwiper>
        </template>
        <template #fallback>
          <div>...loading</div>
        </template>
      </Suspense>
    </header>
    <section>
      <Suspense>
        <template #default>
          <HomeContent
            :cityList="cityList"
            :accessControlList="accessControlList"
          ></HomeContent>
        </template>
        <template #fallback>
          <div>...loading</div>
        </template>
      </Suspense>
    </section>
  </div>
</template>
 
<script lang="ts">
import { defineComponent, reactive, toRefs, computed, onMounted } from 'vue'
import { Store, useStore } from 'vuex'
import { IGlobalState } from "@/store";
import * as Types from "@/store/modules/Home/types";

/**
 * Purpose of the hook: personal understanding:
 *  1,Public method similar to global; You can consider mentioning it in tool class functions
 *  2,cityList´╝î accessControlList They are all data for display only, without subsequent modification; Therefore, it can be considered to be extracted and managed by the parent component
 *  3,If the method has more internal logic and other pages need to be used, it is more appropriate to extract
 *  4,Of course, there is no problem to choose freely and implement it in the step method, but it is not conducive to the reference of other pages
 *  5,vuex actions,mutations Function logic should be as few as possible to facilitate maintenance; Logical processing should be inside the page
 */
function useContentData(store: Store<IGlobalState>) {
  let cityList = computed(() => store.state.home.cityList)
  let accessControlList = computed(() => store.state.home.accessControlList)
  onMounted(() => {
    if (cityList.value.length === 0) store.dispatch(`home/${Types.GET_CITY_LIST}`)
    if (accessControlList.value.length === 0) store.dispatch(`home/${Types.GET_ACCESS_CONTROL_LIST}`, 13)
  })
  return {
    cityList,
    accessControlList
  }
}

export default defineComponent({
  name: 'home',
  isComponents: true,
  setup() {
    let store = useStore<IGlobalState>()
    let { cityList, accessControlList } = useContentData(store)
    const state = reactive({
      active: 0,
    })
    return {
      ...toRefs(state),
      cityList,
      accessControlList
    }
  }
})
</script>
 
<style scoped lang="less">
.home-container {
  height: 100%;
  background: #f6f6f6;
  header {
    overflow: hidden;
    height: 500px;
    background-size: cover;
    background-position: center 0;
    background-image: url("~@/assets/images/home_page_bg.png");
  }
  section {
    position: relative;
    top: -120px;
    padding: 0 20px;
  }
}
</style>

4, Project ui


5, Conclusion

The above is the summary of personal actual project development. If there is anything wrong, please leave a message and correct it

Tags: Vue.js

Posted by toasty2 on Mon, 09 May 2022 21:28:53 +0300