学习vue第二天

toRef方法

  • toRef方法用于将响应式数据内部的普通数据转换为响应式数据,并且转化后的数据和原始的数据纯在引用关系,存在引用关系意味着原始数据发生变化后,toRef转换后的数据也会根着变化
export default{
  setup(){
    const person = ref({name:"张三"})
    return { name: toRef(person.value.name)}
  }
}

toRefs方法

  • 通过 toRef 方法一次只能转换一个数据,通过 toRefs 方法可以实现批量数据转换。
  • toRefs 方法接收引用数据类型的响应式数据,它可以将数据中的第一层属性全部转换为响应式数据, 返回值是一个对象, 对象中存储了所有转换之后的响应式数据。
export default {
  name: "App",
  setup() {
    const person = reactive({
      name: "张三",
      age: 20,
      brand: { title: "宝马", year: 1 },
    });
    return { ...toRefs(person) };
  },
};

组件通讯

  • 父组件通过 props 向子组件传递数据
<template>
  <div>I am parent component</div>
  <hr /> 
  <ChildComp :msg="msg"></ChildComp>
</template>

<script>
import ChildComp from "./components/ChildComp.vue"
import { ref } from "vue"
export default {
  components: { ChildComp },
  setup() {
    const msg = ref("a message from parent")
    return { msg }
  },
}
</script>
<template>
  <div>
    {{ childMsg }}
    <hr />
    {{ msg }}
  </div>
</template>
<script>
import { computed } from "vue";
export default {
  name: "ChildComponent",
  props: ["msg"],
  setup(props) {
    // 当父组件更新 props 时 setup 函数是不会重新执行的
    // 所以在 setup 函数中使用 props 时需要用到 computed 或者 watch 来响应 props 的变化
    // 注意: 直接在模板中使用 props 数据是没有这个问题的
    const childMsg = computed(() => props.msg + "😀😀");
    return { childMsg };
  },
};
</script>
  • 子组件通过自定义事件向父组件传递数据
<template>
  <div>
    {{ childMsg }}
    <hr />
    {{ msg }}
    <hr />
    <button @click="onMsgChanged">change msg</button>
  </div>
</template>
<script>
import { computed } from "vue";

export default {
  name: "ChildComponent",
  props: ["msg"],
  setup(props, { emit }) {
    const onMsgChanged = () => {
      emit("onMsgChanged", "changed msg from children");
    };
    return { onMsgChanged };
  },
};
</script>
<template>
  <ChildComponent :msg="msg" @onMsgChanged="onMsgChanged" />
</template>

<script>
import { ref } from "vue";
import ChildComponent from "./components/child-component.vue";
export default {
  components: { ChildComponent },
  name: "App",
  setup() {
    const msg = ref("i am a message");
    const onMsgChanged = (data) => {
      msg.value = data;
    };
    return { msg, onMsgChanged };
  },
};
</script>

组件生命周期

  • setup 组件初次挂在前,重新挂载前都会执行
  • onMounted 组件挂载完成后执行
  • onUpdated 组件数据更新后执行
  • onUnmounted 组件卸载后执行

与服务端通信

  • 向服务器端发送请求获取列表数据渲染列表数据, 没有数据要显示暂无数据, 如果请求报错展示错误信息, 加载过程显示loading.
<script>
import { ref } from "vue";
import axios from "axios";
  
export default {
  name: "App",
  setup() {
    // 用于存储列表数据
    const data = ref(null);
    // 用于标识加载状态
    const loading = ref(false);
    // 用于存储错误信息
    const error = ref(null);
    // 用于发送请求的方法
    async function getPosts() {
      // 更新加载状态
      loading.value = true;
      try {
        // 发送请求
        let response = await axios.get(
          "https://jsonplaceholder.typicode.com/posts"
        );
        // 存储列表数据
        data.value = response.data;
      } catch (err) {
        // 存储错误信息
        error.value = err.message;
      }
      // 更新加载状态
      loading.value = false;
    }
    // 调用方法 发送请求
    getPosts();
    // 返回模板所需数据
    return { data, loading, error };
  },
};
</script>

