个人随笔
目录
Vue3技术的学习:创建项目、ref、组件、插槽、数据传递、生命周期
2024-10-27 19:05:25

一、创建项目

1、安装node

Node.js的下载安装

2、安装淘宝镜像源

  1. #查看源地址
  2. npm config get registry
  3. #设置源地址
  4. npm config set registry https://registry.npmmirror.com

3、创建一个vue应用

参考文档
https://cn.vuejs.org/guide/quick-start.html

  1. npm create vue@latest

这里我们去到自己的工作目录下面,然后执行,上面的命令,这里我全部选择否,后面要用到再说,启动发现要vite,那么我这里是直接在该项目下面安装

  1. npm install vite --save-dev

当然全局安装更好

  1. npm install -g vite



4、用npm init vite@latest来创建项目

  1. npm init vite@latest

我这里选的是Vue和JavaScript
然后执行如下命令即可启动

  1. cd vue02
  2. npm install
  3. npm run dev


可以看到这两个命令创建的页面还是有点区别的,本人比较喜欢第二种

  1. npm init vite@latest

5、总之,执行下面的命令即可

  1. npm init vite@latest
  2. cd 项目
  3. npm install
  4. npm run dev

参考:https://cn.vuejs.org/guide/scaling-up/tooling.html
然后用开发工具打开即可,vscode或者IDEA

二、基础知识点

我们从官网就可以学到所有的内容,这里只是自我学习列出一些关键知识点,我们这里的学习都用的是组合式API,具体有什么区别可以见官网
https://cn.vuejs.org/

1、通过ref()声明响应式状态

  1. <script setup>
  2. import { ref } from 'vue'
  3. const count = ref(0)
  4. </script>
  5. <template>
  6. <div>
  7. <div @click="count++">点我增加{{count}}</div>
  8. </div>
  9. </template>
  10. <style scoped>
  11. </style>

2、模版语法

文本插值、原始HTML、Attribute 绑定、JavaScript 表达式等

  1. <script setup>
  2. import { ref } from 'vue'
  3. const count = ref(0)
  4. const msg = ref('文本插值')
  5. const rawHtml = ref('<div style="color:red">原始HTML</div>')
  6. const xiaolin = ref('xiaolin')
  7. const name = ref('pidan')
  8. </script>
  9. <template>
  10. <div>
  11. <div @click="count++">点我增加{{count}}</div>
  12. <div>{{msg}}</div>
  13. <p>Using text interpolation: {{ rawHtml }}</p>
  14. <p>Using v-html directive: <span v-html="rawHtml"></span></p>
  15. <!-- v-bind可以简写为:id-->
  16. <div v-bind:id="xiaolin" :name="name">Attribute 绑定</div>
  17. 使用 JavaScript 表达式
  18. <div>{{count+100}}</div>
  19. </div>
  20. </template>
  21. <style scoped>
  22. </style>

更多模版语法教程见
https://cn.vuejs.org/guide/essentials/template-syntax.html

3、计算属性

模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。

  1. import { ref ,computed } from 'vue'
  2. const count = ref(0)
  3. const publishedBooksMessage = computed(() => {
  4. console.log("计算过程执行了")
  5. return count.value+1;
  6. })
  7. ...
  8. <div @click="count++">点我增加{{count}}</div>
  9. <div>计算属性的值1={{publishedBooksMessage}}</div>
  10. <div>计算属性的值2={{publishedBooksMessage}}</div>
  11. ...

可以看到,count的变化会导致publishedBooksMessage的变化,并且页面初始化的时候,计算属性也只计算了一次,这个也是跟方法的区别,方法的话如果你div里面用了两次,那么肯定会执行两次,具体可以看
https://cn.vuejs.org/guide/essentials/computed.html

4、侦听器

计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。

  1. import { ref ,computed,watch } from 'vue'
  2. // 可以直接侦听一个 ref
  3. watch(count, async (newQuestion, oldQuestion) => {
  4. console.log("newQuestion="+newQuestion)
  5. console.log("oldQuestion="+oldQuestion)
  6. })
  7. ...
  8. <div @click="count++">点我增加{{count}}</div>
  9. ...

