DEV Community

Cover image for ๐Ÿ”ฅA mall project based on vue 3.x
gankai
gankai

Posted on

๐Ÿ”ฅA mall project based on vue 3.x

Alt Text

vue3-jd-h5

GitHub stars
English| ็ฎ€ไฝ“ไธญๆ–‡

Project Introduction

vue3-jd-h5 is an e-commerce H5 page front-end project, based on Vue 3.0.0 + Vant 3.0.0 implementation, mainly including homepage, category page, my page, shopping cart, etc. .

๐Ÿ“–Local offline code vue2.6 in the branch demo , use mockjs data for development, please click for the renderings ๐Ÿ”— Here

โš ๏ธThe master branch is the code of the online production environment, because part of the background interface has been hung up ๐Ÿ˜ซ, the actual effect may not be seen.

๐Ÿ“Œ There are still many shortcomings in this project. If you have partners who want to contribute to this, please send us a PR or issue;

๐Ÿ”‘ This project is free and open source. If you have a partner who wants to carry out secondary development on a secondary basis, you can clone or fork the entire warehouse. If it can help you, I will be very happy. If you think this project is good, please give it back Start! ๐Ÿ™

Vue3 build steps

  1. First, select a file locally and clone the code locally:
git clone https://github.com/GitHubGanKai/vue-jd-h5.git 
Enter fullscreen mode Exit fullscreen mode
  1. View all branches:
gankaideMacBook-Pro:vue-jd-h5 gankai$ git branch -a
  demo
  vue-next
  dev
  feature
  gh-pages
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/demo
  remotes/origin/vue-next
  remotes/origin/dev
  remotes/origin/feature
  remotes/origin/gh-pages
  remotes/origin/master
Enter fullscreen mode Exit fullscreen mode
  1. Switch to the branch vue-next to start development!

  2. Run the command in the IDEA command line: npm install, download related dependencies;

  3. ๐Ÿ”ง Development environment Run the command in the IDEA command line: npm run dev, run the project;

  4. ๐Ÿ“ฆRun the command in the IDEA command line: npm run dll:build, package the project, ๐Ÿ“ฑscan the QR code below ๐Ÿ‘‡ to view!

Alt Text

Initialization of the project

๐Ÿ’กIf you are slow when installing the package, it is because the NPM server is abroad. Here is a tool for you to switch NPM mirroring at any time. ๐Ÿ‘‰NRM, sometimes when we are developing, in order to speed up the installation of the installation package, we need to switch the mirror source to domestic, but if we need to publish some of our own components to NPM, we have to switch back and forth again. With this We are much more convenient! Use $ npm install -g nrm to install globally, and then use nrm ls to view all mirrors:

gankaideMacBook-Pro:~ gankai$ nrm ls

  npm -------- https://registry.npmjs.org/
* yarn ------- https://registry.yarnpkg.com/
  cnpm ------- http://r.cnpmjs.org/
  taobao ----- https://registry.npm.taobao.org/
  nj --------- https://registry.nodejitsu.com/
  npmMirror -- https://skimdb.npmjs.com/registry/
  edunpm ----- http://registry.enpmjs.org/
Enter fullscreen mode Exit fullscreen mode

If you need to use Taobao mirror, execute: nrm use taobao You can switch the source at any time, of course, there is also an npm package version management tool nvm, mainly for management In the package version, if you are interested, you can find out for yourself, here is not a long time ๐Ÿ˜Š!

Installation

Enter the root directory of the project that was cloned just now, install related dependencies, and experience the new features of vue3.

npm installation:

npm install
Enter fullscreen mode Exit fullscreen mode

yarn installation:

yarn
Enter fullscreen mode Exit fullscreen mode

CDN

<script src="https://unpkg.com/vue@next"></script>
Enter fullscreen mode Exit fullscreen mode

use

In the entry file main.js:

import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';

Vue.use(VueCompositionApi);
Enter fullscreen mode Exit fullscreen mode

