Vue3.0

139 阅读3分钟

Vue3.0

选项Api和组合Api

  • 组合API 写法
    • 代码风格:一个功能逻辑的代码组织在一起(包含数据,函数...)
  • 优点:功能逻辑复杂繁多情况下,各个功能逻辑代码组织再一起,便于阅读和维护
  • 缺点:需要有良好的代码组织能力和拆分逻辑能力

vue3也支持vue2的写法

<template>
  <div class="container">123</div>
</template>
<script>
export default {
  name: "App",
  //1. 组合API的起点,将来的组合API的代码,基本上在这里
  //2. 可理解为:在beforeCreate钩子执行前执行,组件实例创建前
  //3. 函数中不能使用this ==>undefined
  //4. 模板中需要使用的数据和函数,需要在setup中返回
  setup() {
    console.log("setup被执行了");
    console.log(this); //undefined
    // 定义数据和函数
    const msg = "hi vue3";
    const say = () => {
      console.log(msg);
    };
    return { msg, say };
  },
  beforeCreate() {
    console.log("beforeCreate执行了");
    console.log(this); //Proxy对象
  },
};
</script>

Vue3生命周期钩子

  • setup 创建实例前
  • onBeforeMount 挂载DOM前
  • onMounted 挂载DOM后
  • onBeforeUpdate 更新组件前
  • onUpdated 更新组件后
  • onBeforeUnmount 卸载销毁前
  • onUnmounted 卸载销毁后

可以多次使用同一个钩子,执行顺序和书写顺序相同。 钩子写在setup内

<template>
  <div class="container">
    container
  </div>
</template>
<script>
import { onBeforeMount, onMounted } from 'vue'
export default {
  setup () {
    onBeforeMount(()=>{
      console.log('DOM渲染前',document.querySelector('.container'))
    })
    onMounted(()=>{
      console.log('DOM渲染后1',document.querySelector('.container'))
    })
    onMounted(()=>{
      console.log('DOM渲染后2',document.querySelector('.container'))
    })
  },
}
</script>

DOM渲染前 null 
DOM渲染后1 div
DOM渲染后2 div

定义响应式数据

reactive是一个函数,它可以定义一个复杂数据类型,成为响应式数据。

<template>
  <div class="container">
    <div>{{ obj.name }}</div>
    <div>{{ obj.age }}</div>
    <button @click="updateName">修改数据</button>
  </div>
</template>
<script>
import { reactive } from "vue";
export default {
  name: "App",
  setup() {
    // 普通数据
    // const obj = {
    //   name: 'ls',
    //   age: 18
    // }
    
    // 定义响应式数据
    const obj = reactive({
      name: "ls",
      age: 18,
    });

    // 修改名字
    const updateName = () => {
      console.log("updateName");
      obj.name = "zs";
    };

    return { obj, updateName };
  },
};
</script>

ref函数,常用于简单数据类型定义为响应式数据

  • 再修改值,获取值的时候,需要.value
  • 在模板中使用ref申明的响应式数据,可以省略.value

其实也可以定义复杂数据类型的响应式数据,对于数据未智的情况下 ref 是最适用的

<template>
  <div class="container">
    <div>{{ name }}</div>
    <div>{{ age }}</div>
    <button @click="updateName">修改数据</button>
  </div>
</template>
<script>
import { ref } from "vue";
export default {
  name: "App",
  setup() {
    // 1. name数据
    const name = ref("ls");
    console.log(name);
    const updateName = () => {
      name.value = "zs";
    };
    // 2. age数据
    const age = ref(10);

    // ref常用定义简单数据类型的响应式数据
    // 其实也可以定义复杂数据类型的响应式数据
    // 对于数据未之的情况下 ref 是最适用的
    // const data = ref(null)
    // setTimeout(()=>{
    //   data.value = res.data
    // },1000)

    return { name, age, updateName };
  },
};
</script>

通过解构响应式数据得到的不再是响应式数据 需要通过toRef函数

<template>
  <div class="container">
    {{ name }} <button @click="updateName">修改数据</button>
  </div>
</template>
<script>
import { reactive, toRef } from "vue";
export default {
  name: "App",
  setup() {
    // 1. 响应式数据对象
    const obj = reactive({
      name: "ls",
      age: 10,
    });
    console.log(obj);
    // 2. 模板中只需要使用name数据
    // 注意:从响应式数据对象中解构出的属性数据,不再是响应式数据
    // let { name } = obj 不能直接解构,出来的是一个普通数据
    const name = toRef(obj, "name");
    // console.log(name)
    const updateName = () => {
      console.log("updateName");
      // toRef转换响应式数据包装成对象,value存放值的位置
      name.value = "zs";
    };

    return { name, updateName };
  },
};
</script>

toRefs是函数,转换响应式对象中所有属性为单独响应式数据,对象成为普通对象,并且值是关联的 使用场景:剥离响应式对象(解构|展开),想使用响应式对象中的多个或者所有属性做为响应式数据。

