一、功能
用户登录 对用户增删改查 中英文切换 全屏
二、核心思想
基于element-plus和vue-cli实现
- vuex存储token数据
- 通过axios发送请求,返回响应,对请求和响应进行拦截
- 通过axios创建一个服务请求
- interceptors.request.use 请求拦截器
- interceptors.response.use 响应拦截器 对响应回来的数据进行进一步取之后再传回去
- localStorage存储token数据
- setItem存
- getItem拿 拿出来之后存到了vuex中的state中去
- element-plus完成的事情
- 实现页面的基本buju
- 完成对一些输入数据的校验,比如说必须输入用户名/密码 等效于input中的required:true,一些正则化操作
- 使用一些icon图标
三、项目初始创建
3.1 项目创建
cmd vue-ui
scss
eslint代码规范
axios依赖
3.2 代码规范
1.在Vscode插件中安装prettier
2.在根目录下导入配置文件
3.在vscode的设置里 搜索save 就能看到并勾上format on save
4.右键 使用...格式化文档 配置默认格式化文档程序 选择prettier
5.在配置下.eslintrc.js里的rules 新增 为了解决eslint与prettier冲突
'indent': 0,
'space-before-function-paren': 0
6.使用husky强制代码格式化 创建配置文件
npx husky add .husky/pre-commit
7.往第六步生成的文件中写入
npx lint-staged
8.把package.json文件的lint-staged修改为
"lint-staged": {
"src/**/*.{js,vue}": [ //src目录下所有的js和vue文件
"eslint --fix", // 自动修复
"git add" // 自动提交时修复
]
}
3.3 git commit规范
1.安装commitizen和cz-customizable
npm install -g commitizen@4.2.4
npm i cz-customizable@6.3.0 --save-dev
2.在package.json中进行新增
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
}
}
3.在根目录下新建.cz-config.js文件并写入配置 之后就可以用git cz来代替 git commit
4.使用husky进行强制git代码提交规范
npm install --save-dev @commitlint/config-conventional@12.1.4 @commitlint/cli@12.1.4
npm install husky@7.0.1 --save-dev // 强制性规范
npx husky install
5.在package.json中新增指令
"prepare": "husky install"
6.并执行
npm run prepare
7.新增husky配置文件 并往里面写入
npx husky add .husky/commit-msg
npx --no-install commitlint --edit
3.4 element-ui使用
1 yarn add element-plus
2 按需导入
yarn add -D unplugin-vue-components unplugin-auto-import
3 在vue.config.js中导入
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = {
configureWebpack: config => {
config.plugins.push(AutoImport({
resolvers: [ElementPlusResolver()],
}))
config.plugins.push(Components({
resolvers: [ElementPlusResolver()],
}))
},
}
3.5 vue3.2新特性
- 在template中不需要再用根标签包裹
- 在css中可以直接访问js变量
<template>
<div class = 'box1'></div>
</template>
<script setup>
const boxwidth = '100px'
</script>
<style lang = 'scss'>
.box1{
width:v-bind(boxwidth)
}
</style>
3.6 初始化项目
- App.vue
<script setup></script>
<template>
<router-view />
</template>
<style lang="scss">
.el-message-box__status {
position: absolute !important;
}
</style>
-
main.js 存放一些方法
-
新建文件夹style 存放一些scss文件 例如一些公用的样式信息,存成变量的形式
-
- scss的特点
-
新建文件夹views 存放各个功能的页面
./login/App.vue 模板内容
<template>
<div>login</div>
</template>
<script setup></script>
<style lang="scss" scoped></style>
- 在路由router文件夹中进行配置 ./router/index.js
import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [
{
path: '/login',
name: 'Login',
component: () => import('../views/login')
},
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
总结
在views中创建页面之后,要在router中进行路由配置
四、功能实现
4.1 登录功能
4.1.1 双向数据绑定
- ref+v-model
import { ref } from 'vue'
const form = ref({
username: '',
password: ''
})
// 在template便可以使用js中的变量
<el-input v-model="form.username" />
请注意我们在取ref传递的数据时,必须要通过value来获得 即form.value.xxx
4.1.2 element-plus icon
// 直接导入图标名
import { User, Lock } from '@element-plus/icons-vue'
// 在template中可以直接使用
<el-icon :size="20" class="svg-container">
<User />
</el-icon>
4.1.3 element-plus+element-ui表单校验
- 在填写过程中进行校验
// required表示是否是必填项 message表示提示语 trigger表示如何触发
const rules = ref({
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur'
}
]
})
// 在需要进行校验的组件中添加规则,并为对应的组件设置props绑定对应的规则
<el-form-item prop="username">
:rules="rules"
- 点击登录按钮时进行统一校验,需要为button按钮绑定点击事件
<el-button round class="login-button" @click="handleLogin">{{
登录
}}</el-button>
const formRef = ref(null) //
const handleLogin = () => {
formRef.value.validate((valid) => {
if (valid) {
...
} else {
console.log('error submit!!')
return false
}
})
}
4.1.4 发起请求操作
- 设置基础路径
- 开发环境在项目根目录下创建.env.development
ENV = 'development'
VUE_APP_BASE_API = '/api'
- 生产环境
ENV = 'production'
VUE_APP_BASE_API = '/prod-api'
- 解决跨域问题 在vue.config.js设置代理
devServer: {
https: false,
// hotOnly: false, // 是否热更新
// hot: 'only',
proxy: {
'/api': { // 基础路径设置为api
target: 'http://43.143.0.76:8889/api/private/v1/', // 接口地址
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
},
- 在src中创建api文件夹,在文件夹中创建request.js 这是一个总的设置
import axios from 'axios'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // 基础路径
timeout: 5000
})
export default service // 导出供外界使用
- 设置单独的login请求操作
这部分代码就是在说:从客户端输入信息后,通过为登录按钮设置点击事件向后台接口发送请求,在api/login.js中通过一个箭头函数返回请求的一些信息。例如:方法、地址、数据。请求的方法是post,请求的url地址是http://api/login 请求传递来的参数是向表单中输入的数据 请求的接口在vue.config.js中设置:'http://43.143.0.76:8889/api/private/v1/'
- 在api文件夹下创建./login.js
import request from './request'
export const login = (data) => { // 导出login方法
return request({
url: 'login', // 发请求的地址
method: 'POST',
data // 接收从登录页面传递过来的参数:用户信息(form)
})
}
- 在.views/login/index.vue中导入login请求
import { login } from '@/api/login'
这样便可以在登录校验时向login传入用户写入的信息,带着他们发送请求
// 登录按钮触发的事件
const handleLogin = () => {
formRef.value.validate(async (valid) => {
if (valid) {
const res = await login(form.value) // 将表单中传递的信息传给了api/login中 从中向服务端发送请求 得到响应数据
console.log(res) // 传递回来的数据
} else {
console.log('error submit!!')
return false
}
})
}
- 使用await的原因?????
4.1.5 响应拦截器 更快的得到想要的信息
利用axios的响应拦截器,从服务端拿到我们想要的信息 并利用element-plus中的ElMessage向外抛出错误信息
在./api/request.js中
import {ElMessage} from 'element-plus'
sevice.interceptors.response.use((response) => {
// console.log(response)
const {data, meta} = response.data // 解构
if(meta.status === 200 || meta.status === 201){
return data // 返回从服务器响应来的的信息
}else{
// 利用element中的ElMessage向外界传递
ElMessage.error(meta.msg)
return Promise.reject(new Error(meta.msg))
}
}, error => {
error.response && ElMessage.error(error.response.data)
return Promise.reject(new Error(error.response.data))
})
4.1.6 将从服务器传来的token保存在vuex和localstorage中
token是存储在后端服务器中,这个项目中的token在用户发送请求后,服务器返回响应,将token返回。
- vuex中的store的主要模板
export default {
namespaced: true,
state: () => ({}),
mutations: {
},
actions: {
}
}
- 在store中新建module/app.js
- 我们通过token来使得他去进行登录,因为我们把登录的请求放到这里 对.views/login/index.vue中的代码进行改进
import { login as loginApi } from '@/api/login'
state: () => ({
token: localStorage.getItem('token') || '', // 从localstorage中取出来token
}),
mutations: {
setToken(state, token) {
state.token = token // 外界传入的token
localStorage.setItem('token', token) // 接收到token并存进去
},
},
actions: {
login({ commit }, userInfo) {
return new Promise((resolve, reject) => { // 发请求
loginApi(userInfo) // 调用api中的login
.then((res) => {
console.log(res)
commit('setToken', res.token)
// 调用settoken,拿到token,执行mutation中的操作
setTokenTime() //
router.replace('/') // 成功之后跳到首页
resolve()
})
.catch((error) => { // 请求失败
reject(error)
})
})
},
logout({ commit }) {
commit('setToken', '')
localStorage.clear()
router.replace('/login')
}
}
- 对.views/login/index.vue中的代码进行改进
import { useStore } from 'vuex'
const store = useStore()
const formRef = ref(null)
const handleLogin = () => {
formRef.value.validate(async (valid) => {
if (valid) {
store.dispatch('app/login', form.value)
// 通过dispatch触发store/module/app中actions中的login
} else {
console.log('error submit!!')
return false
}
})
}
总结
利用vuex和localstorage完成login请求,并在请求成功时,拿到token并进行保存。我们在views/login/index.vue中的登录的单击响应函数中通过dispatch方法向调用vuex中的login方法,这个login放在写在store/module/app.js的actions中,actions中完成的操作为:发送login请求,并且在请求成功时,拿到响应中的token,触发mutation中的操作,将token保存在vuex(即state中)和localStorage中
- vuex总结
- 利用请求拦截器对每一个接口添加上token信息
service.interceptors.request.use(
(config) => {
config.headers.Authorization = localStorage.getItem('token')
return config
},
(error) => {
return Promise.reject(new Error(error))
}
)
4.2 对密码可见和不可见以及图标进行切换
对密码的输入框绑定动态的type、图标绑定单击响应函数
<el-input v-model="form.password" :type="passwordType" show-password />
<el-icon @click="changeType" :size="20" class="svg-container"> // 这里采用的是up主提到的矢量图
const passwordType = ref('password')
// 单击响应函数
const changeType = () => {
if(passwordType.value === 'password'){
passwordType.value = 'text'
}else{
passwordType.value = 'password'
}
}
4.3 路由守卫 只允许登录的用户访问主页面
- router下创建permission.js
- ./router/permisson.js
import router from './index' // 需要使用路由
import store from '@/store' // 判断是否登录 所以需要有token
const whiteList = ['/login']
router.beforeEach((to, from, next) => {
if (store.getters.token) { // 如果有token表示登录成功
if (to.path === '/login') { // 判断路径是否是login
next('/')
} else {
next() // 不去login 随便去其他哪里
}
} else { //
if (whiteList.includes(to.path)) { // 设置白名单 在没有登录的时候也可以访问
next()
} else { // 如果都不在白名单内就需要登录
next('/login')
}
}
})
- 为了方便拿到token,在store中创建一个getters.js
export default {
token: (state) => state.app.token,
}
- 使用路由守卫 在main.js导入
import '@/router/permission'
总结
使用vue-router中的beforeEach全局前置守卫,需要接收三个参数。
4.4 主页面布局
4.4.1 layout父组件
- element-plus设置布局
- 在router/index.js中配置路由
path: '/',
name: '/',
component: () => import('../layout'),
- 将我们事先设置的变量值全局导入,通过
webpack配置来完成这个操作
在vue.config.js中写入
css: {
loaderOptions: {
sass: {
additionalData: //或 prependData: // sass-loader版本为8用prependData:
`
@import "@/styles/variables.scss"; // scss文件地址
@import "@/styles/mixin.scss"; // scss文件地址
`
}
}
}
每次更改完webpack都要重新启动服务器
4.4.2 menu子组件
- 新建menu子组件文件夹
- 利用element-plus创建菜单
- 在父组件中引入子组件
import Menu from './Menu'便可以在template中作为标签使用 - 向后端发起请求拿到接口中的数据./api/menu.js
import request from './request'
export const menuList = () => {
return request({
url: '/menus' //接口中的地址
})
}
- 在menu/index.vue中使用
import { menuList } from '@/api/menu'
const initMenusList = async () => { // 定义方法发请求
menusList.value = await menuList()
// console.log(res)
}
- 进行数据渲染
const menusList = ref([])
将上述从接口中拿来的数据存放到menuList中 数据为一级和二级数据
- 在template中将数据显示
<el-sub-menu :index="item.id" v-for="(item, index) in menusList" :key="item.id">
<template #title>
<span>{{ item.authName }}</span>
</template>
<el-menu-item index="1-3" v-for="child in item.children" :key="child.id">
<template #title>
<span>{{ child.authName }}</span>
</template>
</el-menu-item>
</el-sub-menu>
- 实现点击子项时,页面跳转,先把页面中的路径配置好
将子项的
index进行修改:index="'/' + child.path" - 对跳转的页面配置路由(router/index.js)和页面(views)
在4.4.1 layout父组件设置的路由下,router/index.js中设置下一级路由
4.4.3 优化
- 优化点击菜单时,点击一项,每一项都展开了
- 在template配置中设置唯一展开
- 默认一个展开项,和我们在router中设置的重定向匹配。在./layout/menu/index.vue
const defaultActive = ref('/users')
:default-active="defaultActive"
- 点击每一项显示的内容要和菜单相匹配。因此在点击每一项时,都要修改defaultActive的值,对子项绑定一个click事件。
<el-menu-item index="1-3" v-for="child in item.children" :key="child.id" @click="savePath(child.path)>- 把点击每一块子项的路径存在sessionStorage
const savePath = (path) => {
sessionStorage.setItem('path', `/${path}`)
}
- 对默认打开的路径进行重新赋值 从sessionStorage中取路径信息
const defaultActive = ref(sessionStorage.getItem('path') || '/users')
- 统一导入icon
- 作为组件在template中被使用。
<component :is="icon"></component>
总结
- 设置变量供全局使用时,采用webpack进行配置
- 父子组件的设计:父组件是框架,子组件是每部分的细节
- 数据的拿取:在api/xxx.js配置接口的url信息,然后在xxx/index.vue中通过async和await结合得到取出来数据存放到事先定义好的响应式数据类型的变量中。注意:一定要存放在.value中。
- 数据的渲染:在xxx.vue中的template中渲染数据,利用v-for or 其他代码
- 对于每个页面的跳转,要在router/index.js中配置相应的路由
4.5 被动退出(设置token有效期)
- 新建./utils/auth.js
// 定义常量值 ./utils/costant.js
export const TOKEN_TIME = ' tokenTime'
export const TOKEN_TIME_VALUE = 2 * 60 * 60 * 1000 // 2小时
import { TOKEN_TIME, TOKEN_TIME_VALUE } from './constant'
// 登录时设置时间
export const setTokenTime = () => {
localStorage.setItem(TOKEN_TIME, Date.now())
}
// 获取登录时的时间
export const getTokenTime = () => {
return localStorage.getItem(TOKEN_TIME)
}
// 是否已经过期
export const diffTokenTime = () => {
const currentTime = Date.now()
const tokenTime = getTokenTime()
return currentTime - tokenTime > TOKEN_TIME_VALUE
}
- 在请求拦截器(api/request.js)中判断token是否过期
import { diffTokenTime } from '@/utils/auth'
if (localStorage.getItem('token')) { // 是否有token
if (diffTokenTime()) {
// 通过vuex实现退出操作
store.dispatch('app/logout')
return Promise.reject(new Error('token 失效了'))
}
}
- 在.router/module/app.js的actions中完成登出操作
logout({ commit }) {
commit('setToken', '') // 将存的token变成一个空字符串
localStorage.clear()
router.replace('/login')
}
- 在登录时也要设置上登录时间
import { setTokenTime } from '@/utils/auth'
.then((res) => {
console.log(res)
commit('setToken', res.token)
setTokenTime()
router.replace('/')
resolve()
})
总结
- 首先新建一个js,定义一个token和token的有效期
- 定义箭头函数保存登录时的时间和判断是否过期
- 将2中返回的值在请求拦截器和router中使用:在请求拦截器中每次发起请求先去判断是否有token,有的话是否过期,若从2中返回为true则利用dispatch触发在store中的actions中的logout方法,用commit提交新的token值存到state中,并跳转到登陆页面。
4.6 头像退出(用户主动退出)
- 对头像设置一个点击事件:
@click:"logout" - 写事件
const logout = () => {
store.dispatch('app/logout')
}
eg:logout是共同使用的,则可以将logout这个方法保存在vuex中的actions中,提高代码的复用。在需要使用的地方利用dispatch方法抓取即可。
4.7 中英文切换i18n
- yarn add vue-i18n@next
- 创建./i18n文件夹
- ./i18n/index.js
import { createI18n } from 'vue-i18n'
// 创建数组源
const messages = {
en: {
msg:{
title:'user'
}
},
zh: {
msg:{
title:'用户'
}
}
}
- 设置语言
const getCurrentLanguage = () => {
const UAlang = navigator.language // 通过浏览器设置上语言
const langCode = UAlang.indexOf('zh') !== -1 ? 'zh' : 'en' // 判断是否为中文
localStorage.setItem('lang', langCode) // 并把当前语言存在本地
return langCode // 返回给外界
}
const i18n = createI18n({ // 创建i18n
legacy: false,
globalInjection: true, // 全局t函数
locale: getCurrentLanguage() || 'zh', // 语言
messages: messages // 数据源
})
export default i18n // 供外界使用
- 在main.js中导入 导入i18n 并利用app.use(i18n)
- 在显示语言的部分利用全局t()函数进行修改
- ./views/login/index.vue
<h3 class="title">{{ $t('msg.title') }}</h3>
- 导入中英文数据源
import EN from './en'
import ZH from './zh'
const messages = {
en: {
...EN
},
zh: {
...ZH
}
}
- 修改各部分
{{ $t('login.title') }}{{ $t('login.btnTitle') }}{{ $t(menus.${item.path}) }}{{ $t(menus.${child.path}) }}
- 实现切换
- 使用一个下拉菜单完成切换
@command="handleCommand"绑定指令事件
const handleCommand = (val) => {
i18n.locale.value = val // val是标签中定义的值 修改i18n中的值
// 将值存储在vuex和localStorage中
store.commit('app/changeLang', val) // 提交给store中的mutations处理
localStorage.setItem('lang', val)
}
优化
当处于中文时,中文不能选中。
- 在内部设置computed函数,用于返回语言的值
- 取当前的语言,利用i18n中的locale
import { useI18n } from 'vue-i18n'
import { computed } from 'vue'
const i18n = useI18n()
const currentLanguage = computed(() => {
return i18n.locale.value
})
- 在template中利用disabled属性,将其动态化
:disabled="currentLanguage === 'zh'"
总结
中英文的切换通过值的改变来更换,这里将值存在store中是利用mutations中,是因为只是传递给值过去,无需再进行什么请求或者其他操作。个人理解:mutations中的操作是简单的一些逻辑操作,actions中的操作可能会向外界发送请求等。
4.8 全屏功能
- yarn add screenfull@5.1.0
- 对图标绑定点击事件handleFullScreen,利用点击事件传来的值和v-if完成图标的切换
<div @click="handleFullScreen" id="screenFull">
<el-icon v-if="icon === false"><FullScreen /></el-icon>
<el-icon v-if="icon === true"><CloseBold /></el-icon>
</div>
- 设置全屏的点击事件,利用toggle()完成
import screenfull from 'screenfull'
const handleFullScreen = () => {
if (screenfull.isEnabled) {
screenfull.toggle()
}
}
- 监听screenfull的改变,普通的watch无法完成监听,需要利用onMounted和onBeforeUnmount
import { ref, onMounted, onBeforeUnmount } from 'vue'
const icon = ref(screenfull.isFullscreen)
const changeIcon = () => {
icon.value = screenfull.isFullscreen
}
onMounted(() => {
screenfull.on('change', changeIcon)
})
onBeforeUnmount(() => { // 取消监听的钩子函数
screenfull.off('change', changeIcon)
})
总结
screenfull图标的的切换需要对状态进行监听,利用vue的生命周期的钩子函数。
仍需学习内容:
选项式API
<script>
export default{
data(){},
method:{updateAge(){}},
computed:{xxx:function(){}},
components: {}
}
</script>
1. vue响应式原理 data() 指定响应式属性
- 借用代理。
// 创建一个对象
const obj = {
name: "孙悟空",
age: 18
}
对这个对象的属性修改不是响应式的,无法使页面重新渲染,展示改变之后的数据,需要借助代理。
// 为对象创建一个代理
const handle = { // handle用来处理代理的行为
// get用来指定读取数据时的行为 他的返回值就是最终读取到的数据
// 指定get后 在通过代理读取对象属性时 就会调用get方法
get(target, prop, receiver){
// 返回值之前做一些操作。。。 track()追踪谁用了这个属性
/*
会收到3个参数
target, 被代理的对象
prop, 读取的属性
receiver 代理对象
*/
return target[prop]
},
// set会在通过代理修改对象时调用
set(target, prop, value, receiver){
// 在值修改之后做一些其他的操作
// console.log(target, prop, value, receiver);
// 被代理的对象 属性名 修改后的属性值 代理对象
target[prop] = value
// 值修改之后做一些其他的事情 trigger() 相当于是一个触发器 触发所有使用该值的位置进行更新
}
}
// 创建代理
// Proxy 参数1 要被代理的对象 参数2 handle 行为 proxy是一个原生的类,专门用来创建代理。
const proxy = new Proxy(obj, handle)
// 修改属性
proxy.age =28 // 实际上就是修改了obj的属性 调用了handle中的set方法
console.log(proxy.age); // 28 // 调用了handle中的get方法
- 在vue中,data()返回的对象会被vue代理,vue代理后,
- 当我们通过代理读取属性时,返回值之前会先做一个跟踪的操作。
- 当通过代理去修改属性时,修改后会通知之前所有用到该值的位置进行更新。
总结
-
vue3的响应式原理是通过proxy代理来完成,代理中传递了两个参数,第一个参数为被代理的对象,第二个参数为handle行为,通过代理来读取某对象的属性时,会调用handle中的get方法对该属性进行追踪,改变某对象的属性时,会调用handle中的set方法,值修改完成后,再通过trigger函数触发,完成更新。
-
只有通过代理去改变属性值才是响应式
-
vue在构建响应式对象时,会同时将对象中的属性也做成响应式属性(深层响应式对象)
-
(浅层响应式对象)
import { shallowReactive } from 'vue' return shallowReactive({})一般不需要 -
在data中可以通过this.$data.xxx = 'xxx' 动态的添加响应数据(不建议这样做),建议将暂时不使用的属性也在data中return,值先设置为null
-
所有组件实例上的属性都可以在模板中直接访问
{{ }} -
补充:响应式原理
- vue3实现原理:
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射): 对源对象的属性进行操作。\
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy.name = 'tom'
- vue2.x的响应式
实现原理:
对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
存在问题:
新增属性、删除属性, 界面不会更新。
直接通过下标修改数组, 界面不会自动更新。
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
2. methods 指定实例对象中的方法
- methods是一个对象,可以在里边定义多个方法,这些方法最终将挂载到组件实例上,可以通过组件实例调用这些方法。
- 在模板中调用时需要加上括号{{ xxx() }}
- 在可以通过@click=''来调用
- methods的this就是组件实例,proxy代理对象
3. computed用来指定计算属性
- 可以把
{{}}中的复杂的表达式写在其中 - 和data一样,返回值在模板中可以直接被读取,无需加括号
()便可以在模板中{{}}去调用,但是它【可以通过一个函数】返回结果 可以写一个逻辑 - 和methods不同的点在于
-
- 计算属性只在其依赖的数据发生变化时才会重新执行 会对数据进行缓存,而methos每次都会重新渲染
-
- 并且无需加括号去调用
- 在computed中尽量只做读取相关的逻辑
- 可以为计算属性设置set 使得计算属性可写(修改数据) 但是不建议这样写
在模板中的{{}}必须要写具有表达式,即有返回值的代码
组合式API
<script>
setup(){return xxx, xxx}
</script>
<script setup></script>
1. setup()钩子函数
- setup()是一个钩子函数 可以通过这个函数向外部暴露组件的配置
- 可以通过返回值来指定哪些内容要暴露给外部
- 在组合式api中直接声明的变量 就是一个普通的变量 不是一个响应式变量(通过ref和reactive)
2. 响应式ref和reactive
- reactive响应式变量
-
- 返回一个对象的响应式代理
const stu = reactive({
name:"swk",
age:18,
gender:"男"
})
-
- 返回一个深层的响应式对象
-
- 也可以使用shallowReactive()创建一个浅层响应式对象
-
- 缺点: 只能返回对象的响应式代理 不能处理原始值
- ref()
-
- 可以接收任意值并返回他的响应式代理
const count = ref(0)
- 可以接收任意值并返回他的响应式代理
-
- ref在生成响应式代理时 将值包装成一个对象0 -> {value:0}
-
- 在js中访问ref对象时需要通过.value访问值
-
- 在模板中访问ref对象时 模板自身会将其自动解包 无需通过value读值
{{ count }}
- 在模板中访问ref对象时 模板自身会将其自动解包 无需通过value读值
在js中无法实现对一个变量的代理
双向数据绑定(如何处理表单)
1. 事件相关的东西
v-on:click简写为@click 绑定事件 等同于dom中的btn01.onclick = () => {}
方法事件处理器的回调函数 vue会将事件对象作为参数传递
这个事件对象就是DOM中原生的事件对象 他里边包含了事件触发时的相关信息
通过该对象 可以获取:触发事件的对象 触发事件时一些情况
同时通过该对象 也可以对事件进行一些配置 取消事件的传播、取消事件的行为
.stop 停止事件的冒泡?似乎只能停止冒泡
.capture 在捕获阶段触发事件
.prevent 取消默认行为
.self 只有事件由自身触发时 才会有效
.once 绑定一个一次性的事件
.passive 只要用于提升滚动事件的性能
2. 表单的单向数据绑定
监听input的变化
let text = ""
function submitHandle() {
console.log(text.value);
// 将text提交给服务器 再根据服务器返回的数据做后续的操作
}
</script>
<template>
// 不希望表单去提交 利用.prevent取消默认行为
<form @submit.prevent="submitHandle">
<div>
<input type="text" @input="(event) => (text = event.target.value)"/>
</div>
<div><button>提交</button></div>
</form>
</template>
- 这里我们将表单项的value属性和变量text做了绑定
- 当value发生变化时 text变量也随之改变(这种情况称为单向绑定)
- 当value或text任意一个发生变化,另一个也随之变化(这种情况称为双向绑定)
3. 表单的双向数据绑定
将表单项的value属性和text进行绑定
import {ref} from "vue"
let text = ref("")
<input type="text" @input="(event) => (text = event.target.value)" :value="text"/>
4. vue双向绑定的优雅形式:v-model
将value的值传递给v-model中
import {ref} from "vue"
let text = ref("")
<input type="text" v-model = 'text'/>
- 选择框的v-model传递一个布尔值
是否:<input type="checkbox" v-model="bool" true-value="是" false-value="否"/>
- 多选框
const hobbies = ref([])
<div>
爱好:
<input v-model="hobbies" type="checkbox" name="hobby" value="足球">足球
<input v-model="hobbies" type="checkbox" name="hobby" value="篮球">蓝球
<input v-model="hobbies" type="checkbox" name="hobby" value="羽毛球">羽毛球
<input v-model="hobbies" type="checkbox" name="hobby" value="乒乓球">乒乓球
</div>
- 下拉框
const friend = ref("")
<div>
朋友
<select v-model="friend">
<option disabled value="">请选择你的好朋友....</option>
<option>孙悟空</option>
<option>猪八戒</option>
<option>唐僧</option>
</select>
</div>
5. v-model的修饰符 v-model.xxx
- .lazy 使用change来处理数据 不会频繁处理事件
- .trim 去除前后的空格
- .number 将数据转换为数值
v-show和v-if
- v-show 切换且资源是静态的推荐v-show
- 可以根据值来决定元素是否显示(通过display来切换元素的显示状态)
- v-show通过css来切换组建的显示与否 切换时不会涉及到组建的重新渲染
- 切换的性能比较高 但是初始化时需要对所有组件初始化 初始化性能比较低
- v-if 资源是动态的,可以根据表达式的值来决定是否显示元素 (直接将元素删除)是懒加载(使用的时候才加载)
- v-if通过删除添加元素的方式来切换元素的显示 切换时会反复渲染组件
- 切换的性能比较低 只对用的元素初始化 初始化性能比较好
- v-if 可以配合其他的组件使用,eg v-else-if v-else结合使用v-if
- 可以配合template使用 template不显示在代码中
动态组件 components
最终以什么标签呈现 由is属性值决定
const isShow = ref(false)
<button @click="isShow = !isShow">点我一下</button>
<component :is="isShow ? A : B">
我是一个component
</component>
组件通信
子组件的数据一般不会再子组件中直接定义,这样会导致数据和视图发生耦合,一般会在创建组件实例中(父组件)传递数据。
1. props|单向流动
- 父组件可以通过props向子组件传递数据(单向流动的数据)
- 父组件传给子组件的props只能读 不能改 锁住的是变量的地址值,但是如果改变对象中的属性值就可以修改
- 即使可以修改 也尽量不要去修改。!!!尽量不要在子组件中修改父组件的值。如果非要修改,后边会讲(自定义事件)
- 尽可能保证 所有的数据修改从同一个入口进入
- eg 只能从根组件App.vue修改,子组件只去读数据
- 定义属性名时 属性名要遵从驼峰命名法 maxLength
使用props
先在子组件中定义props
const props = defineProps(["item"]) 表示属性名
然后在父组件中对props定义的属性赋值 再通过父组件传递给子组件
import {ref} from 'vue'
const player = ref({...})
<TabItem :item="palyer"></TabItem>
2. props配置
const props = defineProps({
count: Number, // 限定属性的数据类型
obj: Object,
isCheck: Boolean, // 默认为true
maxLength:{
type: String,
require: true, // 必须有这个属性
default: "HAHA", // 默认值
validator(value){ // 用于在开发过程中进行验证 返回flase则不弹出警告 返回true就会有警告
// 用于判断是否传入了一些不想要的东西
// console.log(value);
return value !== "嘻嘻"
}
},
})
3. 插槽
解决问题:
- 传递层级太多 相比props的特点:
- 不用传递属性之类的,插槽入口可以是标签、值等任意
<!--
希望在父组件中指定在子组件中的内容
可以通过插槽实现该需求<slot>
- 非命名插槽的入口:
<MyButton>插槽的入口</MyButton>
<button>
<slot></slot> 插槽的出口
</button>
- 具名插槽的入口:
方式1:<template v-slot:插槽的名字></template>
方式2:<template #插槽的名字></template>
出口:<slot name="插槽的名字"></slot>//
-->
4. 自定义事件(可以由子组件向父组件传递)
(App中无法知道这个参数存在)在模板中可以通过$emit()来触发自定义事件 但是空参
如何传参:$emit(要触发的事件名,传递的参数)
但是通常不这样使用,更好的方式:
// // 使用自定义事件时 最好通过defineEmits来完成
const emits = defineEmits(['delStu'])
<StudentItem :stus="props.stus" @del-stu="emits('delStu', $event)"></StudentItem>
5. 依赖注入,要求发生在层级关系之间,兄弟不可以
- 解决从根组件到子子子子子..组件传值的问题
- 通过依赖注入 可以跨越多层组件 向其他组件传递数据
- 两步使用依赖注入
- 设置依赖(provide) provide("name","value")
- 输入数据(inject) const value = inject(name, default)
- 注意: 依赖注入存在于有层次关系的组件中 并且取值符合就近原则
6. vuex
7. 全局事件总线 eventBus
Vue3全局组件通信之EventBus - 简书 (jianshu.com)
vuex的工作原理
- state
- vuex 管理的状态对象
- 它应该是唯一的
- actions
- 值为一个对象,包含多个响应用户动作的回调函数
- 通过 commit( )来触发 mutation 中函数的调用, 间接更新 state
- 如何触发 actions 中的回调? 在组件中使用: $store.dispatch('对应的 action 回调名') 触发
- 可以包含异步代码(定时器, ajax 等等)
- mutations
- 值是一个对象,包含多个直接更新 state 的方法
- 谁能调用 mutations 中的方法?如何调用? 在 action 中使用:commit('对应的 mutations 方法名') 触发
- mutations 中方法的特点:不能写异步代码、只能单纯的操作 state
- getters
- 值为一个对象,包含多个用于返回数据的函数
- 如何使用?—— $store.getters.xxx
- modules
- 包含多个 module
- 一个 module 是一个 store 的配置对象
- 与一个组件(包含有共享数据)对应
vue的生命周期,比如一些钩子函数
- vue2
- created之前是创建数据代理和数据监测
- mounted这个过程结束才会将页面渲染成功,在这里再做一些初始化的事情,eg:发送网络请求,开启定时器等;
- 完成一些收尾工作
常用的生命周期钩子:
1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
关于销毁Vue实例
1.销毁后借助Vue开发者工具看不到任何信息。
2.销毁后自定义事件会失效,但原生DOM事件依然有效。
3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
- vue3 将vue2中的最后一组钩子更名为:unmounted和mounted,在初始化之前需要万事俱备,如下所示,才可以下一步操作
//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)
//挂载
app.mount('#app')
通过组合式api
vue的路由 hash和history
服务器路由和客户端路由
服务端路由指的是服务器根据用户访问的 URL 路径返回不同的响应结果。当我们在一个传统的服务端渲染的 web 应用中点击一个链接时,浏览器会从服务端获得全新的 HTML,然后重新加载整个页面。
然而,在单页面应用中,客户端的 JavaScript 可以拦截页面的跳转请求,动态获取新的数据,然后在无需重新加载的情况下更新当前页面。这样通常可以带来更顺滑的用户体验,尤其是在更偏向“应用”的场景下,因为这类场景下用户通常会在很长的一段时间中做出多次交互。
在这类单页应用中,“路由”是在客户端执行的。一个客户端路由器的职责就是利用诸如 History API 或是 hashchange 事件这样的浏览器 API 来管理应用当前应该渲染的视图。
不同的客户端路由模式
hash 模式是一种把前端路由的路径用井号
# 拼接在真实 URL 后面的模式。当井号 # 后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发 hashchange 事件。