获取DOM对象

  • 获取单个DOM对象
import { ref, onMounted } from "vue"
export default {
  setup() {
    const divRef = ref(null)
    onMounted(() => {
      console.log(divRef.value)
    })
    return { divRef }
  },
}
  • 获取一组 DOM 对象
import { ref, onMounted, onUpdated } from "vue"
export default {
  setup() {
    const list = ref(["a", "b", "c"])
    const elms = ref([])
    const onClickHandler = () => list.value.push("d")
    onMounted(() => console.log(elms.value))
    onUpdated(() => console.log(elms.value))
    return { list, elms, onClickHandler }
  },
}

provide, inject函数

  • 通过 provide、inject 函数的配合使用,可以实现跨组件传递数据(组件与组件存在嵌套关系)
<!-- 父组件 App -->
<template>
  <ChildComponent />
</template>

<script>
import { ref, provide } from "vue";
import ChildComponent from "./components/ChildComponent.vue";

export default {
  components: { ChildComponent },
  name: "App",
  setup() {
    const person = ref({ name: "张三" });
    const changePerson = () => {
      person.value.name = "李四";
    };
    provide("person", person);
    provide("changePerson", changePerson);
  },
};
</script>
<!-- 子组件 -->
<template>
  <LastComponent />
</template>
<script>
import LastComponent from "./LastComponent.vue";
export default {
  components: { LastComponent },
  name: "ChildComponent",
};
</script>
<!-- 孙组件 -->
<template>
  {{ person.name }}
  <button @click="changePerson">button</button>
</template>
<script>
import { inject } from "vue";
export default {
  name: "LastComponent",
  setup() {
    const person = inject("person");
    const changePerson = inject("changePerson");
    return { person, changePerson };
  },
};
</script>

teleport组件

  • teleport 组件可以将指定组件渲染到应用外部的其他位置。
  • 比如弹框组件,它可能在任意组件中使用,但它不属于任意组件,所以不能在使用它的组件中渲染它,我们需要将它渲染到指定位置。
<!-- Modal.vue -->
<template>
  <div class="wrapper">
    <div class="content">
      <a class="close" href="javascript:">关闭</a>
    </div>
  </div>
