本节重点:
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>
若碰到其他的问题 可以私信我 一起探讨学习
如果对你有所帮助还请 点赞
收藏 谢谢~!
关注收藏博客 持续更新中