具体看
https://cn.vuejs.org/guide/essentials/watchers.html

5、模版引用

虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作,但在某些情况下,我们仍然需要直接访问底层 DOM 元素。要实现这一点,我们可以使用特殊的 ref attribute:

  1. <script setup>
  2. import { useTemplateRef, onMounted } from 'vue'
  3. // 第一个参数必须与模板中的 ref 值匹配
  4. const input = useTemplateRef('my-input')
  5. onMounted(() => {
  6. input.value.focus()
  7. })
  8. </script>
  9. <template>
  10. <input ref="my-input" />
  11. </template>

更多参考:https://cn.vuejs.org/guide/essentials/template-refs.html

组件上的 ref

模板引用也可以被用在一个子组件上。这种情况下引用中获得的值是组件实例:
使用了<script setup>的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup>的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露:

  1. <script setup>
  2. import { ref } from 'vue'
  3. const count = ref(0)
  4. function haha(){
  5. count.value++;
  6. }
  7. defineExpose({
  8. count,
  9. haha
  10. })
  11. </script>
  12. <template>
  13. {{count}}
  14. </template>

然后父组件

  1. <script setup>
  2. import { ref,useTemplateRef } from 'vue'
  3. import HelloWorld from './components/HelloWorld.vue'
  4. const hello = useTemplateRef("helloWorld");
  5. function add(){
  6. console.log("hello.count",hello.value.count)
  7. // hello.value.haha();
  8. hello.value.count++;
  9. }
  10. </script>
  11. <template>
  12. <HelloWorld ref="helloWorld"></HelloWorld>
  13. <button @click="add()">点击</button>
  14. </template>

上面可以通过子组件的ref调用方法以及修改值。这里不需要再解包,这个点有点奇怪!

6、组件的定义和使用

定义组件

组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。

  1. <script setup>
  2. import { ref } from 'vue'
  3. const count = ref(0)
  4. </script>
  5. <template>
  6. <button @click="count++">You clicked me {{ count }} times.</button>
  7. </template>
使用组件
  1. <script setup>
  2. import HelloWorld from './components/HelloWorld.vue'
  3. </script>
  4. <template>
  5. <HelloWorld></HelloWorld>
  6. </template>

参考:https://cn.vuejs.org/guide/essentials/component-basics.html

三、组件进阶知识点

我们在上面已经学过来组件的基本定义和使用,下面来学一些组件的高级用法,具体可见官网
https://cn.vuejs.org/guide/components/registration.html

1、父组件向子组件传递参数

这里用的是props,一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props
在子组件中定义

  1. <script setup>
  2. const props = defineProps(['foo'])
  3. </script>
  4. <template>
  5. <div>{{props.foo}}</div>
  6. </template>

此时,父组件只需要

  1. <script setup>
  2. import HelloWorld from './components/HelloWorld.vue'
  3. </script>
  4. <template>
  5. <HelloWorld foo="123"></HelloWorld>
  6. </template>

即可把123传过去,当然可以用:foo=”foo”动态变化。

2、子组件向父组件传递参数

这个要用组件事件
在组件的模板表达式中,可以直接使用 $emit 方法触发自定义事件 (例如:在 v-on 的处理函数中):

  1. <script setup>
  2. </script>
  3. <template>
  4. <button @click="$emit('someEvent','123')">Click Me</button>
  5. </template>

然后在父组件中

  1. <script setup>
  2. import HelloWorld from './components/HelloWorld.vue'
  3. function callback(a){
  4. console.log(a);
  5. }
  6. </script>
  7. <template>
  8. <HelloWorld @some-event="callback"></HelloWorld>
  9. </template>

这样就可以把123从子组件传递到父组件了。

