一、创建项目
1、安装node
2、安装淘宝镜像源
#查看源地址
npm config get registry
#设置源地址
npm config set registry https://registry.npmmirror.com
3、创建一个vue应用
参考文档
https://cn.vuejs.org/guide/quick-start.html
npm create vue@latest
这里我们去到自己的工作目录下面,然后执行,上面的命令,这里我全部选择否,后面要用到再说,启动发现要vite,那么我这里是直接在该项目下面安装
npm install vite --save-dev
当然全局安装更好
npm install -g vite
4、用npm init vite@latest来创建项目
npm init vite@latest
我这里选的是Vue和JavaScript
然后执行如下命令即可启动
cd vue02
npm install
npm run dev
可以看到这两个命令创建的页面还是有点区别的,本人比较喜欢第二种
npm init vite@latest
5、总之,执行下面的命令即可
npm init vite@latest
cd 项目
npm install
npm run dev
参考:https://cn.vuejs.org/guide/scaling-up/tooling.html
然后用开发工具打开即可,vscode或者IDEA
二、基础知识点
我们从官网就可以学到所有的内容,这里只是自我学习列出一些关键知识点,我们这里的学习都用的是组合式API,具体有什么区别可以见官网
https://cn.vuejs.org/
1、通过ref()声明响应式状态
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<div>
<div @click="count++">点我增加{{count}}</div>
</div>
</template>
<style scoped>
</style>
2、模版语法
文本插值、原始HTML、Attribute 绑定、JavaScript 表达式等
<script setup>
import { ref } from 'vue'
const count = ref(0)
const msg = ref('文本插值')
const rawHtml = ref('<div style="color:red">原始HTML</div>')
const xiaolin = ref('xiaolin')
const name = ref('pidan')
</script>
<template>
<div>
<div @click="count++">点我增加{{count}}</div>
<div>{{msg}}</div>
<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
<!-- v-bind可以简写为:id-->
<div v-bind:id="xiaolin" :name="name">Attribute 绑定</div>
使用 JavaScript 表达式
<div>{{count+100}}</div>
</div>
</template>
<style scoped>
</style>
更多模版语法教程见
https://cn.vuejs.org/guide/essentials/template-syntax.html
3、计算属性
模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。
import { ref ,computed } from 'vue'
const count = ref(0)
const publishedBooksMessage = computed(() => {
console.log("计算过程执行了")
return count.value+1;
})
...
<div @click="count++">点我增加{{count}}</div>
<div>计算属性的值1={{publishedBooksMessage}}</div>
<div>计算属性的值2={{publishedBooksMessage}}</div>
...
可以看到,count的变化会导致publishedBooksMessage的变化,并且页面初始化的时候,计算属性也只计算了一次,这个也是跟方法的区别,方法的话如果你div里面用了两次,那么肯定会执行两次,具体可以看
https://cn.vuejs.org/guide/essentials/computed.html
4、侦听器
计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。
import { ref ,computed,watch } from 'vue'
// 可以直接侦听一个 ref
watch(count, async (newQuestion, oldQuestion) => {
console.log("newQuestion="+newQuestion)
console.log("oldQuestion="+oldQuestion)
})
...
<div @click="count++">点我增加{{count}}</div>
...
具体看
https://cn.vuejs.org/guide/essentials/watchers.html
5、模版引用
虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作,但在某些情况下,我们仍然需要直接访问底层 DOM 元素。要实现这一点,我们可以使用特殊的 ref attribute:
<script setup>
import { useTemplateRef, onMounted } from 'vue'
// 第一个参数必须与模板中的 ref 值匹配
const input = useTemplateRef('my-input')
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="my-input" />
</template>
更多参考:https://cn.vuejs.org/guide/essentials/template-refs.html
组件上的 ref
模板引用也可以被用在一个子组件上。这种情况下引用中获得的值是组件实例:
使用了<script setup>
的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup>
的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露:
<script setup>
import { ref } from 'vue'
const count = ref(0)
function haha(){
count.value++;
}
defineExpose({
count,
haha
})
</script>
<template>
{{count}}
</template>
然后父组件
<script setup>
import { ref,useTemplateRef } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
const hello = useTemplateRef("helloWorld");
function add(){
console.log("hello.count",hello.value.count)
// hello.value.haha();
hello.value.count++;
}
</script>
<template>
<HelloWorld ref="helloWorld"></HelloWorld>
<button @click="add()">点击</button>
</template>
上面可以通过子组件的ref调用方法以及修改值。这里不需要再解包,这个点有点奇怪!
6、组件的定义和使用
定义组件
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
使用组件
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld></HelloWorld>
</template>
参考:https://cn.vuejs.org/guide/essentials/component-basics.html
三、组件进阶知识点
我们在上面已经学过来组件的基本定义和使用,下面来学一些组件的高级用法,具体可见官网
https://cn.vuejs.org/guide/components/registration.html
1、父组件向子组件传递参数
这里用的是props,一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props
在子组件中定义
<script setup>
const props = defineProps(['foo'])
</script>
<template>
<div>{{props.foo}}</div>
</template>
此时,父组件只需要
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld foo="123"></HelloWorld>
</template>
即可把123传过去,当然可以用:foo=”foo”动态变化。
2、子组件向父组件传递参数
这个要用组件事件
在组件的模板表达式中,可以直接使用 $emit 方法触发自定义事件 (例如:在 v-on 的处理函数中):
<script setup>
</script>
<template>
<button @click="$emit('someEvent','123')">Click Me</button>
</template>
然后在父组件中
<script setup>
import HelloWorld from './components/HelloWorld.vue'
function callback(a){
console.log(a);
}
</script>
<template>
<HelloWorld @some-event="callback"></HelloWorld>
</template>
这样就可以把123从子组件传递到父组件了。
声明触发的事件
#我们在 <template> 中使用的 $emit 方法不能在组件的 <script setup> 部分中使用,但 defineEmits() 会返回一个相同作用的函数供我们使用:
<script setup>
const emit = defineEmits(['some-event'])
function send(){
emit("some-event","123")
}
</script>
<template>
<button @click="send()">Click Me</button>
</template>
父组件一样可以拿到数据。
3、通过组件 v-model来实现双向绑定传值
v-model 可以在组件上使用以实现双向绑定。
子组件
<script setup>
const model = defineModel()
</script>
<template>
<div>子组件中的值: {{ model }}</div>
<button @click="model++">子组件中的按钮</button>
</template>
父组件
<script setup>
import { ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
const countModel =ref(0)
</script>
<template>
<HelloWorld v-model="countModel"></HelloWorld>
<div>父组件中的值{{countModel}}</div>
<button @click="countModel++">父组件中的按钮</button>
</template>
效果是,我们不管点击父组件中的按钮还是子组件中的按钮修改了值,父子都是同时变化。
感叹:这方法好啊
https://cn.vuejs.org/guide/components/v-model.html
4、插槽
在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。此时就需要用到插槽slot.
官方文档:https://cn.vuejs.org/guide/components/slots.html
子组件
<script setup>
</script>
<template>
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口 -->
</button>
</template>
父组件
<script setup>
import { ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
const count =ref(0)
</script>
<template>
<HelloWorld>
我是插槽内容{{count}}
</HelloWorld>
<button @click="count++">add</button>
</template>
点击父组件的add,插槽内容是会变化的应为,插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。
父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。
匿名插槽
上面的例子就是你们插槽,也是默认插槽
具名插槽
插槽可以带有名字
子组件
<script setup>
</script>
<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
父组件
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld>
<template v-slot:header>
<h1>具名插槽显示内容header</h1>
</template>
<template #default>
默认插槽显示内容
</template>
<template #footer>
<p>具名插槽显示内容footer</p>
</template>
</HelloWorld>
</template>
v-slot 有对应的简写 #,因此<template v-slot:header>
可以简写为<template #header>
。其意思就是“将这部分模板片段传入子组件的 header 插槽中”。
当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非<template>
节点都被隐式地视为默认插槽的内容。所以上面也可以写成:
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld>
123
<template v-slot:header>
<h1>具名插槽显示内容header</h1>
</template>
默认插槽显示内容
<template #footer>
<p>具名插槽显示内容footer</p>
</template>
</HelloWorld>
</template>
插槽获取子组件的内容
插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的,但是我们假设想用子组件的内容来渲染插槽怎么办呢,此时就需要用作用域插槽。
我们也确实有办法这么做!可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes:
<script setup>
import { ref } from 'vue'
const greetingMessage = ref("子组件的数据")
</script>
<template>
<slot :text="greetingMessage" :count="1"></slot>
</template>
当需要接收插槽 props 时,默认插槽和具名插槽的使用方式有一些小区别。下面我们将先展示默认插槽如何接受 props,通过子组件标签上的 v-slot 指令,直接接收到了一个插槽 props 对象:
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld v-slot="slotProps">
{{slotProps.text}},{{slotProps.count}}
</HelloWorld>
</template>
具名插槽其实也差不多
<script setup>
import { ref } from 'vue'
const greetingMessage = ref("子组件的数据")
</script>
<template>
<slot name="abc" :text="greetingMessage" :count="1"></slot>
</template>
父组件
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld #abc="slotProps">
{{slotProps.text}},{{slotProps.count}}
</HelloWorld>
</template>
5、依赖注入
通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。想象一下这样的结构:有一些多层级嵌套的组件,形成了一棵巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦。
参考文档:https://cn.vuejs.org/guide/components/provide-inject.html
provide 和 inject 可以帮助我们解决这一问题 。一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
Provide (提供)
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
<template>
{{message}}
</template>
Inject (注入)
<script setup>
import { provide } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
provide('message','hello!')
</script>
<template>
<HelloWorld></HelloWorld>
</template>
如果注入的是个ref
<script setup>
import { ref,provide } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
const count = ref(0)
provide('message',count)
</script>
<template>
<HelloWorld></HelloWorld>
<button @click="count++">点击</button>
</template>
父组件值变化后,子组件中也会同时变化。
子组件修改值,父组件也会变化
<script setup>
import { ref,provide } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
const count = ref(0)
provide('message',count)
</script>
<template>
{{count}}
<HelloWorld></HelloWorld>
<button @click="count++">点击</button>
</template>
思考:这岂不是也是一种父子之间传递值的好方法?
四、有用的API
1、生命周期钩子
参考:https://cn.vuejs.org/api/composition-api-lifecycle.html
2、nextTick()
等待下一次 DOM 更新刷新的工具方法。
当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。
<script setup>
import { ref, nextTick } from 'vue'
const count = ref(0)
async function increment() {
count.value++
// DOM 还未更新
console.log(document.getElementById('counter').textContent) // 0
await nextTick()
// DOM 此时已经更新
console.log(document.getElementById('counter').textContent) // 1
}
</script>
<template>
<button id="counter" @click="increment">{{ count }}</button>
</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(父<->子)