python 全栈开发,Day90(Vue组件,前端开发工具包)
昨日内容回顾
1. Vue使用 1. 生成Vue实例和DOM中元素绑定 2. app.$el --> 取出该vue实例绑定的DOM标签 3. app.$data --> 取出该vue实例绑定的data属性 2. 模板语法 1. { {name}} --> 在标签中间引用data属性中的变量 2. v-html='msg' --> 在页面上显示我定义的标签元素 3. v-if='ok' --> 控制标签在不在DOM中 4. v-show='!ok' --> 控制标签的display属性的值(是否显示) 5. v-bind:href='url' --> 将标签的属性和vue实例的data属性绑定起来 6. v-on:click='dianwo'--> 给标签绑定一个点击事件,方法需要在vue实例的methods中定义 7. v-model='article' --> 用在input标签和textarea标签中,将用户输入和vue实例中的数据属性建立双向绑定 3. 计算属性和侦听器 1. 计算属性 (字符串翻转的例子) 1. 计算属性需要定义在vue实例的 computed 中 2. 多用于对变量做一些自定义的操作 3. 我们在页面上像使用普通data属性一样使用计算属性 2. 侦听器 多用于一些复杂的运算或者异步操作 侦听器需要放在 vue实例的 watch 中 4. class和style属性 1. 基于对象的绑定 2. 基于数组的绑定 5. 条件渲染 v-if 如果想控制多个标签的显示与否,可以使用 template 标签把它们包起来 v-if/v-else v-if/v-else if /v-else 6. 列表渲染 v-for循环 7. 事件处理 详见2.模板语法 8. 表单绑定 详见2.模板语法 二. 小清单 1. 对象的深拷贝 2. 根据索引删除数组中的元素 splice(索引,删除个数,插入的元素)
一、Vue组件
组件基础
组件的组织
通常一个应用会以一棵嵌套的组件树的形式来组织:
将上图理解为一个网页
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们的组件都只是通过 Vue.component
全局注册的:
Vue.component('my-component-name', { // ... options ... })
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue
) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
到目前为止,关于组件注册你需要了解的就这些了,如果你阅读完本页内容并掌握了它的内容,我们会推荐你再回来把组件注册读完。
组件注册
组件名
在注册一个组件的时候,我们始终需要给它一个名字。比如在全局注册时:
Vue.component('my-component-name', { /* ... */ })
该组件名就是 Vue.component
的第一个参数。
你给予组件的名字可能依赖于你打算拿它来做什么。当直接在 DOM 中使用一个组件 (而不是在字符串模板或单文件组件) 的时候,我们强烈推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML 元素相冲突。
你可以在风格指南中查阅到关于组件名的其它建议。
组件名大小写
定义组件名的方式有两种:
使用 kebab-case
Vue.component('my-component-name', { /* ... */ })
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如
。
使用 PascalCase
Vue.component('MyComponentName', { /* ... */ })
当使用 PascalCase (驼峰式命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说
和
都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。
全局注册
到目前为止,我们只用过 Vue.component
来创建组件:
Vue.component('my-component-name', { // ... 选项 ... })
这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue
) 的模板中。比如:
js代码:
Vue.component('component-a', { /* ... */ }) Vue.component('component-b', { /* ... */ }) Vue.component('component-c', { /* ... */ }) new Vue({ el: '#app' })
html代码:
"app">
在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用。
常规举例:
"en"> "UTF-8">Title "app">
如果页面其他地方,也需要弹框效果呢?代码复制一遍?
这不符合编程习惯,需要使用组件
组件是可复用的 Vue 实例,且带有一个名字
组件举例:
"en"> "UTF-8">Title "app">
刷新网页,效果同上!
在这个例子中是
因为组件是可复用的 Vue 实例,所以它们与 new Vue
接收相同的选项,例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。
组件的复用
你可以将组件进行任意次数的复用:
"app">
注意当点击按钮时,每个组件都会各自独立维护它的 count
。因为你每用一次组件,就会有一个它的新实例被创建。
刷新网页,效果如下:
data
必须是一个函数
当我们定义这个
组件时,你可能会发现它的 data
并不是像这样直接提供一个对象:
data: {
count: 0
}
取而代之的是,一个组件的 data
选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () { return { count: 0 } }
如果 Vue 没有这条规则,点击一个按钮就可能会影响到其它所有实例:
举例:点击按钮,数字加1
"en"> "UTF-8">Title "app">
刷新网页,效果如下:
局部注册
全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:
js代码
var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } var ComponentC = { /* ... */ }
然后在 components
选项中定义你想要使用的组件:
new Vue({ el: '#app' components: { 'component-a': ComponentA, 'component-b': ComponentB } })
对于 components
对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。
注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentA
在 ComponentB
中可用,则你需要这样写:
var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, // ... }
举例:
"en"> "UTF-8">Title "app">
刷新网页,效果同上!
总结:
注意事项: 组件中的data属性必须设置成一个函数!!! 1. 注册全局组件 Vue.component(组件名,{ template: ``, data: function(){ return {} }, methods: {...} }) 2. 注册局部组件(当前vue实例才能使用的组件) new Vue({ el: '#app', components: { alert: { template: ``, data: function(){ return {} }, methods: {...} } } })
通过 Prop 向子组件传递数据
早些时候,我们提到了创建一个博文组件的事情。问题是如果你不能向这个组件传递某一篇博文的标题或内容之类的我们想展示的数据的话,它是没有办法使用的。这也正是 prop 的由来。
Prop 是你可以在组件上注册的一些自定义特性。当一个值传递给一个 prop 特性的时候,它就变成了那个组件实例的一个属性。为了给博文组件传递一个标题,我们可以用一个 props
选项将其包含在该组件可接受的 prop 列表中:
js代码:
Vue.component('blog-post', { props: ['title'], template: '{ { title }}
' })
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。在上述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data
中的值一样。
一个 prop 被注册之后,你就可以像这样把数据作为一个自定义特性传递进来:
html代码:
"My journey with Vue"> "Blogging with Vue"> "Why Vue is so fun">
效果图
然而在一个典型的应用中,你可能在 data
里有一个博文的数组:
js代码:
new Vue({ el: '#blog-post-demo', data: { posts: [ { id: 1, title: 'My journey with Vue' }, { id: 2, title: 'Blogging with Vue' }, { id: 3, title: 'Why Vue is so fun' } ] } })
并想要为每篇博文渲染一个组件:
html代码:
post v-for="post in posts" v-bind:key="post.id" v-bind:title="post.title" >
如上所示,你会发现我们可以使用 v-bind
来动态传递 prop。这在你一开始不清楚要渲染的具体内容,比如从一个 API 获取博文列表的时候,是非常有用的。
到目前为止,关于 prop 你需要了解的大概就这些了,如果你阅读完本页内容并掌握了它的内容,我们会推荐你再回来把 prop 读完。
举例:公司客服,每点击一次,表示接待了一个客户
"en"> "UTF-8">Title "app">for="item in list" v-bind:name="item">
刷新网页,效果如下:
父子传值,一定要使用props。这里说的父,指的是网页整体。子,指的是tankuang局部组件。
网页点击之后,需要将值传给button按钮,最终展示数据!
关键步骤如下:
1.定义了一个列表list,使用for循环遍历每一个元素
2.将值赋给item
3.item动态赋值给name
4.name传给props里面的name,name必须是字符串。
5.通过校验后,将值传给{ {name}},前端来渲染。
子组件向父组件传值
需要统计每一个客服,一共接待了多少客户。每增加一个客户,总数加1。
"en"> "UTF-8">Title "app">本月接待客户数:{ {totalCount}}
for="item in list" v-bind:name="item">
刷新网页,效果如下:
发现一个问题,总数没有变动?为什么呢?
因为这一段代码
methods:{ ttt:function(){ //自增 this.count +=1 } }
改变时,没有把外面的总数加1。那么如何改变父层的元素呢?需要使用自定义事件
自定义事件
事件名
跟组件和 prop 不同,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。举个例子,如果触发一个 camelCase 名字的事件:
js代码:
this.$emit('myEvent')
则监听这个名字的 kebab-case 版本是不会有任何效果的:
html代码:
"doSomething">
跟组件和 prop 不同,事件名不会被用作一个 JavaScript 变量名或属性名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on
事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent
将会变成 v-on:myevent
——导致 myEvent
不可能被监听到。
因此,我们推荐你始终使用 kebab-case 的事件名。
举例:
"en"> "UTF-8">Title "app">本月接待客户数:{ {totalCount}}
for="item in list" v-bind:name="item" v-on:jiedai="add">
刷新网页,效果如下:
这样设计的好处是,子组件只能修改自己的。如果需要修改父组件,必须通过自定义事件来完成!
插槽
插槽内容
Vue 实现了一套内容分发的 API,这套 API 基于当前的 Web Components 规范草案,将
元素作为承载分发内容的出口。
它允许你像这样合成组件:
html代码:
"/profile"> Your Profile
然后你在
的模板中可能会写为:
html代码:
<a v-bind:href="url" class="nav-link" >
当组件渲染的时候,这个
元素将会被替换为"Your Profile"。插槽内可以包含任何模板代码,包括 HTML:
"/profile"> class="fa fa-user"> Your Profile
甚至其它的组件:
html代码:
"/profile"> "user"> Your Profile
如果
没有包含一个
元素,则任何传入它的内容都会被抛弃。
举例:
"en"> "UTF-8">Title "app">本月接待客户数:{ {totalCount}}
for="item in list" v-bind:name="item" v-on:jiedai="add"> 上班了!
刷新网页,效果如下:
注意:template必须包含一个
元素,否则
网页无法展示。template必须使用闭合标签,否则报错!
具名插槽
有些时候我们需要多个插槽。例如,一个假设的
组件多模板如下:
html代码:
class="container">
对于这样的情况,
元素有一个特殊的特性:name
。这个特性可以用来定义额外的插槽:
class="container">"header">
在向具名插槽提供内容的时候,我们可以在一个父组件的 元素上使用
slot
特性:
"header"> Here might be a page title
A paragraph for the main content.
And another one.
"footer">Here's some contact info
另一种 slot
特性的用法是直接用在一个普通的元素上:
"header">Here might be a page title
A paragraph for the main content.
And another one.
"footer">Here's some contact info
我们还是可以保留一个未命名插槽,这个插槽是默认插槽,也就是说它会作为所有未匹配到插槽的内容的统一出口。上述两个示例渲染出来的 HTML 都将会是:
class="container">Here might be a page title
A paragraph for the main content.
And another one.
举例:
"en"> "UTF-8">Title "app">本月接待客户数:{ {totalCount}}
for="item in list" v-bind:name="item" v-on:jiedai="add"> "he"> 上班了! 打卡! "heng">哼哼!
刷新网页,效果如下:
总结:
1. 父组件 -传值-> 子组件 v-bind:变量='值' 注意事项: 子组件接收值需要在props里面声明 2. 子组件 -传值-> 父组件 触发自定义事件的方式,为了确保父组件的值不会被子组件直接修改 子组件触发事件: this.$emit('事件名') 父组件监听事件: v-on:事件名='处理的方法' 3. 插槽和带名字的插槽 引用子组件的时候,我可以传递一些额外的标签元素
二、前端开发工具包
node.js
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。
Node.js 的包管理器 npm,是全球最大的开源库生态系统。
官网:
https://nodejs.org/en/
下载最新版本
安装,疯狂点击下一步,就可以了!
安装完成后,打开cmd控制台,输入命令node -v 查看版本:
C:\Users\xiao>node -v
v10.7.0
本质上还是JS,跑在V8引擎上的,让JS能够和操作系统进行交互
和Python\php类似的脚本语言
npm
npm 是 JavaScript 世界的包管理工具,并且是 Node.js 平台的默认包管理工具。通过 npm 可以安装、共享、分发代码,管理项目依赖关系。
NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题
打开cmd控制台,输入命令npm -v 查看版本:
C:\Users\xiao>npm -v
6.1.0
类似于Python中的pip,帮助我们解决包和包之间版本的依赖关系
webpack
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
安装webpack,使用一下命令:
npm install webpack -g
npm install webpack-cli -g
关闭cmd窗口,再次打开,查看版本
C:\Users\xiao>webpack -v
4.16.2
准备文件
新建a.js
(function(){ //定义全局变量 //这种方式经常被用到一个匿名函数执行后将一些函数公开到全局 window.ryf = 'ruanyifeng'; })();
一般定义需要被导入的变量或者方法,使用function定义。
如果直接使用var 声明,那么html导入js之后,就直接有了全局变量,可能会影响当前html的全局变量,会被覆盖!
推荐使用方法,调用时,才使用变量
新建test.html,引入a.js
"en"> "UTF-8">
访问网页,效果如下:
发现Console打印出变量了
Nodejs 编写模块相当的自由,开发者只需要关注 require,exports,module 等几个变量就足够,而为了保持模块的可读性,很推荐把不同功能的代码块都写成独立模块,减少各模块耦合。
module.exports
用来导出代码,初始值为一个空对象 {}
修改a.js
(function(){ //定义全局变量 //这种方式经常被用到一个匿名函数执行后将一些函数公开到全局 window.ryf = 'ruanyifeng'; })(); //当前这个包 向外提供一个变量:ryf //module.exports 则用来导出代码,初始值为一个空对象 {} module.exports = { ryf: ryf }
require
webpack中可以写commonjs格式的require同步语法
经典的commonjs同步语法
新建b.js
var obj = require('./a.js');
此时webpack会将a.js打包进引用它的文件中,这是最普遍的情形!
正式使用Webpack
webpack可以在终端中使用,在基本的使用方法如下:
# {extry file}出填写入口文件的路径,本文中就是上述main.js的路径, # {destination for bundled file}处填写打包文件的存放路径 # 填写路径的时候不用添加{} webpack {entry file} {destination for bundled file}
指定入口文件后,webpack将自动识别项目所依赖的其它文件,不过需要注意的是如果你的webpack不是全局安装的,那么当你在终端中使用此命令时,需要额外指定其在node_modules中的地址。
在终端中输入如下命令:
必须先进入到b.js文件的目录
C:\Users\xiao>e: C:\Users\xiao>cd E:\python_script\Vue
再执行命令
webpack b.js
效果如下:
由于没有指定打包文件的存放路径,执行完成之后,会在当前目录生成dist目录,里面有一个文件main.js
查看main.js
!function(e){var r={};function t(n){ if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=r,t.d=function(e,r,n){t.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:n})},t.r=function(e){ "undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,r){ if(1&r&&(e=t(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(t.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var o in e)t.d(n,o,function(r){ return e[r]}.bind(null,o));return n},t.n=function(e){var r=e&&e.__esModule?function(){ return e.default}:function(){ return e};return t.d(r,"a",r),r},t.o=function(e,r){ return Object.prototype.hasOwnProperty.call(e,r)},t.p="",t(t.s=0)}([function(e,r,t){t(1)},function(e,r){window.ryf="ruanyifeng",e.exports={ryf:ryf}}]);
发现代码被压缩成一行了,代码被打乱了,看不懂的
修改test.html,改为导入mian.js
"en"> "UTF-8">
刷新网页,效果如下:
发现变量依然有效!