我是南城余!阿里云开发者平台专家博士证书获得者!
欢迎关注我的博客!一同成长!
一名从事运维开发的worker,记录分享学习。
专注于AI,运维开发,windows Linux 系统领域的分享!
本章节对应知识库
南城余 — Java全栈 · 语雀
本内容来自尚硅谷课程,此处在知识库做了个人理解
————————————————
6.4 双向绑定
单项绑定和双向绑定
- 单向绑定: 响应式数据的变化会更新dom树,但是dom树上用户的操作造成的数据改变不会同步更新到响应式数据
- 双向绑定: 响应式数据的变化会更新dom树,但是dom树上用户的操作造成的数据改变会同步更新到响应式数据
- 用户通过表单标签才能够输入数据,所以双向绑定都是应用到表单标签上的,其他标签不行
- v-model专门用于双向绑定表单标签的value属性,语法为
v-model:value=''
,可以简写为v-model=''
- v-model还可以用于各种不同类型的输入,
<textarea>
、<select>
元素。
<script type="module" setup>//引入模块import { reactive,ref} from 'vue' let hbs = ref([]); //装爱好的值let user = reactive({username:null,password:null,introduce:null,pro:null}) function login(){alert(hbs.value);alert(JSON.stringify(user));}function clearx(){//user = {};// 这中写法会将数据变成非响应的,应该是user.username=""user.username=''user.password=''user.introduce=''user.pro=''hbs.value.splice(0,hbs.value.length);;}
</script><template><div>账号: <input type="text" placeholder="请输入账号!" v-model="user.username"> <br>密码: <input type="text" placeholder="请输入账号!" v-model="user.password"> <br>爱好: 吃 <input type="checkbox" name="hbs" v-model="hbs" value="吃"> 喝 <input type="checkbox" name="hbs" v-model="hbs" value="喝">玩 <input type="checkbox" name="hbs" v-model="hbs" value="玩">乐 <input type="checkbox" name="hbs" v-model="hbs" value="乐"><br>简介:<textarea v-model="user.introduce"></textarea><br>籍贯:<select v-model="user.pro"><option value="1">黑</option><option value="2">吉</option><option value="3">辽</option><option value="4">京</option><option value="5">津</option><option value="6">冀</option></select> <br><button @click="login()">登录</button> <button @click="clearx()">重置</button><hr>显示爱好:{{ hbs }}<hr>显示用户信息:{{ user }}</div>
</template> <style scoped>
</style>
6.5 属性计算
模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。比如说,我们有这样一个包含嵌套数组的对象:
<script type="module" setup>//引入模块import { reactive,computed} from 'vue'const author = reactive({name: 'John Doe',books: ['Vue 2 - Advanced Guide','Vue 3 - Basic Guide','Vue 4 - The Mystery']})</script><template><div><p>{{author.name}} Has published books?:</p><span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span></div>
</template> <style scoped>
</style>
- 这里的模板看起来有些复杂。我们必须认真看好一会儿才能明白它的计算依赖于
author.books
。更重要的是,如果在模板中需要不止一次这样的计算,我们可不想将这样的代码在模板里重复好多遍。
因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑。这是重构后的示例:
<script type="module" setup>//引入模块import { reactive,computed} from 'vue'const author = reactive({name: 'John Doe',books: ['Vue 2 - Advanced Guide','Vue 3 - Basic Guide','Vue 4 - The Mystery']})// 一个计算属性 refconst publishedBooksMessage = computed(() => {console.log("publishedBooksMessage")return author.books.length > 0 ? 'Yes' : 'No'})// 一个函数let hasBooks = ()=>{console.log("hasBooks")return author.books.length > 0?'Yes':'no'}</script><template><div><p>{{author.name}} Has published books?:</p><span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span><span>{{ hasBooks() }}</span><!-- 调用方法,每个标签都会调用一次 --><span>{{ hasBooks() }}</span><p>{{author.name}} Has published books?:</p><span>{{ publishedBooksMessage }}</span><!-- 属性计算,属性值不变时,多个个标签只会调用一次 --><span>{{ publishedBooksMessage }}</span></div>
</template> <style scoped>
</style>
-
我们在这里定义了一个计算属性
publishedBooksMessage
。computed()
方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过publishedBooksMessage.value
访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加.value
。 -
Vue 的计算属性会自动追踪响应式依赖。它会检测到
publishedBooksMessage
依赖于author.books
,所以当author.books
改变时,任何依赖于publishedBooksMessage
的绑定都会同时更新。
计算属性缓存 vs 方法
- 若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要
author.books
不改变,无论多少次访问publishedBooksMessage
都会立即返回先前的计算结果!
6.6 数据监听器
计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数:
- watch主要用于以下场景:
- 当数据发生变化时需要执行相应的操作
- 监听数据变化,当满足一定条件时触发相应操作
- 在异步操作前或操作后需要执行相应的操作
监控响应式数据(watch):
<script type="module" setup>//引入模块import { ref,reactive,watch} from 'vue'let firstname=ref('')let lastname=reactive({name:''})let fullname=ref('')//监听一个ref响应式数据watch(firstname,(newValue,oldValue)=>{console.log(`${oldValue}变为${newValue}`)fullname.value=firstname.value+lastname.name})//监听reactive响应式数据的指定属性watch(()=>lastname.name,(newValue,oldValue)=>{console.log(`${oldValue}变为${newValue}`)fullname.value=firstname.value+lastname.name})//监听reactive响应式数据的所有属性(深度监视,一般不推荐)//deep:true 深度监视//immediate:true 深度监视在进入页面时立即执行一次watch(()=>lastname,(newValue,oldValue)=>{// 此时的newValue和oldValue一样,都是lastnameconsole.log(newValue)console.log(oldValue)fullname.value=firstname.value+lastname.name},{deep:true,immediate:false})
</script><template><div>全名:{{fullname}} <br>姓氏:<input type="text" v-model="firstname"> <br>名字:<input type="text" v-model="lastname.name" > <br></div>
</template> <style scoped>
</style>
监控响应式数据(watchEffect):
- watchEffect默认监听所有的响应式数据
<script type="module" setup>//引入模块import { ref,reactive,watch, watchEffect} from 'vue'let firstname=ref('')let lastname=reactive({name:''})let fullname=ref('')//监听所有响应式数据watchEffect(()=>{//直接在内部使用监听属性即可!不用外部声明//也不需要,即时回调设置!默认初始化就加载!console.log(firstname.value)console.log(lastname.name)fullname.value=`${firstname.value}${lastname.name}`})
</script><template><div>全名:{{fullname}} <br>姓氏:<input type="text" v-model="firstname"> <br>名字:<input type="text" v-model="lastname.name" > <br></div>
</template> <style scoped>
</style>
watch
vs.watchEffect
watch
和watchEffect
都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:watch
只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch
会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。watchEffect
,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。
6.7. Vue生命周期
6.7.1 生命周期简介
每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为
生命周期钩子的函数
,让开发者有机会在特定阶段运行自己的代码!
- 周期图解:
- 常见钩子函数
- onMounted() 注册一个回调函数,在组件挂载完成后执行。
- onUpdated() 注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。
- onUnmounted() 注册一个回调函数,在组件实例被卸载之后调用。
- onBeforeMount() 注册一个钩子,在组件被挂载之前被调用。
- onBeforeUpdate() 注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。
- onBeforeUnmount() 注册一个钩子,在组件实例被卸载之前调用。
6.7.2 生命周期案例
<script setup>import {ref,onUpdated,onMounted,onBeforeUpdate} from 'vue'let message =ref('hello')// 挂载完毕生命周期onMounted(()=>{console.log('-----------onMounted---------')let span1 =document.getElementById("span1")console.log(span1.innerText)})// 更新前生命周期onBeforeUpdate(()=>{console.log('-----------onBeforeUpdate---------')console.log(message.value)let span1 =document.getElementById("span1")console.log(span1.innerText)})// 更新完成生命周期onUpdated(()=>{console.log('-----------onUpdated---------')let span1 =document.getElementById("span1")console.log(span1.innerText)})
</script><template><div><span id="span1" v-text="message"></span> <br><input type="text" v-model="message"></div></template><style scoped>
</style>
6.8 Vue组件
6.8.1 组件基础
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。组件就是实现应用中局部功能代码和资源的集合!在实际应用中,组件常常被组织成层层嵌套的树状结构:
- 这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。
传统方式编写应用:
组件方式编写应用:
-
组件化:对js/css/html统一封装,这是VUE中的概念
-
模块化:对js的统一封装,这是ES6中的概念
-
组件化中,对js部分代码的处理使用ES6中的模块化
6.8.2 组件化入门案例
案例需求: 创建一个页面,包含头部和菜单以及内容显示区域,每个区域使用独立组建!
1 准备vue项目
npm create vite
cd vite项目
npm install
2 安装相关依赖
npm install sass
npm install bootstrap
3 创建子组件 在src/components文件下 vscode需要安装Vetur插件,这样vue文件有快捷提示
- Header.vue
<script setup type="module">
</script><template><div>欢迎: xx <a href="#">退出登录</a></div>
</template><style>
</style>
- Navigator.vue
<script setup type="module">
</script><template><!-- 推荐写一个根标签--><div><ul><li>学员管理</li><li>图书管理</li><li>请假管理</li><li>考试管理</li><li>讲师管理</li></ul></div>
</template><style>
</style>
- Content.vue
<script setup type="module">
</script><template><div>展示的主要内容!</div>
</template><style>
</style>
- App.vue 入口组件App引入组件
<script setup>import Header from './components/Header.vue'import Navigator from './components/Navigator.vue'import Content from './components/Content.vue'
</script><template><div><Header class="header"></Header><Navigator class="navigator"></Navigator><Content class="content"></Content></div>
</template><style scoped>.header{height: 80px;border: 1px solid red;}.navigator{width: 15%;height: 800px;display: inline-block;border: 1px blue solid;float: left;}.content{width: 83%;height: 800px;display: inline-block;border: 1px goldenrod solid;float: right;}</style>
4 启动测试
npm run dev
6.8.3 组件之间传递数据
6.8.3.1 父传子
Vue3 中父组件向子组件传值可以通过 props 进行,具体操作如下:
-
首先,在父组件中定义需要传递给子组件的值,接着,在父组件的模板中引入子组件,同时在引入子组件的标签中添加 props 属性并为其设置需要传递的值。
-
在 Vue3 中,父组件通过 props 传递给子组件的值是响应式的。也就是说,如果在父组件中的传递的值发生了改变,子组件中的值也会相应地更新。
- 父组件代码:App.vue
<script setup>import Son from './components/Son.vue'import {ref,reactive,toRefs} from 'vue'let message = ref('parent data!')let title = ref(42)function changeMessage(){message.value = '修改数据!'title.value++}
</script><template><div><h2>{{ message }}</h2><hr><!-- 使用子组件,并且传递数据! --><Son :message="message" :title="title"></Son><hr><button @click="changeMessage">点击更新</button></div>
</template><style scoped>
</style>
- 子组件代码:Son.vue
<script setup type="module">import {ref,isRef,defineProps} from 'vue'//声明父组件传递属性值defineProps({message:String ,title:Number})
</script><template><div><div>{{ message }}</div><div>{{ title }}</div></div>
</template><style>
</style>
6.8.3.2 子传父
- 父组件: App.vue
<script setup>import Son from './components/Son.vue'import {ref} from 'vue'let pdata = ref('')const padd = (data) => {console.log('2222');pdata.value =data;}//自定义接收,子组件传递数据方法! 参数为数据!const psub = (data) => {console.log('11111');pdata.value = data;}
</script><template><div><!-- 声明@事件名应该等于子模块对应事件名!调用方法可以是当前自定义!--><Son @add="padd" @sub="psub"></Son><hr>{{ pdata }}</div>
</template><style>
</style>
- 子组件:Son.vue
<script setup>import {ref,defineEmits} from 'vue'//1.定义要发送给父组件的方法,可以1或者多个let emites = defineEmits(['add','sub']);let data = ref(1);function sendMsgToParent(){console.log('-------son--------');//2.出发父组件对应的方法,调用defineEmites对应的属性emites('add','add data!'+data.value)emites('sub','sub data!'+data.value)data.value ++;}
</script><template><div><button @click="sendMsgToParent">发送消息给父组件</button></div>
</template>
6.8.3.3 兄弟传参
- Navigator.vue: 发送数据到App.vue
<script setup type="module">import {defineEmits} from 'vue'const emits = defineEmits(['sendMenu']);//触发事件,向父容器发送数据function send(data){emits('sendMenu',data);}
</script><template><!-- 推荐写一个根标签--><div><ul><li @click="send('学员管理')">学员管理</li><li @click="send('图书管理')">图书管理</li><li @click="send('请假管理')">请假管理</li><li @click="send('考试管理')">考试管理</li><li @click="send('讲师管理')">讲师管理</li></ul></div>
</template><style></style>
- App.vue: 发送数据到Content.vue
<script setup>import Header from './components/Header.vue'import Navigator from './components/Navigator.vue'import Content from './components/Content.vue'import {ref} from "vue"//定义接受navigator传递参数var navigator_menu = ref('ceshi');const receiver = (data) =>{navigator_menu.value = data;}
</script><template><div><hr>{{ navigator_menu }}<hr><Header class="header"></Header><Navigator @sendMenu="receiver" class="navigator"></Navigator><!-- 向子组件传递数据--><Content class="content" :message="navigator_menu"></Content></div>
</template><style scoped>.header{height: 80px;border: 1px solid red;}.navigator{width: 15%;height: 800px;display: inline-block;border: 1px blue solid;float: left;}.content{width: 83%;height: 800px;display: inline-block;border: 1px goldenrod solid;float: right;}</style>
- Content.vue
<script setup type="module">defineProps({message:String})
</script><template><div>展示的主要内容!<hr>{{ message }}</div>
</template><style>
</style>