使用npm
npm init vue@latestxxxxxxxxxxnpm ixxxxxxxxxxnpm run dev
使用pnpm
xxxxxxxxxxpnpm create vue@latestxxxxxxxxxxpnpm ixxxxxxxxxxpnpm dev
reactive可以将对象类型的数据转换为响应式的数据
ref可以将简单类型和对象类型的数据转换为响应式的数据
但是ref转换简单类型的数据的本质是把简单类型的数据外面包一层转换为对象类型的数据
reactive的使用
ximport { reactive } from 'vue'
const msg = reactive({ count: 100})
{{ msg.count }}
@click="msg.count++"ref的使用
xxxxxxxxxx<script setup>import { ref } from 'vue'
const count = ref(0) // 在括号里面填写数据
const addCount = () => { count.value++} // 在script脚本中使用是需要加上.value</script><template> {{ count }} // 在template里面使用时 <button @click="count++">按钮</button> // 这里也是在template里面所以访问修改直接使用count</template>
ref在template里面时直接使用名称即可访问在script脚本里面使用时需要加上.value
最佳实践:统一使用ref定义响应式数据
直接定义为
xxxxxxxxxxconst 方法名 = () => { 方法逻辑}在基于setup语法糖的情况下后面可以直接调用
先导入
xxxxxxxxxximport { computed } from 'vue'简单写法
xxxxxxxxxxconst 计算属性名 = computed(() => { return 计算后的值})完整写法
xxxxxxxxxxconst 计算属性名 = computed({ get: () => { return 计算后的值 }, set: (val) => { 编写逻辑 }})最佳实践:避免直接修改计算属性的值,除特殊情况无法避免
先导入
xxxxxxxxxximport { watch } from 'vue'侦听单个
xxxxxxxxxxwatch(监听的ref对象, (newVal, oldVal) => { 发生变化时执行的逻辑})侦听多个
xxxxxxxxxxwatch([监听的ref对象1, 监听的ref对象2], (newVal, oldVal) => { 发生变化时执行的逻辑 此时的 newVal 是 [ref对象1的值, ref对象2的值] })深度侦听
xxxxxxxxxxwatch(监听的ref对象, (newVal, oldVal) => { 逻辑 },{ immediate: true, // 立即执行一次逻辑 deep: true // 开启深度侦听})精确侦听对象中的某个值
xxxxxxxxxxwatch( () => 对象.value.键名, (newVal, oldVal) => 发生变化时执行的操作)
在不开启深度侦听的情况下
watch进行的是浅层侦听
就是只有当整个发生变化时才会触发
eg:[1, 2, 3, 4, 5, 6] 如果 把整个数组都改变 那浅层也能侦听到
如果只改了数组里面的某一个元素的值,那就只有开启深度侦听才能侦听到
| vue2 | vue3 | 时机 |
|---|---|---|
| created | setup | 一进来就调用(实例初始化完成以后调用) |
| mounted | onMounted | 实例挂载到DOM完成后调用 |
注意vue3中的声明周期函数需要导入,完整语法演示如下
xxxxxxxxxximport { onMounted } from 'vue'<script setup>const getData = () => {console.log("发送请求,获取数据")}onMounted(() => {console.log("触发onMounted生命周期函数")})getData() // 调用setup生命周期函数 与写的位置无关 此项会在onMounted之前执行</script>可以多次调用,多次调用时会按顺序执行,不会冲突
局部导入
在script里面导入后可以直接使用
不需要注册
xxxxxxxxxx<script setup>// 导入import SonCom from '@/components/son-com.vue'</script><template><div>这是父组件的区域,下面是子组件// 直接使用<son-com></son-com></div></template>
在父组件中给子组件标签添加自定义属性
在子组件中使用
xxxxxxxxxxconst props = defineProps({ 自定义属性名称: 类型})
// 或完整写法const props = defineProps({ 自定义属性名: { type: 类型, // 更多配置项 }})
在子组件中对于props传递过来的数据
在模版中( template )
可以直接使用自定义属性名访问
xxxxxxxxxx{{ 自定义属性名 }}在脚本中( script )
必须使用
props.自定义属性名的格式才能访问xxxxxxxxxxprops.自定义属性名
也可以传递动态的值使用:data
在子组件中使用编译器宏注册自定义事件
在父组件中监听自定义事件
子组件中
xxxxxxxxxxconst emit = defineEmits(['自定义事件名'])
emit('自定义事件名', 数据)父组件中
xxxxxxxxxx@自定义事件名 = ""注意在这里的自定义事件名后面同样跟vue2一样可以直接使用
$event直接获取传过来的参数
首先定义一个ref对象里面
然后使用ref绑定到标签
后续可以使用名称.value获取dom对象或组件实例
获取dom对象演示
xxxxxxxxxxconst Href = ref(null)
<input type="text" ref="Href"></input>
// 获取
onMounted(() => { console.log(Href.value) // 获取dom对象 Href.value.focus() // 聚焦输入框})获取组件实例演示
xxxxxxxxxxconst Gref = ref(null)
<HmCom ref="Gref"></HmCom> // 绑定到组件xxxxxxxxxx// 在组件中const data = "这是一个数据"const getData = () => { console.log(data)}
使用defineExpose()将数据暴露出去
defineExpose({ data, getData})xxxxxxxxxxconst Gref = ref(null)
<HmCom ref="Gref"></HmCom> // 绑定到组件
// 获取Gref.value.data // 获取 data 数据Gref.value.getData() // 调用 getData 方法,可以正常调用使用使用defineExpose({})暴露属性方法
用于跨多层组件的通信
在父组件中
xxxxxxxxxximport { provide } from 'vue'
provide('名称', 数据) // 可以传静态数据也可以传响应式数据provide('名称', (val) => { 也可以传递函数})在子孙组件中
xxxxxxxxxximport { inject } from 'vue'
const message = inject('名称') // 接收数据// 如果是响应式的ref数据同样可以直接使用 {{ message }} 渲染const 函数名称 = inject('传过来的函数名称')
函数名称(参数值)注意只能用于更高层的组件向自己的子孙组件传递
不能反过来
这里面可以写OptionsAPI中的内容
原因是有些属性不能再setup里面写
演示
xxxxxxxxxx<script setup> defineOptions({ name: 'LoginIndex' }) </script>比如给组件命名这样的操作就需要在这里面写
写在里面的内容是与setup平级的
在vue2中v-model是通过绑定value属性和监听@input事件来实现双向绑定
在vue3中v-model改为了绑定modelValue和监听@updata:modelValue
注意vue3中的
v-model等于v-model:modelValue可以改写为
v-mode:任意名称此时相当于绑定了任意名称和监听@updata:任意名称
相当于做了vue2中的
.sync的结合
演示:
父组件:
xxxxxxxxxx<script setup>import SonCom from '@/components/son-com.vue'import { ref } from 'vue'const data = ref('100')
</script><template> <div> <h1>{{ data }}</h1> <son-com v-model="data"></son-com> </div></template>子组件:
xxxxxxxxxx<script setup>const props = defineProps({ modelValue: String})const emit = defineEmits(['update:modelValue'])</script><template> <input type="text" :value="modelValue" @input="e => emit('update:modelValue', e.target.value)"></template><style></style>为了解决这个繁琐的操作,提供了defineModel编译器宏
这个功能在vue3.3中是实验性功能,在vue3.4中正式发布
首先在父组件中通过v-model传值给子组件
xxxxxxxxxxconst data = ref('100')
<son-com v-model="data"></son-com>然后在子组件中使用defineModel接收
xxxxxxxxxxconst 自定名称 = defineModel()
下面就可以使用修改这个值了(相当于已经实现双向绑定)
<input type="text" :value="自定名称" @input="e => 自定名称 = e.target.value">在父组件中用v-model将数据传过去
然后在子组件中使用defineModel接收
然后在子组件中就可以使用和修改这个数据,且父组件中可以同步修改
定义
xxxxxxxxxx// 新建一个store文件夹用于存放pinia仓库中的数据import { defineStore } from 'pinia'export const useChannelStore = defineStore('仓库名称', () => { const list = ref([]) // 相当于 vuex 中的 state const setList = () => { // 这里面可以直接修改上面的数据 } const getList = computed(() => { return 数据 // 这个相当于之前的的getters })})使用
xxxxxxxxxx// 在想要使用的组件中导入import { 定义时导出的名称 } from '@/...'
const 自定义名称 = 定义时导出的名称()
// 然后就可以直接使用自定义名称.list自定义名称.setList()自定义名称.getList
表格对比
| Vuex | Pinia | 备注 |
|---|---|---|
| mutations | 没有 | 修改vuex中用于修改state种的数据,pinia中可以直接修改,不需要 |
| state | const data = ref() | Pinia中直接定义数据就是state中的数据 |
| getters | const getList = computed(() => { return 返回数据 }) | Pinia中直接定义computed就是计算属性 |
| actions | const 函数名 = () => { 函数内容 } | 在Pinia中可以直接在函数中执行同步或者异步操作 |
注意在
const 自定义名称 = 定义时导出的名称()此处导入时如果直接对数据(普通数据、计算属性)使用解构则会丢失响应式
可以对方法直接解构
对数据解构的话要使用
const { 解构数据 } = storeToRefs(自定义名称)要多写一行,不能直接
const { 解构数据 } = 定义时导出的名称()且需要导入
import { storeToRefs } from 'pinia'
开始 | Pinia Plugin Persistedstate
下载插件
xxxxxxxxxxpnpm add pinia-plugin-persistedstate在main.js中对piniause这个插件对象
xxxxxxxxxximport { createPinia } from 'pinia'import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()pinia.use(piniaPluginPersistedstate)使用时
xxxxxxxxxx// 对你创建的defineStore添加第三个参数persist: true
// eg:export const counterStore = defineStore('counter', () => { // ..... return { }}, { persist: true // 添加这个})这时刷新页面数据就会持久化存储不会重新初始化
如果需要对插件进行配置
xxxxxxxxxxexport const counterStore = defineStore('counter', () => { // ..... return { }}, { persist: { // 在这里面填写配置项 }})具体配置看官方文档配置 | Pinia Plugin Persistedstate
vue3中的路由配置与vue2中有所不同的地方,但是大致相同,初始化项目后配置如下
xxxxxxxxxximport { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [],})
export default router
createWebHistory是指以history模式创建
createWebHashHistory是以hash模式创建
里面的参数是路径的前缀,默认是/
如果import.meta.env.BASE_URL换成/aa,那么所有的路径前面都会加上/aa,eg:localhost:3000/aa/list
import.meta.env这个是环境变量的前缀,这里的是base环境变量,可以在vite.config中通过base配置项更改
区别
| Vue版本 | 获取路由对象 | 获取路由信息 | 备注 |
|---|---|---|---|
| 2 | this.$router | $route | 无需导入 |
| 3 | useRouter() | useRoute() | 需要导入 |
演示
xxxxxxxxxx<script setup>import { useRouter, useRoute } from 'vue-router'const router = useRouter()const route = useRoute()router.push()route.query.参数名</script>拓展:
直接导入的router对象
xxxxxxxxxximport router from '@/router'router.push('/home')和使用钩子函数导入的
xxxxxxxxxx// 导入方式import { useRouter } from 'vue-router'const router = useRouter()router.push('/home')区别:
前者可以在任何地方使用,后者由于useRouter()只能在setup中调用,所以后者只能在组件中使用
对应vue2中的element-ui
执行下面的命令
xxxxxxxxxxpnpm install element-pluspnpm install -D unplugin-vue-components unplugin-auto-import将下面代码集成到vite.config.js
xxxxxxxxxximport { defineConfig } from 'vite'import AutoImport from 'unplugin-auto-import/vite'import Components from 'unplugin-vue-components/vite'import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({ // ... plugins: [ // ... AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ],})接下来就可以直接使用了,会自动导入
注册完成过后你的组件注册也可以直接使用
他会自动导入
xxxxxxxxxx<script setup>// 不需要在这里引入</script><template><el-button>按钮</el-button><HmCom></HmCom></template>
配置
xxxxxxxxxxconst instance = axios.create({ baseURL, // 请求链接 timeout: 5000, // 超时时间})
instance.interceptors.request.use( (config) => { if (TokenStore.token) { config.headers.Authorization = TokenStore.token // 如果有token的话配置响应头 Authorization 专门用来设置token } return config }, (err) => { Element.error('请求配置阶段出错,请刷新页面重试') // 额外反应 Promise.reject(err) // 这个相当于是告知失败 但他不会处理失败(页面不会有额外反应) },)
instance.interceptors.response.use( (res) => { if (res.data.code === 0) { // res.data 是返回的那个东西的整体 return res } ElMessage.error(res.data.message || '服务异常') return Promise.reject(res.data) // 返回一个对象类型的 }, (err) => { ElMessage.error(err.response.data.message || '服务异常') // err.response.data.messsage 可以获取错误的具体信息 if (err.response?.status === 401) { router.push('/login') } return Promise.reject(err) },)
自定义校验规则步骤
基础模版
xxxxxxxxxx<el-form> <el-item> <el-input></el-input> </el-item></el-form>1、首先定义一个响应式的对象用于存放校验的数据
xxxxxxxxxxconst userForm = ref({ username: '', password: '', repassword: ''})2、定义一个非响应式的对象用于存放校验规则
xxxxxxxxxxconst rules = { username:[ { required: true, message: '请输入密码', trigger: 'blur' }, { min: 3, max: 10, message: '用户名必须是 3 - 10 位', trigger: 'change' } ], password:[ { validator: (rule, value, callback) => { // rule 为当前校验规则的相关信息 // value 为当前的表单元素值 // callback 代表的是是否通过校验通过callback(new Error(错误信息))来返回错误信息 } } // 这里没有配置 trigger 默认为 change ]}一个里面可以填写多条规则,每条规则以对象的形式编写
trigger中有两个值blur代表 失焦时进行规则校验
change代表 发生改变时进行规则校验
自定义校验时注意callback( )无论成功失败都要返回
成功
callback()失败
callback(new Error(错误信息))且最好不要省略else
即:
xxxxxxxxxxif(){callback()}else{callback(new Error(错误信息))}// 最好不要写成下面的形式if(){callback()}callback(new Error(错误信息))
当存在
validator时可以填写其他信息,但是此时的其他信息会被视为自定义属性,不会被视为校验规则eg:
xxxxxxxxxxrepassword: [{ pattern: /^\S{6,15}$/, message: '密码必须为6-15位的非空字符', trigger: 'blur' },{min: 7,validator: (rule, value, callback) => {if (value !== userForm.value.password) {callback(new Error('两次输入密码不一致'))} else { callback() }}}]
此时的min不会被视为校验规则,只会被视为一个普通的自定义属性
3、将userForm通过:model绑定到el-form,将rules通过:rules绑定到el-form
xxxxxxxxxx<el-form :model="userForm" :rules="rules"> <el-form-item> <el-input></el-input> </el-form-item></el-form>4、给el-input通过v-model绑定userForm中的对应属性
xxxxxxxxxx<el-form :model="userForm" :rules="rules"> <el-form-item> <el-input v-model="userForm.username"></el-input> </el-form-item></el-form>5、给el-item通过prop绑定rules中的对应属性注意不需要"对象.属性"
xxxxxxxxxx<el-form :model="userForm" :rules="rules"> <el-form-item prop="username"> 注意此处 <el-input v-model="userForm.username"></el-input> </el-form-item></el-form>拓展:
注意事项
在配置了拦截器时,调用接口后不要使用
alert()他会使拦截器里面的有些方法失效eg:
xxxxxxxxxxconst register = async () => {// 注册成功之前先再次验证 验证成功之后开始注册逻辑 验证失败的话他也会自动给出提示await form.value.validate()await userRegisterService(userForm.value)ElMessage.success('注册成功')// alert('注册成功') 这里如果使用了 alert() 的话 拦截器中的 ElMessage.error() 不会生效isRegister.value = false}
form.value.validate( )是按需进行验证的,他会自动的匹配当前表单中有的项目进行验证,没有的他不会验证,因为这一点,可以给登录注册共用一个对象进行绑定,在验证时也不会产生冲突
常用演示
xxxxxxxxxx<el-menu active-text-color="#ffd04b" background-color="#232323" :default-active="$route.path" text-color="#fff" router> <el-menu-item index="/article/channel"> <el-icon><Management/></el-icon> <span>名称</span> </el-menu-item></el-menu>active-text-color这个参数是指激活时的文字颜色,就是点到哪个时哪个的文字颜色就会变成参数值
:default-active这个是默认激活的菜单项
router指的是开启vue-router模式当开启时,会在激活导航时以 index 作为 path 进行路由跳转 使用
default-active来设置加载时的激活项
$route.path是指获取的当前路由
vue2导航守卫参考文档导航守卫 | Vue Router
vue3导航守卫参考文档导航守卫 | Vue Router
vue3中返回undefined或者true就是放行
返回false就是拦回from的地址页面
return '/login' 或return { name: 'Login' } 就是返回具体地址
vue3导航守卫示例
xxxxxxxxxxrouter.beforeEach((to) => { const userStore = useTokenStore() if ( !userStore.token && to.path !== '/login' ) { return '/login' }})vue3中不再通过next来进行拦截,而是通过返回值来进行判断
示例代码
xxxxxxxxxx<template> <el-dropdown @command="handleCommand"> <span class="el-dropdown-link"> Dropdown List<el-icon class="el-icon--right"><arrow-down /></el-icon> </span> <template #dropdown> <el-dropdown-menu> <el-dropdown-item command="a">Action 1</el-dropdown-item> <el-dropdown-item command="b">Action 2</el-dropdown-item> <el-dropdown-item command="c">Action 3</el-dropdown-item> <el-dropdown-item command="d" disabled>Action 4</el-dropdown-item> <el-dropdown-item command="e" divided>Action 5</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown></template>xxxxxxxxxxconst handleCommand = (key) => { // 这里的key就是触发时的对应参数 (这里是a、b、c、d、e) // 操作逻辑} 拓展
xxxxxxxxxxconst handCommand = async (key) => { if (key === 'logout') { await ElMessageBox.confirm('你确定要退出登录吗', '温馨提示', { type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }) tokenStore.removeToken() tokenStore.setUserInfo({}) router.push('/login') } else { router.push(`/user/${key}`) }}使用
async和await配合来达到消息确认框的效果
由于组件用法较多此处不再一一演示
更多用法查阅官网即可
官网解释非常详细