<template>
  <div class="container">
    <div>{{name}}</div>
    <div>{{age}}</div>
    <button @click="updateName">修改数据</button>
  </div>
</template>
<script>
import { reactive, toRef, toRefs } from 'vue'
export default {
  name: 'App',
  setup () {
    // 1. 响应式数据对象
    const obj = reactive({
      name: 'ls',
      age: 10
    })
    console.log(obj)
    // 2. 解构或者展开响应式数据对象
    // const {name,age} = obj
    // console.log(name,age)
    // const obj2 = {...obj}
    // console.log(obj2)
    // 以上方式导致数据就不是响应式数据了
    const obj3 = toRefs(obj)
    console.log(obj3)

    const updateName = () => {
      // obj3.name.value = 'zs'
      obj.name = 'zs'
    }

    return {...obj3, updateName}
  }
}
</script>

computed函数

computed函数,是用来定义计算属性的,计算属性不能修改。

普通用法的计算属性无法用v-model绑定 它是仅读的 高级用法可以使计算属性变为可写的 可以被v-model绑定

目的:让计算属性支持双向数据绑定。

给computed传入函数,返回值就是计算属性的值
给computed传入对象,get获取计算属性的值,set监听计算属性改变。

<template>
  <div class="container">
    <div>今年:{{age}}岁</div>
    <div>后年:{{newAge}}岁</div>
  </div>
</template>
<script>

//普通用法
// import { computed, ref } from 'vue'
// export default {
//   name: 'App',
//   setup () {
//     // 1. 计算属性:当你需要依赖现有的响应式数据,根据一定逻辑得到一个新的数据。
//     const age = ref(16)
//     // 得到后年的年龄
//     const newAge = computed(()=>{
//       // 该函数的返回值就是计算属性的值
//       return age.value + 2
//     })

//     return {age, newAge}
//   }
// }

// 高级用法
import { computed, ref } from 'vue'
export default {
  name: 'App',
  setup () {
    // 1. 计算属性:当你需要依赖现有的响应式数据,根据一定逻辑得到一个新的数据。
    const age = ref(16)
    // 得到后年的年龄
    // const newAge = computed(()=>{
    //   // 该函数的返回值就是计算属性的值
    //   return age.value + 2
    // })

    // 计算属性高级用法,传人对象
    const newAge = computed({
      // get函数,获取计算属性的值
      get(){
        return age.value + 2
      },
      // set函数,当你给计算属性设置值的时候触发
      set (value) {
        age.value = value - 2
      }
    })


    return {age, newAge}
  }
}
</script>

watch函数

watch函数,是用来定义侦听器的

  • watch函数,是用来定义侦听器的

  • 监听ref定义的响应式数据

  • 监听多个响应式数据数据

  • 监听reactive定义的响应式数据

  • 监听reactive定义的响应式数据,某一个属性

  • 深度监听

  • 默认执行

<template>
  <div class="container">
    <div>
      <p>count的值:{{count}}</p>
      <button @click="add">改数据</button>
    </div>
    <hr>
    <div>
      <p>{{obj.name}}</p>
      <p>{{obj.age}}</p>
      <p>{{obj.brand.name}}</p>
      <button @click="updateName">改名字</button>
      <button @click="updateBrandName">改品牌名字</button>
    </div>
  </div>
</template>
<script>
import { reactive, ref, watch } from 'vue'
export default {
  name: 'App',
  setup () {
    const count = ref(0)
    const add = () => {
      count.value++
    }
    // 当你需要监听数据的变化就可以使用watch
    // 1. 监听一个ref数据
    // 1.1 第一个参数  需要监听的目标
    // 1.2 第二个参数  改变后触发的函数
    // watch(count, (newVal,oldVal)=>{
    //   console.log(newVal,oldVal)
    // })


    const obj = reactive({
      name: 'ls',
      age: 10,
      brand: {
        id: 1,
        name: '宝马'
      }
    })
    const updateName = () => {
      obj.name = 'zs'
    }
    const updateBrandName = () => {
      obj.brand.name = '奔驰'
    }
    // 2. 监听一个reactive数据
    watch(obj, ()=>{
      console.log('数据改变了')
    })

    watch(()=>obj.brand, ()=>{
      console.log('brand数据改变了')
    },{
      // 5. 需要深度监听
      deep: true,
      // 6. 想默认触发
      immediate: true
    })

    // 3. 监听多个数据的变化
    // watch([count, obj], ()=>{
    //   console.log('监听多个数据改变了')
    // }) 


    // 4. 此时监听对象中某一个属性的变化 例如:obj.name 
    // 需要写成函数返回该属性的方式才能监听到
    // watch(()=>obj.name,()=>{
    //   console.log('监听obj.name改变了')
    // })

    return {count, add, obj, updateName, updateBrandName}
  }
}
</script>

ref绑定dom元素

使用ref属性绑定DOM或组件

