搭建vue3.0项目

502 阅读10分钟

vue3.0出来很久了,一直都没用过,今天打算搭建个适用于移动端项目

后台管理系统:

vben:vue3后台管理项目框架

h5:jspang.com/article/64

使用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 片段

关键词代码片段
Importimport {...} from '...'
Datadata(){return {...}}
Setupsetup(){...return{...}}
vTextv-text="..."
vHtmlv-html="..."
vShowv-show="..."
vIfv-if="..."
velsev-else
velseifv-else-if="..."
vForv-for="... in ..." :key="..."
vFor(withoutKey)v-for="... in ..."
vOnv-on="..."
vBindv-bind="..."
vModelv-model="..."
vSlotv-slot="..."
vOncev-once
iscomponent
vpropsconst props = defineProps({ foo: String })
vemitsconst emit = defineEmits(['...', '...'])

vue-router 片段

关键词代码片段
beforeeachrouter.beforeEach((to, from, next) =>{...}
beforeresolverouter.beforeResolve((to, from, next) => {...}
afterEachrouter.afterEach((to, from) => {...}
beforeenterbeforeEnter(to, from, next) {...}
beforeRouteEnterbeforeRouteEnter(to, from, next) {...}
beforeRouteLeavebeforeRouteLeave(to, from, next) {...}
vroute{'path':...,name:...,component: () => import('...')}

Vue3 的新知识点

  1. setup
  • setup 函数的用法,可以代替 Vue2 中的 date 和 methods 属性,直接把逻辑卸载 setup 里就可以
  • 缺点:setup中要改变和读取一个值的时候,还要加上value。这种代码一定是可以优化的,需要引入一个新的 APIreactive
  1. ref
  • ref函数的使用,它是一个神奇的函数,我们这节只是初次相遇,要在template中使用的变量,必须用ref包装一下。
  1. return
  • return出去的数据和方法,在模板中才可以使用,这样可以精准的控制暴漏的变量和方法。
  1. reactive
  • reactive 它也是一个函数(方法),只不过里边接受的参数是一个 Object(对象)。
  • 缺点:在template中,每次输出变量前面都要加一个data,优化:新函数toRefs()
  1. toRefs
  • 使用toRefs方法对data进行包装,把 data 变成refData,再通过...()return出数据,在template中就可以去掉 data,可以直接使用变量名和方法

vue3 的生命周期

  1. setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
  2. onBeforeMount() : 组件挂载到节点上之前执行的函数。
  3. onMounted() : 组件挂载完成后执行的函数。
  4. onBeforeUpdate(): 组件更新之前执行的函数。
  5. onUpdated(): 组件更新完成之后执行的函数。
  6. onBeforeUnmount(): 组件卸载之前执行的函数。
  7. onUnmounted(): 组件卸载完成后执行的函数
  8. onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。
  9. onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行。
  10. 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()钩子函数

  1. onRenderTracked 状态跟踪

onRenderTracked直译过来就是状态跟踪,它会跟踪页面上所有响应式变量和方法的状态,也就是我们用return返回去的值,他都会跟踪。只要页面有update的情况,他就会跟踪,然后生成一个event对象,我们通过event对象来查找程序的问题所在。

使用onRenderTracked同样要使用import进行引入。

import { .... ,onRenderTracked,} from "vue";

引用后就可以在setup()函数中进行引用了。

onRenderTracked((event) => {   
  console.log("状态跟踪组件----------->");   
  console.log(event); 
});

写完后可以到终端中启动测试服务yarn serve,然后看一下效果,在组件没有更新的时候onRenderTracked是不会执行的,组件更新时,他会跟组里边每个值和方法的变化。

  1. onRenderTriggered 状态触发

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

区别:

  1. router路由跳转传参
  2. 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创建的项目

  1. 安装vant
# Vue 3 项目,安装最新版 Vant
 npm i vant@next -S 

# 通过 yarn 安装
yarn add vant
  1. 全局注册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);
  1. 全局引入
//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>
  1. 使用 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
  1. 使用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.不知道是否生效

不知道打包之后的什么样式算是按需加载成功的

  1. 使用文件抽离的方式实现按需引入

参考文件: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>
  1. amfe-flexible安装:用于自动设置根节点字体大小

移动端适配配置方法:www.jianshu.com/p/eb1705880…

npm i amfe-flexible -D
or
yarn add amfe-flexible -D

vue3.0+vuex+ts

  1. 首先创建如下的目录结构,modules文件夹里面的名称根据自己情况来

  1. 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, 计算属性
}
  1. 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
  1. 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,
})
  1. type.ts页面
// 模块state类型引入
import {userState} from './modules/user'

export interface TypeRootState {
	count: number
}
// vuex所有state类型定义集成
export interface TypeAllState extends TypeRootState {
	user: userState
}
  1. 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')
  1. 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版本要一致

  1. 版本一致

目前使用版本(配置成功)

"sass": "^1.26.5",
"sass-loader": "^8.0.2",

在构建改项目的时候,vue-cli创建的版本号如下:(启动失败)

"sass": "^1.32.7",
"sass-loader": "^12.0.0",
  1. 必须是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';`,
        },
      },
	},
}