vue3基础(二)
- 了解响应式数据原理(数据劫持)
- vue2.0 如何实现双向绑定原理
- vue3.0 如何实现双向绑定原理
- 组合API-ref函数
- 做个小案例试试
- 组合API-computed函数
- 组合API-watch函数
- 组合API-ref属性
- 组合API-父子通讯
- 依赖注入
- 逻辑复用
- 补充-mixins语法
- vuex持久化
了解响应式数据原理(数据劫持)
1,响应式数据,数据变化的时候去驱动视图的更新
2.需要去监听数据的变化,数据劫持,数据代理,去做额外的事情
3.我们需要一个特殊对象,能够在数据变化的时候去做别的事情
给对象定义一个属性Object.defineProperty(对象,‘属性名’)
vue2.0 如何实现双向绑定原理
// vue2.0 如何实现双向绑定原理
// 定义一个被代理的对象
const obj = {
name: 'tom',
age:17
}
// ES5原理
const vm = {}
// 给vm对象定义一个属性:name
Object.defineProperty(vm,'name',{
get () {
// 返回值 vm.name 的值
// 通过 vm 对象来代理 name 属性的值
// 这里返回值要写被代理的对象
return obj.name
},
Set (value){
// 设置值 vm.name = 'jack' 此时的value就是jack
obj.name = value
}
})
当然上面这样是没法驱动视图的
- 定义一个函数
- 定义一个变量使用模板字符串
- 获取到对应的标签元素,将值显示出来
- 调用声明的函数将数据更新渲染出来
单个数据驱动视图
<div id="app"></div>
<script>
// vue2.0 如何实现双向绑定原理
// 定义一个被代理的对象
const obj = {
name: 'tom',
age:17
}
// ES5原理
const vm = {}
// 给vm对象定义一个属性:name
Object.defineProperty(vm,'name',{
get () {
// 返回值 vm.name 的值
// 通过 vm 对象来代理 name 属性的值
// 这里返回值要写被代理的对象
return obj.name
},
set (value){
// 设置值 vm.name = 'jack' 此时的value就是jack
obj.name = value
// 这里也要调用函数,随着数据的更新视图也要更新
render()
}
})
const render = () =>{
const template = `
<p>姓名: ${obj.name} </p>
<p>年龄: ${obj.age} </p>
`
document.querySelector('#app').innerHTML = template
}
// 调用一下函数
render()
</script>
多个数据驱动视图
// 取出obj中的所有属性名称,键名
const keys = Object.keys(obj)
keys.forEach(key=>{
Object.defineProperty(vm,key,{
// 获取属性值
get () {
// 根据key取值
return obj[key]
},
// 设置属性值
set (value) {
// 根据key去obj设值
obj[key] = value
// 更新视图
render()
}
})
})
vue3.0 如何实现双向绑定原理
vue3.0双向绑定就更简单了
// ES6原理 Vue3.0
// Vue3.0 不需要自己再手动去定义一个空对象来接收代理对象
// new Proxy()对象 Proxy( 代理的数据源 , 存入get和set的对象)
const vm = new Proxy(obj, {
get(target, prop) {
// target 当前代理的数据源,就是obj
// prop 获取数据时候的属性
return target[prop]
},
set(target, prop, value) {
// target 当前代理的数据源,就是obj
// prop 获取数据时候的属性
// value 设置的值
target[prop] = value
// 更新视图
render()
}
})
组合API-ref函数
ref函数,常用于简单数据类型定义为响应式数据
再修改值,获取值的时候,需要.value
在模板中使用ref申明的响应式数据,可以省略.value
<template>
<div class="container">
<p>{{ count }}</p>
<button @click="add">+1</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
name: "App",
setup() {
// 1.声明一个简单数据类型的响应式数据
// 2.通过proxy代理需要对象,ref函数会包装成对象{value:0},使用 proxy进行代理
// 3.__v_isRef: true 通过ref声明的响应数据特点,模板解析的时候遇见这个标准,直接取value值
const count = ref(0);
const add = () => {
count.value++;
};
return { count, add };
},
};
</script>
使用场景:
当你明确知道需要的是一个响应式数据 对象 那么就使用 reactive 即可
其他情况使用ref
做个小案例试试
- 实现功能
- 获取到鼠标X轴和Y轴的位置
- 点击按钮自增
<template>
<div class="container">
<p>X轴:{{x}}</p>
<p>Y轴:{{y}}</p>
<p>{{ count }}</p>
<button @click="updataCount">+1</button>
</div>
</template>
<script>
import { onBeforeUnmount, onMounted,ref, reactive, toRefs } from "vue";
export default {
name: "App",
setup() {
// 1.鼠标在页面移动,记录鼠标的坐标,显示
// 声明数据
const obj = reactive({
x: 0,
y: 0,
});
//
const move = e =>{
obj.x = e.pageX
obj.y = e.pageY
}
// 监听mousemove事件
onMounted(() => {
document.addEventListener("mousemove", move);
});
onBeforeUnmount(()=>{
document.removeEventListener("mousemove", move);
})
// 2.点击按钮自增
const count = ref(0)
const updataCount = () =>{
count.value ++
}
return { ...toRefs(obj),updataCount,count}
},
};
</script>
这样写起来是不是很乱不方便优化,所以要写成下面的的形式(便于区分)
<template>
<div class="container">
<p>X轴:{{ x }}</p>
<p>Y轴:{{ y }}</p>
<p>{{ count }}</p>
<button @click="updataCount">+1</button>
</div>
</template>
<script>
import { onBeforeUnmount, onMounted, ref, reactive, toRefs } from "vue";
// 提供鼠标坐标业务逻辑
const useMouse = () => {
// 1.鼠标在页面移动,记录鼠标的坐标,显示
// 声明数据
const obj = reactive({
x: 0,
y: 0,
});
// 移动鼠标
const move = (e) => {
obj.x = e.pageX;
obj.y = e.pageY;
};
// 监听mousemove事件
onMounted(() => {
document.addEventListener("mousemove", move);
});
// 清除mousemove事件
onBeforeUnmount(() => {
document.removeEventListener("mousemove", move);
});
return obj;
};
// 提供数据自增业务的逻辑
const useCount = () => {
// 2.点击按钮自增
const count = ref(0);
const updataCount = () => {
count.value++;
};
return { count, updataCount };
};
export default {
name: "App",
setup() {
// 1.鼠标在页面移动,记录鼠标的坐标,显示
const obj = useMouse()
// 2.点击按钮自增
const { count, updataCount } = useCount()
return { ...toRefs(obj), updataCount, count };
},
};
</script>
组合API-computed函数
定义计算属性:computed函数,是用来定义计算属性的,计算属性不能修改。
基本使用及高级用法
<template>
<div class="container">
<p>count数据:{{ count }}</p>
<button @click="count++">+1</button>
<p>newCount数据:{{newCount}}</p>
<hr>
<p>今年:{{age}} 后年:{{newAge}}</p>
<p><input type="text" v-model="newAge"></p>
</div>
</template>
<script>
import { computed, ref } from "vue";
export default {
name: "App",
setup() {
const count = ref(0)
// 1.计算属性的基本用法
// const newCount = computed(()=>{
// return count.value * 2
// })
// 2.计算属性-高级用法
// 我们可以使用get()set()方法来修改原来的值导致现在的值更新
// 举个小例子
// 现在年龄
const age = ref(18)
// 后年年龄
const newAge = computed({
// 读取数据走get
get(){
return age.value+2
},
set(value){
// 22 是后年年龄 20 岁是现在年龄
age.value = value - 2
}
})
return { count,age,newAge };
},
};
</script>
页面显示
更改值后
组合API-watch函数
watch函数,是用来定义侦听器的(六种方法)
1.监听ref定义的响应式数据
2.监听多个响应式数据数据
3.监听reactive定义的响应式数据,对象暂时无法区分(因为是改完之后的,数据是一样的没有办法区分)
4.监听reactive定义的响应式数据,某一个属性
5.深度监听
6.默认执行
如果监听的是一个ref()函数的简单类型那么一定要加.value,返回出去
代码演示:
<template>
<div class="container">
<p>姓名:{{obj.name}} <button @click="updateName">修改</button></p>
<p>姓名:{{obj.age}} <button @click="updateAge">修改</button></p>
<p>{{count}} <button @click="count++">修改</button></p>
<hr>
<p>姓名:{{obj.user.gender}} <button @click="obj.user.gender='女'">修改</button></p>
</div>
</template>
<script>
import { reactive, ref, watch } from "vue";
export default {
name: "App",
setup() {
// 1.侦听器: 监听数据的变化,然后去做其他事情
const obj = reactive({
name:'tom',
age:18,
user:{
gender:'男',
password:123456
}
})
const updateName = () =>{
obj.name = 'jack'
}
const updateAge = () =>{
obj.age = 20
}
// 1. 监听到一个数据的变化
// 我们即可以侦听reactive的数据也可以侦听ref数据的变换
// 第一个参数:监听的对象,第二个参数:变化后回调函数,第三个参数对象深度监听{使用deep}
// reactive的复杂数据
// watch(obj, (newVal,oldVal)=>{
// // 暂时无法区分(因为是改完之后的,数据是一样的没有办法区分)
// console.log(newVal)
// console.log(oldVal);
// })
// ref的简单数据
const count = ref(0)
// watch(count,(newVal,oldVal)=>{
// console.log(newVal)
// console.log(oldVal);
// })
// 2.还可以监听多个数据的变化
// watch(([obj,count]),()=>{
// console.log('ok');
// })
// 3.监听对象中的一个属性变化
// 因为是对象中的属性不能直接监听,需要使用函数将对象中的属性返回出去
// watch(()=>obj.name,()=>{
// console.log('ok')
// })
// 4.监听对象中的一个属性,当他是复杂数据类型
// 第三个参数: 配置对象
// watch(()=>obj.user,()=>{
// console.log('ok');
// },{
// // 开启深度监听
// deep: true
// })
// 5. 默认执行
watch(()=>obj.user,()=>{
console.log('ok')
},{
// 开启深度监听
deep: true,
// 初始化执行
immediate: true
})
return {obj,updateName,updateAge,count}
}
};
</script>
组合API-ref属性
获取DOM或者组件实例可以使用ref属性,写法和vue2.0需要区分开
获取单个DOM或者组件
vue2.0 写在标签上ref=‘dom’, 使用this.$refs.dom获取
vue3.0 使用ref函数的声明响应式数据返回给模板使用,给需要获取的标签上绑定这个数据
<template>
<div class="container">
<p ref="pDom">我是p元素</p>
<ul>
<li v-for="i in 4" :key="i">我的LI元素{{i}}</li>
</ul>
</div>
</template>
<script>
import { onMounted, ref } from "vue";
export default {
name: "App",
setup() {
//
const pDom = ref(null)
onMounted(()=>{
console.log(pDom.value)
})
return {pDom}
}
};
</script>
获取多个DOM或者组件
vue3.0 先定义一个空数组,然后定一个收集dom的函数,返回这个函数给遍历的元素使用ref绑定这个函数
<template>
<div class="container">
<ul>
<!-- :ref需要加冒号不然变成字符串 -->
<li :ref="setList" v-for="i in 4" :key="i">我的LI元素{{i}}</li>
</ul>
{{count}} <button @click="count++">加加</button>
<button @click="logList">打印liList</button>
</div>
</template>
<script>
import { ref , onMounted, onBeforeUpdate} from 'vue'
export default {
name: "App",
setup() {
let liList = []
const setList = el => {
console.log(el)
// v-for执行的时候,遍历一次执行一次
liList.push(el)
}
onMounted(()=>{
console.log(liList)
})
onBeforeUpdate(()=>{
// 注意:数据更新的时候需要重置dom数组
liList = []
})
const logList = () => {
console.log(liList)
}
const count = ref(0)
return { setList, count, logList}
}
};
</script>
总结:
单个元素:先申明ref响应式数据,返回给模版使用,通过ref绑定数据
遍历的元素:先定义一个空数组,定一个函数获取元素,返回给模版使用,通过ref绑定这个函数
组合API-父子通讯
定义:使用props选项和emits选项完成父子组件通讯
代码演示:
App.vue
<template>
<div class="container">
父组件 {{money}}
<hr>
<!-- <Son :money="money" @update:money="money=$event" /> -->
<!-- vue2.0的简写 :money.sync="money" -->
<!-- vue3.0的简写 v-model:money="money" -->
<Son v-model:money="money" />
</div>
</template>
<script>
import { ref } from 'vue'
import Son from './Son.vue'
export default {
name: 'App',
components: {Son},
setup () {
// 共有财产
const money = ref(1000)
return { money }
}
}
</script>
<style scoped lang="less"></style>
Son.vue
<template>
<div class="container">
子组件 {{ money }} <button @click="updateMoney">花500</button>
</div>
</template>
<script>
export default {
name: "Son",
props: {
money: {
type: Number,
default: 0,
},
},
// props 父组件传递的数据
// emit 触发自定义事件函数
setup(props, { emit }) {
const updateMoney = () => {
// 花500 修改父组件 money数据
emit('update:money', 500)
};
return { updateMoney };
},
};
</script>
<style scoped lang="less"></style>
注意:vue3.0不同于vue2.0
v-model可以使用多个
总结:
父传子:在setup种使用props数据 setup(props){ // props就是父组件数据 }
子传父:触发自定义事件的时候emit来自 setup(props,{emit}){ // emit 就是触发事件函数 }
在vue3.0中 v-model 和 .sync 已经合并成 v-model 指令
补充: vue3.0中v-model语法糖
vue3.0 v-modex 简写 :modelValue= ‘money’ @updata:modelValue=’‘money=$event’’
可继续简写成 v-model=‘money’
依赖注入
使用场景:有一个父组件,里头有子组件,有孙组件,有很多后代组件,共享父组件数据
注入后代
// 把数据传给后代
// 把money数据共享给后代组件
// provide(数据名称,具体数据) 这里提供的数据可以给所有后代组件使用
provide('money',money)
后代拿取数据
const money = inject('money')
return { money }
后代修改数据
谁(祖先组件)定义谁修改
provide('updateMoney',data=>{
money.value = data
})
后代组件使用
const updateAppMoney = inject('updateMoney')
const updateMoney = () =>{
// 花500
updateAppMoney(500)
}
总结:
provide函数提供数据和函数给后代组件使用
inject函数给当前组件注入provide提供的数据和函数
逻辑复用
1、根据功能去抽离函数
2、函数在抽离函数中去定义一个data变量+修改data的方法
3、这个函数可以根据需要,单独放到一个js中导出
补充-mixins语法
官方话术:
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项
理解全局混入:所有组件混入了这些逻辑代码
// 全局混入 全局mixin
// vue2.0 写法 Vue.mixin({})
app.mixin({
methods: {
say () {
console.log(this.$el,'在mounted中调用say函数')
}
},
mounted () {
this.say()
}
})
<template>
<div class="container1">
<h1> 作者:周杰伦 <a href="javascript:;">关注</a> </h1>
<hr>
<Son />
</div>
</template>
<script>
import Son from './Son.vue'
export default {
name: 'App',
components: {
Son
}
}
</script>
<template>
<div class="container2">
<h2> 作者:周杰伦 <button>关注</button> </h2>
</div>
</template>
<script>
export default {
name: 'Son'
}
</script>
<style scoped lang="less"></style>
理解局部混入:通过mixins选项进行混入
// 配置对象
export const followMixin = {
data () {
return {
loading: false
}
},
methods: {
followFn () {
this.loading = true
// 模拟请求
setTimeout(()=>{
// 省略请求代码
this.loading = false
},2000)
}
}
}
<template>
<div class="container1">
<h1> 作者:周杰伦 <a href="javascript:;" @click="followFn">{{loading?'请求中...':'关注'}}</a> </h1>
<hr>
<Son />
</div>
</template>
<script>
import Son from './Son.vue'
import {followMixin} from './mixins'
export default {
name: 'App',
components: {
Son
},
mixins: [followMixin]
}
</script>
<template>
<div class="container2">
<h2> 作者:周杰伦 <button @click="followFn">{{loading?'loading...':'关注'}}</button> </h2>
</div>
</template>
<script>
import {followMixin} from './mixins'
export default {
name: 'Son',
mixins: [followMixin]
}
</script>
<style scoped lang="less"></style>
总结: 在vue2.0中一些可复用的逻辑可以使用mixins来封装,当是需要考虑逻辑代码冲突问题。vue3.0的组合API很好的解决了这个问题,就不在推荐使用mixins了
Vuex持久化
在开发的过程中,像用户信息(名字,头像,token)需要vuex中存储且需要本地存储,再例如,购物车如果需要未登录状态下也支持,如果管理在vuex中页需要存储在本地
持久化:1、数据存储到vuex(内存) 2、数据同时存到本地(硬盘localStorage)
这里使用插件让在vuex中管理的状态数据自动同时存储在本地免去自己存储的环节
实现步骤
- 安装
vuex-persistedstate
插件 - vuex中准备
user模块
和cart模块
- 将插件配置到vuex的
plugins
选项中,配置user模块和cart模块进行状态持久化 - 修改state数据就会触发自动同步机制,修改一下数据检测是否同步成功
代码落地
1)安装一个vuex的插件vuex-persistedstate
来支持vuex的状态持久化
npm i vuex-persistedstate
2)在src/store
文件夹下新建 modules
文件,在 modules
下新建 user.js
和 cart.js
src/store/modules/user.js
// 用户状态
export default {
namespaced: true,
state: () => ({
profile: {
id: '',
avatar: '',
nickname: '',
account: '',
mobile: '',
token: ''
}
})
}
src/store/modules/cart.js
// 购物车状态
export default {
namespaced: true,
state: () => ({
list:[]
})
}
3)在 src/store/index.js
中导入 user 和 cart 模块
import { createStore } from 'vuex'
import user from './modules/user'
import cart from './modules/cart'
export default createStore({
modules: {
user,
cart
}
})
4)使用vuex-persistedstate插件来进行持久化
import { createStore } from 'vuex'
import createPersistedstate from 'vuex-persistedstate'
import user from './modules/user'
import cart from './modules/cart'
export default createStore({
modules: {
user,
cart
},
plugins: [
createPersistedstate({
key: 'erabbit-client-pc-store',
paths: ['user', 'cart']
})
]
})
-
默认是存储在
localStorage
中,可以对存储的方法进行自定义 -
key是存储数据的键名
-
paths是存储state中的那些数据,如果是模块下具体的数据需要加上模块名称,如
user.profile.token
=>只存储user模块下token数据到本地 -
修改state中的数据即可触发同步机制,可以看到本地存储数据的的变化
-
插件vue2也可以使用,API具体查看插件文档
测试效果
user模块定义一个mutation在main.js去调用下,观察浏览器application的localStorage下是否已经有了数据
src/store/modules/user.js
// 测试代码
mutations: {
setUser (state) {
state.profile.token = 'hash'
}
}
src/main.js
store.commit('user/setUser')