本节重点:
vue3最佳实践
ref
reactive
computed
watch、watchEffect
讲解重点之后下面会带大家开发一个页面(表单+表格),之后会有一个TodoList的小练习,文末附有小练习的代码参考。跟着练习一定带你可以上手开发vue项目。
我在gitcode上也建了了对应的vue学习项目,会跟随我的专栏进行定期代码更新,欢迎克隆下载GitCode - 全球开发者的开源社区,开源代码托管平台
响应式基础
vue3最佳实践
-
单文件组件(即 *.vue 文件)
-
组合式API+TypeScript
注:组合式API一般都会配合
<script setup>
语法使用,<script setup>
是在单文件组件中使用组合式API的编译时语法糖(setup了解)。
<script setup> VS <script>
- 更少的样板内容,更简洁的代码。
- 能够使用纯 TypeScript 声明 props 和自定义事件。
- 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
- 更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)
Vue3写法最佳实践:单文件组件+ 组合式API+
<script setup>
语法糖+TypeScript
演练场
API
组合式API
Composition API是V3和V2.7的内置功能,具体是指一系列API的集合,可以使用函数的方式书写vue组件它包括如下api:
-
响应式API(ref、reactive等)
-
生命周期钩子(onMounted、onUnmounted等)
-
依赖注入(provide、inject)
响应式基础
ref
ref 函数用来创建响应式的数据。它接收一个参数,并返回一个包含该参数的响应式数据。
使用场景——
-
可以使用ref()方法创建一个任何值类型的响应式。它将传入的参数包装成一个带.value属性的ref对象。声明Object类型时内部通过reactive来转为代理对象。
-
ref能创造一种对任意值的“引用”,并且不丢失响应性。
注意:
- 可以被复写、赋值给某一个局部变量,解构或者被传入函数时,不会丢失响应性。
- 模板中调用ref,不需要添加.value
<!-- 页面功能 -->
<template>
<div class="zk_box">
<H3> 响应式对象被赋值给另一个本地变量时,本地变量调整不会影响响应式变量</H3>
<p>Count: {
{ count.name }}</p>
<button @click="increment">Increment</button>
<button @click="externalIncrement">externalIncrement</button>
<br/>
<p>Count: {
{ countObj.name }}</p>
<button @click="increment1">Increment1</button>
<button @click="externalIncrement1">externalIncrement</button>
</div>
</template>
<script setup>
import {ref, reactive} from 'vue';
const count = ref({
name: 0
});
const increment = () => {
count.value.name++; // 增加计数器的值
};
const externalIncrement = () => {
let oldValue = count;
oldValue.value = {
name: 100
}
};
const countObj = reactive({
name: 0
})
const increment1 = () => {
countObj.name++; // 增加计数器的值
};
const externalIncrement1 = () => {
let oldValue = countObj;
oldValue = {
name: 100
}
}
</script>
<style scoped>
.zk_box{
width: 80%;
height: 80%;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
</style>
reactive
reactive 函数用来创建响应式的数据。它接收一个参数,并返回一个包含该参数的响应式数据。
使用场景——
-
可以使用reactive() 函数创建一个响应式对象或数组。
-
reactive仅对对象类型有效(对象、数组、Map、Set),原始类型(string、number、boolean)无效。
注意:
- 不可用随意“替换”(复写)一个响应式对象,会导致对于初始应用的响应性连接丢失。
- 将响应式变量解构,之后修改值,不会影响原始响应式变量。
const state = reactive({ count: 0 })
// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++
由于reactive的部分局限性,官方建议:使用 ref() 作为声明响应式状态的主要 API
computed
computed 函数用来创建计算属性。它接收一个参数,并返回一个包含该参数的响应式数据。
使用场景——
-
常用于计算衍生值。 默认是只读的。 return返回的是一个计算属性ref,其他方法中可通过.value取值。
注意:
- 计算属性只做计算,不要在此处做异步请求或者更新DOM
<template>
<p>{
{bookObj.author}}:是否写过书?</p>
<span>{
{hasBook}}</span>
</template>
<script setup>
import{ reactive,computed}from 'vue'
const bookObj = reactive({
author:'张爱玲',
age:'40',
books:['book1','book2','book3']
});
// 定义一个计算属性
const hasBook = computed(()=>{
return bookObj.books.length > 0 ? 'Yes':'No'
})
</script>
watch 和 watchEffect
侦听器,跟踪某一个值或者多个值的变化之后处理某种操作。
使用场景——
计算属性不能做的事,可以通过侦听器进行,例如根据异步操作的结果修改另一个值。
-
watch 函数用来监听响应式数据的变化。它接收两个参数,并返回一个包含该参数的响应式数据。第一个参数可以是ref、响应式对象、getter函数、或者数组。
-
immediate :是否在侦听器创建时立即执行回调函数
-
deep :是否深度监听
-
-
watchEffect 立即执行且会自动跟踪回调的响应式依赖。主要应用有多了依赖项的侦听器,不需要传递监听源。
watch | watchEffect |
|
|
<template>
<button @click="handleIdChange">{
{todoId}}</button>
<button @click="reset">重置</button>
<div>
data:{
{data}}
</div>
</template>
<script setup>
import { ref, watch, watchEffect } from 'vue'
const todoId = ref(1)
const data = ref(null);
const handleIdChange = ()=>{
todoId.value++ ;
}
const reset = ()=>{
todoId.value = 1;
}
// 下面2个监听是等效的
watch(
todoId,
async () => {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json();
console.log(data.value);
}, {
immediate: true
}
)
watchEffect(async () => {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
</script>
依赖注入
-
provide()
-
inject()
provide() 提供一个值,可以被后代组件注入。 主要为解决props只能逐级透传的问题。父组件作为所有子组件的依赖提供者,后续所有的后代组件都可以使用
-
之前
-
现在
<script setup>
import { ref, provide } from 'vue'
// 提供响应式的值
const count = ref(0)
provide('count', count)
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location');
const count = inject('count');
</script>
<template>
<button @click="updateLocation">{
{ location }}</button>
<div>{
{count}}</div>
</template>
总结
setup 语法糖帮助更简洁地写代码,减少代码量。
ref 和 reactive 声明响应式数据,ref 可以创建任意值类型的响应式数据,使用需要添加.value,reactive 只能创建对象类型和数组类型的响应式数据。
computed、watch和watchEffect用于衍生数据的计算和数据侦听 。watchEffect 不需要指明监听的属性,它会在回调中用到哪个属性,就监听哪个属性。
provide 和 inject 用于依赖注入,解决组件层级过深传递数据的问题。
实战
结合之前准备阶段安装的demo项目,开发一个t表单+表格的页面功能。UI框架使用的是ant design vue。
准备阶段demo项目安装参考链接——零基础Vue学习1——Vue学习前环境准备-CSDN博客。
执行如下命令安装UI框架依赖:
-
antdv安装
pnpm i --save ant-design-vue@4.x
-
修改main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(Antd)
app.mount('#app')
先带大家写一个简单的页面结构:表单+表格,效果如下:
使用antdv的组件实现
1、本地新建一个模拟数据的文件,demoData.ts
// 表格数据
// 生成随机数据
const buildData = (size) => {
let data = [];
for (let i = 0; i < size; i++) {
data.push({
key: i,
name: `张小${i}`,
sex: Math.random() > 0.5 ? '男' : '女',
age: Math.floor(Math.random() * 30),
address: `西湖区湖底公园 ${i} 号`,
});
}
return data;
}
export const tableData = buildData(20);
export const tableColumn = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '性别',
dataIndex: 'sex',
key: 'sex',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '住址',
dataIndex: 'address',
key: 'address',
},
];
2、在App.vue文件中导入demoData文件
<template>
<a-form ref="formRef" :model="formData" layout="inline">
<a-form-item label="名称" name="name">
<a-input v-model:value="formData.name" placeholder="请输入名称"/>
</a-form-item>
<a-form-item label="性别" name="sex">
<a-select style="width:120px;" v-model:value="formData.sex" placeholder="请选择性别">
<a-select-option v-for="item in sexList" :value="item">{
{ item }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="query">查询</a-button>
<a-button @click="reset">重置</a-button>
</a-space>
</a-form-item>
</a-form>
<h1> {
{ title }}</h1>
<a-table :columns="columns" :data-source="tableDataSource">
</a-table>
</template>
<script setup>
import {ref, reactive, watch, computed, onMounted} from 'vue'
import {tableColumn, tableData} from "./views/demoData.ts" // 模拟数据
// 表单数据
const formData = reactive({
name: '',
sex: ''
})
// 表格数据
const columns = tableColumn;
const tableDataSource = ref([])
const title = computed(() => {
return `共计${tableDataSource.value.length}条`
})
// 性别列表
const sexList = ref([])
const getSexList = () => {
sexList.value = ['男', '女'];
formData.sex = sexList.value[0]
}
const formRef = ref()
/**
* 查询
*/
const query = () => {
// 根据formData过滤数据
let data = tableData;
if (formData.name) {
data = tableData.filter(item => {
return item.name.includes(formData.name);
});
}
if (formData.sex) {
data = data.filter(item => {
return item.sex === formData.sex;
})
}
tableDataSource.value = data;
}
/**
* 重置
*/
const reset = () => {
formRef.value.resetFields()
}
onMounted(() => {
getSexList()
})
// 表单数据变化时,查询
watch(formData, () => {
query()
})
</script>
<style scoped>
</style>
练习题
练习题参考答案:
<!--开发一个todo 列表应用-->
<template>
<div class="container">
<a-input placeholder="请输入待办事项" v-model:value="inputValue" @pressEnter="addTodo"/>
<a-list>
<a-list-item v-for="(item,index) in todoList" :key="item">
<a-checkbox v-model:checked="item.checked" :disabled="item.disabled"
@change="selectTodo(index)">
{
{ item.label }}
</a-checkbox>
<a-button size="small" @click="deleteTodo(index)">x</a-button>
</a-list-item>
</a-list>
</div>
</template>
<script setup>
import {ref, reactive} from 'vue';
const inputValue = ref('');
const todoList = reactive([]);
// 添加事件
const addTodo = () => {
if (inputValue.value.trim()) {
todoList.push({
label: inputValue.value,
value: inputValue.value,
disabled: false,
checked: false
});
inputValue.value = '';
}
};
// 复选框选中事件
const selectTodo = (index) => {
todoList[index].checked = !!todoList[index].checked;
todoList[index].disabled = todoList[index].checked;
};
// 删除事件
const deleteTodo = (index) => {
todoList.splice(index, 1);
};
</script>
<style scoped>
.container {
width: 50%;
margin: 10px auto;
}
</style>
若碰到其他的问题 可以私信我 一起探讨学习
如果对你有所帮助还请 点赞
收藏 谢谢~!
关注收藏博客 持续更新中