前言
上一篇连接:vue2 整理:基础篇。
组件化开发
组件概念
组件,对于学Java的人来说的话,这个词所要表达的意思再熟悉不过了。
所谓组件就是:面向对象中的抽象、封装思想;而所谓的组件化就是:把功能用多组件的方式搭配起来编写。
- 有一个根组件,旗下有N多微型组件 ,粗暴理解就是:SpringCloud中的
main()
方法可以搭配很多不同功能的注解,main()
方法就是根组件,不同功能的注解就是微型组件,那这些功能组成的应用程序就是一个组件化应用,因此:这样做之后,好处就是利于维护和提高代码的复用性了。
但是对于前端的人来说,这个东西就需要特别解释一下,直接下定义就是: 实现应用中局部功能代码和资源的集合。
瞄一下官网,它里面有一个图:
所以:现在就可以理解前面下的定义为什么是局部功能代码和资源的集合了,局部功能就是某一个模块,是针对这个模块内部的,而这个模块内部的编写不就是CSS、HTML片段、JS代码吗,同时png、mp3等等这些就是资源咯。
至于为什么要学组件化开发?
- 一是:因为做的应用页面都是很复杂的,如果使用传统的三剑客CSS+HTML+JS,那么就会出现很多的js文件,不利于维护 和 编写很费力的。
- 二是:因为组件化开发可以极好的复用代码、简化项目代码、所以也就提高了运行效率。
同时组件又有单文件组件( 真实开发玩的)和 非单文件组件
- 单文件组件:就是指只有一个组件组成( 即:是一个
.vue
的文件)。 - 非单文件组件:就是有N多个组件组成。
非单文件组件
使用组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>玩一下组件</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<!-- 3、使用组件 -->
<person></person>
<hr/>
<hobbys></hobbys>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 玩组件三板斧
// 1、创建组件
const person = Vue.extend({
// 这里在基础篇中怎么玩就怎么玩,相应的也有watch、computed.....
// 但是:切记:不可以用el和对象式data(必须是函数式)
/*
不可以用el的原因是:el指向的是具体的容器,这是根组件做的事情,现在这是小弟
不可以用data对象式,而必须用函数式:是因为对象是一个引用地址嘛(玩Java的人很熟悉这个对象的事情)
如果用引用一是Vue直接不编译、报错,二是就算可以用对象式,那几个变量都可以
指向同一个对象,那么就会产生:一个变量修改了对象中的东西,那么另一个变量指向
的是同一个对象,因此:数据也会发生改变
而函数式则不会,因为:函数式就会是哪个变量用的,里面的return返回值就是属于哪个变量
*/
// 使用模板,这个就需要直接写在组件里面了,如果:放到div容器的模板中,是会报错的
template: `
<div>
<h2>{{name}}</h2>
<h2>{{age}}</h2>
<h2>{{sex}}</h2>
</div>
`,
// 切记:这里是使用data的另一种写法 —— 函数式,必须用,前面基础篇说过了
data(){
return {
name: '紫邪情',
age: 18,
sex: '女'
}
}
})
// 再创建一个组件
const hobbys = Vue.extend({
template: `
<div>
<h2>{{one}}</h2>
<h2>{{two}}</h2>
<h2>{{three}}</h2>
</div>
`,
data(){
return {
one: '抠脚',
two: '玩',
three: '酒吧'
}
}
})
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
// 这里面也可以使用这个编写data,和以前一样
data:{},
// 2、注册组件
components: {
// 前为 正式在页面中用的组件名( div模板中用的名字 ) 后为组件所在位置
// person: person, // 这种同名的就可以简写
// 简写
person,
hobbys
}
});
</script>
</body>
</html>
小结:使用组件
-
Vue中使用组件的三板斧
- 创建组件;
- 注册组件;
- 使用组件(写组件标签即可)。
-
如何定义一个组件?
-
使用
Vue.extend( { options }
)创建,其中options 和new Vue( { options } )
时传入的哪些option“几乎一样”,区别就是:-
1)、el不要写 ——— 因为最终所有的组件都要经过一个vm的管理( 根组件),由vm中的el决定服务哪个容器。
-
2)、data必须写成函数式 ———— 因为可以避免组件被复用时,数据存在引用关系。
-
-
另外:template选项可以配置组件结构。
-
-
如何注册组件?
-
1)、局部注册: 靠new Vue的时候传入components选项。
-
2)、全局注册:靠
Vue.component('组件名' , 组件)
。
-
-
真实开发中一般都是用前面玩的局部组件,局部变全局都是一样的套路,去掉s,然后使用Vue来调,最后加入相应的名字和配置即可。
-
编写组件标签。
<person></person>
使用组件的注意点
- 创建组件时的简写问题
// 1、创建局部组件( 完整写法 )
const person = Vue.extend({
template: `
<div>
<h2>{{name}}</h2>
<h2>{{age}}</h2>
</div>
`,
data(){
return {
name: '紫邪情',
age: '女'
}
}
})
// 简写形式
const person2 = {
template: `
<div>
<h2>{{name}}</h2>
<h2>{{age}}</h2>
</div>
`,
data(){
return {
name: '紫邪情',
age: '女'
}
}
}
上面两种都可以。但是:简写形式在底层其实也调用了 Vue.extend()
验证完了,记得把源码的断点去掉。
- 组件名的问题
- 1)、组件名为一个单词时,使用全小写字母 / 首字母大写都没问题。
- 2)、组件名为多个单词组成时,全部用小写 或 使用
-
进行分割都没问题。
还有一种就是上图中的效果:驼峰命名
但是:这种驼峰命名需要注意,有些人就不可以。因为:严格来说这种命名是后续的技术使用脚手架玩时的方式,但是:有些人就可以,比如我上图中的效果,因为这是Vue版本的问题,要看源码的话,从下图这里往后看即可。
- 3)、注意组件名别和HTML中的原生标签名一致,会冲突报错,HTML的限制就是上图中看源码中的哪些,如果非要用HTML标签名,让人见名知意,那就在原生HTML标签名前加一些特定的词,如;my-input这种,千万别用:input、h2....此类名字来命名组件名。
- 关于组件在使用时的注意项
-
1)、可以使用双标签,如:
<my-info></my-info>
,这种肯定没任何问题。 -
2)、也可以使用自闭合标签,如:
<my-info/>
,但是这种有坑,这种使用方式需要脚手架支持,否则数据渲染会出问题。
使用组件注意点总结
- 关于组件名
- 一个单词组成时:
- 1)、全小写,如:person。
- 2)、首字母大写,如:Person。
- 多个单词组成时:
- 1)、全小写,如:myinfo。
- 2)、使用
-
分割,如:my-info。 - 3)、驼峰命名,如:MyInfo,但注意:目前没用脚手架之前最好别用,是因为指不定一会好使,一会不好使。
- 注意事项:
- 1)、组件名最好别和HTML的标签名一致,从而造成冲突(非要用,可以采用加词 或 使用
-
分割)。 - 2)、可以在创建组件时,在里面配置name选项,从而指定组件在Vue开发者工具中呈现的名字。
- 1)、组件名最好别和HTML的标签名一致,从而造成冲突(非要用,可以采用加词 或 使用
此种形式在第三方组件时会见到
- 关于组件标签(使用组件)
- 1)、使用双闭合标签也行,如:
<my-info></my-info>。
- 2)、使用自闭合标签也行,如:
<my-info/>
。但是:此种方式目前有坑,会出现后续组件不能渲染的问题,所以需要等到后续使用脚手架时才可以。
- 创建组件的简写形式
const person = Vue.extend({
配置选项
})
可以简写为:const person = { 配置选项 }
组件的嵌套
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>组件嵌套</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<!-- 3、使用组件 -->
<info></info>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 1、定义组件
const person = {
template: `
<div>
<h2>{{name}}</h2>
<h2>{{sex}}</h2>
</div>
`,
data(){
return {
name: '紫邪情',
sex: '女'
}
}
}
const info = Vue.extend({
template: `
<div>
<h2>{{address}}</h2>
<h2>{{job}}</h2>
<!-- 这个组件中使用被嵌套的组件 -->
<person></person>
</div>
`,
data(){
return {
address: '浙江杭州',
job: 'java'
}
},
// 基础组件嵌套 —— 这个组件中嵌套person组件
/*
注意前提:被嵌套的组件 需要比 当前嵌套组件先定义( 如:person组件是在info组件前面定义的 )
原因:因为Vue解析模板时,会按照代码顺序解析,如果定义顺序反了
就会出现:这里用到的组件 在 解析时由于在后面还未解析从而出现找不到
*/
components: {
person,
}
})
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{},
// 2、注册组件 —— 由于info组件中 嵌套了 person组件,所以在这里只需要注册 info组件即可
components: {
info,
}
});
</script>
</body>
</html>
另一种嵌套:开发中玩的,我对那个div容器起的id值为app,是有用的
在开发中的嵌套是一个vm管理独一无二的app(就是application 应用程序的意思),然后由app管理众多小弟:
所以,现在来玩一下这种组件嵌套
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vm管app,app管众多组件</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app"></div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 1、定义组件
const person = {
template: `
<div>
<h2>{{name}}</h2>
<h2>{{sex}}</h2>
</div>
`,
data(){
return {
name: '紫邪情',
sex: '女'
}
}
}
const info = Vue.extend({
template: `
<div>
<h2>{{address}}</h2>
<h2>{{job}}</h2>
<!-- 这个组件中使用被嵌套的组件 -->
<person></person>
</div>
`,
data(){
return {
address: '浙江杭州',
job: 'java'
}
},
components: {
person,
}
})
// 再定义一个app组件,用来管理其他组件
const app = {
// 这个app组件没有其他的东西,就是注册和使用被管理组件而已
components: {
// 有其他组件也可以注册在这里面,这里由于info管理了person,所以只注册info即可
info
},
template: `
<div>
<info></info>
</div>
`,
}
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{},
// 由于组件被app管理,所以:只注册app组件即可
components: { app },
// 使用组件
template: `
<div>
<app></app>
</div>
`,
});
</script>
</body>
</html>
认识VueComponent()函数
- 来看一下组件到底是谁?
基础代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>认识VueComponent</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app"></div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 1、定义组件
const person = Vue.extend({
template: `
<div>
<h2>{{name}}</h2>
<h2>{{job}}</h2>
<h2>{{address}}</h2>
</div>
`,
data(){
return {
name: '紫邪情',
job: 'java',
address: '浙江杭州'
}
}
})
const app = {
components: {person},
template: `
<div>
<person></person>
</div>
`,
}
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{},
components: {app},
template: `
<div>
<app></app>
</div>
`,
});
</script>
</body>
</html>
现在就来见一下组件的真身(在前面玩this的时候说过,this指向的是:Vue实例 / 组件实例对象),因此:用this就可以知道组件的真身。
既然知道了组件真身是VueComponent()
,那么先去源码中看一下它:
源码提取出来就是下面的样子:
var Sub = function VueComponent (options) {
this._init(options); <!--里面的重要逻辑封装在了_init()中了,目前不要去看-->
};
return Sub
};
经过前面的分析和查看源码得出两个结论:
- 所有的组件指的都是
VueComponent()
。 - 每一个组件都调用了
VueComponent()
,但它们都不一样(源码中有嘛,每次都是创建了一个全新的sub,sub就是VueComponent()
,最后把这个sub返回去了,验证一下咯。
但是:这里有一个有意思的东西,就是第一条说的组件就是指VueComponent()
,从而会出现不了解的人认为:每个组件都是调了同一个VueComponent()
,来看一下:
这两个长得一模一样,所以就会让人误会,但这是一个构造函数。
构造函数,那就是Vue每次解析模板时(div容器使用的组件标签),就会去帮忙创建对应的组件,调用了构造函数。
怎么调用的?new出来的嘛,所以:这两个组件对象肯定不一样(前面先验证是否一样就是为了注意这点,看起来一样,实质不一样 ,两个组件创建的是不同的VueComponent()
)。
同时上面说到,Vue解析模板时,会帮忙去创建VueComponent()
,那么是谁去帮忙创建的?
- 答案就是创建组件时,里面的
Vue.extend()
去帮忙创建的,这不是我们程序员自己整出来的,看一下源码:
小结:VueComponent()
-
组件本质是一个名为
VueComponent()
的构造函数,且不是程序员自己定义的,是Vue.extend()
生成的。 -
我们只需要写组件标签(如
<person></person>
或<person/>
),Vue解析时会帮我们创建组件的实例对象,即:Vue帮我们执行了new VueComponent( { options配置选项 } )
- 注意点:每次调用
Vue.extend()
,返回的都是一个全新的VueComponent
- 注意点:每次调用
-
关于this的指向问题
- 1)、在组件配置中:data函数、methods函数、watch中的函数、computed中的函数,它们的this均是【VueComponent实例对象】。
- 2)、在
new Vue()
配置中:data函数、methods函数、watch中的函数、computed中的函数,它们的this均是【vue实例对象,即:前面玩的vm 】。
-
VueComponent实例对象,简称:vc(或:组件实例对象);Vue实例对象,简称:vm。
但是:vm和vc也有一个坑
观察结构会发现:vm和vc如出一辙,什么数据代理、数据监测等等,vm有的,vc都有,所以vm中的配置项在vc中都可以配置,但是:vm和vc不能画等号,它们两个不一样
- vm是Vue实例对象,vc是组件实例对象
- vm中可以使用el选项,而vc中不可以(只要用el就报错)
- 在vm中,data可以用函数式和对象式,但是在vc中data只能用函数式
- vm是大哥,vc是小弟,vc是vm的组件,或者直接说:组件实例对象vc是小型的Vue实例对象
- vm和vc之间很多东西只是复用了而已。
这里说的复用,里面有大门道,vm和vc之间是有关系的,这里需要原型对象知识
1)、函数肯定有ProtoType(显示原型属性);
2)、而实例(如:const person = vue.extend()
中的person)肯定有 _ _proto _ _
(隐式原型属性);
而这二者指向的是同一个对象:即,该实例的原型对象,而vc和vm之间就是通过这二者关联起来的。
vm和vc之间的内置关系是:VueComponent.prototype._ _proto _ _ === Vue.prototype
图中:VueComponent的原型对象通过 _ _proto _ _
理论上应该直接指向object的原型对象,但是:Vue做了巧妙的事情:就是让VueComponent的原型对象通过 _ _proto _ _
指向了Vue的原型对象,这样做的好处就是:让组件实例对象 可以访问到 Vue原型上的属性、方法。
以上的内容就属于非单文件组件相关的,接下来就看单文件组件,也是开发中会做的事情。
单文件组件
单文件组件:就是只有一个文件嘛,xxxx.vue
。
而xxxx就是前面说过的组件命名:
- 单个单词:全小写、首字母大写。
- 多个单词:用
-
进行分割、大驼峰命名。 - 而开发中最常用的就是:首字母大写和大驼峰命名。
疏通单文件组件的编写流程
前提:如果自己的编辑器是vscode,那么就给编辑器安装vetur插件(用其他的也行),然后重启vscode,这个插件就是为了能够识别xxxx.vue
文件的;如果自己是用的IDEA编辑器来写的vue,那么安装了vue.js插件就可以了。
创建xxxx.vue文件
这个创建的就是单文件组件,前面玩非单文件组件,不是有三板斧吗,对照来看。
创建了xxx.vue
之后,是一个空文件,里面要写的东西就三样(模板template【即html+vue】、交互script、样式style),里面内容也对照非单文件组件来看。
<template>
<div class="temp">
<!-- 这里面就是模板 以前在非单文件组件中用的template选项是怎么写的,这里面就是怎么写的-->
<h2>{{name}}</h2>
</div>
</template>
<script>
// 这里面就是交互( data、methods、watch、computed..... )
// 就是非单文件组件中的定义组件
/* const person = vue.extend({
// 这里就最好配置name选项了,一般都是当前创建的xxxx.vue中的xxxx名字即可
name: 'Person',
data() {
return {
name: '紫邪情'
}
},
// 这里面还可以写什么methods、watch.....之类的
})
*/
// 但是:上面是对照非单文件组件来写的,在这个单文件中其实换了一下下
// 1、这个组件是可以在其他地方复用的,所以:需要把这个组件暴露出去,然后在需要的地方引入即可
/*
这里需要使用到js中模块化的知识
export暴露 import引入嘛
但是:export暴露有三种方式
1、分别暴露 export const person = vue.extend({ 配置选项 }),
就是在前面加一个export而已
可是:根据前面非单文件的知识来看,这个是可以进行简写的
export person {}
2、统一暴露 就是单独弄一行代码,然后使用 export { 要进行暴露的名字 },多个使用 , 逗号隔开即可
3、默认暴露( vue中采用的一种,因为引入时简单 ) export default 组件名{ 配置选项 }
但是:组件名就是当前整个文件,所以可以省略
默认暴露引入: import 起个名字 from 它在哪里
而其他的暴露方式在引入时会有点麻烦
*/
// 正宗玩法
export default {
name: 'Person',
data() {
return {
name: '紫邪情'
}
},
// 再配置其他需要的东西也行 如:methods、watch、computed.....
}
</script>
<style>
/* 这里面就是template中的样式编写, 有就写,没有就不写 */
.temp{
color: purple;
}
</style>
xxx.vue文件,如果使用的是vscode+vetur,那么上面的模板可以用快捷键 :就是输入
<v
然后回车就可以生成了(也可以百度“vue生成模板”进行快捷键配置)。
注册组件到app中
前面玩过,vm管app,app管其他的小弟,所以需要一个app组件,创建一个app.vue
:
<template>
<div>
<!-- 3、使用app管理的组件 -->
<person></person>
</div>
</template>
<script>
// 1、引入定义的person组件(要是有其他组件要引入是一样的套路)
// 1Person.vue这个名字不正规啊,我只是为了排序才加了一个1
import person from "./1Person.vue"
export default {
name: 'App'
// 2、注册引入的组件
components: {person} // 完成了引入和注册之后,在这里面就可以用引入的组件了
}
</script>
<style>
/* app是为了管理其他所有的组件,所以这个style其实不写也行( 按需要来吧 ) */
</style>
将app和vm绑定起来
新建一个main.js
文件,创建这个文件的目的:一是让app和vm绑定,二是浏览器并不能识别.vue
文件,所以根本展示不了,因此:需要将.vue
文件转成js文件,这样浏览器就能解析了。
// 1、引入app组件
import App from "./2App.vue"
// 2、把app组件和vm进行绑定
new Vue({
// 这里面和以前一样写法,当然:这里的el值绑定的是容器id,怕误会改成root也行
el: '#App',
components: {App}
})
创建容器
前面app组件和vm绑定了,但是vm中指定的el值,它绑定的容器还没有啊,因此:创建index.html
。
<!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>创建el容器</title>
<!--
记得要引入js,而此时就需要引入两个js,一个是main.js,一个是vue.js
可是:在解析下面的容器时,可能会导致js渲染不及时出问题
因此:引入js最好放在下面容器的后面引入
-->
</head>
<body>
<div id="App">
<!--
2、使用app组件,可以在这里使用,也可以不在这里使用直接在app.vue中使用template选项进行使用
-->
<App></App>
</div>
<!--
1、引入js
vue.js是因为:main.js中new vue()需要它,所以:先引入vue.js
其次再引入main.js
-->
<script src="../../../js/vue.js"></script>
<script src="./3Main.js"></script>
</body>
</html>
经过上面的操作之后,玩Vue单文件组件的流程也就完了,整体结构就是如下所示:
而整个流程按照解析的逻辑来看就是如下流程:
- 进入
index.html
,创建了div id = "App"
容器,然后引入vue.js
,再引入main.js
- 但是:引入
main.js
,去main.js
里面开始解析时,发现:需要引入App.vue
,所以:接着引入App.vue
- 进入
App.vue
,又发现需要引入Person.vue
- 将所有东西都引入完了之后,就可以依次进行渲染了(逻辑就不说明了),而经过上面的逻辑梳理之后会发现:
main.js
就是入口,是从main.js
开始引入,从而把其他的东西也给引入进来了。
当然:以上的东西弄完之后,还启动不了,一启动就会报错。这是因为:浏览器不能解析ES6语法,这需要使用另外一个技术,脚手架来支持
认识脚手架 vue cli
cli全称: command line interface ,即:命令行接口工具,但是:一般说的都是脚手架,正规名字说起来太官方、绕口。
在vue官网有这个脚手架生态:
使用nodejs配置脚手架
前提:保证自己的电脑有nodejs。
nodejs的配置很简单,官网 进行下载(版本最好选择大于等于16的,后续学其他东西时方便)、一直next、最后修改环境变量。LTS就是稳定版,而CURRENT就是更新版(新特性就丢在这里面的,可能会出现bug,所以不推荐下载)
有个注意点:选择安装目录时,别把nodejs安装到系统C盘了,不然很大可能出现权限不足、无法操作的问题,特别是:如果自己的电脑没升级,还是家庭版的而不是专业版的,这种问题更常见(当然话说回来,事非绝对,有些人就是不会出问题,所以其实可以先安装C盘试一下,遇到了就当碰壁学经验了)。
- 出现这种问题就需要切换到管理员身份运行cmd才可以进行安装vue-cli了,甚至有时会奇葩点:需要在管理员身份下运行
npm clean cache –force。
。 - 然后再进入到 C:/users/appdata/roaming 下把一个叫做nom-cache的缓存文件删了,最后再用管理员身份运行
npm clean cache –force
清除缓存,搞完这些才可以安装vue cli脚手架。
安装成功之后是如下效果
查看一下是否成功:进入DOS窗口(win+r,输入cmd回车),以下内容表明成功
但是:现在npm的配置和缓存文件都在 C:/users/appdata/roaming/npm 和 appdata/local/npm-cache 中
因此:我们需要去改动这两个地方(知道了这两个目录,不用改也可以,后面什么事都可以不做了,对后续的操作没影响的 ,嫌麻烦就可以改)
- 在安装的nodejs中新建 node_global 和 node_cache 两个文件夹(前为全局配置路径,后为npm缓存路径)。
- 用管理员权限打开DOS窗口,执行如下命令(路径记得复制成自己的)。
全局设置
C:\WINDOWS\system32>npm config set prefix "D:\install\Nodejs\node_global"
C:\WINDOWS\system32>npm config set cache "D:\install\Nodejs\node_cache"
检查是否成功
C:\WINDOWS\system32>npm config get prefix
D:\install\Nodejs\node_global
C:\WINDOWS\system32>npm config get cache
D:\install\Nodejs\node_cache
可见成功修改,但是:还需要做最后一步,去改环境变量。
在改环境变量之前,在刚刚新建的 node_global 目录下,再新建一个 node_modules 文件夹
- 修改环境变量
最后一路点OK即可,测试是否成功,可以选择安装一个vue包来测试
npm i -g vue
此时可能出现报一堆的ERROR,最后一行的大概意思就是让使用 root/ admin...用户(也就是让用管理员运行dos窗口,再执行命令)。
报一堆ERROR错误的解决办法
此时:做一个操作即可,回到nodejs安装的根目录
右键选择属性、安全、高级
当然:要是自己的电脑在这个安全界面中,直接编辑权限,然后把“写入权限、修改权限”√上、应用确定是可以的,那就可以了,要是不行就接着往后看。
然后再使用npm install -g xxx
就可以了(前面安装vue包报错的,以防万一先去前面配置的npm_cache中将不完整的vue文件夹删掉【shift+del彻底删除】,再执行此命令)。
安装之后,在刚刚新建的 node_global 和 node_cache 中是有东西的:
如果想要把全局配置恢复为初始化配置的话,也很简单,系统C:/users/.npmrc的文件,删了就可以了。
配置成功了nodejs之后,就可以使用npm指令了。
可以添加镜像仓库链接:npm config set registry https://registry.npmmirror.com
别用 npm config set registry http://registry.npm.taobao.org
(2022 年 5 月 31 日 停止服务)。
拉取镜像这里开始就一定要保证自己的网络流畅,不然很容易导致一是淘宝镜像拉取失败(看起来成功了,但是一用就报cnpm不是内部命名 ,这种情况要么权限不够,需要管理员身份打开dos窗口;要么cnpm没拉完整),二是后面安装脚手架时,要是网络不好,也很容易出现看起来成功了,但是:一用就发现vue不是内部指令。
【2024更新】更多npm镜像源:
npm 官方原始镜像网址是:https://registry.npmjs.org/
淘宝 NPM 镜像:https://registry.npmmirror.com
阿里云 NPM 镜像:https://npm.aliyun.com
腾讯云 NPM 镜像:https://mirrors.cloud.tencent.com/npm/
华为云 NPM 镜像:https://mirrors.huaweicloud.com/repository/npm/
网易 NPM 镜像:https://mirrors.163.com/npm/
中科院大学开源镜像站:http://mirrors.ustc.edu.cn/
清华大学开源镜像站:https://mirrors.tuna.tsinghua.edu.cn/
下图是我重新拉取一遍的效果:
- 全局安装vue cli。指令:
npm install -g @vue/cli
。
- npm 是nodejs的指令 拉取了淘宝镜像之后,就可以使用cnpm代替了 。
- install 是安装的意思,可以简写为 i。
- -g 是全局安装。
- @vue/cli 是安装的东西。
有个注意点:要是有人知道可以使用
npm install -g vue-cli
这样安装脚手架的话,可以用,没错的,但是:目前别这么安装。它安装的脚手架是2.x的,用这种方式安装的不能保证vue(目前版本是1 - 3)和vue-cli(目前版本是1 - 4 )的版本很适合,所以后续使用一些命令时可能会出现版本不足的问题,让把版本升级,而使用@vue/cli
安装的是最新版本。
- 创建一个文件夹,并进入目录中。 使用指令:
vue create xxxx
vue create
是cli 3.x的命令,要是前面安装脚手架时是乱整的,就会发现:这个命令用不了,要是出现这样的话,那么执行一遍这个命令,会提示你:卸载以前的cli,然后执行什么命令安装cli。xxx
就是要创建的项目名。
出现如上图就说明在开始拉取依赖了。
- 启动项目
想要退出启动的程序,一是直接点窗口右上角的×,二是按两次ctrl+c即可。
补充:以下内容是以前使用
vue init webpack xxx
来安装的vue-cli。
查看一下自己的vue-cli安装成功没有,指令: vue list
vue-cli安装成功之后,想找它就在 C/user/appdata/roaming/npm/node_modules 中可以看到(前面配置了环境变量就在配置的路径中)。
当然:有可能有些不听劝的人在安装nodejs时,搞了一些不必要的操作,导致有些文件需要管理员权限才可以。
因此:就会出现这里安装vue-cli时失败,报的错就是什么node_glob.....什么爪子之类的,总之:就是权限不够,然后使用管理员身份做前面的操作就发现突然吃鸡了,这就是典型的权限不够导致的。这样的话,以后:你使用vue相关的东西时,就都得使用管理员权限才可以进行操作。
- 创建一个文件夹,然后进入文件夹,使用指令:
vue init webpack xxxx
,进去之后会做一些操作。
init
是初始化。webpack
就是骨架,就像建楼一样建好的地基。xxxx
就是要创建的项目名。
当然:上图中的那个babale单词可能是错的,我记不清了(也懒得百度)^ _ ^ 。反正单词大概是那个。
上面这些记不住没关系,在没选择安路由之前一路回车,然后开始选择时一路no,最后选择use npm即可。后续把东西学完了,那就可以把对应的东西装上了。
拉取完了之后,就是下面的样子:
然后进到创建的项目中,使用 npm run dev
,会得到一个网址,浏览器访问就可以了
上面这个窗口别关了啊,不然使用地址访问不了的。
分析cli构建的项目
使用vscode打开刚刚编译的项目。
- package-lock.json
- package.json
以上就是基础的东西,接下来就对照前面手写的单文件组件思路来分析接下来的东西,那时说过:
main.js
是入口,所以cli脚手架程序就从main.js
入手(在cli中为什么它是入口?因为所以,自然道理,豌豆米米,脚手架底层做的处理)。
- main.js
- 引入了App.vue组件,那就接着分析App.vue组件
注意:
- assets目录是专门用来放静态资源的,如:png、mp3...(后端的人就把这个目录当做是SpringBoot中的那个static目录)。
- components目录是专门用来放组件的(App.vue是一人之下【vm】万人之上【其他任何组件】,所以不在这里面)。
-
上面引入了helloword组件,而那里面就是一堆正常的组件写法
-
分析得差不多了,但是:还少了一个重要的东西,容器在哪里?
就在index.html
中,而这个东西有一个专门的public目录来放
经过前面的分析之后,再结合上次写的单文件组件,就知道对应的东西写在哪里了。
顺便说一下:vscode中启动vue程序,按ctrl + `飘字符( esc下面、tab上面的那个按键)唤出控制台,后面的就知道怎么做了(做了修改之后,按ctril+s保存时会自动重新编译)。@紫邪情
认识 render() 函数
把前面编译好的例子改一下, 我的是把前面疏通流程的代码拷贝进来的。
原因就是:代码中的一句代码。
那就去看一下vue到底有哪些版本?按住ctrl+鼠标点引入的vue哪里,点vue就进去了。
点开它的包说明:就发现引入的是 dist/vue.runtime.esm.js
随便选择一个右键在资源管理器中显示,就可以看到文件大小(可以和vue.js对比,就少了100kb作用而已,少的就是模板解析器,但是:少的这部分用处很大)
vue为什么要搞出这么多版本?
- 这是因为vue其实就是将webpack进行了封装,然后添加了一些技术,从而搞出来的,所以webpack在进行打包时会将
.vue
转成.js
从而实现渲染(后端人员不懂webpack的,就粗暴地把它当做是maven中的install打包,当然:compile编译等等这些指令功能也在vue中有相同的效果的实现机制)。 - 而程序编写完了之后,webpack本身就支持将
.vue
转成.js
,那使用webpack打包时,模板解析器就不应该出现了(模板解析器就是编写时解析而已),所以真实打包时如果出现了模板解析器就会出现一个骚气的事情,举个例子:去做某事。
- 整出那么多版本的原因就知道了呗,减少程序体积、提高性能嘛。
回到前面报的错,怎么解决?控制台已经把答案给的很明确了。
-
使用包含模板解析器的vue就可以了。
-
使用
render()
函数实现模板解析器的效果。 -
使用包含模板解析器的vue最简单。引入的vue有问题,那就改一下嘛
- 使用
render()
函数来实现解析template的功能
知道了render()
的结构,那就去实现解析template的功能
然后兰姆达表达式简写不就成原来的样子了吗
- App就是h2,因为:App是一个组件,使用时就是
<App/>
或<App><App>
。 - 这个
render()
函数也就在vm绑定容器时会用到,其他地方见都见不到的。
关闭语法检测
官网中有,改cli的全局配置也是一样的套路。
官网里面就是说新建一个
vue.config.js
的文件,这个文件必须和package.json
是同级目录,然后要配置东西时,找对应的配置项,最后复制粘贴对应的内容(或改动一点自己要的配置项即可)。
关闭语法监测,就是名为lintOnSave的配置项罢了。
创建vue.config.js
文件:
经过上面的操作之后,就算定义一个变量,然后后面一直没用这个变量也不会导致项目启动不了了,否则:使用npm run serve
时,就会报错,导致启动不了。
而创建这个vue.config.js
文件能够修改cli的配置是因为:cli是在webpack的基础上做出来的,而webpack是在nodejs的基础上整出来的,因此:程序最后是将自己写的vue.config.js
去和webpack中的进行了合并,从而就成功了。
注意点:cli中不是什么都可以修改的。
想要重新改动,就在刚刚新建的vue.config.js
中配置即可,但是注意:
vue.config.js
每一次更新都要使用npm run serve
重新启动项目,否则不生效。vue.config.js
中不可以说:不用了加个注释,然后等后面用的时候再解开注释,这种是不行的,要么就配置,要么就不配置,否则:启动不了项目。
最后:vue隐藏了webpack的配置,要想查看默认配置的话,那就使用 ctrl+飘字符 唤出控制台后,输入:vue inspect > output.js
,然后就会在项目中生成一个output.js
文件,里面就有默认配置。
VSCode设置Vue文件模板
文件内容如下:
prefix
为你要使用的快捷键
{
"vue文件自定义模板": {
"prefix": "vue",
"body": [
"<template>",
" <div class='$1'></div>",
"</template>",
"",
"<script>",
" /* 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)",
" * 例如:import 组件名称 from '组件路径';",
" */",
"",
" export default {",
" // import引入的组件需要注入到对象中才能使用",
"",
" components: {},",
" data() {",
" // 这里存放数据",
" return {",
" };",
" },",
"",
" // 监听属性 类似于data概念",
" computed: {},",
" // 监控data中的数据变化",
" watch: {},",
" //方法集合",
" methods: {",
" },",
"",
" // 生命周期 - 创建完成(可以访问当前this实例)",
" created() {",
" },",
" // 生命周期 - 挂载完成(可以访问DOM元素)",
" mounted() {",
" },",
" // 生命周期 - 创建之前",
" beforeCreate() {},",
" // 生命周期 - 挂载之前",
" beforeMount() {},",
" // 生命周期 - 更新之前",
" beforeUpdate() {},",
" // 生命周期 - 更新之后",
" updated() {},",
" // 生命周期 - 销毁之前",
" beforeDestroy() {},",
" // 生命周期 - 销毁完成",
" destroyed() {},",
" // 如果页面有keep-alive缓存功能,这个函数会触发",
" activated() {}, ",
" }",
"</script>",
"",
"<style scoped>",
" /* @import url(); 引入公共css类 */",
"</style>"
],
"description": "生成vue模板"
},
"http-get请求": {
"prefix": "httpget",
"body": [
"this.\\$http({",
"url: this.\\$http.adornUrl(''),",
"method: 'get',",
"params: this.\\$http.adornParams({})",
"}).then(({ data }) => {",
"})"
],
"description": "httpGET请求"
},
"http-post请求": {
"prefix": "httppost",
"body": [
"this.\\$http({",
"url: this.\\$http.adornUrl(''),",
"method: 'post',",
"data: this.\\$http.adornData(data, false)",
"}).then(({ data }) => { });"
],
"description": "httpPOST请求"
}
}
认识 ref 属性
这个属性是用来给“元素”或“子组件“注册引用信息(也就是id属性的替代者),这个小东西很重要,关系到后续组件与组件之间的通信。
重新复制一份src文件夹,用的时候把名字改回来就可以了( 需要哪一个就把哪一个改为src,然后执行npm run serve
就行了)
运行效果如下:
现在有一个需求:获取下图中的DOM结构。
使用传统js来操作的话,就是document.getElementById
进行获取,但是:Vue中提供得有ref属性来进行操作:ref属性用来给“元素”或“子组件“注册引用信息(也就是id属性的替代者),所以来见识第一个“元素”注册引用信息(在HTML元素中这个ref属性就和id属性是一样的效果)
第二种:ref属性是在子组件上的。这种很重要,后面组件与组件之间交互的基础。
但是:这种和id就不同了,id是直接获得了子组件的DOM结构,而ref是获得了组件本身VueComponent。
小结:ref属性
- 被用来给元素(id的替代者)或子组件注册引用信息。
- 应用在HTML标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象(vc)。
- 使用方式:
- 做标识:
<h1 ref="xxx">......<h1>
或<Person ref="xxx"><Person>。
- 获取:
this.$refs.xxx
。
- 做标识:
props 配置 - 获取外传数据
组件中的props配置就是为了获取从外部传到组件中的数据。
这个东西很有用,后续玩子传父、父传子就需要这个东西,而且开发中这个东西会经常看到。
只接收数据
Person.vue
组件编写内容
<template>
<div>
<h2>{{name}}</h2>
<h2>{{sex}}</h2>
<h2>{{age}}</h2>
<h2>{{job}}</h2>
<h2>{{address}}</h2>
</div>
</template>
<script>
export default {
name: 'person',
// 使用props配置,使这个Person组件中的数据从外部传进来(封装的思想来咯)
// 第一种方式:只接收数据即可(数组写法) 此种方式:接收的数据统统都是字符串
props: ['name','sex','age','job','address']
}
</script>
App.vue
组件编写内容
<template>
<div>
<h1 ref="content">欢迎来到对抗路,对手信息如下</h1>
<!-- 使用组件 并 传入数据 -->
<Person name="紫邪情" sex="女" age="18" job="java" address="浙江杭州"/>
</div>
</template>
<script>
import Person from "./components/Person.vue"
export default {
name: 'App',
components: {Person},
}
</script>
- ctrl+s重新编译
效果如下:
接收数据 + 数据类型限定
至于限定类型有哪些? 可以是下列原生构造函数中的一种 。
String
Number
Boolean
Array
Object
Date
Function
Symbol
任何自定义构造函数 或 上述内容组成的数组
接收数据 + 限定类型 + 数据有无接收的必要 + 数据默认值
这个东西,玩Java的人看起来很熟悉,和Elastic-Search中的mapping映射很像
处理外部传入数据类型问题
解决props接收数据之后,修改它的值
props配置中不是什么属性名的值都可以接收的,如:key、ref。
意思就是:key不能作为props接收的数据,原因就是因为:key这个属性被Vue给征用了,Vue底层需要使用diff算法嘛,它用了这个key关键字,所以我们不可以用它。
回到正题,props接收了数据怎么修改它?
- 首先要知道,props被底层监视了的,所以它的东西只可以接收,不可以修改,想要接收了数据,再修改它,我们就需要借助data,中转一下。
- 先来看不中转,然后修改props中数据的下场。
- 使用data中转,从而修改数据。
props配置总结
-
功能:让组件接收外部传进来的数据。
-
传递数据:
<Person name="xxxx">
。 -
接收数据
-
1)、只接收:
props: ['name']
。 -
2)、接收数据 + 数据类型限定
props: { name:String }
- 3)、接收数据 + 数据类型限定 + 必要性限制 + 数据默认值
-
props: {
type:String,
required:true,
default:'紫邪情' 注:一个数据字段不会同时出现required和defautle
}
- 注意:props中的数据是只读的,Vue底层会监测对props的修改,如果进行了修改,就会发出警告。
- 如果业务需要修改,则:把props中的数据复制一份到data中,然后去修改data中的数据就可以了。
- 须知:vue中优先使用props中的数据,然后再使用data中的数据( 可以让data中的数据和props中的数据一样,然后去渲染,发现渲染出来的数据是props中的)。
mixin 混入 / 混合
这个东西就是把多个组件中共同的配置给抽取出来,然后单独弄成一个js文件,使用一个对象把相同配置给封装起来,然后在需要的组件中引入、使用mixins配置进行使用即可。
这种思想再熟悉不过了,工具类的编写不就是这么回事吗。当然这样的需求一般都用后续要玩的“状态机Vuex”了。
使用
基础代码
在上面的代码中,methods中的代码是相同的,因此:使用mixin混入来进行简化,也是三板斧而已。
- 新建js文件(名字根据需要取即可)。
- 在需要的组件中引入抽取的代码 和 利用mixins配置进行使用。
- 运行效果
mixin混入的一些注意点。
-
除了生命周期,如果其他配置是在mixin中定义了,同时在组件中也定义了,那么:优先使用的是组件中自己定义的(无论二者相同与否都一样)。
-
如果在mixin中定义了生命周期钩子函数,那么:优先使用mixin中的钩子函数。
如果二者不同,那么就会造成二者中定义的都会被调用。
mixin混入是支持全局配置的,不过这种操作不当会出现问题。
因此:目前不建议用,思路如下:
- 一样的创建js文件。
- 在
App.vue
中引入。 - 在
App.vue
中使用vue.mixin( 暴露对象1 )
、vue.mixin( 暴露对象2 )
.......。
- 使用了这三板斧之后,就可以实现全局配置了。
mixin 混入总结
功能:把多个组件共同的配置提取成一个混入对象。
使用方法:
- 新建
xxx.js
定义混入,如:
暴露方式 const 对象名 {
data(){......},
methods:{........},
........
}
-
在需要的组件中引入混入。
-
在需要使用混入中定义的功能的组件中使用混入,如:
- 1)、局部混入:
mixins: [ xxxxxx , ......... ]
, 注意:这里必须用数组。 - 2)、全局混入:
Vue.mixin( xxxxx )
。
插件
这个东西的作用很大,它可以合理的增强Vue。
使用,还是三板斧
-
创建js文件(包含
install()
方法的一个对象)。 -
引入插件。
-
使用插件。
玩一下插件
基础代码效果
- 创建一个包含
install()
方法的对象的js文件。
- 在
main.js
中引入、使用插件。
- 效果如下
可以得知:创建的插件中传递的那个参数Vue就是vm(Vue实例对象)的缔造者 —— vue构造函数。
得到上面那个结果就很重要了,试想:我们使用这个构造函数做过什么事?
-
自定义全局指令
Vue.directive
。 -
定义全局过滤器
Vue.filter
。 -
定义全局混入
Vue.mixin
。 -
........
-
把这些东西放到创建插件的
install()
中可行?它接受的参数就是Vue嘛。
正宗玩法
- 创建包含
install()
方法的对象的js文件。
- 在
main.js
中引入插件、应用组件。
- 使用插件中的东西。
- 效果如下:
当然:我们也可以给插件中传东西进去。
插件总结
功能:用于增强Vue。
本质:是包含install()
方法的一个对象的js文件,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
插件的玩法:
- 定义插件:
// 1、创建插件 export default 是自己选择的暴露方式
export default {
install(Vue,[ other params ]){
// 定义全局过滤器
Vue.filter( ..... ),
Vue.directive( ...... ),
Vue.mixin( ....... )
.........
}
}
-
在
main.js
中引入插件。 -
在
main.js
中向Vue应用插件Vue.use( 插件名 )
。 -
[ 使用插件中定义的东西 ] ———— 可有可无,看自己的代码情况。
- 这里注意一个东西:定义插件中的
install()
第一个参数是Vue,即:vm的缔造者,Vue构造函数(这里可以联想原型对象,也就是前面说的vm和vc的内置关系:VueComponent.prototype._ _proto _ _ === Vue.prototype
),这也就是说在Vue身上加点东西,那么:vm和vc都可以拿到,如:
Vue.prototype.$myMethod = function(){ ...... }
Vue.prototype.$myProperty = xxxx
prototype 路线是加东西
_ _proto_ _ 路线是取东西
scoped 局部样式
作用:让样式在局部生效,防止冲突。
写法:<style scoped>。
假如有两个组件,里面都用了同一个class类名,但是做的style却是不一样的。
此时如果把两个class名字改成一样呢?开发中样式多了这种事情是在所难免的。
凭什么就是Person2.vue
组件中的样式优先?这和App.vue中引入组件的顺序有关。
不然:把组件引入的顺序换一下就发现变了。
那上述样式冲突了怎么解决?答案就是使用scoped限制作用域(后端人员,这个东西就类似maven中依赖的作用域,是一样的效果)
当然:style标签不止支持scoped属性,还可以用其他的。
另外:less需要less-loader支持,所以需要安装less-loader,但是:有坑(版本的问题)。
# 控制台输入
npm view webpack versions # 查看webpack所有版本(旧+新)
cli中的webpack版本:
安装适合cli的webpack的less-loader版本:
npm install less-loader@版本号
最后还有一个问题:scoped使用在
App.vue
中就会发生很诡异的事情。
App.vue
是大哥,所以这里面的style会全局生效,也就是多组件都在使用的样式,就可以提到App.vue
中写,然后在其他需要的组件中使用即可 。- 但是:如果
App.vue
中style使用了scoped,那么就会导致:样式只在App.vue
中有效,那么:其他组件中想要用,对不起,管不了,最后页面就会出现诡异的事情 —— 不是自己写的样式的样子。 - 所以:
App.vue
中最好别用scoped属性,而其他组件中最好都加上scoped属性。
组件化应用应该怎么去写?
实例:实现如下的效果。 就是一个人名录入,然后可以对名字做一点操作罢了。
组件编写流程,基本上都是一样的套路,接下来按照套路去弄就可以了。
分析结构 / 拆分组件
根据页面结构来看,可以拆分成如下的结构:
- 外面大的框就是一个组件 —— 也就是
App.vue
父组件,而App组件中又包含的如下组件:
- 1.1、输入框是一个组件(子)。
- 1.2、人名的展示框是一个组件(子 ,却是下面两个的“父”)
- 1.2.1、然后发现展示框里面又有每一个人名组件(子)。
- 1.2.2、还有一个全选 / 显示已选人数的组件( 子)。
创建对应组件并编写内容
创建并编写组件对应内容( 统称编写静态组件 )
- App组件
- 1)、HTML结构。
<template>
<div id="root">
<div class="name-container">
<div class="name-wrap">
<NameHeader></NameHeader>
<NameList></NameList>
<NameFooter></NameFooter>
</div>
</div>
</div>
</template>
<script>
import NameHeader from "./components/NameHeader.vue"
import NameList from "./components/NameList.vue"
import NameFooter from "./components/NameFooter.vue"
export default {
name: 'App',
components: {NameHeader,NameList,NameFooter}
}
</script>
- 2)、CSS样式 + 后续需要的通用样式。
body{
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px rgba(255,255,255,0.2),0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.name-container {
width: 600px;
margin: 0 auto;
}
.name-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
- 输入框组件。
- 1)、HTML结构。
<div class="name-footer">
<label>
<input type="checkbox">
</label>
<span>
<span>已选择0</span> / 共计2
</span>
<button class="btn btn-danger">清除已选人员</button>
</div>
- 人名展示框。
- 1)、HTML结构。
<template>
<NameObj></NameObj>
</template>
<script>
import NameObj from "./NameObj.vue"
export default {
name: 'NameList',
components: {NameObj}
}
</script>
- 2)、CSS样式。
/* #region list */
.name-main {
margin-left: 0px;
border: 1px solid s#ddd;
border-radius: 2px;
padding: 0px;
}
.name-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
/* #endregion */
- 每个人名展示。
- 1)、HTML结构
<template>
<ul class="name-main">
<li>
<label>
<input type="checkbox"/>
<span>xxxxx</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
</ul>
</template>
<script>
export default {
name: 'NameObj',
}
</script>
- 2)、CSS样式
/* #region item */
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: 1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
/* #endregion */
运行效果如下:
自此:静态组件编程就搞定,后续就可以做数据的动态绑定和交互这些了。
将纯HTML + CSS + JS转成组件化开发也是一样的套路,流程如下:
-
把整个HTML结构放到
App.vue
组件的template模板中。 -
把所有的CSS放到
App.vue
组件的style中。 -
有JS的话,那么把js文件创好,然后引入到
App.vue
组件中。 -
开始去分析结构,然后拆分成组件,之后把对应的内容放到对应组件中,最后做后续的动态数据绑定、交互即可。
动态数据绑定
按照分析,要展示的数据是一堆数据,而数据显示的地方是NameList,所以:data数据就放到NameList中去。本实例中数据放到这里有坑啊,但是:先这么放,后续遇到坑了再调整,所以改一下源码。
数据是弄好了,但是:真正展示数据的是NameObj组件来显示出来的,而NameObj是NamelList的子组件(这就是:父传子),这种就需要借助props配置项( 从外向内传嘛),所以:开始操作。
- 父传子
- 获取输入的内容并添加到显示的顶部 —— 和后续知识挂钩的重点来了。
输入框组件是App组件的子组件,而数据展示是NameList组件中的NameObj组件,即:现在关系就是如下的样子。
上面这种就是不同组件之间的通信情况,现在来了解原生的解决办法
- 原生的不同组件通讯
App.vue
是大哥,用它做文章,这样就变成了App这个父组件和输入框以及Namelist这两个子组件之间的关系了。
第一步:将data搬到App组件中去并传给NameList数据展示组件。
然后NameList数据显示区再传给数据显示组件
这样父传子、子传孙....这样就串通一条路了,即:下图右边部分。
第二步:将输入框组件中的数据收集起来,并传给父组件(这就是子传父)。
将数据传给父组件 —— 开始子传父
子传父的技巧就是:父组件传给子组件一个函数( props的变样版,传的不是对象、key-value,而是整一个函数传过去)、然后子组件利用props接收到函数之后调用一下(把传递数据当做参数),那么父组件中的函数的参数就是要传递的数据了。
上图id是使用random()
生成的随机数,这有弊端的,数字有穷尽的时候,所以:严格来说用uuid、身份证号、电话号码......作为id最好。
- 将子组件传递的数据添加到数据栏的顶部去
交互编写
实现选择和数据的改变
1)、最简单粗暴的方式 —— 但是:不建议用
成功是成功了,但是:说了不建议用,因为:这种方式违背了Vue的设计(但是:开发中又喜欢用,简单实用嘛)。
- 违背了Vue的设计是因为:props在底层是被检测了的,Vue不支持去修改它的东西,但是:按上述的方式做了之后,却会发现:并不会报错,这是因为:修改的是值和对象的区别。
2)、按照逻辑老实编写
这种实现方式的思路就是:在页面中点击 / 改变选择框时,拿到这条数据的id,然后利用id去所有的数据中遍历,找到对应的这条数据,最后改变这条数据isCheck的值即可。
下面的操作也是一个父传子的使用过程:这里有一句话:数据在哪里,对数据的操作(methods)就在哪里。
实现每条数据的删除功能
1)、先把样式解开,让鼠标悬浮时删除按钮可见。
2)、实现数据与删除按钮交互(还是子传父的套路)。
实现逻辑简单:拿到要删除数据的id,然后去众多数据中把要删除数据给过滤掉不显示即可(逻辑删除)。
实现底部的已选择人数 和 总计人数功能
最原生的方式
父传子 —— 传递persons这个数据。
使用数组的 reduce() API
使用数组的reduce()
这个API,这个API专做数据统计的。
认识一下reduce()
reduce()
最终的返回值是:程序执行的最后一次的nextValue值。
使用reduce()
实现功能。
接下来就只剩下底部的全选和清除已选这两个功能了。
实现全选交互 和 清除已选人员功能
实现全选交互
实现全选交互(子传父 + 计算属性使用技巧):
上面这个全选使用分步利用子传父实现每一步也是可以的,只是有点复杂罢了。
清除已选人员
这个的思路更简单了,就是查看页面中的数据哪些的isCheck为true,然后过滤掉这些数据即可(实现方式一样简单,也是子传父)。
功能改善
localstorage和sessionstorage。 前者浏览器关闭不会清空,后者关闭浏览器会清空,二者常用的API都一模一样,二者里面存的数据都是key-value的形式。
- 存数据
setItem( 'key', 'value' )
使用:LocalStorage.setItem( 'key' , 'value' )
其他的都是差不多的。 - 取数据
getItem( 'key' )
注:若key不存在,则:此API返回值是null;若key是对象字符串 那么用JSON.parse()
转成对象,返回值也是null。
-
删除某一个数据
removeItem()
。 -
清空所有
clear()
。
二者存储的内容大小一般为5M的字符串( 不同浏览器可能会不一样 )。
组件化开发流程总结
组件化编写流程:
-
拆分静态组件:组件要按照功能点拆分,命名不要和HTML元素名冲突。
-
实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用。
- 一个组件在用:把数据放到组件自身即可。
- 一些组件在用:把数据放到它们共同的父组件上【 这也叫状态提升 】。
- 实现交互:从绑定事件开始。
props配置总结:适用于以下过程
-
父组件 ——> 子组件 通信。
-
子组件 ——> 父组件 通信。
v-model总结
- 使用v-model时要切记:v-model绑定的是值,但是:这个值不能是props中传递过来的值,因为:props底层被Vue监测了的,不允许修改。
注:props中传过来的若是对象类型的值时,虽然修改"对象中的属性"时Vue不会报错,但是:不建议用。