ref
vue3中的数据默认都是不具备响应式的,想要把数据变成响应式就需要做处理
ref用于处理基础数据类型,reactive用于处理引用类型的数据
//vue3中,所有方法都是按需引入的,需要什么自己手动引入
import { computed, defineComponent ,reactive,ref,toRef, toRefs} from 'vue';
setup() {
const obj = reactive({ //处理引用类型数据,使其变成响应式
msg: 0,
count: 1,
})
const info=ref('nihao') //用ref包裹使其变成响应式数据
const double= computed(() => { return obj.count * 2 })
const change = () => { obj.msg++;info.value='hello' }
//如果是对象,我们要把数据return出去为了方便书写,一般我们要把对象直接解构
//直接解构出去不是响应式数据,会造成页面数据不更新,这时候我们用torefs包裹再解构就可以了
const obj2=toRefs(obj)
return { //最后要吧数据和方法return出去
...obj2,double,change,info
}
}
watch的用法
//用法一 ,监听一个值
//第一个参数传要简单的数据,这个数据变化了会执行第二个函数
// 函数里面默认记录监听数据的新值和旧值
watch(gretings, (nValue, oValue) => {
document.title = "updata" + gretings.value;
console.log(nValue, oValue);
});
// 用法二 监听多个值,
//这个时候第一个参数可以是数组,会监听数组里每一个值的变化
setup() {
const data = reactive({
count: 0,
});
const gretings = ref("");
const datas = toRefs(data);
const updataGretings = () => {
gretings.value += "hello";
};
const addCount = () => {
data.count++;
};
//直接把data对象类型的数据放进来监听,我们发现它的值打印出来是proxy,
//那我们不要监听整个对象,就想监听对象里的某一个数据
watch([gretings, data], (nValue, oValue) => {
document.title = "updata" + gretings.value;
console.log("新", nValue);
console.log("旧", oValue);
});
return {
...datas,
updataGretings,
addCount,
};
},
//监听对象里的某一个数据
setup() {
const data = reactive({
count: 0,
});
const gretings = ref("");
const datas = toRefs(data);
const updataGretings = () => {
gretings.value += "hello";
};
const addCount = () => {
data.count++;
};
//监听对象里的某一个对象,我们可以用箭头函数的写法
watch([gretings, ()=>data.count], (nValue, oValue) => {
document.title = "updata" + gretings.value;
console.log("新", nValue);
console.log("旧", oValue);
});
return {
...datas,
updataGretings,
addCount,
};
},
watch监听对象中的某一个值变化,完美拿到变化值
一个小案例
实现点击页面 显示坐标
setup() {
let x = ref(0);
let y = ref(0);
const updataMouse = (e: MouseEvent) => {
x.value = e.pageX;
y.value = e.pageY;
};
onMounted(() => {
// 挂载完毕执行
document.addEventListener("click", updataMouse);
});
onUnmounted(() => {
// 实例卸载成功后移除点击事件
document.removeEventListener("click", updataMouse);
});
return {
updataMouse,
x,
y,
};
},
vue3最伟大之处,组件抽离,实现逻辑复用,
把刚刚那个小案例,封装成一个hooks,调用的时候引入
注意一般我们自定义的hooks都要用use开头命名
新建 useMounse.ts 文件
import { ref , onMounted,onUnmounted} from 'vue'
function useMounse() {
const x = ref(0);
const y = ref(0);
const updataMouse = (e: MouseEvent) => {
x.value = e.pageX;
y.value = e.pageY;
};
onMounted(() => {
// 挂载完毕执行
document.addEventListener("click", updataMouse);
});
onUnmounted(() => {
// 实例卸载成功后移除点击事件
document.removeEventListener("click", updataMouse);
});
return {
x,y
}
}
export default useMounse
把刚刚封装的代码引入使用
// 引入封装好的hooks
import useMounse from "../hooks/useMounse";
export default defineComponent({
name: "HomeView",
setup() {
// 取出x , y 一样能实现效果,而且代码更简洁
const { x, y } = useMounse();
return {
x,
y,
};
},
});
封装一个异步请求,加载状态
// 封装一个自定义 hooks 来请求图片
import { reactive,toRefs} from 'vue'
import axios from 'axios'
function useLoading(url:string) {
const state = reactive({
loading: true,
loaded: false,
result: null,
error:null
})
axios.get(url).then((e) => {
state.result = e.data
state.loading = false
state.loaded = true
}).catch((e) => {
state.error = e
})
const states = toRefs(state)
return {
...states
}
}
export default useLoading
vue组件中使用
<template>
<div class="home">
<!-- 请求没回来就显示加载中 -->
<h1 v-if="loading">加载中....</h1>
<!-- 加载完毕后 把图片显示出来 -->
<img v-if="loaded" :src="result.message" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
// 引入封装好的hooks
import useUrlLoading from "../hooks/useLoading";
export default defineComponent({
name: "HomeView",
setup() {
const { result, loading, loaded } = useUrlLoading(
//传入url
"https://dog.ceo/api/breeds/image/random"
);
return {
result,
loading,
loaded,
};
},
});
</script>
把小案例改造成支持TS类型推断
// 利用泛型 对接口返回值定义类型
// hooks页面
import { reactive,toRefs} from 'vue'
import axios from 'axios'
// 使用ts改造,让返回结构有类型提示
// 这里使用泛型
function useLoading<T>(url:string) {
const state = reactive({
loading: true,
loaded: false,
result:<T|null> null, //result初始值还是设置成null 类型是传入的泛型或者null
error:null
})
axios.get(url).then((e) => {
state.result = e.data
state.loading = false
state.loaded = true
}).catch((e) => {
state.error = e
})
const states = toRefs(state)
return {
...states
}
}
export default useLoading
vue组件页面
<template>
<div class="home">
<!-- 请求没回来就显示加载中 -->
<h1 v-if="loading">加载中....</h1>
<!-- 加载完毕后 把图片显示出来 -->
<!-- 返回值要判断,如果不是null我们才让它dianmessage -->
<img v-if="loaded" :src="result?.message" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
// 引入封装好的hooks
import useUrlLoading from "../hooks/useLoading";
export default defineComponent({
name: "HomeView",
setup() {
// 申明泛接口
interface useResult {
// 接口返回什么数据就申明什么数据
message: string;
status: string;
}
const { result, loading, loaded } = useUrlLoading<useResult>(
//传入url
"https://dog.ceo/api/breeds/image/random"
);
return {
result,
loading,
loaded,
};
},
});
</script>
setup语法糖
defindefineEmits,defineProps
setup语法糖模式下 不需要引入,直接使用发射事件
子组件中
//子组件中
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<input type="button" value="子级按钮" @click="jumpClick" />
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const num = ref(10);
//直接使用无需引入
//父传子
defineProps({
msg: String,
});
// 声明泛型接口,约束类型
interface Emits {
(event: 'clickChange', num: number): void;
}
//子传父
// 先定义emit 不能直接使用 defindefineEmits
const emit = defineEmits<Emits>();
const jumpClick = function () {
emit('clickChange', num.value);
};
</script>
父组件中
<template>
<div class="home">
<HelloWorld
msg="Welcome to Your Vue.js + TypeScript App"
@click-change="accept"
/>
<button @click="change">点我</button> <br />
{{ obj.msg }} <br />
{{ double }} <br />
{{ info }}
</div>
</template>
<script lang="ts" setup>
//组件导入直接使用 无需注册,数据直接使用,无需返回
import { computed, reactive, ref, toRef, toRefs } from 'vue';
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
const obj = reactive({
msg: 0,
count: 1,
});
const info = ref('nihao');
const double = computed(() => {
return obj.count * 2;
});
const change = () => {
obj.msg++, (info.value = 'hello');
};
//接受子组件传过来的值
const accept = (hello: number) => {
console.log(hello);
};
</script>
defineExpose暴露自己的属性供父组件使用
子组件中
// 想让父组件引用子组件ts不提示报错,那我们就要什么个数据接口来限制类型
export interface sonApi {
num: number;
outPush: () => void; //定义函数类型无返回值
}
const num = ref(100);
const outPush = () => { // 函数类型定义
console.log('hello word');
};
//把我们想暴露出去的数据方法写这里
defineExpose({
num,
outPush,
});
父组件中
// 获取子组件值的声明,值开始必须是null,名字要跟模板上ref='child'一致
// 接口定义联合类型,null或者sonApi,这样下面就不会报错
const child = ref<sonApi | null>(null);
// 挂载成功后获取子组件暴露出来的值
onMounted(() => {
console.log(child.value?.num);
// 可以传函数
child.value?.outPush();
});