</template>
<script>
export default {
  name: "Modal",
};
</script>
<style scoped>
.wrapper {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.4);
}
.content {
  width: 660px;
  height: 400px;
  background: white;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
.close {
  position: absolute;
  right: 10px;
  top: 10px;
  color: #999;
  text-decoration: none;
}
</style>
<!-- App.vue -->
<template>
  <teleport to="#modal">
    <Modal />
  </teleport>
</template>
<script>
import Modal from "./components/Modal.vue";
export default {
  components: { Modal },
  name: "App",
};
</script>

Suspense组件

  • Suspense 用于确保组件中的 setup 函数调用和模板渲染之间的执行顺序。先执行 setup 后渲染模板。
  • 当组件中的 setup 被写成异步函数的形式, 代码执行的顺序就变成了先渲染模板后执行 setup 函数了。
<!-- Posts.vue -->
<template>
  <pre>{{ data }}</pre>
</template>
<script>
import axios from "axios";

export default {
  name: "Posts",
  async setup() {
    let response = await axios.get(
      "https://jsonplaceholder.typicode.com/posts"
    );
    return { data: response.data };
  },
};
</script>
<!-- App.vue -->
<template>
  <Suspense>
    <Posts />
  </Suspense>
</template>
<script>
import Posts from "./components/Posts.vue";
export default {
  components: { Posts },
  name: "App",
};
</script>

过度动画

  • Vue 提供了 transition 组件供我们执行过渡动画, 我们只需要使用 transition 组件包裹你要执行动画的元素即可。
  • 执行过渡动画的前提条件是元素具有创建与销毁的操作
  • 当创建元素时, transiton 组件会为执行动画的元素添加三个类名, 我们可以通过这三个类名为元素添加入场动画。
.v-enter-from {}    // 元素执行动画的初始样式 (动画起点样式)
.v-enter-to {}      // 元素执行动画的目标样式 (动画终点样式)
.v-enter-active {}  // 可以用于指定元素指定动画的类型
.v-enter-from { opacity: 0 }
.v-enter-to { opacity: 1 }
.v-enter-active { transition: opacity 2s ease-in } // ease-in 先慢后快
  • 当销毁元素时, transition 组件会为执行动画的元素添加三个类名, 我们可以通过这个三个类名为元素添加离场动画样式。
.v-leave-from {}      // 元素执行动画的初始样式 (动画起点样式)
.v-leave-to {}        // 元素执行动画的目标样式 (动画终点样式)
.v-leave-active {}    // 可以用于指定元素指定动画的类型
.v-leave-from { opacity: 1 }
.v-leave-to { opacity: 0 }
.v-leave-active { transition: opacity 2s ease-out } // ease-out 先快后慢
  • 如果在页面中有多个元素要执行动画, 而多个元素要执行的动画不同时, 为了对多个元素的动画样式进行区分, 在调用 transiton 组件时需要为它添加 name 属性以区分样式类名。
<transition name="fade">
  <h1>hello world</h1>
</transition>
.fade-enter-from {}
.fade-enter-to {}
.fade-enter-active{}

.fade-leave-from {}
.fade-leave-to {}
.fade-leave-active {}

状态管理Vuex

  • 首先vue3只能使用vuex4版本。
#首先安装vuex
npm i vuex 

#然后在src目录下创建一个store文件来存放代码
import { createStore } from 'vuex'
 
export default createStore({
    state: {
        name:"小王"
    },
    mutations: {
    },
    actions: {
    },
    modules: {
    }
})
 

#在mian.js中导入
import { createApp } from 'vue'
import App from './App.vue'
import store from './store';
 
const app = createApp(App)
 
app.use(store);
app.mount('#app')
#页面展示
<template>
  <div>
    <div>{{ store.state.name }}</div>
  </div>
</template>
 
<script setup lang="ts">
import { useStore } from "vuex";
const store = useStore();
</script>
 
<style scoped>
</style>

双向数据绑定

  • 双向数据绑定是指数据和视图进行绑定,数据变化同步视图,视图更改了数据,将更改同步回数据
    • App.vue
<template>
  <Test v-model:firstName="firstName" v-model:lastName="lastName" />
  <button @click="onClickHandler">我是App组件中的 button</button>
</template>
<script>
import Test from "./components/Test.vue";
import { ref } from "vue";
export default {
  components: { Test },
  name: "App",
  setup() {
    const firstName = ref("张三");
    const lastName = ref("李四");
    const onClickHandler = () => {
      firstName.value = "孙悟空";
      lastName.value = "猪八戒";
    };
    return { firstName, lastName, onClickHandler };
  },
};
</script>

  - Test.vue
<template>
  <div>
    {{ firstName }} {{ lastName }}
    <button @click="onClickHandler">我是Test组件中的button</button>
  </div>
</template>
<script>
export default {
  props: ["firstName", "lastName"],
  setup(props, { emit }) {
    const onClickHandler = () => {
      emit("update:firstName", "刘备");
      emit("update:lastName", "诸葛亮");
    };
    return { onClickHandler };
  },
};
</script>

customRef

  • 防抖:监听用户的连续操作,最终指响应连续操作中的最后一次操作
< script setup>
const keyword = useDeb("Holle", 400);
function useDeb(initalValue, delay) {
  let timer = null;
  return customRef((track, trigger) => {
    return {
      get() {
        track();
        return initalValue;
      },
      set(newValue) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          initalValue = newValue;
          trigger();
        }, delay);
      },
    };
  });
}
</script>

<template>
  <input type="text" v-model="keyword" />
  {{ keyword }}
</template>

Vue3语法糖

<script setup></script> 是一种编译时语法糖,用于在单文件组件中使用组合式API,如果你同时使用单文件组件和组合式API,建议使用该语法。

  • 子组件可不用手动注册