<template>
  <div class="container">
    <!-- vue2.0 获取单个元素 -->
    <!-- 1. 通过ref属性绑定该元素 -->
    <!-- 2. 通过this.$refs.box获取元素 -->
    <!-- <div ref="box">我是box</div> -->
    <!-- vue2.0 获取v-for遍历的多个元素 -->
    <!-- 1. 通过ref属性绑定被遍历元素 -->
    <!-- 2. 通过this.$refs.li 获取所有遍历元素  -->
    <!-- <ul>
      <li v-for="i in 4" :key="i" ref="li">{{i}}</li>
    </ul> -->

    <!-- 单个元素 -->
    <div ref="dom">我是box</div>
    <!-- 被遍历的元素 -->
    <ul>
      <li v-for="i in 4" :key="i" :ref="setDom">第{{ i }}LI</li>
    </ul>
  </div>
</template>
<script>
import { onMounted, ref } from "vue";
export default {
  name: "App",
  setup() {
    // 1. 获取单个元素
    // 1.1 先定义一个空的响应式数据ref定义的
    // 1.2 setup中返回该数据,你想获取那个dom元素,在该元素上使用ref属性绑定该数据即可。
    const dom = ref(null);
    onMounted(() => {
      console.log(dom.value);
    });
    
    // 2. 获取v-for遍历的元素
    // 2.1 定义一个空数组,接收所有的LI
    // 2.2 定义一个函数,往空数组push DOM
    const domList = [];
    const setDom = (el) => {
      domList.push(el);
    };
    onMounted(() => {
      console.log(domList);
    });
    return { dom, setDom };
  },
};
</script>
  • 单个元素:先申明ref响应式数据,返回给模版使用,通过ref绑定数据

  • 遍历的元素:先定义一个空数组,定一个函数获取元素,返回给模版使用,通过ref绑定这个函数

  • 有一个边界问题:组件更新的时候会重复的设置dom元素给数组:

// ref获取v-for遍历的DOM元素,需要在组件更新的时候重置接受dom元素的数组。
onBeforeUpdate(()=>{
  list = []
})

父子组件传值

父组件

<template>
  <div class="container">
    <h1>父组件</h1>
    <p>{{ money }}</p>
    <hr />
    <!-- <Son :money="money" @change-money="updateMoney" /> -->
    <!-- <Son :money="money" @update:money="updateMoney" /> -->
    <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(100);
    const updateMoney = (newMoney) => {
      money.value = newMoney;
    };
    return { money, updateMoney };
  },
};
</script>

子组件

<template>
  <div class="container">
    <h1>子组件</h1>
    <p>{{ money }}</p>
    <button @click="changeMoney">花50元</button>
  </div>
</template>
<script>
import { onMounted } from "vue";
export default {
  name: "Son",
  // 子组件接收父组件数据使用props即可
  props: {
    money: {
      type: Number,
      default: 0,
    },
  },
  setup(props, { emit }) {
    // 获取父组件数据money
    console.log(props.money);

    // 向父组件传值
    const changeMoney = () => {
      // 消费50元
      // 通知父组件,money需要变成50
      //   emit("change-money", 50);
      emit('update:money',50);
    };
    return { changeMoney };
  },
};
</script>

依赖注入

使用场景:有一个父组件,里头有子组件,有孙组件,有很多后代组件,共享父组件数据。

  • provide函数提供数据和函数给后代组件使用
  • inject函数给当前组件注入provide提供的数据和函数

父组件

<template>
  <div class="container">
    <h1>父组件 {{money}} <button @click="money=1000">发钱</button></h1>
    <hr>
    <Son />
  </div>
</template>
<script>
import { provide, ref } from 'vue'
import Son from './Son.vue'
export default {
  name: 'App',
  components: {
    Son
  },
  setup () {
    const money = ref(100)
    const changeMoney = (saleMoney) => {
      console.log('changeMoney',saleMoney)
      money.value = money.value - saleMoney
    }
    // 将数据提供给后代组件 provide
    provide('money', money)
    // 将函数提供给后代组件 provide
    provide('changeMoney', changeMoney)

    return { money }
  }
}
</script>

子组件

<template>
  <div class="container">
    <h2>子组件 {{money}}</h2>
    <hr>
    <GrandSon />
  </div>
</template>
<script>
import { inject } from 'vue'
import GrandSon from './GrandSon.vue'
export default {
  name: 'Son',
  components: {
    GrandSon
  },
  setup () {
    // 接收祖先组件提供的数据
    const money = inject('money')
    return { money }
  }
}
</script>

孙组件

<template>
  <div class="container">
    <h3>孙组件 {{money}} <button @click="fn">消费20</button></h3>
  </div>
</template>
<script>
import { inject } from 'vue'
export default {
  name: 'GrandSon',
  setup () {
    const money = inject('money')
    // 孙组件,消费50,通知父组件App.vue组件,进行修改
    // 不能自己修改数据,遵循单选数据流原则,大白话:数据谁定义谁修改
    const changeMoney = inject('changeMoney')
    const fn = () => {
      changeMoney(20)
    }
    return {money, fn}
  }
}
</script>

参考小兔仙Vue3:zhoushugang.gitee.io/erabbit-cli…