How to optimize your vue project

Keywords: Javascript Front-end Vue Vue.js

catalogue

Why optimize the project

Route lazy loading

Component cache

Article content code highlight

Give users feedback when optimizing requests

Insensitive refresh token

Lazy loading of pictures

Custom command (autofocus)

  Extract component registration

  Persistent storage

Encapsulate unified prompt information

Scroll bar position

  Corresponding to the above (channel list scroll bar position)

  • Why optimize the project

    • After our project is written, in order to optimize the user experience and reduce the pressure on the server, we go back to a series of optimizations to make our project load faster, so we need to optimize the code
    • There are many ways to optimize, as follows
      • Route lazy loading
      • Component cache
      • The user has no feeling to refresh the token (the unfinished operation is completed after logging in)
      • Lazy loading of pictures
      • Reduce code redundancy (reusable code encapsulation function)
      • For later maintenance, the following tool files can be encapsulated
        • Add, delete, modify and query token
        • Encapsulate api interfaces (various interfaces can be subdivided into files)
        • Third party toolkit introduced (similar to day.js,moment..)
        • Addition, deletion and modification of local storage
        • Imported component library (vant..)
        • Various functional component files (home page is home page component, personal information is personal information component...)
        • Custom instruction
        • Network request (axios,jq...)
        • Vuex , router
        • Global front guard (rear guard)
  • Route lazy loading

    • Route lazy loading is used for optimization. See the author's blog for details Route lazy loading (link)
  • Component cache

    • A keep alive component is cached in the outer layer of the route view of the route mount point
    • The mounting points of the primary route and the secondary route are different, and the cache is added according to the demand
    • Problems after caching
      • The article list will also be cached. If you enter the same article multiple times, the content will be consistent
      • The cached personal information will be the last login information when logging in next time
      • The search result component searches for the same result
    •   resolvent
    • exclude Components not cached
      include Cached components
      ArticleDetail,Login,Search,SearchResult For internal components name name
      <template>
        <div>
          <keep-alive exclude="ArticleDetail,Login,Search,SearchResult">
            <router-view></router-view>
          </keep-alive>
        </div>
      </template>
      Two life cycle hook functions are generated after component caching
      activated(Component activation) , deactivated(Loss of activation)
      Replace initialization with activated it's fine too
    •   After the component is cached, it will cause a series of possible problems. For different problems, by not caching or modifying the life cycle function, it is similar to the following problem that the user avatar is not updated
    • The avatar does not update after component caching
      • Solution 1: replace the created hook function with the activated hook function
      • Solution II  
        • Save the avatar variable to vuex, define changes, and modify the avatar function
        • After requesting avatar data,   Save to vuex variable
      • Extended: Avatar, nickname, mobile phone number and gender are the same
  • Article content code highlight

    • There is no code highlighting effect after the obtained article list
    • If you want to highlight the code, you must use a rich text editor to wrap the desired code segments with pre+code tags when users publish articles
    • The front end can obtain these tag names, specify class names, and give corresponding styles respectively
    • Solution: code snippet based on highlight.js beautifying details page( highlight.js Chinese website )
    • Mode of use
      • Download this plug-in into the project
      • yarn add highlight.js -D
      • Import in the entry file main.js
      • import hljs from 'highlight.js' // hljs object
        import 'highlight.js/styles/default.css' // Code highlighted style
      • Register highlight code (custom instruction)
      •  
        Vue.directive('highlight', function (el) { // Customize a code highlighting instruction
            const highlight = el.querySelectorAll('pre, code') // Get all pre or code tags inside
            highlight.forEach((block) => {
                hljs.highlightElement(block) // Highlight these tags (as well as the internal code, which automatically identifies the language)
            })
        })
      • Use the custom instruction v-highlight instruction on the label of the laying article  
  • Give users feedback when optimizing requests

    • When the network is slow, you can give the user a prompt that it is loading
    • When there is no article, it indicates that the article is loading
    • Use the unique loading effect prompt (vant, element UI) in the component library
    • Take vant as an example
    • Find the loading effect prompt component of the component library and register it globally in main.js
    • import { Loading } from 'vant';
      
      Vue.use(Loading);
    • Idea: when initiating a network request to load, the article and the loading component should be life and death. When the article is not fully loaded, the loading component is displayed. After loading, the loading component should disappear and the article is displayed. Therefore, v-if and v-else are used. When v-if judgment is made, the condition is judged by the data requested by the article content. When the data is un When defined, the article is not displayed. After the request is completed, the data is not undefined, that is, the article is displayed
    • artObj.title For the requested article information
      <!-- Article loading... -->
      <van-loading color="#1989fa "class =" loading "V-IF =" artobj. Title = = = undefined "> Article loading... < / Van loading >
      <div v-else>
      	<!-- Article information area -->
      </div>
    • Note: when using a component, remember to look at the div structure. The component is not displayed. It may be that the above elements cover the component
  • Insensitive refresh token

    • The insensitive refresh token requires a corresponding interface in the background. When logging in, two tokens are received, one for user authentication and the other for expired login. The insensitive refresh token
    • When the user authentication fails, the user will jump to the login page
    • In some places, user authentication cannot be used, but after the login operation is performed, the user needs to log in to the page. After the login is completed, continue to perform the operation just now
    • Idea: when sending a network request, axios provides a request responder and an interception responder. You only need to make corresponding judgment when the response is wrong
    • // Adding a response interceptor to axios
      axios.interceptors.response.use(function (response) {
        // Do something about the response data
        return response
      }, async function (error) {
       // Print the error message to find the conditions that need to be judged
        console.dir(error)
        // Do something about response errors
        if (error.response.status === 401) {
          // Clear token
          removeToken()
          // Request to refresh token
          const res = await newToken()
          // Get the refreshed token and continue to set a new token
          setToken(res.data.data.token)
          // Carry the request header, which is the new token given by the background after refreshing the token 
          // Without the request header, it will enter an endless loop
          error.config.headers.Authorization = 'Bearer ' + res.data.data.token
          // error.config stores the user's request for the above operations. After the expiration, log in to the token to continue to complete the last incomplete operation
          //  Remember to return before you can execute it
          return axios(error.config)
          // router.replace('/login')
        } else if (error.response.status === 500 && error.response.config.url === '/v1_0/authorizations') {
          // The judgment is made after the refresh token and retoken have expired
          // Clear token
          removeToken()
          // Store the acquired data locally
          localStorage.removeItem('refresh_token')
          // The jump page carries the routing address and parameters after failure
          // Judge while logging in
          router.push({
            path: '/login',
            query: {
              pa: router.currentRoute.fullPath
            }
          })
        }
        return Promise.reject(error)
      })
    •   When logging in, judge whether the routing address has parameters
    • this.$router.push({
                path: this.$route.query.pa || '/layout' 
              // If you have an attempted address, go back. If not, go to / layout
              })
  • Lazy loading of pictures

    • When the picture label enters the viewport, the picture is loaded
    • The src of the picture will call the picture resource requested by the browser
    • Replace the src attribute and start loading the picture at the right time, that is, when the picture is in the viewport
    • The principle of lazy loading of native images: replace src with another attribute data src to monitor the user's scrolling events. When the distance between the volume in the dom document and the window is greater than the distance from the picture to the top of the document, it indicates that the picture already exists in the viewport. At this time, replace data src with src to load the picture
    • Component use (vant as an example), consult the document, find the image lazy loading component, and introduce it to main.js for use
    • Usage: replace all img src with v-lazy instruction
    • <!-- Slot in the title area -->
      <template #title>
      <div class="title-box">
          <!-- title -->
          <span>{{ obj.title }}</span>
          <!-- Single graph -->
          <img
               class="thumb"
               v-lazy="obj.cover.images[0]"
               v-if="obj.cover.type === 1"
               />
      </div>
      <!-- Three pictures -->
      <div class="thumb-box" v-if="obj.cover.type > 1">
          <img
               class="thumb"
               v-for="(imgUrl, index) in obj.cover.images"
               :key="index"
               v-lazy="imgUrl"
               />
      </div>
      </template>
    • Vue.use(Lazyload, {
        preLoad: 1.0 // Picture start loading judgment range
        // 1.0 (loaded as soon as it appears in the viewport)
        // 1.3 (30% more downward loading range)
      })
      // Global entry main.js file configuration
      // Use according to vant documentation
  • Custom command (autofocus)

    • When users modify the content of the pop-up box type, it is similar to commenting, modifying the nickname, name and mobile phone number
    • Only the first autofocus problem (click the second time, there is no autofocus)
    • Autofocus relies on the user-defined instruction inserted for execution
      • The inserted method is triggered only when Dialog is inserted into the real DOM for the first time
      • After Dialog, counties and cities at the css level will appear in the primary election, and the inserted method will not be triggered
    • Solution: add the updata method to the user-defined instruction and specify the DOM to be executed when it is updated
    • export default function (Vue) {
        Vue.directive('fofo', {
          // The tag of the instruction is only triggered when it is inserted into the real DOM
          inserted (el) {
            // The van search component encapsulates a set of div containing input
            // el is a native div tag
            // Get input from
            // JS trigger tag event, directly. Event name ()\
            if (el.nodeName === 'TEXTAREA' || el.nodeName === 'INPUT') {
              el.focus()
            } else {
              // el is not an input box. Try to look in
              const inp = el.querySelector('input')
              const textA = el.querySelector('textarea')
              if (inp) {
                inp.focus()
              }
              if (textA) {
                textA.focus()
              }
            }
          },
          // Triggered when the label of the instruction is updated (for example: display:none hidden - > appear)
          update (el) {
            if (el.nodeName === 'TEXTAREA' || el.nodeName === 'INPUT') {
              el.focus()
            } else {
              // el is not an input box. Try to look in
              const inp = el.querySelector('input')
              const textA = el.querySelector('textarea')
              if (inp) {
                inp.focus()
              }
              if (textA) {
                textA.focus()
              }
            }
          }
        })
      }
  •   Extract component registration

    • There are too many entry codes in the entry file main.js, which will disperse the consistent ones to facilitate later management and maintenance
    • Take the vant component library as an example
    • Copy the vant registration code to another file
    • import Vue from 'vue'
      import { NavBar, Form, Field, Button, Tabbar, TabbarItem, Icon, Tab, Tabs, Cell, List, PullRefresh, ActionSheet, Popup, Row, Col, Badge, Search, Divider, Tag, CellGroup, Image, Dialog, DatetimePicker, Loading, Lazyload } from 'vant'
      
      ... // Omit some of the above
      Vue.use(Lazyload)
      Vue.use(Field)
      Vue.use(Button)
      Vue.use(NavBar)
    • Introduced in main.js, if you only need to execute the code, you don't need to export all or on demand
    •  
      import './VantRegister'
      // The name of the VantRegister js file
  •   Persistent storage

    • Encapsulate the local persistent storage into a file
    • Create utils/storage.js file and define 4 methods
    • // Local storage mode
      // If both sessionStorage and localStorage are available, two copies can be packaged
      // Now I just encapsulate a unified way
      export const setStorage = (key, value) => {
        localStorage.setItem(key, value)
      }
      export const getStorage = (key) => {
        return localStorage.getItem(key)
      }
      export const removeStorage = (key) => {
        localStorage.removeItem(key)
      }
      export const clearStorage = () => {
        localStorage.clear()
      }
      
    • Replace all places using local storage with the methods defined here
    • Benefits: if you switch the local storage mode in the future, you can directly modify the real implementation in utils/storage.js
  • Encapsulate unified prompt information

    • If you need to change the prompt box for future projects, you only need to modify it here
    • Similar to the notty hint in vant
    • // Unified notification of the whole project
      // First use the Notify method in the vant component library
      // import { Notify } from 'vant'
      import { Toast } from 'vant'
      
      export default {
      Introduced into main.js, use Vue.use Registration results install implement
        install (Vue) {
          // On the Vue prototype, add the attribute $notify - > notification method
          // Later, you only need to modify the component library method of the prototype
          Vue.prototype.$notify = Toast
        }
      }
      /* 
      export const MyNotify = ({ type, message }) => {
      //   Notify({
      //     type: type,
      //     message: message
      //   })
      
        if (type === 'warning') {
          Toast({
            type: 'fail',
            message
          })
        } else if (type === 'success') {
          Toast({
            type,
            message
          })
        }
      }
      */
    •   When using, you only need to call the methods on the prototype
    • Encapsulate the unified UI pop-up window. Later, change the internal implementation of the encapsulated user-defined function, and replace all UI pop-up windows of the whole project
  • Scroll bar position

    • After scrolling, the user can switch other components and still return to the place where the user moved
    • If optimization is not carried out, the user still returns to the first item after switching, and the user experience is not good
    • Original idea: listen for window scrolling events, record the scrolling position, and come back when the user leaves  , Re assign the scrolled value to the scrollTop of html
    • created () {
            window.addEventListener('scroll', () => {
            console.log('Rolling')
            // Scroll event window/document
            // Get / set scroll position - > HTML
            // console.log(document.documentElement.scrollTop)
            this.$route.meta.scrollT = document.documentElement.scrollTop
          })
        },
      // Component activation
      activated () {
          // Save additional information on the routing object and scroll back to the location setting
          document.documentElement.scrollTop = this.$route.meta.scrollT
      },
      Particular attention : Key points 
      1. You cannot assign a scrolling value to a variable , because keep-alive After component caching , The value of the variable is changed directly to 0
      2. So the question is where to store the variables , Routing meta information meta:{ } in
      {
              path: 'home',
              meta: {
                scrollT: 0 // Home page scroll position
              }, // Routing meta information (more useful data is saved in the routing object)
              component: () => import('@/views/Home')
      }
  •   Corresponding to the above (channel list scroll bar position)

    • Clear structure. When we click on different channels, scroll and save the values to the corresponding channels
    • Use one-to-one mapping of objects
    • // Correspondence between "channel name" and "scroll bar position",
      // Format {'recommendation ID': 211, 'htmlID': 30, 'developer information ID': 890}
      const nameToTop = {}
    • Check the vant component library document to find the corresponding method for tabs
    •  
          // tabs switching event
          tabsChangeFn () {
            // 2. Set the scroll bar position corresponding to the channel ID (on the nameToTop object) to
            // active is the binding between the id information of the channel and the vant component
            const st = nameToTop[this.active]
            // Note: when the tab bar is switched, the DOM update is asynchronous, so the scrolling position can be performed after the DOM slows down
            setTimeout(() => {
              document.documentElement.scrollTop = st
            }, 0)
          },
          // tabs switch the callback function before
          // return false prevents tabs from switching
          beforeChangeFn () {
            // tabs switch. If you switch from the bottom to another tab, the scroll bar will come up and the position of 0 will be covered
            // So you can't save values here
            // 1. Before switching channels, scroll the current channel position and save it in the object first
            nameToTop[this.active] = document.documentElement.scrollTop
            return true
          }

Posted by turtleman8605 on Fri, 22 Oct 2021 21:25:21 -0700