2022-06-16.12.23.23.mov
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
📌 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! 🙏
💥 2024 Vue3.x ,Vite5.x, Vant,TypeScript latest version update branch: 👉 vue3-vite-vant-ts
- First, select a file locally and clone the code locally:
git clone https://github.com/GitHubGanKai/vue-jd-h5.git
- 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
-
Switch to the branch vue-next to start development!
-
Run the command in the IDEA command line: npm install, download related dependencies;
-
🔧 Development environment Run the command in the IDEA command line:
npm run dev
, run the project; -
📦Run the command in the IDEA command line:
npm run dll:build
, package the project, 📱scan the QR code below 👇 to view!
Enter the root directory of the project that was cloned just now, install related dependencies, and experience the new features of vue3.
npm
installation(node v16.20.2):
npm install
yarn
installation:
yarn;
CDN
<script src="https://unpkg.com/vue@next"></script>
In the entry file main.js
:
import Vue from "vue";
import VueCompositionApi from "@vue/composition-api";
Vue.use(VueCompositionApi);
After installing the plug-in, you can use the new Composition API to develop components.
# in an existing Vue CLI project
vue add vue-next
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
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 |
---|---|
setup(replace) | |
setup(replace) | |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
errorCaptured | onErrorCaptured |
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
},
};
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,
};
},
};
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}`);
});
- Because the
setup
function receives 2 formal parameters, the first isinitProps
, 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
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>
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
}
}
<template>
<p>Activity status: {{active}}</p>
<p>Activity time: {{timeData}}</p>
</template>
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
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>
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 of
function, 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
}
}
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>
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>
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 {}
}
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!
}
}
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>
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!
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
watch(source, cb, options?)
The watch
API is exactly equivalent to 2.x this.$watch
(and the corresponding watch
option).
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) => {
/* ... */
}
);
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,
}
);
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();
<div class="search-con">
<svg-icon class="search-icon" icon-class="search"></svg-icon>
<input v-focus placeholder="search, keyword" v-model="searchText" />
</div>
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
}
}
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:
- Mixins
- Higher-order Components (aka HOCs)
- Renderless Components (components based on scoped slots / scoped slots encapsulation logic)
In general, the above models have the following problems:
- 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.
- 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.
- 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!