声明触发的事件

  1. #我们在 <template> 中使用的 $emit 方法不能在组件的 <script setup> 部分中使用,但 defineEmits() 会返回一个相同作用的函数供我们使用:
  2. <script setup>
  3. const emit = defineEmits(['some-event'])
  4. function send(){
  5. emit("some-event","123")
  6. }
  7. </script>
  8. <template>
  9. <button @click="send()">Click Me</button>
  10. </template>

父组件一样可以拿到数据。

3、通过组件 v-model来实现双向绑定传值

v-model 可以在组件上使用以实现双向绑定。
子组件

  1. <script setup>
  2. const model = defineModel()
  3. </script>
  4. <template>
  5. <div>子组件中的值: {{ model }}</div>
  6. <button @click="model++">子组件中的按钮</button>
  7. </template>

父组件

  1. <script setup>
  2. import { ref } from 'vue'
  3. import HelloWorld from './components/HelloWorld.vue'
  4. const countModel =ref(0)
  5. </script>
  6. <template>
  7. <HelloWorld v-model="countModel"></HelloWorld>
  8. <div>父组件中的值{{countModel}}</div>
  9. <button @click="countModel++">父组件中的按钮</button>
  10. </template>

效果是,我们不管点击父组件中的按钮还是子组件中的按钮修改了值,父子都是同时变化。
感叹:这方法好啊
https://cn.vuejs.org/guide/components/v-model.html

4、插槽

在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。此时就需要用到插槽slot.
官方文档:https://cn.vuejs.org/guide/components/slots.html

子组件

  1. <script setup>
  2. </script>
  3. <template>
  4. <button class="fancy-btn">
  5. <slot></slot> <!-- 插槽出口 -->
  6. </button>
  7. </template>

父组件

  1. <script setup>
  2. import { ref } from 'vue'
  3. import HelloWorld from './components/HelloWorld.vue'
  4. const count =ref(0)
  5. </script>
  6. <template>
  7. <HelloWorld>
  8. 我是插槽内容{{count}}
  9. </HelloWorld>
  10. <button @click="count++">add</button>
  11. </template>

点击父组件的add,插槽内容是会变化的应为,插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。

父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。

匿名插槽

上面的例子就是你们插槽,也是默认插槽

具名插槽

插槽可以带有名字
子组件

  1. <script setup>
  2. </script>
  3. <template>
  4. <div class="container">
  5. <header>
  6. <slot name="header"></slot>
  7. </header>
  8. <main>
  9. <slot></slot>
  10. </main>
  11. <footer>
  12. <slot name="footer"></slot>
  13. </footer>
  14. </div>
  15. </template>

父组件

  1. <script setup>
  2. import HelloWorld from './components/HelloWorld.vue'
  3. </script>
  4. <template>
  5. <HelloWorld>
  6. <template v-slot:header>
  7. <h1>具名插槽显示内容header</h1>
  8. </template>
  9. <template #default>
  10. 默认插槽显示内容
  11. </template>
  12. <template #footer>
  13. <p>具名插槽显示内容footer</p>
  14. </template>
  15. </HelloWorld>
  16. </template>

v-slot 有对应的简写 #,因此<template v-slot:header>可以简写为<template #header>。其意思就是“将这部分模板片段传入子组件的 header 插槽中”。

当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非<template> 节点都被隐式地视为默认插槽的内容。所以上面也可以写成:

  1. <script setup>
  2. import HelloWorld from './components/HelloWorld.vue'
  3. </script>
  4. <template>
  5. <HelloWorld>
  6. 123
  7. <template v-slot:header>
  8. <h1>具名插槽显示内容header</h1>
  9. </template>
  10. 默认插槽显示内容
  11. <template #footer>
  12. <p>具名插槽显示内容footer</p>
  13. </template>
  14. </HelloWorld>
  15. </template>

插槽获取子组件的内容

插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的,但是我们假设想用子组件的内容来渲染插槽怎么办呢,此时就需要用作用域插槽。

