目录
2.2:子组件需要注册到根组件app.vue中,分为导入、注册、使用
4.3:reactive函数实现响应式数据(只支持对象类型)
8.1:普通标签的ref属性,用来屏蔽标签的id相同情况下的错误
[9:props 父组件向子组件传递数据,子组件不能修改](#9:props 父组件向子组件传递数据,子组件不能修改)
1:创建自己的第一个vue
1.1:项目结构
首先我们知道了vue的项目结构

1.2:创建自己的第一个vue3项目
首先删除src下的所有文件,自己创建main.ts和App.vue
1.2.1:main.ts项目入口
TypeScript
// 第一步:创建main.ts文件,作为项目的入口文件
//1:引入Vue的createApp函数和根组件App.vue,作用是创建Vue应用实例和挂载根组件
import { createApp } from 'vue'
//2:引入根组件App.vue,作为应用的根组件
import App from './App.vue'
//3:创建Vue应用实例
const app11 = createApp(App)
//4:挂载应用实例到index.html中的#app元素上
app11.mount('#app')
1.2.2:App.vue项目根组件
html
<!-- 根组件 每一个vue组件都是从这里开始的,文件扩展名是.vue -->
<!-- 一个合法的Vue组件有三部分组成 :模板template、脚本script、样式style-->
<template>
<div id="app">
<h1>Hello Vue3!</h1>
</div>
</template>
<!-- 基于ts -->
<script lang="ts">
// 默认导出 在main.ts中会导入这个组件
export default {
// 影响到VUE浏览器插件的显示
name: 'App',
setup() {
// 组件的逻辑代码
}
}
</script>
<!-- scoped 局部样式 -->
<style scoped>
/* #.id .class 设置样式*/
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
background-color: #f5f5f5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
1.2.3:启动项目

2:添加子组件

2.1:子组件person
html
<!-- person子组件 -->
<template>
<div class="person">
<h1>我是person组件</h1>
<h2>姓名:{{ name }}</h2>
<h2 class="phone"></h2>
<button @click="showPhone()">显示联系电话@click</button>
<button v-on:click="showPhone()">显示联系电话v-on:click</button>
</div>
</template>
<script lang="ts">
export default {
// vue的插件组件显示名字
name: 'Person',
//vue3中使用setup函数来定义组件的逻辑代码 ,组件的逻辑代码。初始化执行一次
//setup只会执行一次
setup() {
//准备数据
let name: string = '张三';
let phone: number = 1234567890;
function showPhone() {
alert(phone)
};
//返回数据给模板使用
return {
name: name,//暴露参数
showPhone: showPhone //暴露方法
};
}
}
</script>
<style lang="css" scoped>
.person {
border: 1px solid #ccc;
padding: 20px;
margin: 20px;
}
</style>
2.2:子组件需要注册到根组件app.vue中,分为导入、注册、使用
TypeScript
<!-- 根组件 每一个vue组件都是从这里开始的,文件扩展名是.vue -->
<!-- 一个合法的Vue组件有三部分组成 :模板template、脚本script、样式style-->
<template>
<div id="app">
<h1>Hello Vue3!</h1>
<!-- 3:使用子组件 -->
<person></person>
</div>
</template>
<!-- 基于ts -->
<script lang="ts">
// 1:导入子组件
import Person from './componnets/person.vue'
// 默认导出 在main.ts中会导入这个组件
export default {
//2:注册子组件
components:{Person}
}
</script>
<!-- scoped 局部样式 -->
<style scoped>
/* #.id .class 设置样式*/
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
background-color: #f5f5f5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
3:子组件分析setup
3.1:setup函数
TypeScript
<!-- person子组件 -->
<template>
<div class="person">
<h1>我是person组件</h1>
<h2>姓名:{{ name }}</h2>
<h2 class="phone"></h2>
<button @click="showPhone()">显示联系电话@click</button>
<button v-on:click="showPhone()">显示联系电话v-on:click</button>
</div>
</template>
<script lang="ts">
export default {
// vue的插件组件显示名字
name: 'Person',
//vue3中使用setup函数来定义组件的逻辑代码。初始化执行一次
setup() {
//准备数据
let name: string = '张三';
let phone: number = 1234567890;
//定义方法
function showPhone() {
alert(phone)
};
//returu 返回数据给模板使用,这里是对象
return {
name: name,//暴露参数
showPhone: showPhone //暴露方法
};
// return function(){
// return "渲染函数,会覆盖掉person组件中template中的所有模板内容"
// };
}
}
</script>
<style lang="css" scoped>
.person {
border: 1px solid #ccc;
padding: 20px;
margin: 20px;
}
</style>
3.2:setup函数语法糖
3.2.1:自动注册app.vue
TypeScript
<!-- 根组件 每一个vue组件都是从这里开始的,文件扩展名是.vue -->
<!-- 一个合法的Vue组件有三部分组成 :模板template、脚本script、样式style-->
<template>
<div id="app">
<h1>Hello Vue3!</h1>
<!-- 3:使用子组件 -->
<person></person>
<cat></cat>
</div>
</template>
<!-- 基于ts setup语法糖 不需要注册了 只需导入和使用组件-->
<script lang="ts" setup>
// 1:导入子组件
import Person from './componnets/person.vue'
import Cat from './componnets/cat.vue';
// // 默认导出 在main.ts中会导入这个组件
// export default {
// //2:注册子组件
// components:{Person,Cat}
// }
</script>
4:ref响应式数据(用于axios的ajax请求)

4.1:默认不是响应式,修改不会改变
TypeScript
<template>
<div>
<h1>狗狗</h1>
<h1>名字:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<button v-on:click="changename">修改名字不会改变,需要响应式</button>
<button v-on:click="changeage">修改age不会改变,需要响应式</button>
</div>
</template>
<script lang="ts" setup>
let name = "小黑"
let age = 0;
function changename() {
name = "小黑1";
}
function changeage() {
age++;
}
</script>
<style></style>
4.2:ref函数实现响应式数据(原始类型)
TypeScript
<template>
<div>
<h1>狗狗</h1>
<h1>名字:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<button v-on:click="changename">修改名字不会改变,需要响应式</button>
<button v-on:click="changeage">修改age不会改变,需要响应式</button>
</div>
</template>
<script lang="ts" setup>
// 1:导入vue框架的ref函数,让数据具有响应式的效果,需要包裹原始类型
import { ref } from 'vue';
//2:使用ref包裹 就有了响应式
//执行ref函数返回的是refimpl对象
//使用name.value 返回
// {
// "dep": {
// "version": 0,
// "sc": 0,
// "__v_skip": true
// },
// "__v_isRef": true,
// "__v_isShallow": false,
// "_rawValue": "小黑",
// "_value": "小黑"
// }
let name = ref("小黑")
let age = ref(0);
console.log(name);
console.log(age);
function changename() {
//这里修改值,实质上是在ref函数中的refimpl对象set设置了新的值,然后渲染新的值
name.value = "小黑1";
}
function changeage() {
age.value++;
}
</script>
<style></style>
4.3:reactive函数实现响应式数据(只支持对象类型)
底层是代理对象
TypeScript
<template>
<div>
<h1>狗狗</h1>
<h1>名字:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<button v-on:click="changename">修改名字不会改变,需要响应式</button>
<button v-on:click="changeage">修改age不会改变,需要响应式</button>
<hr>
<h1>user.name1:{{ user.name1 }}</h1>
<h1>user.age1:{{ user.age1 }}</h1>
<h1>user.address.city:{{ user.address.city }}</h1>
<button v-on:click="changeUser">修改对象的age不会改变,需要响应式</button>
<button v-on:click="changeUserAddress">修改user的address,需要响应式</button>
</div>
</template>
<script lang="ts" setup>
// 1:导入vue框架的ref函数,让数据具有响应式的效果,需要包裹原始类型
import { ref } from 'vue';
import { reactive } from 'vue';
//2:使用ref包裹 就有了响应式
//执行ref函数返回的是refimpl对象
//使用name.value 返回
// {
// "dep": {
// "version": 0,
// "sc": 0,
// "__v_skip": true
// },
// "__v_isRef": true,
// "__v_isShallow": false,
// "_rawValue": "小黑",
// "_value": "小黑"
// }
let name = ref("小黑")
let age = ref(0);
// reactive包裹代理对象
let user = reactive({
name1: "哈哈",
age1: 20,
address: {
city: "洛阳",
street: "枫叶街"
}
})
//类型"number"的参数不能赋给类型"object"的参数。ts-plugin(2345) reactive只支持对象
//let a=reactive(0);
console.log(name);
console.log(age);
function changename() {
//这里修改值,实质上是在ref函数中的refimpl对象set设置了新的值,然后渲染新的值
name.value = "小黑1";
}
function changeage() {
age.value++;
}
function changeUser() {
//底层原理:先找到用户对应的代理对象Proxy
// //你表面上访问的是用户对象的用户名
// //实际上底层访问的是代理对象上的用户名属性。
// //访问代理对象age属性的时候,底层会做两件事:
// //第一件事:修改目标对象上的age属性的值。
// //第二件事:将模板页面重新渲染。
user.age1++;
}
function changeUserAddress() {
//底层原理:先找到用户对应的代理对象Proxy
// //你表面上访问的是用户对象的用户名
// //实际上底层访问的是代理对象上的用户名属性。
// //访问代理对象age属性的时候,底层会做两件事:
// //第一件事:修改目标对象上的age属性的值。
// //第二件事:将模板页面重新渲染。
let num=1;
user.address.city = "洛阳" + (num++);
}
</script>
<style></style>
5:vue指令
5.1:v-bind绑定属性
TypeScript
template>
<div>
<h1>vue指令1:
1:所有的指令需要写在template中
2:v-开头
v-bind指令:
1:v-bind:将setup函数暴露的数据,绑定到标签的属性
</h1>
<h2>v-bing指令</h2>
<img v-bind:src="imgObj.src" :alt="imgObj.alt">
<button @click="changeImg">改变图片1</button>
<button @click="changeImg" v-bind:disabled="haha">改变图片2</button>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
let haha=false;
let imgObj = reactive({
src: "https://picsum.photos/id/1036/800/500",
alt: "没有图片"
})
function changeImg() {
imgObj.src = "https://picsum.photos/id/1048/800/500"
}
</script>
<style></style>
5.2:v-model双向数据绑定
修改脚本数据,页面数据更新,修改页面数据,脚本数据也会更新
TypeScript
名字1:<input type="text" v-model="inputname">
<br>{{ inputname }}
<button v-on:click="changeinputname1">修改名字</button>
let inputname = ref("张三")
function changeinputname1() {
inputname.value = "lili"
}
5.3:v-for循环解析数据
TypeScript
<div>
<h2>v-for</h2>
<ul>
<li v-for="(person,index) in listPerson" :key="person.id">
ID:{{person.id}},名字: {{person.name}}, 年龄:{{person.age}} ,index {{index}}
</li>
</ul>
<ul>
<li v-for="(value,name,index) in persons" :key="name">
属性值:{{value}},属性名字: {{name}}, 属性索引:{{index}}
</li>
</ul>
</div>
let listPerson = [
{ id:1,name: '哈哈1', age: 21 },
{ id:2,name: '哈哈2', age: 22 },
{ id:3,name: '哈哈3', age: 23 },
]
let persons={id:1,name: '哈哈1', age: 21}
6:计算属性computed(只读不能修改)有缓存机制
TypeScript
<div>
<h1>计算属性</h1>
姓氏:<input type="text" v-model="firstName">{{ firstName }}
<br>
名字:<input type="text" v-model="lastName">{{ lastName }}
<br>
全名:{{ firstName.slice(0, 1).toLocaleUpperCase() + firstName.slice(1) }}-{{ lastName }}
全名方法1:{{ fullName1() }}
全名方法2:{{ fullName1() }}
计算属性1:{{ fullName2 }}
计算属性2:{{ fullName2 }}
</div>
let persons = { id: 1, name: '哈哈1', age: 21 }
let firstName = ref("zhang")
let lastName = ref("san")
const fullName1 = (() => {
console.log("方法,2次调用,就会调用2次")
return (
firstName.value.slice(0, 1).toLocaleUpperCase() +
firstName.value.slice(1) +
'-' +
lastName.value
);
});
//computed 计算属性,返回值
const fullName2 = computed(() => {
console.log("计算属性只会调用一次,当之发生变化的时候")
return (
firstName.value.slice(0, 1).toLocaleUpperCase() +
firstName.value.slice(1) +
'-' +
lastName.value
);
});
7:watch监视用来数据修改后执行回调函数

TypeScript
<template>
<div>
<h1>watch监视</h1>
<h1>ref计数器:{{ count }}</h1>
<button @click="addone">修改ref响应数据的基本类型</button>
<h1>ref对象:{{ user }},{{ user.age }},{{ user.name }}</h1>
<button @click="changeName">修改ref响应数据的对象name</button>
<button @click="changeAge">修改ref响应数据的对象age</button>
<button @click="changeUser">修改ref响应数据的对象user</button>
</div>
</template>
<script lang="ts" setup>
import { ref, watch,reactive } from "vue";
let count = ref(0);
let addone = function changCount() {
count.value++;
}
//1:监视ref响应数据的基本类型
//watch(被监视数据,回掉函数,配置项),这里监视count
const stopWatch = watch(count, (newValue, oldValue) => { //a是新的值 b是旧的值
console.log("count被修改了", newValue, oldValue);
if (newValue >= 5) {
stopWatch();//调用停止监视
}
}, { immediate: true })//immediate:true 页面打开就开始监视,就会执行回掉函数
//2:监视ref响应数据的对象
//watch(被监视数据,回掉函数,配置项),这里监视count
let user = ref({
name: "哈哈",
age: 10
})
function changeName() {
user.value.name = "11";
}
function changeAge() {
user.value.age++;
}
function changeUser() {
// Object.assign(user,{ //用于reactive监视
// name: "张三11",
// age: 100
// })
user.value = {
name: "张三11",
age: 100
}
}
// 默认 只有changeUser方法修改了user才会被监视,单单修改name和age不会被监视
watch(user, (newValue, oldValue) => {
console.log("user被修改了", newValue, oldValue);
}, {deep:true}) //这里开启深度监视,才能监视到name和age的修改
</script>
<style></style>
8:标签的ref属性
8.1:普通标签的ref属性,用来屏蔽标签的id相同情况下的错误
TypeScript
父组件标签
<template>
<div class="fu">
<!-- ref的名字一样。也不影响 -->
<h1 ref="h1">父组件1</h1>
<button @click="getfu">父元素点击</button>
<zi></zi>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import zi from './zi.vue';
let h1 = ref();
function getfu() {
console.log("父", h1.value) //<h1>父组件1</h1>
}
</script>
<style>
.fu {
background-color: blue;
}
</style>
子组件标签
<template>
<div class="zi">
<h1>子组件1</h1>
<!-- ref的名字一样。也不影响 -->
<button ref="h1">子组件点击</button>
<button @click="getzibut">子组件点击获取</button>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
//一下代码在组件创建过程中执行
let h1=ref();//zibut必须和ref="h1"的名字一样
function getzibut(){
console.log(h1.value) //<button>子组件点击</button>
}
</script>
<style>
.zi {
background-color: aqua;
}
</style>
8.2:组件标签的ref属性,用来子组件向父组件传递数据
TypeScript
父组件
<template>
<div class="fu">
<!-- ref的名字一样。也不影响 -->
<h1 ref="h1">父组件1</h1>
<button @click="getfu">父元素点击</button>
<!-- 用ref="muzi"标记子组件 创建整个子组件实例 -->
<zi ref="muzi"></zi>
<button @click="getfushili">父元素点击获取子组件实例</button>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import zi from './zi.vue';
let h1 = ref();
//这行代码核心 获取到子组件实例,需要子组件暴露
let muzi = ref();
function getfu() {
console.log("父", h1.value) //<h1>父组件1</h1>
}
function getfushili() {
//子组件实例 Proxy(Object) {__v_skip: true}[[Handler]]:
// Object[[Target]]: Proxy(Object)[[IsRevoked]]: false
console.log("子组件实例", muzi.value)
console.log("普通数据1", muzi.value.a)
console.log("响应数据1", muzi.value.b)
muzi.value.b.age=100; //修改数据,子组件数据也会改变
muzi.value.myfunction();
}
</script>
<style>
.fu {
background-color: blue;
}
</style>
子组件
<template>
<div class="zi">
<h1>子组件1</h1>
<!-- ref的名字一样。也不影响 -->
<button ref="h1">子组件点击</button>
<button @click="getzibut">子组件点击获取</button>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
//一下代码在组件创建过程中执行
let h1 = ref();//zibut必须和ref="h1"的名字一样
function getzibut() {
console.log(h1.value) //<button>子组件点击</button>
}
//子组件普通数据和响应式数据
let a = "子组件普通数据";
let b = reactive({
name: "haha",
age: 20
});
function myfunction() {
console.log("子组件函数执行了");
}
//暴露子组件的数据和函数,以便父组件使用
defineExpose({
a:a,
b:b,
myfunction:myfunction
})
</script>
<style>
.zi {
background-color: aqua;
}
</style>
9:props 父组件向子组件传递数据,子组件不能修改
TypeScript
父组件
<template>
<div class="fu">
<h1>父组件1</h1>
<!-- 父组件通过props传递数据 -->
<zi name="jack" :age="age" :list1="lists"></zi>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import zi from './zi.vue';
let age = ref(20)
let lists = ref([
{ name: "aa", age: 1 },
{ name: "bb", age: 2 }
])
</script>
<style>
.fu {
background-color: blue;
}
</style>
子组件
<template>
<div class="zi">
<h1>子组件1</h1>
<h1>名字:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<ul>
<li v-for="item in list1" :key="item.id">
id: {{ item.id }}
name: {{ item.name }}
age: {{ item.age }}
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
// 全局函数,数组接受父组件数据
defineProps(["name", "age", "list1"])
// console.log("name", name);
// console.log("age", age);
</script>
<style>
.zi {
background-color: aqua;
}
</style>
10:vue3的生命周期
1:创建 2:挂载 3:更新 4:卸载

TypeScript
<template v-if="flag">
<div>
<h1>组件的生命周期</h1>
<h2>计数器:{{num}}</h2>
<button @click="changeNum">点击</button>
</div>
</template>
<script lang="ts" setup>
import { onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated,ref } from 'vue';
let num=ref(0);
let flag=ref(true);
function changeNum(){
num.value++;
}
// 生命周期:创建执行一次
console.log("生命周期:创建")
//生命周期:挂载
onBeforeMount(()=>{
console.log("挂载前")
})
onMounted(()=>{
console.log("挂载后")
})
//生命周期:修改
onBeforeUpdate(()=>{
console.log("修改前")
})
onUpdated(()=>{
console.log("修改后")
})
//生命周期:修改
onBeforeUnmount(()=>{
console.log("销毁前")
})
onUnmounted(()=>{
console.log("销毁后")
})
</script>
11:组合式API的Hooks(逻辑复用)
11.1:使用hooks之前
TypeScript
<template>
<div>
<h1>不使用hooks</h1>
<h1>数字{{ num }}</h1>
<button @click="add">计数器+1</button>
<div>
<img v-for="item in images" :key="item" :src="item" alt="加载失败">
<button @click="getImage">添加图片</button>
</div>
</div>
</template>
<script lang="ts" setup>
import axios from 'axios';
import { ref } from 'vue';
let num = ref(0);
function add() {
num.value++;
}
//'https://api.btstu.cn/sjbz/api.php?format=json'
let images = ref(["https://picsum.photos/800/600"]);
async function getImage() {
// let response=await axios.get("https://api.btstu.cn/sjbz/api.php?format=json");
// let josn=response.data;
// console.log(josn)
const res = await axios.get('https://httpbin.org/image/png', {
responseType: 'blob'
})
const url = URL.createObjectURL(res.data)
console.log('图片blob地址:', url)
images.value.push(url);
}
</script>
<style></style>
11.2:使用hook之后
新建userCount.ts和userImg.ts,做到代码复用
TypeScript
import { ref } from "vue";
//默认导出
export default function () {
//数据
let num = ref(0);
//方法
function add() {
num.value++;
}
//返回值
return { num, add };
}
import { ref } from "vue";
//默认导出
export default function () {
//数据
let num = ref(0);
//方法
function add() {
num.value++;
}
//返回值
return { num, add };
}
<template>
<div>
<h1>不使用hooks</h1>
<h1>数字{{ num }}</h1>
<button @click="add">计数器+1</button>
<div>
<img v-for="item in images" :key="item" :src="item" alt="加载失败">
<button @click="getImage">添加图片</button>
</div>
</div>
</template>
<script lang="ts" setup>
import useCount from '../hooks/useCount';
import userImg from '../hooks/userImg';
//调用钩子函数
const { num, add } = useCount();
const { images, getImage } = userImg()
</script>
<style></style>