After installing the plug-in, you can use the new Composition API to develop components.

โš ๏ธAt present, vue officially provides a plug-in for vue-cli vue-cli-plugin-vue-next, you can also directly Add the latest version directly to the project!

# in an existing Vue CLI project
vue add vue-next
Enter fullscreen mode Exit fullscreen mode
If you have a small partner who wants to experience the new version from scratch, you can use this method to install it. Since our project relies on third-party libraries, if you install it globally, the third-party UI library of the entire project cannot run! So we still choose to install `@vue/composition-api` to experience, so as to slowly transition to the latest version of vue3

Vue 3.0 Composition-API Basic Features Experience

setup function

The setup() function is a new attribute specially provided for components in vue3, which is equivalent to the created function in the 2.x version. The component logic options of the previous version are now handled in this function. It provides a unified entry point for us to use the new features of vue3 Composition API. The setup function will be executed after beforeCreate and before created relative to 2.x ! For details, please refer to the following:

vue2.x vue3
beforeCreate setup(replace)
created setup(replace)
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
errorCaptured onErrorCaptured

New hook

In addition to the 2.x life cycle equivalent, the Composition API also provides the following debugging hooks:

-onRenderTracked
-onRenderTriggered

Both hooks received the options of DebuggerEvent and onTrack and onTrigger observers:

export default {
    onRenderTriggered(e){
      Debugger
      //Check which dependency caused the component to re-render
    }
}
Enter fullscreen mode Exit fullscreen mode

Dependency Injection

provider and inject enable dependency injection similar to the 2.x provide/inject option. Both can only be called during the current active instance of setup().

import { provide, inject } from '@vue/composition-api'

const ThemeSymbol = Symbol()

const Ancestor = {
  setup() {
    provide(ThemeSymbol, 'dark')
  }
}

