api及语法
双向绑定原理
- Vue2使用Object.defineProperty 为每个属性创建getter和setter,通过getter和setter来捕获操作以实现响应式更新; 很多情况下,属性的新增和删除拦截不到(比如数组的长度变化)
- Vue3使用Proxy,Proxy可以拦截对象和数组的变化,包括新增和删除属性,数组的长度变化等,从而实现更精确的响应式更新
v2 使用object.defineProperty来劫持数据的setter和getter方法,对象改变需要借助api去深度监听;
v3 使用proxy来劫持整个对象,不需要深度监听,性能更好,删除了一些api($on,$once,$off) fiter等,优化了Block tree,solt,diff 算法等
Vue2在data中定义的数据就会自动遍历绑定Object.defineProperty以实现响应式;Vue3中要用ref包装,通过返回值的 .value属性获取响应式的值 ,修改也需要对 .value进行修改.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <template> <div>
{{ testOne }} {{ testTwo.directionD}} </div> </template> <script setup> const testOne= ref(0) const testTwo= ref({ directionD: '', directionA: '', arr: [] }) const dataThree= ref({}) console.log(testOne.value); console.log(dataThree.value); console.log(testTwo.value.directionD); console.log(testTwo.value.directionD); </script>
|
二者用法差异不在此赘述,可见大佬文章:http://t.csdnimg.cn/wpu6r
vue3支持碎片化
- Vue2中,每个组件必须有一个根元素,而Vue3中,组件可以没有根元素,可以返回一个数组,数组中的每个元素都可以是一个独立的组件
- Vue3中,组件可以返回一个数组,数组中的每个元素都可以是一个独立的组件,这样就可以实现更灵活的布局和组件组合
下列情况若子组件中存在多个根节点, 则使用组件时不能使用 v-show(无法对子组件根节点添加display)
解决:
1.可用v-if代替(可能存在首次渲染失效)
2.只保留一个根节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <div class="container"> <son v-show="false"></son> </div> </template> -- son组件 -- <template> <div class="son_container"> 1 </div> <div class="son_container2"> 2 </div> <div class="son_container3"> 3 </div> </template>
|
生命周期
vue2 |
vue3 |
|
beforeCreate() |
setup() |
组件开始创建数据实例之前 |
created() |
setup() |
组件实例完成 |
beforeMount() |
onBeforeMount() |
DOM挂载之前 |
mounted() |
onMounted() |
DOM挂载完成 |
beforeUpdate() |
onBeforeUpdate() |
组件更新之前 |
undated() |
onUpdated() |
组件更新之后 |
beforeDestroy() |
onBeforeunmount() |
组件销毁之前 |
destroyed() |
onUnmounted() |
组件销毁之后 |
去除this
Vue3中没有this, 使用this报错 需要组件内的某个方法直接使用即可(注意使用的数据必须在调用前定义)
组件传值props和emit
- Vue2中是 props和 this.$emit
- Vue3中则是[defineEmits defineProps] props emit;
需要注意的是 Vue2中传值给子组件的属性可以直接使用,
Vue3中子组件接收的值在props对象中,所以在ts/js中使用需要 props.name,
emit触发的事件,需要defineProps声明接收数据,defineEmits 声明以明确事件定义.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| <son ref="bottomContract" @transferData="transferData" @reloadEcharts="reloadEcharts" :dataOne="tableTime" :dataTwo="echartsColumnData" :dataThree="dataThree" />
<script lang="ts" setup> const props = defineProps({ dataOne: { default: () => 0 }, dataThree: { type: Number, default: () => 0 }, dataTwo: { default: () => [] } })
const test = () => { console.log(props.dataOne) }
const emit = defineEmits(["transferData", "reloadEcharts"]) const testTwo = ()=> { emit("transferData", 'value') emit("transferData", 'value') } </script>
|
watch和computed
watch
1 2 3 4 5
| watch: { scrollTop(n) { } }
|
- Vue3,监听watch第一个参数是直接传入要监听的对象 ;深度监听复杂对象 {deep: true}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const demo = reactive({ name: '前端', nickName: '1', Yiqian: { name: '', nickName: '' } })
watch(demo, (newValue, oldValue) => { console.log('watch 已触发', newValue) }, {deep: true})
watch(() => ({ ...demo }), (newValue, oldValue) => { console.log('watch 已触发', newValue) })
|
>>> 监听一个属性就要用一个watch,是不是不太妙? 那当然也可以组合到一起,此时的第一个参数是一个数组,第二参数箭头函数的参数也是数组的形式,按照数组下标对应监听值
1 2 3 4 5
| watch(() => [demo.name, demo.nickname], (newValue, oldValue) => { console.log(newValue); console.log(newValue[0]) console.log(newValue[1]) })
|
computed
与vue2的computed配置写法基本一致 .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const user = ref({ testOne: 'A', testTwo: 'B' });
const fullName1 = computed(() => { return user.value.testOne+ '-' + user.value.testTwo; })
const fullName2 = computed({ get () { return user.value.testOne+ '-' + user.value.testTwo; }, set (value: string) { const names ='111'; user.value.testOne= names; user.value.testTwo= names; } });
|
子组件实例,调用组件方法
- Vue2中 子组件定义
ref="name"
后使用this.$refs.name
就能拿到组件name
的实例;同时可以this.$refs.name.test()
的方式直接调用子组件的test()
方法
- Vue3中,子组件定义
ref="name"
,需要用ref()
来定义引用,将其绑定到对应子组件上;若想直接调用子组件的方法,需要在子组件中defineExpose
显示暴露出对应的方法(组件封装性),若不暴露出来则子组件实例上不会存在此方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <template> <ChildComponent ref="timingEchartsModule" /> </template> <script setup> import { ref } from 'vue'; import ChildComponent from './ChildComponent.vue';
const timingEchartsModule = ref();
onMounted(() => { console.log(timingEchartsModule.value); timingEchartsModule.value.test() }); </script>
<script setup> const test= () => { console.log('someMethod called'); };
defineExpose({ test }) </script>
|
选项式api和组合式api
mixins和hooks
Vue 2 中,Mixins 是一种全局特性,可以在多个组件之间共享代码。你可以创建一个 Mixin 对象,然后在组件中通过 Mixins 选项引入这个对象,从而将 Mixin 中的属性和方法合并到组件中;如果多个 Mixins 中有相同的属性或方法,可能会导致命名冲突。另外,由于 Mixins 是全局的,它们会增加组件的耦合度,使得代码难以维护。
Vue3的Hooks允许你将相关的逻辑组合到一起,形成一个逻辑单元,组件内部使用的,而不是全局的,这减少了命名冲突和耦合度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import { ref } from 'vue' export default function() { const count = ref(0); const add = () => { count.value++; } const decrement = () => { count.value--; } return { count, add, decrement } }
<script setup> import useCount from "../hooks/useCount" const { count, add, decrement } = useCount() </script>
|
插件
vue-router
基本类似,在使用时需要引入; route和router, router获取路由器实例 ; route对象包含了当前路由的各种信息
1 2 3 4 5 6 7 8
| const router = useRouter() router.push({path:'name'}) router.back(); const route = useRoute() console.log(route.params) console.log(route.query)
|
状态管理Vuex 和 Pinia
- Vuex使用 store、state、mutations、actions 和 getters 的概念,结构化更严格
- Pinia 更简洁和模块化,使用 defineStore 函数创建状态,避免了冗长的代码结构
pina
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import { defineStore } from 'pinia' const useTimingInfoStore = defineStore('timingApproval', { state: () => ({ testOne: 1, testTwo: 2, }), actions: { addSchemeData(data){ this.testOne= data }, addTimingData(data){ this.testTwo= data } }, getters: { doubleCount: (state) => state.testOne* 2 } }) export default useTimingInfoStore
import { useTimingInfoStore} from '@/stores/timingApproval'; const timingInfoStore = useTimingInfoStore() console.log(timingInfoStore.testOne) timingInfoStore.addSchemeData(2)
|
一、Pinia是什么
二、Pinia使用
1.基本使用
2.state
3.getters
4.actions
5.模块化
Pinia是什么
伴随vue2到vue3的升级,官方已经用Pinia代替了Vuex成为官方的状态管理库。
所以,推荐使用Pinia作为以后不论是vue2还是vue3状态管理库的首选!
Pinia使用
1 2 3
| yarn add pinia # 或者使用 npm npm install pinia
|
在mian.js中,创建根存储
1 2
| import { createPinia } from 'pinia' app.use(createPinia())
|
store
实例相当于一个容器,里面存放的有类似于data
,计算属性,方法之类的东西。通过defineStore()
方法定义
在src下面创建一个store
文件夹,再创建与之对应的js文件,比如user.js
1 2 3 4 5 6 7
| import { defineStore } from 'pinia'
export const useUser = defineStore('user', { })
|
1 2 3 4 5
| <script setup> import { useUser } from '@/store/user.js' const store = useUser() console.log(store) </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { defineStore } from 'pinia'
export const useUser = defineStore('user', { state: () => { return { userName: 'jack', avatar: 'https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/363.jpg' } } })
|
state
读取state
1 2 3 4
| import { storeToRefs } from 'pinia' const userStore = useUser()
const { userName, avatar } = storeToRefs(userStore)
|
修改state
1
| userStore.userName = '张三'
|
批量修改state
1 2 3 4 5 6 7 8 9
| userStore.$patch({ userName: '张三' })
userStore.$patch((state) => { state.avatar = 'https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/596.jpg' })
|
重置state
直接替换整个state(几乎不用)
1
| userStore.$state = { userName: 'rose', avatar: 'https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/854.jpg' }
|
getters
类似计算属性。推荐里面传递一个形参的写法,不容易出错
1 2 3 4 5 6 7
| getters: { isAdult: (state) => { return state.age >= 18 ? '成年人' : '未成年' } }
<div>{{userStore.isAdult}}</div>
|
调用本模块其他getters
1 2 3 4 5 6 7 8 9
| getters: { isAdult: (state) => { return state.age >= 18 ? '成年人' : '未成年' }, msg: (state) => { return state.userName + state.isAdult } }
|
getters传参
1 2 3 4 5 6 7 8 9
| getters: { isAdmin: (state) => { return (name) => name === 'admin' ? '是管理员' : '不是管理员' } }
{{userStore.isAdmin}}
|
调用其他store里面的getter
商品模块goods.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { defineStore } from 'pinia'
export const useGoods = defineStore('goods', { state: () => { return { goodsName: 'iphone' } }, getters: { newIphone: (state) => { return state.goodsName + ' 14 pro' } } })
|
想在user模块的getter中去获取goods模块的newIphone这个getter
1 2 3 4 5 6 7 8 9
| import { useGoods } from './goods.js'
getters: { info: (state) => { const goodsStore = useGoods() return state.userName + '买了' + goodsStore.newIphone } }
|
actions
1 2 3 4 5 6 7 8 9
| actions: { changeNameAsync (newName) { setTimeout(() => { this.userName = newName }, 1000) } }
|
模块化
在实际开发中,不可能把多个模块的数据都定义到一个store中,而是一个模块对应一个store,最后通过一个根store进行整合
随便建立两个store,并导出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
import { defineStore } from 'pinia' const useUserStore = defineStore('user', { state: () => { return { name: 'haha', age: 18, } }, }) export default useUserStore
import { defineStore } from 'pinia' const useCounterStore = defineStore('user', { state: () => { return { count: 1 } }, }) export default useUserStore
|
新建store/index.js
1 2 3 4 5 6 7 8 9 10
| import useUserStore from './user' import useCounterStore from './counter'
export default function useStore() { return { user: useUserStore(), counter: useCounterStore(), } }
|
组件中使用
1 2 3 4 5 6 7 8
| <script setup> import { storeToRefs } from 'pinia' import useStore from './store' const { counter } = useStore()
const { count } = storeToRefs(counter) </script>
|