vue3.0出来很久了,一直都没用过,今天打算搭建个适用于移动端项目
后台管理系统:
vben:vue3后台管理项目框架
使用vue-cli 搭建 Vue3 开发环境
安装
npm install -g @vue/cli
# OR
yarn global add @vue/cli
建议使用npm进行安装,而且要做全局安装。因为我在使用yarn进行安装后,也可以安装成功,但是安装完成不能使用vue命令。
检查版本命令
vue --version
//@vue/cli 5.0.4
这时候可以展示出类似这样的版本信息@vue/cli 5.0.4。如果你的版本低于这个,可以再使用npm install -g @vue/cli来进行安装。
使用 vue-cli 命令行创建项目
vue create vue3-h5-test
选择手动自定义
按空格键选中
自定义最终选择
意思参考链接:blog.csdn.net/elanbom/art…
cd vue3-h5-test
npm run serve
or
yarn serve
vscde插件下载:vue3-snippets-for-vscode
模版片段
| 关键词 | 代码片段 |
|---|---|
| vue3 | |
| template | |
| scripte | |
| style | |
| css | |
| scss | |
| Sass | |
| Less |
script&vue 片段
| 关键词 | 代码片段 |
|---|---|
| Import | import {...} from '...' |
| Data | data(){return {...}} |
| Setup | setup(){...return{...}} |
| vText | v-text="..." |
| vHtml | v-html="..." |
| vShow | v-show="..." |
| vIf | v-if="..." |
| velse | v-else |
| velseif | v-else-if="..." |
| vFor | v-for="... in ..." :key="..." |
| vFor(withoutKey) | v-for="... in ..." |
| vOn | v-on="..." |
| vBind | v-bind="..." |
| vModel | v-model="..." |
| vSlot | v-slot="..." |
| vOnce | v-once |
| iscomponent | |
| vprops | const props = defineProps({ foo: String }) |
| vemits | const emit = defineEmits(['...', '...']) |
vue-router 片段
| 关键词 | 代码片段 |
|---|---|
| beforeeach | router.beforeEach((to, from, next) =>{...} |
| beforeresolve | router.beforeResolve((to, from, next) => {...} |
| afterEach | router.afterEach((to, from) => {...} |
| beforeenter | beforeEnter(to, from, next) {...} |
| beforeRouteEnter | beforeRouteEnter(to, from, next) {...} |
| beforeRouteLeave | beforeRouteLeave(to, from, next) {...} |
| vroute | {'path':...,name:...,component: () => import('...')} |
Vue3 的新知识点
- setup
- setup 函数的用法,可以代替 Vue2 中的 date 和 methods 属性,直接把逻辑卸载 setup 里就可以
- 缺点:setup中要改变和读取一个值的时候,还要加上value。这种代码一定是可以优化的,需要引入一个新的 APIreactive
- ref
- ref函数的使用,它是一个神奇的函数,我们这节只是初次相遇,要在template中使用的变量,必须用ref包装一下。
- return
- return出去的数据和方法,在模板中才可以使用,这样可以精准的控制暴漏的变量和方法。
- reactive
- reactive 它也是一个函数(方法),只不过里边接受的参数是一个 Object(对象)。
- 缺点:在template中,每次输出变量前面都要加一个data,优化:新函数toRefs()
- toRefs
- 使用toRefs方法对data进行包装,把 data 变成refData,再通过...()return出数据,在template中就可以去掉 data,可以直接使用变量名和方法
vue3 的生命周期
- setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
- onBeforeMount() : 组件挂载到节点上之前执行的函数。
- onMounted() : 组件挂载完成后执行的函数。
- onBeforeUpdate(): 组件更新之前执行的函数。
- onUpdated(): 组件更新完成之后执行的函数。
- onBeforeUnmount(): 组件卸载之前执行的函数。
- onUnmounted(): 组件卸载完成后执行的函数
- onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。
- onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行。
- onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数(以后用到再讲,不好展现)。
注:使用<keep-alive>组件会将数据保留在内存中,比如我们不想每次看到一个页面都重新加载数据,就可以使用<keep-alive>组件解决。
import {
reactive,
toRefs,
onMounted,
onBeforeMount,
onBeforeUpdate,
onUpdated,
} from "vue";
export default defineComponent({
name: 'TemplateBox',
components: {},
setup() {
console.log('1-开始创建组件-----setup()')
const data: DataProps = reactive({
name: 'name',
nameAry: ['小明', '小红', '小微'],
selectName: '',
selectNameFun: (index: number) => {
data.selectName = data.nameAry[index]
},
})
onBeforeMount(() => {
console.log('2-组件挂载到页面之前执行-----onBeforeMount()')
})
onMounted(() => {
console.log('3-组件挂载到页面之后执行-----onMounted()')
})
onBeforeUpdate(() => {
console.log('4-组件更新之前-----onBeforeUpdate()')
})
onUpdated(() => {
console.log('5-组件更新之后-----onUpdated()')
})
const refData = toRefs(data)
return {
...refData,
}
},
})
Vue2.x 和 Vue3.x 生命周期对比
Vue2--------------vue3
beforeCreate -> setup()
created -> setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
deactivated -> onDeactivated
errorCaptured -> onErrorCaptured
新增onRenderTracked()和 onRenderTriggered()钩子函数
- onRenderTracked 状态跟踪
onRenderTracked直译过来就是状态跟踪,它会跟踪页面上所有响应式变量和方法的状态,也就是我们用return返回去的值,他都会跟踪。只要页面有update的情况,他就会跟踪,然后生成一个event对象,我们通过event对象来查找程序的问题所在。
使用onRenderTracked同样要使用import进行引入。
import { .... ,onRenderTracked,} from "vue";
引用后就可以在setup()函数中进行引用了。
onRenderTracked((event) => {
console.log("状态跟踪组件----------->");
console.log(event);
});
写完后可以到终端中启动测试服务yarn serve,然后看一下效果,在组件没有更新的时候onRenderTracked是不会执行的,组件更新时,他会跟组里边每个值和方法的变化。
onRenderTriggered直译过来是状态触发,它不会跟踪每一个值,而是给你变化值的信息,并且新值和旧值都会给你明确的展示出来。
如果把onRenderTracked比喻成散弹枪,每个值都进行跟踪,那onRenderTriggered就是狙击枪,只精确跟踪发生变化的值,进行针对性调试。
使用它同样要先用import进行引入
import { .... ,onRenderTriggered,} from "vue";
在使用onRenderTriggered前,记得注释相应的onRenderTracked代码,这样看起来会直观很多。 然后把onRenderTriggered()函数,写在setup()函数里边,
onRenderTriggered((event) => {
console.log("状态触发组件--------------->");
console.log(event);
});
对 event 对象属性的详细介绍:
-
key 那边变量发生了变化
-
newValue 更新后变量的值
-
oldValue 更新前变量的值
-
target 目前页面中的响应变量和函数
watch监听
vue3.0路由router/route的使用
vue3.0新增API:useRouter和useRoute
区别:
- router路由跳转传参
- route获取路由中所带参数
使用
1.创建文件router/index.ts
import {createRouter, createWebHistory, RouteRecordRaw} from 'vue-router'
import HomeView from '@/views/HomeView.vue'
import TestHome from '@/views/TestView/TestHome.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
//文件引入方式一
component: HomeView,
},
{
path: '/about',
name: 'about',
//文件引入方式二
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue'),
},
{
path: '/test',
// path: '/test/:userId?', //选填
// path: '/test/:userId', //必填
name: 'test',
component: TestHome,
},
]
//调用createRouter方法
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
})
export default router
2.提供了useRoute, useRouter两个API方法
2.1 home.ts页面 useRouter路由跳转可带参数
<script lang="ts">
import {defineComponent} from 'vue'
import {useRouter} from 'vue-router'
export default defineComponent({
name: 'HomeView',
setup() {
const router = useRouter() //调用useRouter方法
const handleLinkToTest = () => {
const userId = '123'
// 注意不同方式的路由跳转
router.push('/test') // -> /user/123
//router.push(`/test/${userId}`) // -> route.params
// router.push({path: '/test', query: {userId}}) // -> route.query
// router.push({name: 'test', params: {userId}}) // -> route.params
// 这里的 params 不生效
//router.push({path: '/test', params: {userId}}) // -> /test页面拿不到params的参数值
}
return {
handleLinkToTest,
}
},
})
</script>
2.2 test.ts页面,useRoute获取拼在链接上的参数
<script lang="ts">
import {defineComponent, onMounted, ref} from 'vue'
import {useRoute} from 'vue-router'
export default defineComponent({
name: 'TestHome',
setup(props, content) {
const route = useRoute() //接收页面定义变量route,获取传过来的变量
//query:route.query.userId;
//params:route.params.userId;
//注意:router传递的方式不同,route获取的方式不同
//router.push(`/test/${userId}`) // -> route.params
//router.push({path: '/test', query: {userId}}) // -> route.query
//router.push({name: 'test', params: {userId}}) // -> route.params
onMounted(() => {
console.log('组件挂载到页面之后执行')
console.log(route.params, 'params--useRoute')
console.log(route.query, 'query--useRoute')
})
return {}
},
})
</script>
参考文档:
包含子组件的router
文件:views/layout
homeLayout.vue
<template>
<div class="homeLayout">
<section class="content safe-area-inset-bottom">
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
</section>
<div class="bottom-bar">
<van-tabbar v-model="active" active-color="#4378BE" inactive-color="#666666" route safe-area-inset-bottom>
<van-tabbar-item v-for="(item, index) in tabbarItems" :key="index" replace :to="item.to">
<span style="font-size: 12px">{{ item.text }}</span>
<template #icon="props">
<em class="tabbar-icon iconfont" :class="item.iconfont" :style="{color: props.active ? '#4378BE' : '#AAAAAA'}"></em>
</template>
</van-tabbar-item>
</van-tabbar>
</div>
</div>
</template>
<script lang="ts">
import {defineComponent, reactive, toRefs} from 'vue'
export default defineComponent({
name: 'LAYOUT',
setup() {
const state = reactive({
active: 0,
tabbarItems: [
{
text: '首页',
to: '/testhome',
iconfont: 'home-o',
},
{
text: '关于',
to: '/testabout',
iconfont: 'award',
},
],
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
.homeLayout {
display: flex;
flex-direction: column;
height: 100vh;
.content {
flex: 1;
overflow: auto;
}
.bottom-bar {
height: 50px;
}
.tabbar-icon {
@include iconfontSize(24);
}
}
</style>
router/index.ts
import {createRouter, createWebHistory, RouteRecordRaw} from 'vue-router'
import HomeLayout from '@/views/layout/homeLayout.vue'
import TestHome from '@/views/TestView/TestHome.vue'
const routes: Array<RouteRecordRaw> = [
{
//包含父子组件的
path: '/',
name: 'HomeLayout',
component: HomeLayout,
children: [
{
path: '/testhome',
name: 'HOME',
component: () => import(/* webpackChunkName: "testhome" */ '../views/HomeView.vue'),
meta: {title: '首页'},
},
{
path: '/testabout',
name: 'ABOUT',
component: () => import(/* webpackChunkName: "testabout" */ '../views/AboutView.vue'),
meta: {title: '关于', keepAlive: true},
},
],
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
})
export default router
vue3.0全局引入vant组件
Vant 3.x 版本 的文档,适用于 Vue 3 开发
通过vue-cli创建的项目
- 安装vant
# Vue 3 项目,安装最新版 Vant
npm i vant@next -S
# 通过 yarn 安装
yarn add vant
- 全局注册Vant 组件,可以在 app 下的任意子组件中使用注册的
import { Button } from 'vant';
import { createApp } from 'vue';
const app = createApp();
// 方式一. 通过 app.use 注册
// 注册完成后,在模板中通过 <van-button> 或 <VanButton> 标签来使用按钮组件
app.use(Button);
// 方式二. 通过 app.component 注册
// 注册完成后,在模板中通过 <van-button> 标签来使用按钮组件
app.component(Button.name, Button);
- 全局引入
//main.ts
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'amfe-flexible' //用于自动设置根节点字体大小
import Vant from 'vant'
import 'vant/lib/index.css'
createApp(App).use(store).use(router).use(Vant).mount('#app')
//引用 home.ts
<van-button type="primary">主要按钮</van-button>
- 使用 babel-plugin-import 插件按需引入
1. 安装插件
yarn add babel-plugin-import -D
2. 配置插件
在.babelrc 或 babel.config.js 中添加配置:
{
"presets": [
"@vue/cli-plugin-babel/preset"
],
"plugins": [
[
"import",
{
"libraryName": "vant",
"libraryDirectory": "es",
"style": true
}
]
]
}
3.使用组件(分离组件) 见第6点
- 使用ts-import-plugin按需引入(构建的项目用的改方法)
在非 vite 的项目中,可以通过 babel 插件来实现按需引入组件。我们需要安装 babel-plugin-import 插件,它会在编译过程中将 import 语句自动转换为按需引入的方式。如果你在使用 TypeScript,可以使用 ts-import-plugin 实现按需引入。
参考文档:blog.csdn.net/qq_39523111…
1.安装插件
yarn add ts-import-plugin webpack-merge -D
{
"ts-import-plugin": "^2.0.0",
"webpack-merge": "^5.8.0"
}
2.配置插件:vue.config.js
const {merge} = require('webpack-merge')
const tsImportPluginFactory = require('ts-import-plugin')
module.exports = {
devServer: {
// 端口号
port: 8080,
},
css: {
loaderOptions: {
// 默认情况下 `sass` 选项会同时对 `sass` 和 `scss` 语法同时生效
// 因为 `scss` 语法在内部也是由 sass-loader 处理的
// 但是在配置 `prependData` 选项的时候
// `scss` 语法会要求语句结尾必须有分号,`sass` 则要求必须没有分号
// 在这种情况下,我们可以使用 `scss` 选项,对 `scss` 语法进行单独配置
sass: {
prependData: ` @import '@/assets/styles/_variables.scss';`,
},
},
},
parallel: false,
chainWebpack: config => {
config.module
.rule('ts')
.use('ts-loader')
.tap(options => {
options = merge(options, {
transpileOnly: true,
getCustomTransformers: () => ({
before: [
tsImportPluginFactory({
libraryName: 'vant',
libraryDirectory: 'es',
style: true,
}),
],
}),
compilerOptions: {
module: 'es2015',
},
})
return options
})
},
}
3.main.ts中注册vant
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'amfe-flexible' //用于自动设置根节点字体大小
import Vant from 'vant'
//此处算是全局引入,不是按需引入 按需引入是 import {Button} from 'vant',
import 'vant/lib/index.css'
//此处全局引入了样式
createApp(App).use(store).use(router).use(Vant).mount('#app')
4.不知道是否生效
不知道打包之后的什么样式算是按需加载成功的
- 使用文件抽离的方式实现按需引入
参考文件:www.jianshu.com/p/19f82a759…
1.创建文件plugins/vant.ts
//import 'vant/lib/index.css' //全局引入样式
//按需引入央视嫌麻烦的话就直接全局引入
import 'vant/es/button/style/index' //按需引入样式
import 'vant/es/field/style/index'
import 'vant/es/cell/style/index'
import 'vant/es/cell-group/style/index'
import {
Button,
Field,
Cell,
CellGroup,
} from 'vant' //按需引入组件
const pluginsVant = [
Button,
Field,
Cell,
CellGroup,
]
export const vantPlugins = {
install: function (vm: any) {
pluginsVant.forEach(item => {
// 方式二. 通过 app.component 注册
// 注册完成后,在模板中通过 <van-button> 标签来使用按钮组件
vm.component(item.name, item)
})
},
}
2.main.ts 引入并按需注册
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'amfe-flexible' //用于自动设置根节点字体大小
import {vantPlugins} from './plugins/vant'
// 方式一. 通过 app.use 注册
// 注册完成后,在模板中通过 <van-button> 或 <VanButton> 标签来使用按钮组件
createApp(App).use(store).use(router).use(vantPlugins).mount('#app')
这样页面中可以不需要引入vant组件,可直接使用
<van-button type="primary">主要按钮</van-button>
<van-cell-group>
<van-cell title="单元格" value="内容" />
<van-cell title="单元格" value="内容" label="描述信息" />
</van-cell-group>
- amfe-flexible安装:用于自动设置根节点字体大小
移动端适配配置方法:www.jianshu.com/p/eb1705880…
npm i amfe-flexible -D
or
yarn add amfe-flexible -D
vue3.0+vuex+ts
- 首先创建如下的目录结构,modules文件夹里面的名称根据自己情况来
- user.ts页面
import {ActionContext} from 'vuex'
//为 store state 声明类型
export type userState = {
isLogin: boolean
userInfo: object
}
//定义state值
const state: userState = {
isLogin: true,
userInfo: {name: '初始化name值'},
}
//变更state参数
const mutations = {
//用户是否登录
SET_IS_LOGIN(state: userState, payload: boolean) {
state.isLogin = payload
},
//用户信息
SET_USER_INFO(state: userState, payload: object) {
state.userInfo = payload
},
}
//赋值或计算state参数
const actions = {
saveIsLogin(ctx: ActionContext<userState, boolean>, data: boolean) {
ctx.commit('SET_IS_LOGIN', data)
},
saveUserInfo: (ctx: ActionContext<userState, object>, data: object) => {
ctx.commit('SET_USER_INFO', data)
},
}
export default {
namespaced: true,
state,
mutations,
actions,
// getters, 计算属性
}
- getters.ts
//vuex全部的modules声明类型
import {TypeAllState} from './type'
//计算属性,返回state参数
const getters = {
//user
isLogin: (state: TypeAllState) => state.user.isLogin,
userInfo: (state: TypeAllState) => state.user.userInfo,
}
export default getters
- index.ts页面
import {createStore} from 'vuex'
import getters from './getters'
import {TypeAllState} from './type'
const modulesFiles = require.context('./modules', true, /.ts$/)
// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules: any, modulePath: any) => {
// set './app.js' => 'app'
const moduleName = modulePath.replace(/^./(.*).\w+$/, '$1')
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {})
export default createStore<TypeAllState>({
modules,
getters,
})
- type.ts页面
// 模块state类型引入
import {userState} from './modules/user'
export interface TypeRootState {
count: number
}
// vuex所有state类型定义集成
export interface TypeAllState extends TypeRootState {
user: userState
}
- main.ts页面
通过app.use将store注册全局
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App).use(store).use(router).mount('#app')
- home.vue页面
API: useStore
<template>
<div class="home">
<van-button plain type="success" @click="handleSaveStore">点击修改store数据(dispatch)</van-button>
<div>store数据直接在template使用:</div>
<van-cell-group>
<van-cell title="$store.getters" :value="$store.getters.isLogin ? 'true' : 'false'" label="$store.getters.isLogin" />
<van-cell title="$store.getters" :value="$store.getters.userInfo.name" label="$store.getters.userInfo.name" />
<van-cell title="$store.state" :value="$store.state.user.isLogin ? 'true' : 'false'" label="$store.state.user.isLogin" />
<van-cell title="$store.state" :value="$store.state.user.userInfo.name" label="$store.state.user.userInfo.name" />
</van-cell-group>
<div>{{ JSON.stringify(userInfo) }}</div>
</div>
</template>
<script lang="ts">
import {defineComponent,computed} from 'vue'
import {useStore} from 'vuex'
export default defineComponent({
name: 'HomeView',
setup() {
const store = useStore() //store
const userInfo = computed(() => store.state.user.userInfo)
console.log(store, 'store')
console.log(store.state, 'state---1')
console.log(store.state.user, 'state---user')
console.log(store.state.user.userInfo.name, 'state---user.userInfo.name')
console.log(store.getters.isLogin, 'getters---isLogin')
console.log(store.getters.userInfo, 'getters---userInfo')
console.log(store.getters.userInfo.name, 'getters---userInfo.name')
const handleSaveStore = () => {
store.dispatch('user/saveIsLogin', !store.getters.isLogin)
store.dispatch('user/saveUserInfo', {name: '修改的name值', age: 18})
}
return {
handleSaveStore,
}
},
})
</script>
\
vue3.0+全局使用scss
安装的sass和sass-loader版本要一致
- 版本一致
目前使用版本(配置成功)
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
在构建改项目的时候,vue-cli创建的版本号如下:(启动失败)
"sass": "^1.32.7",
"sass-loader": "^12.0.0",
- 必须是vue.config.js文件下配置
ts项目的配置文件也要是以.js结尾的配置文件,不能是.ts文件
module.exports = {
css: {
loaderOptions: {
// 默认情况下 `sass` 选项会同时对 `sass` 和 `scss` 语法同时生效
// 因为 `scss` 语法在内部也是由 sass-loader 处理的
// 但是在配置 `prependData` 选项的时候
// `scss` 语法会要求语句结尾必须有分号,`sass` 则要求必须没有分号
// 在这种情况下,我们可以使用 `scss` 选项,对 `scss` 语法进行单独配置
sass: {
prependData: ` @import '@/assets/styles/_variables.scss';`,
},
},
},
}