const Descendent = {
  setup() {
    const theme = inject(ThemeSymbol, 'light' /* optional default value */)
    return {
      theme
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

inject accepts an optional default value as the second parameter. If no default value is provided, and the property cannot be found in the Provide context, then inject returns undefined.

Inject responsive data

In order to maintain the responsiveness between the provided value and the injected value, you can use ref

// in the parent component
const themeRef = ref('dark')
provide(ThemeSymbol, themeRef)

// in the component
const theme = inject(ThemeSymbol, ref('light'))
watchEffect(() => {
   console.log(`theme set to: ${theme.value}`)
})
Enter fullscreen mode Exit fullscreen mode
  1. Because the setup function receives 2 formal parameters, the first is initProps, which is the value passed by the parent component! , The second parameter is a context object

setupContext, the main attributes of this object are:

attrs: Object // equivalent to this.$attrs in vue 2.x
emit: ฦ’ () // equivalent to this.$emit()
isServer: false // Is it server-side rendering
listeners: Object // equivalent to this.$listeners in vue2.x
parent: VueComponent // equivalent to this.$parent in vue2.x
refs: Object // equivalent to this.$refs in vue2.x
root: Vue // This root is the globally unique instance object returned when we use newVue() in main.js. Be careful not to confuse this with this in the single file assembly
slots: {} // equivalent to this.$slots in vue2.x
ssrContext:{} // server-side rendering related
Enter fullscreen mode Exit fullscreen mode

โš ๏ธNote: The this cannot be accessed in the setup() function, regardless of whether this this refers to the global vue object (ie: the global generated by using new in main.js The vue instance object), still refers to the object of the single file component.

But what if we want to access the instance object of the current component? We can introduce the api of getCurrentInstance, and the return value is the instance of the current component!

import { computed, getCurrentInstance } from "@vue/composition-api";
export default {
  name: "svg-icon",
  props: {
    iconClass: {
      type: String,
      required: true
    },
    className: {
      type: String
    }
  },
  setup(initProps,setupContext) { 

    const { ctx } = getCurrentInstance();
    const iconName = computed(() => {
      return `#icon-${initProps.iconClass}`;
    });
    const svgClass = computed(() => {
      if (initProps.className) {
        return "svg-icon " + initProps.className;
      } else {
        return "svg-icon";
      }
    });
    return {
      iconName,
      svgClass
    };
  }
};
</script>

Enter fullscreen mode Exit fullscreen mode

Ref automatically expand (unwrap)

The ref() function is used to create a reactive data object according to the given value. The return value of the ref() function call is a wrapped object (RefImpl), There is only one .value property on this object. If we want to access the value of the object in the setup function, we can get it through .value, but if it is in the <template>** template **, just visit directly, no need for .value!

import {ref} from'@vue/composition-api'

setup() {
     const active = ref("");
     const timeData = ref(36000000);
     console.log('output ===>',timeData.value)
     return {
        active,
        timeData
     }
}
Enter fullscreen mode Exit fullscreen mode
<template>
   <p>Activity status: {{active}}</p>
   <p>Activity time: {{timeData}}</p>
</template>
Enter fullscreen mode Exit fullscreen mode

โš ๏ธNote: Do not put Array in ref, the array index property cannot be expanded automatically, and **do not use Array to directly access the ref object:

const state = reactive({
   list: [ref(0)],
});
// will not be expanded automatically, you must use `.value`
state.list[0].value === 0; // true

state.list.push(ref(1));
// will not be expanded automatically, you must use `.value`
state.list[1].value === 1; // true
Enter fullscreen mode Exit fullscreen mode

When we need to manipulate the DOM, such as when we use swiper in a project to get the DOM, then we can still do this ๐Ÿ‘‡!

  <div class="swiper-cls">
      <swiper :options="swiperOption" ref="mySwiper">
        <swiper-slide v-for="(img ,index) in tabImgs.value" :key="index">
          <img class="slide_img" @click="handleClick(img.linkUrl)" :src="img.imgUrl" />
        </swiper-slide>
      </swiper>
   </div>
Enter fullscreen mode Exit fullscreen mode

Then define a const mySwiper = ref(null); in the setup function. Previously in vue2.x, we used this.$refs.mySwiper to get the DOM object. Now you can also use ref Instead offunction, the returned mySwiper should be the same as the ref bound in the template!

import { ref, onMounted } from "@vue/composition-api";
setup(props, { attrs, slots, parent, root, emit, refs }) {
    const mySwiper = ref(null);
  onMounted(() => {
    // You can get the DOM object through mySwiper.value!
     // At the same time, refs.mySwiper in vue2.x can also be used. In fact, mySwiper.value is the same DOM object!
    mySwiper.value.swiper.slideTo(3, 1000, false);
  });
  return {
    mySwiper
  }
}
Enter fullscreen mode Exit fullscreen mode

reactive

The reactive() function receives an ordinary object and returns a reactive data object, which is equivalent to the Vue.observable() function in vue 2.x. vue 3.x provides reactive' () function, used to create a reactive data object Observer, in ref we generally store basic type data, if it is a reference type, we can use the reactive function.

When the received type in the reactive function is an array of Array, we can wrap a layer of objects outside the Array, and then add an attribute to the object such as: value (this attribute name is your You can call it whatever you want), his value is this array!

<script>
// Must be introduced before using related aip
import { ref, reactive } from "@vue/composition-api";
export default {
  name: "home",
  setup(props, { attrs, slots, parent, root, emit, refs }) {

    const active = ref("");
    const timeData = ref(36000000);
    // Turn each object in the tabImgs array into a responsive object
    const tabImgs = reactive({
      value: []
    });
    const ball = reactive({
      show: false,
      el: ""
    });
    return {
      active,
      timeData,
      tabImgs,
      ...toRefs(ball),
    };
  }
};
</script>

Enter fullscreen mode Exit fullscreen mode

So when we want to access this array in the template template, we need to use the form of .value to get the value of this array.

<template>
    <div class="swiper-cls">
      <swiper :options="swiperOption" ref="mySwiper">
        <swiper-slide v-for="(img ,index) in tabImgs.value" :key="index">
          <img class="slide_img" @click="handleClick(img.linkUrl)" :src="img.imgUrl" />
        </swiper-slide>
      </swiper>
    </div>
</template>
Enter fullscreen mode Exit fullscreen mode

isRef

isRef() is used to determine whether a value is an object created by ref(); when you need to expand a value that may be created for ref(), you can use isRef to judge!

import { isRef } from '@vue/composition-api'

setup(){
  const headerActive = ref(false);
  // In the setup function, if it is a responsive object, when accessing properties, you must add .value to access!
  const unwrapped = isRef(headerActive) ? headerActive.value : headerActive
  return {}
}
Enter fullscreen mode Exit fullscreen mode

toRefs

The toRefs function will convert the reactive object into an ordinary object, where each attribute on the returned object is a ref that points to the corresponding attribute in the original object, and all the objects on an object It will be very useful when the attribute is converted to responsive!

import { reactive,toRefs } from '@vue/composition-api'
setup(){
  // ball is a Observer
  const ball = reactive({
    show: false,
    el: ""
  });
  // ballToRefs is an ordinary Object, but all the attributes in ballToRefs are responsive (RefImpl)
  const ballToRefs  = toRefs(ball)
  // ref and original attributes are "linked"
  ball.show = true
  console.log(ballToRefs.show) // true
  ballToRefs.show.value = false
  console.log(ballToRefs.show) // false
  return {
    ...ballToRefs    // Expand the ballToRefs object, we can directly use all the attributes on this object in the template template!
  }
}

Enter fullscreen mode Exit fullscreen mode

Click the add button, the ball flies into the shopping cart animation:

<template>  
  <div class="ballWrap">
      <transition @before-enter="beforeEnter" @enter="enter" @afterEnter="afterEnter">
       <!-- You can use v-show-->
        <div class="ball" v-if="show">
          <li class="inner">
            <span class="cubeic-add" @click="addToCart($event,item)">
              <svg-icon class="add-icon" icon-class="add"></svg-icon>
            </span>
          </li>
        </div>
      </transition>
   </div>
</template>
Enter fullscreen mode Exit fullscreen mode

computed

The first parameter of the computed function can receive a function or an object! If it is a function, it defaults to a getter function, and returns a read-only ref object for the value returned by getter.

import { computed } from '@vue/composition-api'

const count = ref(1)
// computed receives a function as an input parameter
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // Error, plusOne is read-only!
Enter fullscreen mode Exit fullscreen mode

Or it can be an object, you can use objects with get and set functions to create writable ref objects.

const count = ref(1)
// computed receives an object as an input parameter
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0
Enter fullscreen mode Exit fullscreen mode

watch

watch(source, cb, options?)

The watch API is exactly equivalent to 2.x this.$watch (and the corresponding watch option).

Observe a single source

The observer data source can be a getter function that returns a value, or it can be directly a ref:

// watching a getter function
const state = reactive({ count: 0 })
watch(
  () => state.count, // Getter function for return value
  (count, prevCount,onCleanup) => {
    /* ... */
  }
)

// directly watching a ref
const count = ref(0)
watch(
  count, // It can also be directly ref
  (count, prevCount,onCleanup) => {
  /* ... */
})
Enter fullscreen mode Exit fullscreen mode

watch multiple sources

Observers can also use arrays to monitor multiple sources at the same time:

const me = reactive({ age: 24, name:'gk' })
// reactive type
watch(
  [() => me.age, () => me.name], // To monitor multiple reactive data sources, you can pass in an array type and return a getter function
  ([age, name], [oldAge, oldName]) => {
    console.log(age) // new age value
    console.log(name) // new name value
    console.log(oldAge) // old age value
    console.log(oldName) // new name value
  },
  // options
  {
    lazy: true //default. The code in the callback function is executed when the watch is created. If lazy is true, how can it not be executed when it is created!
  }
)

setInterval(() => {
  me.age++
  me.name ='oldMe'
}, 7000000)

// ref type
const work = ref('web')
const addres = ref('sz')
watch(
  [work,address], // monitor multiple ref data sources
  ([work, addres], [oldwork, oldaddres]) => {
   //...
  },
  {
    lazy: true
  }
)

Enter fullscreen mode Exit fullscreen mode

watch is bound to the life cycle of the component. When the component is uninstalled, the watch will automatically stop. In other cases, it returns a stop handle, which can be called to stop the watcher explicitly:

// watch returns a function handle, we can decide the stop and start of the watch!
const stopWatch = watch(
  [work,address], // monitor multiple ref data sources
  ([work, addres], [oldwork, oldaddres]) => {
   //...
  },
  {
    lazy: true
  }
)

// Call the stop function to clear the monitoring of work and address
stopWatch()
Enter fullscreen mode Exit fullscreen mode

Clear invalid asynchronous tasks in watch

<div class="search-con">
  <svg-icon class="search-icon" icon-class="search"></svg-icon>
  <input v-focus placeholder="search, keyword" v-model="searchText" />
</div>
Enter fullscreen mode Exit fullscreen mode
setup(props, {attrs, slots, parent, root, emit, refs }){
  const CancelToken = root.$http.CancelToken
  const source = CancelToken.source()
  // Define responsive data searchText
  const searchText = ref('')

  // Send an asynchronous request to the background
  const getSearchResult = searchText => {
   root.$http.post("http://test.happymmall.com/search",{text:searchText}, {
     cancelToken: source.token
   }).then(res => {
    // .....
   });
  return source.cancel
}

// define watch monitor
watch(
  searchText,
  (searchText, oldSearchText, onCleanup) => {
    // Send an axios request and get the cancel function to cancel the axios request
    const cancel = getSearchResult(searchText)

    // If the watch is repeatedly executed, the last unfinished asynchronous request will be cleared first
    onCleanup(cancel)
  },
  // watch is not executed when it is just created
  {lazy: true}
)

  return {
    searchText
  }
}
Enter fullscreen mode Exit fullscreen mode

At last

Vue3 adds Composition API. The new API is compatible with Vue2.x. You only need to introduce the @vue/composition-api package separately in the project to solve our current individual problems in Vue2.x. For example: how to organize logic, and how to extract and reuse logic among multiple components. Based on the current API of Vue 2.x, we have some common logic reuse patterns, but there are more or less problems:

These modes include:

  1. Mixins
  2. Higher-order Components (aka HOCs)
  3. Renderless Components (components based on scoped slots / scoped slots encapsulation logic)

In general, the above models have the following problems:

  1. The source of the data in the template is not clear. For example, when multiple mixins are used in a component, it is difficult to tell which mixin a property comes from by just looking at the template. HOC has similar problems.
  2. Namespace conflict. There is no guarantee that mixins developed by different developers will not use exactly the same attribute or method name. HOC has similar problems in injected props.
  3. Performance. Both HOC and Renderless Components require additional component instance nesting to encapsulate logic, resulting in unnecessary performance overhead.

In vue3, Composition API is added. And the new API is compatible with Vue2.x, only need to introduce the package @vue/composition-api separately in the project, which can solve most of our current problems. At the same time, if I directly upgrade to Vue3.x, I will have more things to do. As long as the third-party ui library used in the current project needs to be remodeled, and many pits after the upgrade have to be filled! At the beginning, I installed and upgraded the vue add vue-next directly on the basis of the current scaffolding, but as long as there are places that rely on third-party ecological libraries, there are many pitfalls. . .

Vue3.x does not export the default object export default. In the third-party ecosystem, Vue.xxx() is often used for dependency. Now these grammars need to be rewritten, and the workload is not small!

If it is a new team or a small project, you can try to use vue3 for trial development, and gradually overstep it. When Vue3.x is officially released and the surrounding ecology keeps up, you can use vue3 directly!

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.