我们也确实有办法这么做!可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes:

  1. <script setup>
  2. import { ref } from 'vue'
  3. const greetingMessage = ref("子组件的数据")
  4. </script>
  5. <template>
  6. <slot :text="greetingMessage" :count="1"></slot>
  7. </template>

当需要接收插槽 props 时,默认插槽和具名插槽的使用方式有一些小区别。下面我们将先展示默认插槽如何接受 props,通过子组件标签上的 v-slot 指令,直接接收到了一个插槽 props 对象:

  1. <script setup>
  2. import HelloWorld from './components/HelloWorld.vue'
  3. </script>
  4. <template>
  5. <HelloWorld v-slot="slotProps">
  6. {{slotProps.text}},{{slotProps.count}}
  7. </HelloWorld>
  8. </template>

具名插槽其实也差不多

  1. <script setup>
  2. import { ref } from 'vue'
  3. const greetingMessage = ref("子组件的数据")
  4. </script>
  5. <template>
  6. <slot name="abc" :text="greetingMessage" :count="1"></slot>
  7. </template>

父组件

  1. <script setup>
  2. import HelloWorld from './components/HelloWorld.vue'
  3. </script>
  4. <template>
  5. <HelloWorld #abc="slotProps">
  6. {{slotProps.text}},{{slotProps.count}}
  7. </HelloWorld>
  8. </template>

5、依赖注入

通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。想象一下这样的结构:有一些多层级嵌套的组件,形成了一棵巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦。
参考文档:https://cn.vuejs.org/guide/components/provide-inject.html

provide 和 inject 可以帮助我们解决这一问题 。一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。

Provide (提供)
  1. <script setup>
  2. import { inject } from 'vue'
  3. const message = inject('message')
  4. </script>
  5. <template>
  6. {{message}}
  7. </template>
Inject (注入)
  1. <script setup>
  2. import { provide } from 'vue'
  3. import HelloWorld from './components/HelloWorld.vue'
  4. provide('message','hello!')
  5. </script>
  6. <template>
  7. <HelloWorld></HelloWorld>
  8. </template>

如果注入的是个ref

  1. <script setup>
  2. import { ref,provide } from 'vue'
  3. import HelloWorld from './components/HelloWorld.vue'
  4. const count = ref(0)
  5. provide('message',count)
  6. </script>
  7. <template>
  8. <HelloWorld></HelloWorld>
  9. <button @click="count++">点击</button>
  10. </template>

父组件值变化后,子组件中也会同时变化。

子组件修改值,父组件也会变化

  1. <script setup>
  2. import { ref,provide } from 'vue'
  3. import HelloWorld from './components/HelloWorld.vue'
  4. const count = ref(0)
  5. provide('message',count)
  6. </script>
  7. <template>
  8. {{count}}
  9. <HelloWorld></HelloWorld>
  10. <button @click="count++">点击</button>
  11. </template>

思考:这岂不是也是一种父子之间传递值的好方法?

四、有用的API

1、生命周期钩子

参考:https://cn.vuejs.org/api/composition-api-lifecycle.html

2、nextTick()

等待下一次 DOM 更新刷新的工具方法。

当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。

nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。

  1. <script setup>
  2. import { ref, nextTick } from 'vue'
  3. const count = ref(0)
  4. async function increment() {
  5. count.value++
  6. // DOM 还未更新
  7. console.log(document.getElementById('counter').textContent) // 0
  8. await nextTick()
  9. // DOM 此时已经更新
  10. console.log(document.getElementById('counter').textContent) // 1
  11. }
  12. </script>
  13. <template>
  14. <button id="counter" @click="increment">{{ count }}</button>
  15. </template>

参考:https://cn.vuejs.org/api/general.html#nexttick

所有api见:https://cn.vuejs.org/api/

五、相关总结

1、父子组件数据传递的方法

  • 1、props(父->子)
  • 2、emit(子->父)
  • 3、依赖注入(父<->子)
  • 4、组件引用(父<->子)
  • 5、v-model(父<->子)
 9

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2