v-model、组件化
补充知识点
watch侦听器注意点
const App = {
template: '#my-app',
data() {
return { //...
},
watch: {
info(newValue, oldValue) {
console.log(newValue, oldValue);
},
"info.name": function(newName, oldName) {
console.log(newName, oldName);
}
}
}
1.不应该使用箭头函数来定义 watcher 函数 。因为箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例。
2.回调函数得到的参数为新值和旧值。
在变更 (不是替换) 对象或数组时,旧值将与新值相同,因为它们的引用指向同一个对象/数组。Vue 不会保留变更之前值的副本。
深拷贝和浅拷贝
浅拷贝:比如assign()
先看下面这段代码。
定义info变量指向一个对象(info保存对象地址),该对象中有个属性friend也是对象(friend保存的也是对象的地址。)
const obj = Object.assign({}, info)
将info拷贝一份给{}
,obj指向返回的对象。
修改info的name属性,不会导致obj.name改变。
修改info.firend.name,会导致obj.friend.name的改变。(info.friend和obj.friend保存同一个地址)
const info = { name: "why", age: 18, friend: { name: "kobe" } };
const obj = Object.assign({}, info);
info.name = "kobe";
console.log(obj.name);//why
info.friend.name = "james";
console.log(obj.friend.name);//james
深拷贝
const obj = JSON.parse(JSON.stringify(info));
info拷贝一份到obj,info无论怎么改变属性值,都不影响obj。即使改变info的对象属性的值也不影响obj
const info = { name: "why", age: 18, friend: { name: "kobe" } };
const obj = JSON.parse(JSON.stringify(info));
info.age = 20
console.log(obj.age); //18
info.friend.name = "james";
console.log(obj.friend.name); //kobe
lodash库实现拷贝
1.引入lodash库
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
2.浅拷贝
const info = { name: "why", age: 18, friend: { name: "kobe" } };
const obj = _.clone(info);//浅拷贝
深拷贝
const info = { name: "why", age: 18, friend: { name: "kobe" } };
const obj = _.cloneDeep(info)//深拷贝
info.friend.name = "james";
console.log(obj.friend.name); //kobe
备注:手写深拷贝参考这篇文章手写深拷贝
v-model基本使用和原理
v-model基本使用
1.v-model指令可在表单input、textarea以及select元素上创建双向数据绑定
2.根据控件类型自动选取正确的方法来更新元素
3.负责监听用户的输入事件来更新数据
实现功能:
<body>
<div id="app"></div>
<template id="my-app">
<input type="text" v-model="message">
<h2>{{message}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World"
}
},
}
Vue.createApp(App).mount('#app');
</script>
</body>
v-model原理
v-bind绑定value属性的值
v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中
<input type="text" :value="message" @input="message=$event.target.value" />
<!--等价于-->
<input type="text" v-model="message">
v-model绑定其他表单类型
绑定textarea文本框
绑定checkbox,单个勾选框和多个勾选框
绑定radio,选择其中一项
绑定select,选中一个值和选中多个值
实现效果:
<template id="my-app">
<!-- 1.绑定textarea -->
<label for="intro">
自我介绍
<textarea name="intro" id="intro" cols="30" rows="10" v-model="intro"></textarea>
</label>
<h2>intro: {{intro}}</h2>
<!-- 2.checkbox -->
<!-- 2.1.单选框 -->
<label for="agree">
<input id="agree" type="checkbox" v-model="isAgree"> 同意协议
</label>
<h2>isAgree: {{isAgree}}</h2>
<!-- 2.2.多选框 -->
<span>你的爱好: </span>
<label for="basketball">
<input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球
</label>
<label for="football">
<input id="football" type="checkbox" v-model="hobbies" value="football"> 足球
</label>
<label for="tennis">
<input id="tennis" type="checkbox" v-model="hobbies" value="tennis"> 网球
</label>
<h2>hobbies: {{hobbies}}</h2>
<!-- 3.radio -->
<span>你的爱好: </span>
<label for="male">
<input id="male" type="radio" v-model="gender" value="male">男
</label>
<label for="female">
<input id="female" type="radio" v-model="gender" value="female">女
</label>
<h2>gender: {{gender}}</h2>
<!-- 4.select -->
<span>喜欢的水果: </span>
<select v-model="fruit" multiple size="2">
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
<h2>fruit: {{fruit}}</h2>
</template>
<script>
//......省略
data() {
return {
intro: "Hello World",
isAgree: false,
hobbies: ["basketball"],
gender: "",
fruit: "orange"
}
}
</script>
v-model的值绑定
我们上面的案例中,大部分的值value都是固定好的。
在真实开发中,我们的数据来自服务器,那该怎么做呢?
先将值请求下来,绑定到data返回的对象中,再通过v-bind进行值 value的绑定。
这个过程就是值绑定。
v-model修饰符
v-model.lazy
默认情况下,v-model进行双向绑定,绑的是input事件
,会在每次输入内容后就将最新值和绑定属性同步。
lazy修饰符:将绑定事件切换为change事件
,只有在提交时(如回车)才会触发。
<input type="text" v-model.lazy="message">
v-model.number
message总是string类型,设置type为number也是string类型。
<!--显示为string类型-->
<input type="number" v-model.number="message">
进行逻辑判断时,如果是string类型,在可以转化的情况下会进行隐式转换。
const score="100"
if(score>90){
console.log("优秀")
}
如果希望转换为数字类型,使用.number修饰符
<!--数字类型-->
<input type="text" v-model.number="message">
v-model.trim
自动过滤用户输入的首尾空白
<input type="text" v-model.trim="message">
Vue组件化
无论三大框架,还是跨平台方案Flutter,甚至移动端,小程序的开发都使用组件化开发思想。
组件化:
1.一个完整的页面分成很多组件,每个组件用于实现页面一个功能模块,每个组件又可进行细分,组件本身可在多个地方复用。
2.createApp函数传入一个对象App,该对象本质就是一个组件,也是应用程序的根组件。
3.任何应用都会被抽象成一个组件树。
组件的注册方式
全局组件:在任何其他的组件中都能够使用
局部组件:只能在注册的组件中才能使用
组件本身可以有自己的代码逻辑:自己的data、computed、methods等
注册全局组件
使用 全局创建的app来注册组件
component(组件名称,组件对象)来注册全局组件
在App组件的template中直接使用这个全局组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<component-a></component-a>
<component-b></component-b>
<component-name></component-name>
</template>
<template id="component-a">
<h2>{{title}}</h2>
<p>{{desc}}</p>
<button @click="btnClick">按钮点击</button>
</template>
<template id="component-b">
<div>
<input type="text" v-model="message"/>
<h2>{{message}}</h2>
</div>
</template>
<template id="component-c">
<h2>ComponentC</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
};
const app = Vue.createApp(App);
// 使用app注册一个全局组件app.component()
app.component("component-a", {
template: "#component-a",
data() {
return {
title: "我是标题",
desc: "我是内容, 哈哈哈哈哈",
};
},
methods: {
btnClick() {
console.log("按钮的点击");
},
},
});
app.component("component-b", {
template: "#component-b",
data() {
return {
message: "Hello World",
};
},
});
app.component('ComponentName', {
template: "#component-c"
})
app.mount("#app");
</script>
</body>
</html>
注册局部组件
在我们需要用到的组件中,通过 components属性选项来进行注册
该components选项对应的是一个对象,对象中的键值对是 组件的名称:组件对象;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{message}}</h2>
<component-a></component-a>
</template>
<template id="component-a">
<h2>我是组件A</h2>
<p>我是内容, 哈哈哈哈</p>
</template>
<script src="../js/vue.js"></script>
<script>
const ComponentA = {
template: "#component-a"
}
const App = {
template: '#my-app',
components: {
// key: 组件名称
// value: 组件对象
ComponentA: ComponentA
},
data() {
return {
message: "Hello World"
}
}
}
const app = Vue.createApp(App);
app.mount('#app');
</script>
</body>
</html>
不推荐注册全局组件
全局组件在应用程序一开始就会全局注册完成,如果某些组件我们没有使用到,也会一起被注册。
比如注册3个全局组件:ComponentA、ComponentB、ComponentC。
在开发中只用到ComponentA和ComponentB,ComponentC没有被使用但依然全局注册,意味着我们用webpack等构建工具打包时,会对其也进行打包。
最终打包出的JavaScript包会有关于ComponentC内容,用户下载对应的JavaScript时也会增加包的大小
组件的名称
定义组件名有两种方式:短横线分割kebab-case,驼峰标识符PascalCase(注意是大驼峰)。
推荐使用:短横线分割命名方式。
驼峰标识直接在DOM中无法使用。
在vue文件中用驼峰标识OK。
Vue的开发模式
真实开发中,我们通过后缀.vue
的single-file components(单文件组件SFC)来写代码。
但是浏览器是无法直接对.vue
文件进行解析的。怎么办呢?使用webpack、vite、rollup等构建工具来对其进行打包。
在这种方式下,可以获得很多特性。比如代码高亮;ES6、CommonJS的模块化能力;组件作用域的CSS;可以使用预处理器,比如TypeScript、Babel、Less、Sass等。
那么,怎么样才可以使用SFC的.vue文件呢?我们有两种方式。
第一种,Vue CLI来创建项目。这种方式项目会默认帮我们配置好所有的配置选项,可在其中直接用vue文件。
第二种,使用webpack、vite、rollup等构建工具来对其进行打包。
以上笔记参考coderwhy老师的Vue3和TypeScript。
老师讲课链接:课程链接