Vue2:组件基础(上)
Date: July 29, 2023
Sum: 生命周期、Vue-cli、组件的使用、小黑记账清单、小兔鲜首页
生命周期:
生命周期介绍
思考:
什么时候可以发送初始化渲染请求?(越早越好)
什么时候可以开始操作dom?(至少dom得渲染出来)
Vue生命周期:就是一个Vue实例从创建 到 销毁 的整个过程。
生命周期四个阶段:① 创建 ② 挂载 ③ 更新 ④ 销毁
1.创建阶段:创建响应式数据
将普通数据转换成响应式数据
2.挂载阶段:渲染模板
结合数据渲染模版
3.更新阶段:修改数据,更新视图
数据修改与更新视图循环
4.销毁阶段:销毁Vue实例
注意:创建和挂载阶段只有一次,但是更新阶段会循环多次
理解:了解了Vue的生命周期后,可以理解在何时去调用相应的代码
比如在创建阶段结束之后,我们才能发送初始化的渲染请求。在挂载阶段结束之后,才能操作dom
生命周期钩子
Vue生命周期过程中,会自动运行一些函数,被称为【生命周期钩子】→ 让开发者可以在【特定阶段】运行自己的代码
注意:挂载阶段在mounted完成后,模版已经渲染完成了
注意:卸载阶段在你关闭浏览器时调用
案例:
Code:
<!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"> <h3>{{ title }}</h3> <div> <button @click="count--">-</button> <span>{{ count }}</span> <button @click="count++">+</button> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { count: 100, title: '计数器' }, // 1. 创建阶段(准备数据) beforeCreate () { console.log('beforeCreate 响应式数据准备好之前', this.count) }, created () { console.log('created 响应式数据准备好之后', this.count) // this.数据名 = 请求回来的数据 // 可以开始发送初始化渲染的请求了 }, // 2. 挂载阶段(渲染模板) beforeMount () { console.log('beforeMount 模板渲染之前', document.querySelector('h3').innerHTML) }, mounted () { console.log('mounted 模板渲染之后', document.querySelector('h3').innerHTML) // 可以开始操作dom了 }, // 3. 更新阶段(修改数据 → 更新视图) beforeUpdate () { console.log('beforeUpdate 数据修改了,视图还没更新', document.querySelector('span').innerHTML) }, updated () { console.log('updated 数据修改了,视图已经更新', document.querySelector('span').innerHTML) }, // 4. 卸载阶段 beforeDestroy () { console.log('beforeDestroy, 卸载前') console.log('清除掉一些Vue以外的资源占用,定时器,延时器...') }, destroyed () { console.log('destroyed,卸载后') } }) </script> </body> </html>
注:在生命周期中销毁后,以上的组件即使去按+或-也没有用了,因为以上DOM已于Vue无关了。
补充:在浏览器console中输入
app.$destory()
即可触发 beforeDestory 和 destroyed
生命周期案例
案例1:
需求:一进页面就发送请求,去获取对应的数据,来渲染出来
思考:在created钩子中发送初始化渲染请求, 采用axios方式请求数据
Code:
<!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> <style> * { margin: 0; padding: 0; list-style: none; } .news { display: flex; height: 120px; width: 600px; margin: 0 auto; padding: 20px 0; cursor: pointer; } .news .left { flex: 1; display: flex; flex-direction: column; justify-content: space-between; padding-right: 10px; } .news .left .title { font-size: 20px; } .news .left .info { color: #999999; } .news .left .info span { margin-right: 20px; } .news .right { width: 160px; height: 120px; } .news .right img { width: 100%; height: 100%; object-fit: cover; } </style> </head> <body> <div id="app"> <ul> <li v-for="(item, index) in list" :key="item.id" class="news"> <div class="left"> <div class="title">{{ item.title }}</div> <div class="info"> <span>{{ item.source }}</span> <span>{{ item.time }}</span> </div> </div> <div class="right"> <img :src="item.img" alt=""> </div> </li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> // 接口地址:http://hmajax.itheima.net/api/news // 请求方式:get const app = new Vue({ el: '#app', data: { list: [] }, async created () { // 1. 发送请求获取数据 const res = await axios.get('http://hmajax.itheima.net/api/news') // 2. 更新到 list 中,用于页面渲染 v-for this.list = res.data.data } }) </script> </body> </html>
案例2:
需求:一进页面就获取焦点
思考:在mounted钩子上获取焦点
注意:挂载阶段在mounted完成后,模版已经渲染完成了
Code:
<!DOCTYPE html> <html lang="zh-CN"> <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>示例-获取焦点</title> <!-- 初始化样式 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset.css@2.0.2/reset.min.css"> <!-- 核心样式 --> <style> html, body { height: 100%; } .search-container { position: absolute; top: 30%; left: 50%; transform: translate(-50%, -50%); text-align: center; } .search-container .search-box { display: flex; } .search-container img { margin-bottom: 30px; } .search-container .search-box input { width: 512px; height: 16px; padding: 12px 16px; font-size: 16px; margin: 0; vertical-align: top; outline: 0; box-shadow: none; border-radius: 10px 0 0 10px; border: 2px solid #c4c7ce; background: #fff; color: #222; overflow: hidden; box-sizing: content-box; -webkit-tap-highlight-color: transparent; } .search-container .search-box button { cursor: pointer; width: 112px; height: 44px; line-height: 41px; line-height: 42px; background-color: #ad2a27; border-radius: 0 10px 10px 0; font-size: 17px; box-shadow: none; font-weight: 400; border: 0; outline: 0; letter-spacing: normal; color: white; } body { background: no-repeat center /cover; background-color: #edf0f5; } </style> </head> <body> <div class="container" id="app"> <div class="search-container"> <img src="https://www.itheima.com/images/logo.png" alt=""> <div class="search-box"> <input type="text" v-model="words" id="inp"> <button>搜索一下</button> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { words: '' }, // 核心思路: // 1. 等input框渲染出来 mounted 钩子 // 2. 让input框获取焦点 inp.focus() mounted () { document.querySelector('#inp').focus() } }) </script> </body> </html>
综合案例-小黑记账清单
1-需求图示
2-需求分析
1.基本渲染
2.添加功能
3.删除功能
4.饼图渲染
3-思路分析
1.基本渲染
▪立刻发送请求获取数据 created
▪拿到数据,存到data的响应式数据中
▪结合数据,进行渲染 v-for
▪消费统计 —> 计算属性
2.添加功能
▪收集表单数据 v-model,使用指令修饰符处理数据
▪给添加按钮注册点击事件,对输入的内容做非空判断,发送请求
▪请求成功后,对文本框内容进行清空
▪重新渲染列表
3.删除功能
▪注册点击事件,获取当前行的id
▪根据id发送删除请求
▪需要重新渲染
4.饼图渲染
▪初始化一个饼图 echarts.init(dom) mounted钩子中渲染
▪根据数据试试更新饼图 echarts.setOptions({…})
实现基本渲染功能:
Code: 实现基本渲染
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <!-- CSS only --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" /> <style> .red { color: red!important; } .search { width: 300px; margin: 20px 0; } .my-form { display: flex; margin: 20px 0; } .my-form input { flex: 1; margin-right: 20px; } .table > :not(:first-child) { border-top: none; } .contain { display: flex; padding: 10px; } .list-box { flex: 1; padding: 0 30px; } .list-box a { text-decoration: none; } .echarts-box { width: 600px; height: 400px; padding: 30px; margin: 0 auto; border: 1px solid #ccc; } tfoot { font-weight: bold; } @media screen and (max-width: 1000px) { .contain { flex-wrap: wrap; } .list-box { width: 100%; } .echarts-box { margin-top: 30px; } } </style> </head> <body> <div id="app"> <div class="contain"> <!-- 左侧列表 --> <div class="list-box"> <!-- 添加资产 --> <form class="my-form"> <input type="text" class="form-control" placeholder="消费名称" /> <input type="text" class="form-control" placeholder="消费价格" /> <button type="button" class="btn btn-primary">添加账单</button> </form> <table class="table table-hover"> <thead> <tr> <th>编号</th> <th>消费名称</th> <th>消费价格</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="(item, index) in list" :key="item.id"> <td>{{ index + 1 }}</td> <td>{{ item.name }}</td> <td :class="{ red: item.price > 500}">{{ item.price.toFixed(2) }}</td> <td><a href="javascript:;">删除</a></td> </tr> </tbody> <tfoot> <tr> <td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td> </tr> </tfoot> </table> </div> <!-- 右侧图表 --> <div class="echarts-box" id="main"></div> </div> </div> <script src="../echarts.min.js"></script> <script src="../vue.js"></script> <script src="../axios.js"></script> <script> /** * 接口文档地址: * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058 * * 功能需求: * 1. 基本渲染 * 2. 添加功能 * 3. 删除功能 * 4. 饼图渲染 */ const app = new Vue({ el: '#app', data: { list: [], }, computed: { totalPrice() { return this.list.reduce((sum, item) => sum += item.price, 0) } }, async created() { // const res = await axios({ // url: 'https://applet-base-api-t.itheima.net/bill', // method: 'get', // params: { // creator: 'Nathan', // } // }) const res = await axios.get('https://applet-base-api-t.itheima.net/bill', { params: { creator: 'Nathan' } }) this.list = res.data.data } }) </script> </body> </html>
实现添加功能:
重点:提交完数据后需要重新axios请求数据渲染页面,以下是建议封装一下axios请求
注意一下get和post的两种请求格式:
get后面的{}中需要加 params
async getList() { const res = await axios.get('https://applet-base-api-t.itheima.net/bill', { params: { creator: 'Nathan' } }) this.list = res.data.data },
post后面的{}中不需要加 params
async add() { if(!this.name) { alert('请输入消费名称') return } if(typeof this.price !== 'number') { alert('请输入正确的价格') } const res = await axios.post('https://applet-base-api-t.itheima.net/bill', { creator: 'Nathan', name: this.name, price: this.price, }) this.getList() this.name = '' this.price = '' }
Code:
实现删除功能:
Code:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <!-- CSS only --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" /> <style> .red { color: red!important; } .search { width: 300px; margin: 20px 0; } .my-form { display: flex; margin: 20px 0; } .my-form input { flex: 1; margin-right: 20px; } .table > :not(:first-child) { border-top: none; } .contain { display: flex; padding: 10px; } .list-box { flex: 1; padding: 0 30px; } .list-box a { text-decoration: none; } .echarts-box { width: 600px; height: 400px; padding: 30px; margin: 0 auto; border: 1px solid #ccc; } tfoot { font-weight: bold; } @media screen and (max-width: 1000px) { .contain { flex-wrap: wrap; } .list-box { width: 100%; } .echarts-box { margin-top: 30px; } } </style> </head> <body> <div id="app"> <div class="contain"> <!-- 左侧列表 --> <div class="list-box"> <!-- 添加资产 --> <form class="my-form"> <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" /> <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" /> <button @click="add" type="button" class="btn btn-primary">添加账单</button> </form> <table class="table table-hover"> <thead> <tr> <th>编号</th> <th>消费名称</th> <th>消费价格</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="(item, index) in list" :key="item.id"> <td>{{ index + 1 }}</td> <td>{{ item.name }}</td> <td :class="{ red: item.price > 500 }">{{ item.price.toFixed(2) }}</td> <td><a @click="del(item.id)" href="javascript:;">删除</a></td> </tr> </tbody> <tfoot> <tr> <td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td> </tr> </tfoot> </table> </div> <!-- 右侧图表 --> <div class="echarts-box" id="main"></div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> /** * 接口文档地址: * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058 * * 功能需求: * 1. 基本渲染 * (1) 立刻发送请求获取数据 created * (2) 拿到数据,存到data的响应式数据中 * (3) 结合数据,进行渲染 v-for * (4) 消费统计 => 计算属性 * 2. 添加功能 * (1) 收集表单数据 v-model * (2) 给添加按钮注册点击事件,发送添加请求 * (3) 需要重新渲染 * 3. 删除功能 * (1) 注册点击事件,传参传 id * (2) 根据 id 发送删除请求 * (3) 需要重新渲染 * 4. 饼图渲染 */ const app = new Vue({ el: '#app', data: { list: [], name: '', price: '' }, computed: { totalPrice () { return this.list.reduce((sum, item) => sum + item.price, 0) } }, created () { // const res = await axios.get('https://applet-base-api-t.itheima.net/bill', { // params: { // creator: '小黑' // } // }) // this.list = res.data.data this.getList() }, methods: { async getList () { const res = await axios.get('https://applet-base-api-t.itheima.net/bill', { params: { creator: '小黑' } }) this.list = res.data.data }, async add () { if (!this.name) { alert('请输入消费名称') return } if (typeof this.price !== 'number') { alert('请输入正确的消费价格') return } // 发送添加请求 const res = await axios.post('https://applet-base-api-t.itheima.net/bill', { creator: '小黑', name: this.name, price: this.price }) // 重新渲染一次 this.getList() this.name = '' this.price = '' }, async del (id) { // 根据 id 发送删除请求 const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`) // 重新渲染 this.getList() } } }) </script> </body> </html>
饼图渲染功能 :
关键:
1-在mounted处利用this把mychart绑定到Vue对象实例上
mounted() { // 利用 this 暂时存取一下myChart this.myChart = echarts.init(document.querySelector('#main')) // 指定图表的配置项和数据 var option = { title: { text: 'Referer of a Website', subtext: 'Fake Data', left: 'center' }, tooltip: { trigger: 'item' }, legend: { orient: 'vertical', left: 'left' }, series: [ { name: 'Access From', type: 'pie', radius: '50%', data: [ { value: 1048, name: 'Search Engine' }, { value: 735, name: 'Direct' }, { value: 580, name: 'Email' }, { value: 484, name: 'Union Ads' }, { value: 300, name: 'Video Ads' } ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] }; // 使用刚指定的配置项和数据显示图表。 this.myChart.setOption(option); }
2-引入echarts后,我们需要对getList()下的内容进行重新渲染,加上myChart.setOption,其关键点在于需要使用this来进行上下绑定。
this.myChart.setOption({ series: [ { // data: [ // { value: 1048, name: 'Search Engine' }, // { value: 735, name: 'Direct' }, // ], data: this.list.map(item => ({ value: item.price, name: item.name })) } ] })
Code:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <!-- CSS only --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" /> <style> .red { color: red!important; } .search { width: 300px; margin: 20px 0; } .my-form { display: flex; margin: 20px 0; } .my-form input { flex: 1; margin-right: 20px; } .table > :not(:first-child) { border-top: none; } .contain { display: flex; padding: 10px; } .list-box { flex: 1; padding: 0 30px; } .list-box a { text-decoration: none; } .echarts-box { width: 600px; height: 400px; padding: 30px; margin: 0 auto; border: 1px solid #ccc; } tfoot { font-weight: bold; } @media screen and (max-width: 1000px) { .contain { flex-wrap: wrap; } .list-box { width: 100%; } .echarts-box { margin-top: 30px; } } </style> </head> <body> <div id="app"> <div class="contain"> <!-- 左侧列表 --> <div class="list-box"> <!-- 添加资产 --> <form class="my-form"> <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" /> <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" /> <button @click="add" type="button" class="btn btn-primary">添加账单</button> </form> <table class="table table-hover"> <thead> <tr> <th>编号</th> <th>消费名称</th> <th>消费价格</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="(item, index) in list" :key="item.id"> <td>{{ index + 1 }}</td> <td>{{ item.name }}</td> <td :class="{ red: item.price > 500 }">{{ item.price.toFixed(2) }}</td> <td><a @click="del(item.id)" href="javascript:;">删除</a></td> </tr> </tbody> <tfoot> <tr> <td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td> </tr> </tfoot> </table> </div> <!-- 右侧图表 --> <div class="echarts-box" id="main"></div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> /** * 接口文档地址: * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058 * * 功能需求: * 1. 基本渲染 * (1) 立刻发送请求获取数据 created * (2) 拿到数据,存到data的响应式数据中 * (3) 结合数据,进行渲染 v-for * (4) 消费统计 => 计算属性 * 2. 添加功能 * (1) 收集表单数据 v-model * (2) 给添加按钮注册点击事件,发送添加请求 * (3) 需要重新渲染 * 3. 删除功能 * (1) 注册点击事件,传参传 id * (2) 根据 id 发送删除请求 * (3) 需要重新渲染 * 4. 饼图渲染 * (1) 初始化一个饼图 echarts.init(dom) mounted钩子实现 * (2) 根据数据实时更新饼图 echarts.setOption({ ... }) */ const app = new Vue({ el: '#app', data: { list: [], name: '', price: '' }, computed: { totalPrice () { return this.list.reduce((sum, item) => sum + item.price, 0) } }, created () { // const res = await axios.get('https://applet-base-api-t.itheima.net/bill', { // params: { // creator: '小黑' // } // }) // this.list = res.data.data this.getList() }, mounted () { this.myChart = echarts.init(document.querySelector('#main')) this.myChart.setOption({ // 大标题 title: { text: '消费账单列表', left: 'center' }, // 提示框 tooltip: { trigger: 'item' }, // 图例 legend: { orient: 'vertical', left: 'left' }, // 数据项 series: [ { name: '消费账单', type: 'pie', radius: '50%', // 半径 data: [ // { value: 1048, name: '球鞋' }, // { value: 735, name: '防晒霜' } ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] }) }, methods: { async getList () { const res = await axios.get('https://applet-base-api-t.itheima.net/bill', { params: { creator: '小黑' } }) this.list = res.data.data // 更新图表 this.myChart.setOption({ // 数据项 series: [ { // data: [ // { value: 1048, name: '球鞋' }, // { value: 735, name: '防晒霜' } // ] data: this.list.map(item => ({ value: item.price, name: item.name})) } ] }) }, async add () { if (!this.name) { alert('请输入消费名称') return } if (typeof this.price !== 'number') { alert('请输入正确的消费价格') return } // 发送添加请求 const res = await axios.post('https://applet-base-api-t.itheima.net/bill', { creator: '小黑', name: this.name, price: this.price }) // 重新渲染一次 this.getList() this.name = '' this.price = '' }, async del (id) { // 根据 id 发送删除请求 const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`) // 重新渲染 this.getList() } } }) </script> </body> </html>
注意事项:
1-如果在箭头函数中需要返回一个对象,那么需要在{}外在加上一对()
data: this.list.map(item => ({ value: item.price, name: item.name }))
工程化开发入门
工程化开发和脚手架
工程化开发模式
概念:
核心包传统开发模式:基于html / css / js 文件,直接引入核心包,开发 Vue。
工程化开发模式:基于构建工具(例如:webpack)的环境中开发Vue
根据上图,通过工程化开发模式能够将不同的源码转成不同版本的js代码
工程化开发模式优点:
提高编码效率,比如使用JS新语法、Less/Sass、Typescript等通过webpack都可以编译成浏览器识别的ES3/ES5/CSS等
工程化开发模式问题:
▪webpack配置不简单
▪雷同的基础配置
▪缺乏统一的标准
为了解决以上问题,所以我们需要一个工具,生成标准化的配置
脚手架 Vue-cli
**概念:**vue-cli是 vue 官方提供的、快速生成 vue 工程化项目的工具。【集成了webpack配置】
特点:
1.开箱即用,零配置
2.内置babel等工具
3.标准化的webpack配置
标准化:你的不同项目可以使用相同的架子
vue-cli 的中文官网首页:https://cli.vuejs.org/zh/
Vue-cli使用步骤
安装 vue-cli
vue-cli 是基于 Node.js 开发出来的工具,因此需要使用 npm 将它安装为全局可用的工具:
# 全局安装 vue-cli npm install -g @vue/cli # 查看 vue-cli 的版本,检查 vue-cli 是否安装成功 vue --version
解决 Windows PowerShell 不识别 vue 命令的问题:
默认情况下,在PowerShell 中执行 vue --version 命令会提示如下的错误消息:
解决方案如下:
① 以管理员身份运行 PowerShell
② 执行 set-ExecutionPolicy RemoteSigned 命令
③ 输入字符 Y ,回车即可
创建项目
vue-cli 提供了创建项目的两种方式:
# 基于 [命令行] 的方式创建 vue 项目 vue create 项目名称 # OR # 基于 [可视化面板] 创建 vue 项目 vue ui
基于 vue ui 创建 vue 项目
步骤1:在终端下运行 vue ui 命令,自动在浏览器中打开创建项目的可视化面板:
步骤2:在详情页面填写项目名称
步骤3:在预设页面选择手动配置项目:
步骤4:在功能页面勾选需要安装的功能(Choose Vue Version、Babel、CSS 预处理器、使用配置文件):
注意:最后一项是把插件的配置信息单独存放到一个配置文件
步骤5:在配置页面勾选 vue 的版本和需要的预处理器:
步骤6:将刚才所有的配置保存为预设(模板),方便下一次创建项目时直接复用之前的配置:
步骤7:创建项目并自动安装依赖包:
vue ui 的本质:通过可视化的面板采集到用户的配置信息后,在后台基于命令行的方式自动初始化项目:
注意:预设在.vuerc配置文件中
项目创建完成后,自动进入项目仪表盘:
基于命令行创建 vue 项目
步骤1:在终端下运行 vue create demo2 命令,基于交互式的命令行创建 vue 的项目:
步骤2:选择要安装的功能:
步骤3:使用上下箭头选择 vue 的版本,并使用回车键确认选择:
步骤4:使用上下箭头选择要使用的 css 预处理器,并使用回车键确认选择:
步骤5:使用上下箭头选择如何存储插件的配置信息,并使用回车键确认选择
步骤6:是否将刚才的配置保存为预设:
步骤7:选择如何安装项目中的依赖包
步骤8:开始创建项目并自动安装依赖包
步骤9:项目创建完成:
总结安装流程:
流程:
1.全局安装(只需安装一次即可) yarn global add @vue/cli 或者 npm i @vue/cli -g
2.查看vue/cli版本: vue --version
3.创建项目架子:vue create project-name(项目名不能使用中文)
4.启动项目:yarn serve 或者 npm run serve(命令不固定,找package.json)
根据下图中的红框处修改:
理解:
前两步搞定之后,以后创建Vue项目就只需要3和4两步来创建项目了
项目目录介绍和运行流程
项目目录介绍:
虽然脚手架中的文件有很多,目前咱们只需认识三个文件即可
main.js 入口文件
App.vue App根组件
index.html 模板文件
运行流程及作用:
main.js作用:导入 App.vue, 并基于 App.vue 创建结构渲染 index.html
App.vue:决定页面的呈现内容
main.js文件详解:
// 1. 导入 Vue 核心包 import Vue from 'vue' // 2. 导入 App.vue 根组件 import App from './App.vue' // 提示:当前处于什么环境(生产环境 / 开发环境) Vue.config.productionTip = false // 3. Vue实例化,提供render方法 -> 基于App.vue创建结构并渲染index.html new Vue({ render: h => h(App), }).$mount('#app')
补充:
提示环境信息:
关于Vue实例化的理解:下面两种方式的效果是一致的
new Vue({ render: h => h(App), }).$mount('#app')
new Vue({ el: 'app' render: h => h(App), })
render的完全写法:
render: h => h(App),
render: (creatElement) => { return creatElement(App) }
组件化开发思想
组件化概念:
概念:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。
好处:便于维护,利于复用 → 提升开发效率。
组件分类:普通组件、根组件。
比如:下面这个页面,若把所有的代码都写在一个页面中会显得代码比较混乱,难易维护。
咱们可以按模块进行组件划分:
案例网站:http://www.ibootstrap.cn/
理解:比如我们把轮播图封装成一个组件,哪个项目需要用,我们就导入到哪个项目中去。
vue 中的组件化开发
vue 是一个完全支持组件化开发的框架。vue 中规定组件的后缀名是 .vue。之前接触到的 App.vue 文件本质上就是一个 vue 的组件。
根组件概念:
概念:整个应用最上层的组件,包裹所有普通小组件
语法高亮插件:Vetur
组件构成:
—template:结构 (有且只能一个根元素)
—script:js逻辑
—style: 样式 (可支持less,需要装包)
其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。
让组件支持less:
(1) style标签,lang=“less” 开启less功能
(2) 装包: yarn add less less-loader -D 或者npm i less less-loader -D
组件的构成:
组件的 template 节点
vue 规定:每个组件对应的模板结构,需要定义到节点中。
<template> <!-- 当前组件的DOM结构,需要定义到template标签的内部> </template>
注:是vue提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的DOM元素
在 template 中使用指令
在组件的节点中,支持使用前面所学的指令语法,来辅助开发者渲染当前组件的 DOM 结构。
在 template 中定义根节点
在 vue 2.x 的版本中,节点内的DOM结构仅支持单个根节点:
代码示例如下:
<template> <div> <h1>App根组件</h1> <p>{{ num }}</p> </div> </template>
理解:如图所示,就是在h1 h2的外面得需要包裹一个div,否则h1 h2就算两个根节点
但是 ,在 vue 3.x 的版本中,中支持定义多个根节点:
<template> <h1>App根组件</h1> <h2>这是副标题</h2> </template>
理解:在h1 h2的外面就算没有包裹一个统一的根节点(比如用div包裹他们两)也没有问题
总结:
总结:template节点
template里面支持使用vue的指令来渲染DOM结构
template不会被渲染成真正的DOM结构,它只起到包裹性质的作用
在template内部,vue3支持多个根结点,vue2仅支持单个根节点
组件的 script 节点
vue 规定:组件内的
--- ### **script 中的 name 节点** 可以通过 name 节点为当前组件定义一个名称: ```jsx <script> export default { // name 属性指向带式当前组件的名称(建议: 每个单词的首字母大写) name: 'App', } </script>
在使用 vue-devtools 进行项目调试的时候,自定义的组件名称可以清晰的区分每个组件:
script 中的 data 节点
vue 组件渲染期间需要用到的数据,可以定义在 data 节点中:
<script> export default { name: 'App', // 组件的数据(data方法中return出去的对象,就是当前组件渲染期间需要用到的数据对象) data() { return { username: 'test' } } } </script>
组件中的 data 必须是函数
vue 规定:组件中的 data 必须是一个函数,不能直接指向一个数据对象。因此在组件中定义 data 数据节点时,下面的方式是错误的:
data: { //组件中,不能直接让 data 指向一个数据对象(会报错) count: 0 }
script 中的 methods 节点
组件中的事件处理函数,必须定义到 methods 节点中,示例代码如下:
export default { name: 'App', data() { return { count: 0, } }, methods: { addCount() { this.count++ } } }
methods指向一个对象,在methods指向的对象中,我们可以声明对应的事件处理函数,在事件处理函数中,this指向当前组件的实例。值得注意的是,当前组件中的数据(如data),可以通过this访问,如图中,我们可以通过this.count++来让实例中的data中的count自增加一。
组件的 style 节点
vue 规定:组件内的
其中 <style> 标签上的 lang="css" 属性是可选的,它表示所使用的样式语言。默认只支持普通的 css 语法,可选值还有 less、scss 等。 --- ### 让 style 中支持 less 语法 如果希望使用 less 语法编写组件的 style 样式,可以按照如下两个步骤进行配置: ① 运行 npm install less -D 命令安装依赖包,从而提供 less 语法的编译支持 ② 在 <style> 标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式 ```jsx <style lang="less"> h1 { font-weight: normal; i { color: red; font-style: normal; } } </style>
个人总结:
vue组件的组成结构:
每个 .vue 组件都由 3 部分构成,分别是:
template -> 组件的模板结构 script -> 组件的 JavaScript 行为 style -> 组件的样式
其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。
组件的 template 节点:
vue 规定:每个组件对应的模板结构,需要定义到节点中。
<template> <!-- 当前组件的DOM结构,需要定义到template标签的内部> </template>
1.template里面支持使用vue的指令来渲染DOM结构
2.template不会被渲染成真正的DOM结构,它只起到包裹性质的作用
3.在template内部,vue3支持多个根结点,vue2仅支持单个根节点
组件的 script 节点:
组件内的
<script> export default { // 组件的名称 name: 'MyApp', // 组件的数据 data() { return { username: 'Jack' } }, //组件的方法 methods: { addCount() { this.count++ } } } </script>
export default:
组件相关的 data 数据,methods 方法等,都需要定义到 export default 所导出的对象中。
data() :
data方法中return 出去的对象,就是当前组件渲染期间所需要用到的数据对象
组件的 style 节点:
组件内的
<style lang="css"> h1 { font-weight: normal; } </style>
组件的基本使用
组件的注册
概念:组件之间可以进行相互的引用,例如:vue 中组件的引用原则:先注册后使用。
vue 中组件的引用原则:先注册后使用。
使用方式:当成html标签使用即可 <组件名></组件名>
命名规范:大驼峰命名法, 如 HmHeader
组件注册命名方式:
在进行组件的注册时,定义组件注册名称的方式有两种:
① 使用 kebab-case 命名法(俗称短横线命名法,例如 my-swiper 和 my-search)
② 使用 PascalCase 命名法(俗称帕斯卡命名法或大驼峰命名法,例如 MySwiper 和 MySearch)
短横线命名法的特点:
必须严格按照短横线名称进行使用
帕斯卡命名法的特点:
既可以严格按照帕斯卡名称进行使用,又可以转化为短横线名称进行使用
注意:在实际开发中,推荐使用帕斯卡命名法为组件注册名称,因为它的适用性更强。
案例:
//kebab-case app.component('my-swiper', Swiper) //PascalCase app.component('MyTest', Test)
补充:
在template中写组件采用tab快速补齐
<HmHeader></HmHeader>
注册组件的两种方式:
vue 中注册组件的方式分为“全局注册”和“局部注册”两种,其中:
被全局注册的组件,可以在全局任何一个组件内使用
被局部注册的组件,只能在当前注册的范围内使用
如图,在全局注册的组件 Swiper,既可以在Home组件中使用,也可以在About组件中使用,
而在Home组件中局部注册的组Search组件,只能在Home组件中使用。
全局注册组件
全局注册组件方式:app.component(’被注册的组件名称’, 组件)
在main.js中操作:
使用app.component进行注册
或者,使用Vue.component进行注册
使用全局注册组件
使用 app.component() 方法注册的全局组件,直接以标签的形式进行使用即可,例如:
按照上面的说明配置main.js和App.vue
局部注册组件
操作流程:
在组件App中导入组件search, 然后在components中注册组件search
具体操作:
全局注册和局部注册的区别
被全局注册的组件,可以在全局任何一个组件内使用
被局部注册的组件,只能在当前注册的范围内使用
应用场景:
如果某些组件在开发期间的使用频率很高,推荐进行全局注册;
如果某些组件只在特定的情况下会被用到,推荐进行局部注册。
通过 name 属性注册组件
在注册组件期间,除了可以直接提供组件的注册名称之外,还可以把组件的 name 属性作为注册后组件的名称,示例代码如下:
注意事项:
如果注册组件的命名与要导入的组件一致,我们可以省略组件的命名:
如下所示,如果我们需要注册MyList组件,我们可以往components中填入:
components: { 'MyList': MyList }
由于组件的命名与导入组件的名称一致,所以可以简化为如下形式:
components: { MyList }
案例:
个人总结:
组件的注册:先注册,后使用
注册方式:全局注册和局部注册
组件注册名称:推荐用帕斯卡命名法
//PascalCase app.component('MyTest', Test)
全局注册组件:被全局注册的组件,可以在全局任何一个组件内使用
局部注册组件:被局部注册的组件,只能在当前注册的范围内使用
解决样式冲突问题:
默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题
为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域:
防止组件间样式冲突:
<style scoped> </style>
组件的 props:
props 是组件的自定义属性,使用者通过 props 把数据传递到子组件内部供使用。
作用:父组件通过 props 向子组件传递数据
示例如下:
// 这是父组件 App.vue <template> <div> <h1>这是 App.vue 根组件</h1> <hr> <!-- 通过自定义属性props,把文章标题和作者,传送到 my-article 组件中 --> <my-article :title="info.title" :author="info.author" :MyTest="info.MyTest"></my-article> </div> </template> <script> import MyArticle from './Article.vue' export default { name: 'MyApp', data() { return { info: { title: 'abc', author: '123', MyTest: 'test' } } }, components: { MyArticle, } } </script>
// 这是子组件 Article.vue <template> <div> <h3>标题:{{title}}</h3> <h5>作者:{{author}}</h5> <h6>发布时间:{{pubTime}}</h6> </div> </template> <script> export default { name: 'MyArticle', // 外界可以传递指定的数据,到当前的组件中 props: ['title', 'author', 'pubTime'] } </script>
Class 与 Style 绑定:
vue 允许开发者通过 v-bind 属性绑定指令,为元素动态绑定 class 属性的值和行内的 style 样式
综合案例-小兔仙首页
拆分模块-局部注册
这里单独讲个模块:新鲜好物模块
我们把商品项拆成模块,并进行全局注册复用:
先搞定商品模块的代码:
Code:
<template> <div> <li class="base-goods-item"> <a href="#"> <div class="pic"><img src="@/assets/images/goods1.png" alt="" /></div> <div class="txt"> <h4>KN95级莫兰迪色防护口罩</h4> <p>¥ <span>79</span></p> </div> </a> </li> </div> </template> <script> export default { } </script> <style> base-goods-item li { width: 304px; height: 404px; background-color: #EEF9F4; } base-goods-item li { display: block; } base-goods-item li .pic { width: 304px; height: 304px; } base-goods-item li .txt { text-align: center; } base-goods-item li h4 { margin-top: 17px; margin-bottom: 8px; font-size: 20px; } base-goods-item li p { font-size: 18px; color: #AA2113; } base-goods-item li p span { font-size: 22px; } </style>
再在main.js中进行全局注册:
import BaseGoodsItem from './components/BaseGoodsItem.vue' Vue.component('BaseGoodsItem', BaseGoodsItem)
最后在XtxNewGoods.vue中进行使用:
<ul> <BaseGoodsItem></BaseGoodsItem> <BaseGoodsItem></BaseGoodsItem> <BaseGoodsItem></BaseGoodsItem> <BaseGoodsItem></BaseGoodsItem> </ul>
补充:多次生成组件
<BaseGoodsItem v-for="item in 5" :key="item"></BaseGoodsItem>
注:这里的item是从1开始的数字