vue3.0基础(二)

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中管理的状态数据自动同时存储在本地免去自己存储的环节
实现步骤

  1. 安装vuex-persistedstate 插件
  2. vuex中准备user模块cart模块
  3. 将插件配置到vuex的plugins选项中,配置user模块和cart模块进行状态持久化
  4. 修改state数据就会触发自动同步机制,修改一下数据检测是否同步成功

代码落地

1)安装一个vuex的插件vuex-persistedstate来支持vuex的状态持久化

npm i vuex-persistedstate

2)在src/store 文件夹下新建 modules 文件,在 modules 下新建 user.jscart.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']
    })
  ]
})

插件说明

  1. 默认是存储在localStorage中,可以对存储的方法进行自定义

  2. key是存储数据的键名

  3. paths是存储state中的那些数据,如果是模块下具体的数据需要加上模块名称,如user.profile.token=>只存储user模块下token数据到本地

  4. 修改state中的数据即可触发同步机制,可以看到本地存储数据的的变化

  5. 插件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')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值