Vue01-框架和库的区别
Vue01-框架和库的区别
概念的区分
库的概念:就是某些特定方法的集合,比如JQuery、extjs、easyui。都是基于JS封装而来的库。
框架:有一套完善代码解决方案,我们可以基于框架的一些规范就能实现页面的交互。
框架带来的好处:
-
减少大家开发的差异性。比如再学习期间用vue来写代码,工作中也是这一套标准。规范性
-
性能考虑,使用jquery的方式来开发项目。能用很原生的一些代码实现我们业务。性能不好优化,性能瓶颈。dom操作非常消耗性能,而且不好维护的。事件委托。
-
开发效率。框架提供了很多api可以帮助你们直接进行业务设计,减少很多比必要代码。
核心思想
-
模块化思想:前端模块化和后端模块化。
import xxx from "" export default {}
后端模块化
const index = require("./index.js") module.exports = 暴露内容
模块化的标准不一样。
-
组件化思想:将页面拆分为一个个独立的模块,来实现代码复用
目前而言,我们如果想要将页面中公共的部分提取出来。
css、js,但是html代码能否提取出来
-
工程化思想:提供完整的项目开发标准,提供了完善的工具链让我们开发变得更加的易于维护,以及性能考虑
webpack的出现就是为了让我们能够实现页面中所有文件的管理,基于这个基础上,我们实现了前端工程化的一些标准。
每个框架都会有一套自己的脚手架(搭建前端工程化)
Vue02-Vuejs基本概念
Vue02-Vuejs基本概念
是什么?
有什么特点?
怎么学?
Vue资料
英文官网:https://vuejs.org
中文官网:https://cn.vuejs.org
github:yyx990803 (Evan You) · GitHub 可以下载源码,自己看源码
学习api:https://cn.vuejs.org/v2/api/
Vue框架介绍
作者:尤雨溪
目前国内使用是最多。
Vue的两种模式
-
在老项目中直接引入Vue(很少用)
-
使用脚手架的方式,搭建前端工程。完善的代码
老项目
(1)创建项目,初始化项目
npm init -y
(2)下载依赖vue
npm i vue@2.6.10
(3)引入vue直接使用
<!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> <script src="./node_modules/vue/dist/vue.min.js"></script> </head> <body> <div id="app"> <h3>电影项目,vue重构</h3> <!-- Vue的模板 --> <p>{{username}}</p> </div> <script> new Vue({ el:"#app", data:{ username:"小王" } }) </script> </body> </html>
各个模块的含义
<script> // 栈里面有一个app变量,存放的地址 // 在堆里面存放一个Vue实例 // 给构造函数传递参数,初始化我们的Vue实例对象 const app = new Vue({ // element 代表节点。要绑定的HTML节点 el:"#app", // 代表页面上的数据 data:{ username:"小王" } }) </script>
开发模式
基于DOM驱动的开发模式
直接在页面上操作节点,获取节点数据、更新节点。
const oapp = document.getElementById("app") const element = document.create("div") oapp.appendChild(element)
好处:思路完整清晰,你需要那一部分数据,找到对应dom就ok
最方便的开发模式。
基于数据驱动的开发模式
目前React\Vue都是数据驱动框架,你更多要关注的数据结构。剩下的工作交给框架来自己完成。
const array = [ {id:1,name:"xiaowang",status:true} ] let temp = "" for(let i=0;i<array.length;i++){ temp+=`<div>${array[i].name}</div><button></button>` } // odiv.appendChild(temp)
Vue03-脚手架搭建项目
Vue03-脚手架搭建项目
Vue这个框架来说,我们会经常用他的完整项目结构,提供了一个脚手架工具来帮助我们创建项目
脚手架工具:Vue底层基于webpack设计的一套前端工程化的标准。提供完整的工具链。比如代码压缩混淆工具
比如语法检测工具、babel代码的转义
一、安装脚手架
npm install -g @vue/cli
@vue/cli就是我们脚手架工具的名字。npm来全局安装这个工具
二、安装好了查看版本
vue --version
只需要全局安装一次,以后就可以创建项目
三、创建项目
Vue创建项目提供了两种方案,命令行的方案,可视化界面
(1)在终端里面找到你要创建项目的目录
vue create 项目名字 vue create vue-demo2
(2)选中项目安装配置
? Please pick a preset: Default ([Vue 2] babel, eslint) Default (Vue 3 Preview) ([Vue 3] babel, eslint) ❯ Manually select features
如果你创建了预设配置,默认显示这个配置,第一次来,默认显示三个选项。
Manually select features
一般都选最后一个,自定义配置工具链
(3)选中你需要安装的插件
? Check the features needed for your project: ◯ Choose Vue version ❯◉ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◯ Router ◯ Vuex ◯ CSS Pre-processors ◯ Linter / Formatter ◯ Unit Testing ◯ E2E Testing
按住空格代表选择取消。
按住回车进入下一步
默认选择:Choose Vue version、Babel、CSS Pre-processors
(4)提示你刚刚选择的插件配置文件存放地址
xxxxxxxxxx ? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)❯ In dedicated config files In package.json
第一个选项:每个插件都有自己独立的配置
第二个选项:所有插件的配置都存存放在package文件中
(5)保存预设路径
? Save this as a preset for future projects? (y/N)
选择Y,输入你的预设名字,下次可以直接用
选择N, 本地用这些配置,下次进来继续重新选择
(6)启动项目
$ cd my-project $ npm run serve
以后你在vue项目中更改了页面配置,默认热更新。
四、创建目录结构
Vue04-组件的概念
Vue04-组件的概念
组件的概念
在我们之前学习过程中我们遇到JS需要多个同时编写,完成一个项目。
模块化:项目中JS部分代码可以分为很多块,每个人负责这个板块。最后统一引入来实现代码效果。
<script src="./js/demo01.js"> var i = 10 <script src="./js/demo02.js"> var i = 20
组件化:拆分你们页面UI部分。可以将你们布局的代码拆分为一个一个模块,最后组合在一起。形成一个完整的页面;
一个组件包含完整的三部分代码:
<template> //HTML代码部分 </template> <script> //Js代码 </script> <style> //CSS代码 </style>
以后再开发过程中一个vue文件就是一个独立的模块,称为组件
组件的拆分没有标准,一般都是按照你们页面自己的布局来决定
创建vue文件,提示更加友好,vscode下载插件
一个基础的组件代码结构
<template> <div>这是demo组件</div> </template> <script> export default { } </script> <style> </style>
Vue05-常用的指令
Vue05-常用的指令
一、数据绑定
Vue提供了一套自己的响应式流程。我们只需要关注的数据层面,页面渲染层面,无法关注底层DOM实现。
<template> <div> <div>这是demo组件</div> <!-- Vue渲染模板 --> <p>{{username}}</p> <p>{{password}}</p> </div> </template> <script> export default { // 当前这个组件内部的数据 data() { return { username:"小王", password:"小飞飞" } } } </script> <style> </style>
没有Vue响应式过程
const op = document.getElementById("p") op.innerHTMl = data().username
二、常用指令分类
vue官方将很多的dom操作给我们封装成了一些指令,通过指令就可以实现对应业务操作
vue官方推出的指令都是v-名字开始,使用指令的时候。按照这个规则来
比如以前要获取文本框数据。
文本指令
v-html、v-text这个两个指令。
<template> <div> <h1>指令演示</h1> <p>{{username}}</p> <p v-text="username"></p> <p v-html="password"></p> </div> </template> <script> export default { data() { return { username:"小王", password:"小飞飞" } } } </script> <style> </style>
页面渲染数据三种语法
{{}} 这个是官方提供的一种方案,可以再指定的位置里面加入内容
v-text这个标签,默认会将指定节点里面的所有内容覆盖。不能标签中间加入内容
v-html这个标签,将你们渲染的字符串解析为页面节点来进行渲染,如果无法解析为页面节点,v-text没有区别
v-show
显示和隐藏页面的原始
<ul> <li>成都</li> <li v-show="status">北京</li> <li>上海</li> </ul> <!-- 事件绑定 --> <button @click="status=!status">显示/隐藏</button> <script> export default { data() { return { status:true } } } </script>
v-show通过控制页面上元素css样式来实现隐藏和显示。display:none
v-for指令
传统的DOM操作我们需要动态生成节点
const array = ["小王","小飞飞"] const temp = "" for(let i=0;i<array.length;i++){ temp+=`<li>${array[i]}</li>` } odiv.innerHTML = temp
再Vue中我们可以直接用一个v-for的指令就完成以上的工作
<table border="1"> <thead> <tr> <th>编号</th> <th>名字</th> <th>年龄</th> </tr> </thead> <tbody> <tr v-for="(element,index) in students"> <td>{{element.id}}</td> <td>{{element.name}}</td> <td>{{element.age}}</td> </tr> </tbody> </table>
再指定的标签身上,定义v-for指令
<div v-for="item in 数据">
v-for在使用的时候,默认要求你们给每一个遍历出来的节点增加key属性。这个key属性的指要保持唯一。
<div v-bind:key="item.id" v-for="item in 数据">
如果后端返回的不是数组,而是一个数字
<div v-for="item in 10">
默认情况,从1开始循环的。
v-bind指令
在Vue中我们如果想要动态才做属性,我们可以使用v-bind指令
你可以在data定义好你的数据,动态绑定页面上的属性
<tr v-bind:key="element.id" v-for="(element) in students"> <td>{{element.id}}</td> <td>{{element.name}}</td> <td>{{element.age}}</td> </tr>
<div v-bind:class="className"> <script> export default { data() { return { className:"as" } } } </script>
如果一个标签身上默认需要静态属性我们可以直接, 如果属性需要动态变化v-bind:属性
<div class="as" v-bind:class="className" id="as">v-bind</div> <script> export default { data() { return { className:"as" } } } </script>
v-bind你们可以简写出来,用冒号就可以表示
<div class="as" :class="className" id="as">v-bind</div>
练习题:
定义一个对象数组,数组里面存放的产生数据 {id:1,name:"小米",price:2300,num:20,status:true,className:"mygreen"}, {id:1,name:"华为",price:2300,num:20,status:true,className:"myred"} 要求: 1. 使用表格的方式将我们数据动态渲染出来。 2. 当statues状态为false的时候,默认隐藏起来。 3. className的值可以自定义颜色。(className="myred",className="mygreen")
Vue06-动态样式绑定
Vue06-动态样式绑定
一、样式的静态绑定
静态样式设计
组件中要设计样式,需要在style标签中些css样式
<template> <div> <span class="osp">web21期</span> </div> </template> <script> export default { } </script> <style> .osp{ font-size: 20px; color: tomato; } </style>
scss的引入
我们在搭建项目的时候,选择在你的项目中引入css预编译器,默认选择scss,以后你的任何一个组件都可以直接用scss来开发
<style lang="scss"> $mycolor:red; $backcolor:yellow; .osp{ font-size: 20px; color: tomato; } .wrapper{ p{ color:$mycolor; background: $backcolor; } } </style>
引入外部样式
<style lang="scss"> @import "../../assets/styles/common.css"; $mycolor:red; $backcolor:yellow; .osp{ font-size: 20px; color: tomato; } .wrapper{ p{ color:$mycolor; background: $backcolor; } } </style>
你可以在assets文件夹下面创建一个common.css公共样式。在指定的地方引入这个样式
@import "路径";
二、样式动态绑定
动态样式绑定,css样式由data中的数据来决定。实现动态切换页面元素的样式
动态样式绑定主要两种方案:
-
动态class绑定
-
动态style属性
动态class绑定
提前将样式写好
<template> <div> <div :class="className"></div> <button @click="className='box2'">切换颜色</button> </div> </template> <script> export default { data() { return { className: "box" } } } </script> <style lang="scss"> .box { width: 100px; height: 100px; background: yellowgreen; } .box2 { width: 100px; height: 100px; background: pink; } </style>
动态class绑定我们还可以用下面的语法来实现
<template> <div> <div :class="{box2:flag}">div2</div> <button @click="flag=!flag">显示\隐藏</button> </div> </template> <script> export default { data() { return { flag:true } } } </script> <style lang="scss"> .box2 { width: 100px; height: 100px; background: pink; } </style>
给class绑定多个样式
<template> <div> <div :class="{box2:flag,radis:flag2}">div3</div> <button @click="flag=!flag">显示\隐藏</button> <button @click="flag2=!flag2">控制圆角</button> </div> </template> <script> export default { data() { return { flag:true, flag2:true } } } </script> <style lang="scss"> .box2 { width: 100px; height: 100px; background: pink; } .radis{ border-radius: 10px; } </style>
动态style属性(了解)
每个标签都可以加上style这个属性。这是W3C规范
<template> <div> <div style="width:100px;height:100px;background-color:red"></div> <p :style="vars">web21</p> <p :style="{color:mycolor,fontWeight:value}">周渝智</p> <p :style="[fontStyle]">周渝智2</p> </div> </template> <script> export default { data(){ return{ vars:"color:red", mycolor:"green", value:800, fontStyle:{ "font-size":"30px", "color":"red" } } } } </script> <style> </style>
三、样式穿透
基于组件的方式来开发项目。
组件里面嵌套子组件
在父组件中引入子组件,父组件样式默认穿透到子组件中。
class名字一样,标签选择器的时候,就直接影响子元素
Parent.vue
<template> <div> <h2>这是Parent--H2</h2> <p class="op">parent-p</p> <ChildVue></ChildVue> </div> </template> <script> import ChildVue from './Child.vue' export default { components:{ ChildVue } } </script> <style> h2{ color:green } .op{ color:red } </style>
Child.vue
<template> <div> <h2>这是Children--H2</h2> <p class="op">children-p</p> </div> </template> <script> export default { } </script> <style> </style>
解决样式穿透问题
<template> <div> <h2>这是Parent--H2</h2> <p class="op">parent-p</p> <ChildVue></ChildVue> </div> </template> <script> import ChildVue from './Child.vue' export default { components:{ ChildVue } } </script> <style scoped> h2{ color:green } .op{ color:red } </style>
只需要在style标签上面添加一个scoped属性。这样就可以让我们这个组件的样式只当前组件。
当你给当前组件style添加了scoped属性,默认在你们这个组件标签上面新增data-v-随机字符
css选择在选中的时候,使用这个data-v-随机字符串来作为选择器的一部分
指定某个属性要穿透到子元素
<style scoped> h2{ color:green } /* 官方要求我们这样写 */ /deep/.op{ color:red } </style>
属性前面增加/deep/默认这个样式要穿透到子元素
1、听、写、说
Vue07-数据变更检测(版本一)
Vue07-数据变更检测(版本一)
数据初次渲染
我们在data中设计了各种类型数据,普通数据类型、引用类型。
<template> <div> <p>{{count}}</p> <p>{{users}}</p> <p>{{student}}</p> <p>123</p> </div> </template> <script> /** * Vue到底如何实现数据变化,页面更新 * 简单的Vue更新流程。版本一 */ export default { data() { return { count:10, users:[ {id:1,name:"xiaowang"} ], student:{ id:1,name:"xiaofeifei" } } } } </script> <style> </style>
思路:
Vue启动项目,加载你这个组件的时候,将data放在内存里面。
扫描你们的template标签,找你的标签上面是否有{{}}。
会将你们{{}}模板进行字符串替换,正则表达式来匹配.匹配你data中的变量名字。将{{}}替换为data中的变量值。
数据变更的时候
只要我们data中的数据发生变化,触发底层的数据劫持程序。这个程序执行render方法,data最新的数据拿到页面上去渲染。
<template> <div> <p>{{count}}</p> <p>{{users}}</p> <p>{{student}}</p> <button @click="count=20">修改</button> <button @click="student={id:2,name:'xiaoqing'}">修改student</button> <button @click="student.id=4">修改student2</button> <button @click="users.push({id:2,name:'xiaowang8'})">添加一个用户</button> </div> </template> <script> /** * Vue到底如何实现数据变化,页面更新 * 简单的Vue更新流程。版本一 */ export default { data() { return { count:10, users:[ {id:1,name:"xiaowang"} ], student:{ id:1,name:"xiaofeifei" } } } } </script> <style> </style>
Vue08-条件判断
Vue08-条件判断
条件判断是以指令的形式表现出来的
v-if:代表if判断
v-else-if:elseif判断语句
v-else:否则
代码实现
<template> <div> <p v-if="age < 18">成都</p> <p v-else-if="age >= 18">重庆</p> <p v-else>上海</p> <p v-show="age>18">武汉</p> </div> </template> <script> export default { data() { return { bool: true, age: 16 } } } </script> <style> </style>
v-show和v-if的区别(面试题)
-
v-if如果不满足条件,页面不会加载这个节点。
-
v-show不满足条件,通过控制css样式display:none控制元素的显示和隐藏
一般如果某个元素频繁切换状态,v-show/如果某个值状态切换很少,v-if
v-if用于控制多个标签显示隐藏
<!-- 虚拟标签,不会页面上渲染出来 --> <template v-if="bool"> <h1>孵化园</h1> <h1>火车南站</h1> <h1>红旗河沟</h1> </template>
面试题:
v-for和v-if能不能同时使用(同一标签)
答案:不能一起使用
<template> <div> <p v-if="age < 18">成都</p> <p v-else-if="age >= 18">重庆</p> <p v-else>上海</p> <p v-show="age > 18">武汉</p> <!-- 虚拟标签,不会页面上渲染出来 --> <template v-if="bool"> <h1>孵化园</h1> <h1>火车南站</h1> <h1>红旗河沟</h1> </template> <ul> <template v-for="item in array" > <li v-if="item.status" :key="item.id"> {{ item.name }}-{{ item.id }} </li> </template> </ul> </div> </template> <script> export default { data() { return { bool: true, age: 16, array: [ { id: 1, name: "xiaowang", status: true }, { id: 2, name: "xiaofeifei", status: false } ] } } } </script> <style> </style>
官方语法不能将v-for和v-if放在同一标签上面,有性能问题。
底层解析代码的时候,v-for优先级会比v-if更高。每次遍历的时候都会执行判断。
分开写在不同的标签上面。
<template v-if="boo" > <li v-for="item in array" :key="item.id"> {{ item.name }}-{{ item.id }} </li> </template>
Vue09-事件机制
Vue09-事件机制
一、事件处理函数
在Vue中我们可以给元素绑定各种事件。
click、change、focus、blur、keyup、keydown、mouseover、mousemove
Vue中我们要绑定事件需要提供几个内容
事件名称:绑定某类事件
事件函数:执行这个事件触发函数
<div v-on:click="事件函数"></div>
<div @click="事件函数"></div>
一般简写过后的语法第二个
<template> <div> <button v-on:click="showMessage">点击</button> <button @click="showMessage">点击</button> </div> </template> <script> export default { data(){ return{ count:10 } }, // 以后你们vue种所有的函数,都要放在这个模块中 methods:{ showMessage(){ console.log(123); } } } </script> <style> </style>
v-on和v-bind搞错
计数器的练习
<template> <div> <button v-on:click="showMessage">点击</button> <button @click="showMessage">点击</button> <button @mousemove="move">点我啊!</button> <span>{{ count }}</span> <button @click="decrement">-</button> <button @click="increment">+</button> </div> </template> <script> export default { data() { return { count: 10 } }, // 以后你们vue种所有的函数,都要放在这个模块中 methods: { showMessage() { console.log(123); }, move() { console.log(444); }, increment() { if (this.count == 10) { alert("不能加了") } else { this.count++ } }, decrement() { this.count-- } } } </script> <style> </style>
只要在script标签里面相互调用,必须通过this的方式
template标签里面使用data不需要this的方式
二、事件传值
在节点绑定事件函数的时候,加上括号,里面加入参数
<template> <div> <table border="1"> <thead> <tr> <th>编号</th> <th>名字</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="(item,index) in list" :key="item.id"> <td>{{ item.id }}</td> <td>{{ item.name }}</td> <td><button @click="deleteRow(item.id,index)">删除</button></td> </tr> </tbody> </table> </div> </template> <script> export default { data() { return { list: [ { id: 1, name: "xiaowang" }, { id: 2, name: "xiaozhang" }, { id: 3, name: "xiaoqing" } ] } }, methods:{ deleteRow(id,index){ console.log(id); console.log(index); } } } </script> <style> </style>
三、事件修饰符
事件修饰符的语法
<div @click.stop="事件函数"></div>
常用的事件修饰符:
stop
:代表阻止事件的传播,默认阻止冒泡。 也可以阻止捕获传播
<div @click.stop="check"></div>
capture
:设置让事件传播方式为捕获。
prevent
:阻止默认事件
<a @click.prevent="goto" href="#">蜗牛一下</a> <form action="http://woniuxy.com" @submit.prevent="mySubmit"> <input type="text"> <button type="submit">提交</button> </form>
once
:事件只触发一次。秒杀场景
<button @click.once="play">一次事件</button> onclick = function(){ } onclick = null
按键修饰符
enter
:代表按了enter键就可以触发
<input @keyup.enter="getvalue" type="text">
tab
:
space delete esc up down left top
四、事件传播对象event
只要你们给元素绑定事件函数,默认产生一个事件传播对象event
<button @click="login($event,1)">事件传播对象</button> methods:{ login(event,val){ console.log(event); console.log(val); } }
没有加括号的时候,默认第一个参数就说event
<button @click="login">事件传播对象</button> methods:{ login(event){ console.log(event); } }
文本框的值要获取,可以使用event的方式来获取
<input @blur="getUsername" type="text"> methods:{ getUsername(event){ console.log(event); console.log(event.target.value); } }
面试题01-this指向问题
面试题01-this指向问题
再JS中this是一个特殊的符号,可以表示为占位。不同环境下this指向有差别。造成给你一段代码,分析this的结果,会出现偏差。
this一般再函数中使用、或者再类中使用。
全局你是在全局环境中,this永远是都执行全局对象
比如一个普通函数this指向window
nodejs中,全局对象global对象{}
总结:
-
this永远指向一个对象
-
this的指向到底是谁,取决于当前this的运行环境(函数再哪个地方调用)
一、再普通函数中this指向
<script> function show(){ // console.log(this); } show() </script>
如果你的函数是再全局作用域里面,默认这个函数就是挂载window对象上面。函数里面this肯定就指向window
window.show()
二、对象方法中输出this
const student = { name:"xiaowang", sayName:function(){ console.log(this); } } student.sayName() //student对象
对象中的普通函数输出的this默认指向当前对象,由对象来调用这个函数
三、事件中的this
const obtn = document.getElementById("btn") obtn.onclick = function(){ console.log(this); }
在事件绑定中,函数中this默认指向当前事件源对象
四、构造函数中this
function Person(){ console.log(this); } Person.prototype.sayName = function(){ console.log(this); } const p = new Person() p.sayName()
构造器中的this需要你实例化对象后才有意义,指向的当前实例化后对象
五、箭头函数
ES6提供了一个新的特性箭头函数,箭头函数中的this跟上面的规则都不一样。
箭头函数内部本身没有自己this指向。我们在访问箭头函数中this的时候,实际访问父级作用域。
作用域:
-
全局作用域
-
局部作用域(函数作用域)
-
块作用域(let const)
官方描述;
箭头函数内部的this,就是定义该函数所在作用域指向的对象。而不是使用时存在的作用域。
六、改变this指向
call、bind、apply
这三个方法都可以改变this的指向问题
const student = { name:"zhangsan", sayName:function(a,b){ console.log(this.name,a,b); } } const user = { name:"lisi" } // 创建一个sayName,来自于另外一个对象 // user.sayName = student.sayName // user.sayName(4,7) student.sayName.call(user,10,20) student.sayName.apply(user,[10,20]) student.sayName.bind(user)(10,40)
应用
// 存在一个数组,求数组中最大值 const array = [1,6,8,4] const max = Math.max.apply(null,array) console.log(max);
Vue10-计算属性
Vue10-计算属性
一、操作模板
mastache模板里面我们可以执行一些简单的运算。在页面渲染之前我们可以处理数据,显示我们处理完成后的
<template> <div> <!-- 修改为大写 --> {{username.toUpperCase()}} <!-- 反序输出 --> {{username.split("").reverse().join("-")}} </div> </template> <script> export default { data(){ return{ username:"xiaowang" } } } </script>
定义在data中的数据,最后要输出渲染的时候,处理完后再渲染。对原来的数据进行计算,得到新结果。
但是遇到复杂的业务,我们就没有办法再mastache语法实现。
提出了计算属性这种规则
二、计算属性语法
Vue提供的一种再原数据身上,进行数据操作,返回新的数据
computed:来实现计算属性。跟methods一样是一个模块,可以再这个模块中写很多计算过程
computed:{ 计算属性名字(){ //执行你的业务 return 新的结果 }, 计算属性名字2(){ return 新的结果 } }
用计算属性来改造我们之前的字符串操作
<template> <div> <!-- 修改为大写 --> {{username.toUpperCase()}} <!-- 反序输出 --> {{username.split("").reverse().join("-")}} {{reverseValue}} </div> </template> <script> export default { data(){ return{ username:"xiaowang", } }, computed:{ // 名字由自己来定 reverseValue(){ // 处理 return this.username.split("").reverse().join("-") } } } </script>
三、计算属性和Data关系
<template> <div> <!-- 修改为大写 --> <!-- {{username.toUpperCase()}} --> <!-- 反序输出 --> <!-- {{username.split("").reverse().join("-")}} --> {{reverseValue}} <button @click="username='xiaofeifei'">修改username</button> <p>{{fullName}}</p> <button @click="firstName='li'">修改姓</button> <button @click="lastName='si'">修改名</button> </div> </template> <script> export default { data(){ return{ username:"xiaowang", firstName:"zhang", lastName:"san" } }, computed:{ // 名字由自己来定 reverseValue(){ // 处理 // 只要你计算属性里面的原数据发生变化,立即再执行一次 return this.username.split("").reverse().join("-") }, fullName(){ return this.firstName + this.lastName } } } </script> <style> </style>
计算属性里面用到的原数据发生变化,计算就会再执行一次
四、计算属性缓存
我们不用计算属性,依然可以实现对数据的处理
目前学习methods,里面可以定义函数执行业务
计算属性可以提供缓存效果,页面上多次使用的时候,一旦更新只会执行一次。methods会执行多次
<template> <div> <p>{{filterList}}</p> --> <p>{{fullName}}</p> <p>{{fullName}}</p> <p>{{fullName}}</p> <p>{{unionName()}}</p> <p>{{unionName()}}</p> <p>{{unionName()}}</p> <button @click="firstName='li'">修改firstName</button> </div> </template> <script> export default { data(){ return{ firstName:"zhang", lastName:"san", } }, methods:{ unionName(){ console.log("unionName"); return this.firstName + this.lastName } }, computed:{ fullName(){ console.log("fullName"); return this.firstName + this.lastName } } } </script> <style> </style>
最终输出结果为:fullName执行一次。unionName执行了三次
计算属性是基于响应式依赖进行缓存的
,只有相关依赖发生变化,才会重新计算值。一旦计算结果缓存起来。
methods里面提供的函数,不具备缓存效果,页面每次都会调用
五、getters和setter
问题:计算属性的结果能否修改?
默认情况下我们无法修改计算属性的。只读属性。
我们需要换一种语法
computed:{ fullName:{ get(){ }, set(){ } } }
执行页面的修改功能
computed:{ fullName:{ get(){ console.log("获取计算结果"); return this.firstName + this.lastName }, set(val){ console.log(val); this.firstName = val.split("-")[0] this.lastName = val.split("-")[1] } } }
以后要修改计算属性,本质上也是再修改原数据。至少set方法做了一次转化
用计算属性做搜索功能
computed: { searchList() { return this.list.filter((item) => { // this.seachType为搜索类型,this.searchVal为搜索的值 if (item[this.searchType].indexOf(this.searchVal) != -1) { return true; } else { return false; } }); }, },
Vue11-帧听器
Vue11-帧听器
在Vue中我们可以通过计算属性来进行数据筛选和汇总。
一定要通过计算属性返回一个新的结果。新的结果去页面渲染
Vue还提供了另外一种方案。wacth(帧听器),可以指定监控页面中的莫某个数据一旦发生变化,立即执行你设计业务代码。也能完成数据的过滤,汇总
一、watch的语法
Vue提供的watch是一个独立的模块。这个模块和computed是兄弟
<script> export default { data(){ return{ } }, components:{ }, methods:{ }, computed:{ }, watch:{ } } </script>
watch的基本语法为:
<script> export default { data(){ return{ } }, components:{ }, methods:{ }, computed:{ }, watch:{ } } </script>
二、监听基本数据类型
基本数据类型监听主要是值的变化,只要值发生变化立即执行watch的函数
watch:{ username(newVal,oldVal){ console.log(newVal,oldVal); // 比如执行日志的保存,比如执行异步请求 } }
执行完侦听任务,我们可以在里面发送异步请求,或者执行日志缓存。
三、监听引用类型
监听引用类型的数据
watch:{ user(){ console.log("user。id被监控"); } }
user是一个对象,我们监听user在监听他的地址是否发生变化,只有地址发生变化我们才会触发watch
四、指定监听引用类型属性
watch:{ "user.id"(){ console.log("user。id被监控"); } }
可以只监控user中的id属性是否发生变化。
五、深度监听
语法和前面不一样,需要用函数的方式来变成
watch:{ user:{ handler(val){ //执行监控的任务也 }, deep:true } }
deep:true
设置为true代表深度监听。user里面任何一个数据发生变化都会执行watch
六、立即侦听
在执行购物车业务的时候,第一次进来计算出购物车的总价,
watch默认第一次进来不会执行的,我们需要设置一个属性控制立即侦听
watch:{ list:{ handler(){ let result = this.list.reduce((sum,obj)=>{ return sum+=obj.price * obj.num },0) this.totalPrice = result }, deep:true, immediate:true } }
immediate:true
设置立即侦听。
七、总结
哪个时候用computed,哪个时候用watch
-
computed默认会执行缓存。当你页面要频繁重复使用某一个结果,推荐计算属性。
-
如果我们需要执行比较复杂的业务逻辑(你如异步请求、本地操作),推荐watch来执行
如果你用watch来实现计算属性,一般还要新增一个变量来保存值
侦听器,还可以侦听路由变化,例:
watch:{ $route:{ immediate:true, deep:true, handler(val){ this.activeIndex = val.path } } }
Vue12-表单数据处理
常用的表单组件主要包含。文本框、密码框、单选按钮、多选按钮、下拉框、文本域
Vue中提供一个指令,来完成表单数据的获取 v-model
一、自己实现表单元素和data的双线绑定
<template> <div> <input type="text" :value="username" @input="inputValue"> <button @click="getValue">获取</button> </div> </template> <script> export default { data(){ return{ username:"小王" } }, methods:{ getValue(){ // console.log(event.target.value); console.log(this.username); }, inputValue(event){ this.username = event.target.value } } } </script> <style> </style>
将data数据默认渲染到input文本框上面。动态value来渲染
文本框发生变化立即得到变化后结果,更新data中的数据
这个过程就是一个双向绑定过程
v-model的语法糖 就是上面这一段代码
class Student{ } function Student(){ }
公共代码我们封装后,模块。组件。
语法封装,语法糖
二、提供指令v-model实现绑定
案列
<template> <div> <input v-model="password" type="password"> <button>登录</button> </div> </template> <script> export default { data(){ return{ password:"" } }, methods:{ } } </script> <style> </style>
三、MVVM思想
在vue中我们采用MVVM思想开开发代码
M:Model 数据模型
V:View视图
VM:ViewModel驱动程序(Vue核心代码)
双向绑定的过程
<input v-model="password">
Vue中提出的一种思想。可以实现页面更新后,model数据也更新。model数据更新后页面动态更新
四、单选和复选
<!-- 复选框 --> <input type="checkbox" v-model="xueli" value="本科"> <input type="checkbox" v-model="xueli" value="专科"> <input type="checkbox" v-model="xueli" value="硕士"> <!-- 下拉框 --> <select name="" id="" v-model="idCard"> <option value="sfz">身份证</option> <option value="jsz">驾驶证</option> <option value="hz">护照</option> </select>
数据定义为
data(){ return{ username:"小王", password:"1234", gender:"", xueli:[ ], idCard:'hz', introduce:"" } },
五、下拉框
<select name="" id="" v-model="idCard"> <option value="sfz">身份证</option> <option value="jsz">驾驶证</option> <option value="hz">护照</option> </select>
数据定义
data(){ return{ idCard:'hz' } },
idCard这个变量默认的值等于option的value,默认被选中
六、复选框要被选中
传统的代码
<template> <div> <table border="1"> <tr> <th> <input type="checkbox"> </th> <th>编号</th> <th>名字</th> <th>操作</th> </tr> <tr v-for="item in list" :key="item.id"> <td> <input type="checkbox" :checked="item.checked"> </td> <td>{{item.id}}</td> <td>{{item.name}}</td> <td><button>删除</button></td> </tr> </table> </div> </template> <script> export default { data(){ return{ list:[ {id:1,name:"小米",checked:true}, {id:2,name:"华为",checked:false}, {id:3,name:"魅族",checked:true}, {id:4,name:"oppo",checked:false} ] } } } </script> <style> </style>
用:checked属性这种方式,问题只能单向的。适合将数据渲染到页面。页面数据变化,我们data无法更新
采用v-mode的方式来实现
<input type="checkbox" v-model="item.checked">
在input框中添加一个readonly属性可以实现input框只读(无法修改)。
<input readonly v-model = "user.age">
Vue13-组件通信规则
Vue13-组件通信规则
一、组件拆分
我们在Vue开发过程中会将很多代码提取出公共组件,比如表格、分页器、表单等等
以后再页面上哪个地方要使用这个组件,引入注册直接使用
二、父子组件通信
在Vue开发过程中我们将组件抽取出来后,会涉及到父传子或者子传父。
因为父组件里面引入很多子组件,比如菜单数据并不是只有Nav组件才用。
每个组件数据来源:
-
内部数据:data定义的数据
-
外部数据:父组件调用你的时候,传递给你的数据
父传子
父组件引入子组件,我们可以在引入子组件身上增加自定义属性。子组件那边可以通过props来接受这个数据
父组件传递子组件的数据,对于子组件来说,就说外部数据
<template> <div class="leftMenu"> <Nav username="xiaowang" :list="navList"></Nav> </div> </template> <script> import Nav from "./Nav.vue" export default { // 有些时候数据并不在子组件 data(){ return{ navList:[ {id:1,title:"首页",selected:true,path:"/home"}, {id:2,title:"产品管理",selected:false,path:"/home"}, {id:3,title:"员工管理",selected:false,path:"/home"} ] } }, components:{ HeaderVue,Nav,ContentVue } } </script> <style scoped lang="scss"> </style>
静态的参数直接在组件上面写死,动态的参数,动态绑定
子组件接受外部数据
<template> <div> <ul> <li v-for="item in list" :key="item.id"> <a :class="{selected:item.selected}" href="#">{{item.title}}</a> </li> </ul> </div> </template> <script> export default { // 组件外部 props:["username","list"], // 组件内部 data(){ return{ } } } </script> <style> .selected{ color: red; } </style>
父组件传递参数给子组件,我们可以在子组件props:[]
数组里面接受的就是外部数据
子传父
子组件那边得到了一个数据,要传递父组件,一般用于修改原始数据
思想1:在父组件传递一个回调函数给子组件,子组件调用这个函数,传递参数到父组件
<template> <div class="container"> // 值为函数 <Nav :getValue="getValue"></Nav> </div> </template> <script> import Nav from "./Nav.vue" export default { // 有些时候数据并不在子组件 data(){ return{ navList:[ {id:1,title:"首页",selected:true,path:"/home"}, {id:2,title:"产品管理",selected:false,path:"/home"}, {id:3,title:"员工管理",selected:false,path:"/home"} ] } }, components:{ HeaderVue,Nav,ContentVue }, methods:{ getValue(val){ console.log("val",val); this.navList.forEach(item=>item.selected=false) const menus = this.navList.find(item=>item.id==val) menus.selected = true } } } </script> <style scoped lang="scss"> </style>
子组件那边调用函数
<template> <div> <ul> <li v-for="item in list" :key="item.id"> <span @click="changeStatus(item.id)" :class="{selected:item.selected}" href="#">{{item.title}}</span> </li> </ul> </div> </template> <script> export default { // 组件外部 // 外部数据不要直接修改,只能使用 readonly props:["username","list","getValue"], methods:{ changeStatus(id){ console.log(id); //知道getValue是一个,子组件可以()调用 this.getValue(id) } } } </script> <style> .selected{ color: red; } </style>
三、兄弟组件通信
Vue14-props规则
Vue14-props规则
props用于接受组件外部数据,每个组件都有这个属性
一、props的语法
我们在子组件里面
export default { props:[] }
接受数组里面的参数就是外部传递进来数据
export default { props:["username","password"] }
props里面的数据,一旦接受成功,根data的数据使用是一样的。this的方式调用
this.username this.password
二、外部传递数据
props的值来自于父组件调用传递值
<Children username="xiaowang" :user="user"></Children> export default { data(){ return { user:{ id:1, name:"xiaowang" } } } }
三、props的验证
子组件那边负责接受外部数据,如果我没有传递数据,或者传递的数据子组件想要的数据不一致。子组件使用的时候就会出现报错。
props:{ username:String, changeUser:Function, list:Array },
之前没有对外部数据进行类型约束的时候,我们只需要提供一个数组就可以了。
如果你需要对外部数据进行约束,针对每个属性的类型进行完整的声明。
username:String
如果一个变量可以允许多种数据类型。我们可以使用数组包含多个类型
props:{ username:[String,Number], changeUser:Function, list:Array },
四、默认值设置
如果外部的数据没有传递的情况下,props可以设置默认值
props:{ username:[String,Number], changeUser:Function, list:{ type:Array, default:[{id:1,name:"xiaomi"}] } },
Vue15-父子通信方案
Vue15-父子通信方案
一、props和回调函数
这种方式是目前最简单的通信方式。我们可以通过props来传递值给子组件。
父组件传递一个回调函数给子组件,子组件执行的时候,数据传回父组件
优点:简单,方便
缺点:需要自己调用回到函数,每个函数都要接受
二、props和$emit函数
父子通信:
父传子:也是通过props的方式来传递数据
子传父:通过自定义的事件来进行触发传递
将这种参数传递的方式称为 自定义事件的方式通信。而且也是目前工作中用的最多的。
父组件:
<template> <div> <h2>父组件数据</h2> <p>{{username}}</p> <ChildrenCompVue :username="username" @mychange="changeUsername"></ChildrenCompVue> </div> </template> <script> import ChildrenCompVue from './ChildrenComp.vue' export default { data(){ return{ username:"xiaowang" } }, components:{ ChildrenCompVue }, methods:{ changeUsername(val){ this.username = val } } } </script> <style> </style>
@mychange="changeUsername"
mychange就是你们自己定义的事件名字。这个事件名字不要跟系统名字一样。click、change、focus等等
子组件触发自定义事件,触发自定义对应事件函数就会立即执行
<template> <div> <h2>子组件</h2> <p>外部数据:{{username}}</p> <button @click="updateUser">修改username</button> </div> </template> <script> export default { // 无需再props接受外部的回调 props:["username"], methods:{ updateUser(){ // 触发自定义事件 this.$emit("mychange","xiaofeifei") } } } </script> <style> </style>
$emit是vue提供给我们的一个api,可以触发自定义事件
优点:子组件通过api触发事件就可以传递参数,无需自己调用函数
缺点:额外新增事件需要自己维护
三、$parent和$children
this代表的当前这个组件,每个组件上面都有$parent 属性代表得到父组件。$children代表所有的子组件。
我们可以再父组件中$children获取所有子组件调用他的属性和方法
我们也可以在子组件中$parent获取到唯一的父组件,调用属性和方法
<template> <div> <ChildrenCompVue :username="username"></ChildrenCompVue> <button @click="showMessage">获取到子组件的数据</button> </div> </template> <script> import ChildrenCompVue from './ChildrenComp.vue' export default { data() { return { username: "xiaowang", } }, components: { ChildrenCompVue }, methods: { showMessage(){ console.log(this.$children[0].password); this.$children[0].show() } } } </script> <style> </style>
获取到所有的子组件,自己选中操作的内容
子组件:
<template> <div> <h2>子组件</h2> <p>外部数据:{{ username }}</p> <button @click="parentMethods">通过$parent调用父组件</button> </div> </template> <script> export default { // 无需再props接受外部的回调 props: ["username"], data() { return { password: "123" } }, methods: { show(){ console.log(123345); }, parentMethods(){ console.log(this.$parent.changeUsername("王二麻子")); } } } </script> <style> </style>
$parent调用父组件的方法
优点:通信方便,任何一个地方得到对象就可以操作
缺点:组件嵌套比较多的情况下,这种操作并不方便
四、$attrs和$listeners
this对象上面有一个$attrs属性。接受外部数据,但是排除props接受过后的数据。
外部数据进入子组件分成两部分:props接受。$attrs来接受
外部数据传到子组件的时候,如果数据在$attrs里,你们可以直接使用
<template> <p>{{$attrs.age}}</p> </template>
子组件要触发父组件自定义事件,我们可以$listeners获取到所有的自定义事件
<button @click="$listeners.changeMyUser">自己触发$listeners</button>
可以直接调用事件,触发这个事件
一般不推荐你们使用
Vue16-生命周期函数
Vue16-生命周期函数
一、生命周期
Vue中声明周期主要描述的Vue实例和Vue中组件。
Vue实例:new Vue这个对象。
Vue中加载了很多组件。App.vue
组件从创建到销毁的整个过程。
实例从创建到销毁的整个过程
二、生命周期流程
Vue的生命周期流程四个阶段:
-
初始化阶段:在执行钩子函数初始化,data数据底层数据劫持初始化。
-
挂载阶段:将数据放在页面上显示,组件的渲染内容,挂载到页面中渲染。看到页面
-
更新阶段:生命周期时间最长的阶段,组件数据发生变化,检测到这个数据变化。
-
销毁阶段:组件被销毁的执行,我们会执行资源回收
初始阶段
<template> <div> <p>{{username}}</p> </div> </template> <script> export default { data(){ return{ username:"xiaowang", age:20 } }, // Vue提供的生命周期函数 beforeCreate(){ console.group("beforeCreate---执行data数据初始化之前"); console.log("el:"+this.$el); console.log("data:"+this.$data); console.groupEnd() }, created(){ console.group("created---data数据初始后"); console.log("el:"+this.$el); console.log("data:"+this.$data); console.log(this.$data); console.groupEnd() // 这个组件要发送异步,推荐在created里发送 } } </script> <style> </style>
created这个函数你们会使用,在这里面发送异步请求
在beforeCreate中获取data和methods
vue在beforeCreate时期是获取不到data及methods里面的数据及方法的,不过还是有办法可以去获取到的,我们可以采用异步的方式,使用this.$nextTick或setTimeout,代码如下:
beforeCreate() { this.$nextTick(() => { this.test1() this.test2() }) setTimeout(() => { this.test1() this.test2() },0) }, created() {}, methods: { test1() { console.log('this is test1') }, test2() { console.log('this is test2') }, }
挂载阶段
将你们写的组件挂载到页面上运行
<template> <div> <p>{{username}}</p> </div> </template> <script> export default { data(){ return{ username:"xiaowang", age:20 } }, // Vue提供的生命周期函数 beforeCreate(){ console.group("beforeCreate---执行data数据初始化之前"); console.log("el:"+this.$el); console.log("data:"+this.$data); console.groupEnd() }, created(){ console.group("created---data数据初始后"); console.log("el:"+this.$el); console.log("data:"+this.$data); console.log(this.$data); console.groupEnd() // 这个组件要发送异步,推荐在created里发送 }, beforeMount(){ console.group("beforeMount---组件挂载之前执行"); console.log("el:"+this.$el); console.log("data:"+this.$data); console.groupEnd() }, mounted(){ // 只有等组件挂载完毕后,才能得到页面上节点。 // 需要获取页面上某个标签,只能在mouted,以及后的生命周期 console.group("mounted---组件挂载完成"); console.log("el:",this.$el); console.log("data:"+this.$data); console.groupEnd() } } </script> <style> </style>
mounted里获取到页面的节点
更新阶段
<template> <div> <p id="op">{{ username }}</p> <button @click="username='xiaofeifei'">修改</button> </div> </template> <script> export default { data() { return { username: "xiaowang", age: 20, }; }, // Vue提供的生命周期函数 beforeCreate() { console.group("beforeCreate---执行data数据初始化之前"); console.log("el:" + this.$el); console.log("data:" + this.$data); console.groupEnd(); }, created() { console.group("created---data数据初始后"); console.log("el:" + this.$el); console.log("data:" + this.$data); console.log(this.$data); console.groupEnd(); // 这个组件要发送异步,推荐在created里发送 }, beforeMount() { console.group("beforeMount---组件挂载之前执行"); console.log("el:" + this.$el); console.log("data:" + this.$data); console.groupEnd(); }, mounted() { // 只有等组件挂载完毕后,才能得到页面上节点。 // 需要获取页面上某个标签,只能在mouted,以及后的生命周期 console.group("mounted---组件挂载完成"); console.log("el:", this.$el); console.log("data:" + this.$data); console.groupEnd(); }, beforeUpdate() { console.group("beforeUpdate---页面数据在更新之前"); console.log("el:", this.$el); console.log("data:" + this.$data.username); console.log(this.username); console.groupEnd(); }, updated(){ console.group("updated---页面数据在更新后"); console.log("el:", this.$el); console.log("data:" + this.$data.username); console.log(this.username); console.groupEnd(); // 调用一个函数,记录日志 } }; </script> <style> </style>
销毁阶段
<template> <div> <p id="op">{{ username }}</p> <button @click="username='xiaofeifei'">修改</button> <button @click="selfDestory">销毁函数</button> </div> </template> <script> export default { data() { return { username: "xiaowang", age: 20, }; }, methods:{ selfDestory(){ this.$destroy() } }, // Vue提供的生命周期函数 beforeCreate() { console.group("beforeCreate---执行data数据初始化之前"); console.log("el:" + this.$el); console.log("data:" + this.$data); console.groupEnd(); }, created() { console.group("created---data数据初始后"); console.log("el:" + this.$el); console.log("data:" + this.$data); console.log(this.$data); console.groupEnd(); // 这个组件要发送异步,推荐在created里发送 }, beforeMount() { console.group("beforeMount---组件挂载之前执行"); console.log("el:" + this.$el); console.log("data:" + this.$data); console.groupEnd(); }, mounted() { // 只有等组件挂载完毕后,才能得到页面上节点。 // 需要获取页面上某个标签,只能在mouted,以及后的生命周期 console.group("mounted---组件挂载完成"); console.log("el:", this.$el); console.log("data:" + this.$data); console.groupEnd(); }, beforeUpdate() { console.group("beforeUpdate---页面数据在更新之前"); console.log("el:", this.$el); console.log("data:" + this.$data.username); console.log(this.username); console.groupEnd(); }, updated(){ console.group("updated---页面数据在更新后"); console.log("el:", this.$el); console.log("data:" + this.$data.username); console.log(this.username); console.groupEnd(); // 调用一个函数,记录日志 }, beforeDestroy(){ console.log("销毁之前要执行的代码"); // 清理内存空间。保存一些数据 }, destroyed(){ console.log("销毁完成后执行的代码"); } }; </script> <style> </style>
Vue目前常用的八个生命周期函数。后面补充一个全局异常的生命周期
常用的生命就周期:
created:发送异步请求
mouted:获取到指定节点
destoryed:组件销毁的时候,清除一些定时任务。防止内存泄漏
练习题:
设计一个组件,进入组件的时候,就开始生成6个车牌号。 车牌纯数字 5位 每个500ms,换一批车牌。 点击暂停,停下来。每个车牌都可以被选中。选中的车牌加一个边框
vue中使用bootstrap
1.在集成终端中输入:npm i bootstrap
2. 在指定的页面中@import 引入css样式
<style lang="scss"> @import "../../../node_modules/bootstrap/dist/css/bootstrap.css" </style>
Vue17-兄弟组件参数传递(扩展)
Vue17-兄弟组件参数传递(扩展)
兄弟组件参数传递是一个比较麻烦的事情
如果是亲兄弟组件还可以通过父组件来进行数据通信。如果是间隔比较远的组件想要实现通信。目前实现的流程就比较繁琐。
提出了事件总线的概念,可以解决目前我们兄弟通信问题
事件总线
两个兄弟组件如果要传递参数。
A组件和B组件。
A组件创建一个事件监听器。B组件触发这个监听器。
事件总线的流程:
这个过程需要我们提供三个模块 A组件需要提供,EmpList这个组件就说A组件。B组件使用EmpUpdate组件
创建事件总线。
(1)在项目种新建一个js文件,src/utils/bus.js
import Vue from "vue" const bus = new Vue export default bus
(2)由于B组件要接受别人传递进来的值。需要在B组件里面引入bus总线,创建一个监听器
import bus from '../utils/bus.js' export default { mounted() { bus.$on('getData', data => { console.log(data); // 组件 A 传递的数据 }) } }
(3)A组件可以传递参数给B组件,A组件触发getData监听器
import bus from '../utils/bus.js' export default { methods: { postData() { bus.$emit('getData', '传递的数据') } } }
优点:在组件比较简单的情况,能解决通信的问题。
缺点:在复杂的业务场景下,事件总线会很维护。一旦通信多了。数据流会非常混乱。
Vue18-单向数据流
Vue18-单向数据流
学习组件通信的时候,主要涉及到场景
父传子:将父组件的数据传递给子组件。数据流的方向—父到子
子传父:将子组件的数据传递父组件。常用的自定义事件,调用父组件的函数接受。
兄弟组件:事件总线的方式的方式实现数据传递。
props单向数据流
一个组件要接受外部数据props来接受
Vue官方文档中描述单向数据流如下:
所有的props都使得其父组子形成一个向下行绑定。父级的prop的更新会向下流动到子组件,但是反过来不行
简单来说:
当父组件通过传递数据给子组件,props来接受数据的时候,只能是父组件数据流向子组件,不能用props将子组件数据反过来传递到父组件,这个过程中单向数据流
案列:
export default { props:["username"], methods:{ changeUser(){ this.username = "xiaofeifei" } } }
上面的代码违背了单向数据流规则。浏览器会给你抛出警告。
Vue这种设计目的是为什么?
-
保证了父子组件之间的数据传递更加规范,约束了标准的数据流向。父到子。将数据传递父组件,更新完毕流向子组件。
-
Vue传递数据是引用类型,子组件直接修改引用类型,也避免这种情况
不要再子组件通过props来直接修改数据。违背单向数据流。
React这个框架直接修改props,立即报错。
props的值处理
使用watch的方式来控制我们子组件数据变化
export default { data(){ return { myUsername:"" } }, props: ["username"], watch:{ username:{ handler(){ this.myUsername = this.username }, deep:true, immediate:true } } };
computed的方式来进行修改props和页面数据变化
export default { props: ["username","user"], computed:{ newUsername:{ get(){ return this.username }, set(val){ //传递给父级,修改父级的username this.$emit("自定义事件名字",val) } } }, };
Vue19-动态组件
Vue19-动态组件
动态组件指的是多个组件可以进行切换。最常见的效果图就是Tab切换
官方提供的动态组件就是用于这种场景,无需自己写CSS效果
动态组件
Vue中提供了一个标签,这个标签搭配is属性来使用
<template> <div> <button @click="selectedComp='Comp1'">登录</button> <button @click="selectedComp='Comp2'">注册</button> <div class="mybox2"> <component :is="selectedComp"></component> </div> </div> </template> <script> import Comp1 from "./Comp1.vue" import Comp2 from "./Comp2.vue" export default { components:{ Comp1,Comp2 }, data(){ return { selectedComp:"Comp1" } } } </script> <style> .mybox2{ width: 200px; height: 300px; border: 1px solid red; } </style>
注意事项:
-
component必须要提供一个is属性,这个is动态绑定
-
is属性动态绑定的是组件的名字。当你的名字执行某个组件,显示这个组件
组件的状态
当我们使用动态组件来进行组件切换时,默认只要离开这个组件,就会执行销毁
<template> <div> <p>Comp1组件</p> <input type="text"> </div> </template> <script> export default { // 代表这个组件的名字 name:"MyComp1", components:{}, props:{ }, destroyed(){ console.log("Comp1组件销毁了"); } } </script> <style> </style>
目前要解决我们动态组件一旦不加载就会默认销毁这个组件问题
-
销毁之前将数据保存一次,下次进来的时候,得到数据显示出来
-
完全不销毁这个组件,借助于Vue提供了一个组件keep-alive来完成
Vue20-keepAlive
Vue20-keepAlive
一、基本用法
目前遇到的问题,就是动态组件切换的时候,组件会默认销毁。
Vue提供的keepalive是一个组件,这个组件可以用于包含其他组件,被keepalive包裹的组件可以实现不销毁的动作。
基本语法
<keep-alive> <component :is="selectedComp“></component> </keep-alive>
当动态组件被keepalive组件包裹的时候,所有组件的状态都会被缓存起来。当你切换组件的时候,组件不会被销毁,数据会一直保留,除非,你指定某个组件不缓存。
好处:减少组件的频繁创建和销毁,可以提升用户体验。
二、常用api
keep-alive这个组件常用的属性介绍:
-
include:值为字符串、数组以及正则表达式。只有名字被匹配了才会缓存当前这个组件,白名单。设置设置了内容在白名单,运行缓存
-
exclude:值为字符串、数组以及正则表达式,只有名字被匹配了才会不缓存指定组件,代表黑名单,只要名字被包含,不会被缓存
-
max:指定能够缓存的最大数量
白名单和黑名单设置值的时候,值类型可以有以下形式:
字符串
<keep-alive include="Comp1,Comp2"> <component :is="showComp"></component> </keep-alive>
设置include的值后面可以写字符串,用逗号隔开。
数组:
<keep-alive :include="['Comp1','Comp2']"> <component :is="showComp"></component> </keep-alive>
正则表达
<keep-alive :include="/Comp1|Comp2/"> <component :is="showComp"></component> </keep-alive>
只要满足条件,那可以被缓存
max属性
<keep-alive :include="/Comp1|Comp2/" max="10"> <component :is="showComp"></component> </keep-alive>
max代表要换成的最大值,底层的算法是采用LRU算法。
如果组件数量在10以内。每个都会被缓存。超过10。继续缓存
将之前最不常用的组件销毁了。腾出空间交给新的组件使用
最长不活动时间的组件。
三、生命周期函数
只要你们使用keep-alive来包裹某个组件,这个组件默认新增两个生命周期函数
activated:进入当前这个组件,会被触发执行一次
deactivated:离开这个组件的时候,会被触发执行一次
<script> export default { name:"Comp2", data(){ return{ list:[] } }, destroyed(){ console.log("Comp2正在销毁"); }, // 代表进入到这个组件 activated(){ console.log("activated"); }, // 离开当前这个组件 deactivated(){ console.log("deactivated"); } } </script>
Vue21-插槽
Vue21-插槽
一、插槽的概念
在Vue中插槽指的是组件插槽。插槽的出现让组件具有更好封装性。
一个组件里面的数据可以动态变化的,但是HTML布局目前我们无法动态变化。
一个组件内部布局也可以动态改变,我们更加灵活的使用这个组件。
例子:我们封装一个表格,表格的数据外部数据动态变化,表头或者表格的按钮,每次调用过程中都会出现差异,这种情况下我们需要利用插槽的方式来动态模板的变化。
插槽就可以实现这个效果
二、插槽的类型
-
匿名插槽
-
具名插槽
-
作用域插槽
匿名插槽(后备插槽)
你在子组件那边设置一个slot来接受外部传递进来布局模块。
<slot></slot>
父组件传递
<template> <div> <button @click="btns=1">基本信息</button> <button @click="btns=2">邮箱激活</button> <button @click="btns=3">完善资料</button> <!-- 动态组件渲染的地方 --> <div> <ShowComp> <h2 v-if="btns==1">这是Slot内容1</h2> <h3 v-else-if="btns==2">这是Slot内容2</h3> <h4 v-else>这是Slot内容3</h4> </ShowComp> </div> </div> </template> <script> import ShowComp from "./ShowComp.vue" export default { components:{ ShowComp }, data(){ return{ btns:"1" } } } </script> <style> </style>
将上面这种插槽的代码称为匿名插槽,插槽没有指定名字。
接受外部插槽内容的时候,默认接受所有内容。
具名插槽
具名插槽就说给插槽增加了命名,因为有时候我们一个组件,想要接受多个插槽数据
父组件在传递值的时候需要template标签指定插槽的名字
<MyButton> <ul> <li>123</li> <li>456</li> </ul> <template v-slot:content> <ol> <li>xiaowang</li> </ol> </template> <template v-slot:content2> <a href="#">超链接</a> </template> </MyButton>
v-slot这个指令官方提供的一个插槽指令,可以指定内容存放哪个插槽
子组件
<template> <div> <p>我的按钮</p> <!-- 第一个插槽接受一部分内容 --> <slot></slot> <!-- 第二个插槽接受第二部分内容 --> <slot name="content"></slot> <slot name="content2"></slot> </div> </template> <script> export default { } </script> <style> </style>
作用域插槽
插槽的内容一般都是由父组件来提供,父组件可以传递data数据给子组件,也可以通过插槽的方式将布局模板传递子组件。
当父组件要访问子组件数据的时候,自定义事件。在作用域插槽里面,实现父组件访问子组件数据
作用域插槽:需要在父组件中访问子组件的数据
子组件MyButton
<template> <div> <slot :students="students"></slot> </div> </template> <script> export default { data(){ return{ students:["张三","王五"] } } } </script> <style> </style>
父组件获取数据
<template> <div> <div> <MyButton v-slot="slotProps"> <p>{{slotProps}}</p> <ul> <li v-for="item in slotProps.students" :key="item">{{item}}</li> </ul> </MyButton> </div> </div> </template> <script> import ShowComp from "./ShowComp.vue" import MyButton from "./MyButton.vue" export default { components:{ ShowComp,MyButton }, data(){ return{ btns:"1" } } } </script> <style> </style>
父组件中需要v-slot="slotProps"
获取子组件传递过来的数据。通过这个数据来执行业务逻辑
最终的应用场景:组件封装
Vue22-过滤器(扩展)
Vue22-过滤器(扩展)
Vue中提供了一个过滤器技术,这个可以实现在页面渲染数据的时候,对数据进行过滤处理。渲染处理完成后的数据。
过滤器在我们Vue中可以针对你们模板数据进行处理
提供两种方式:
-
局部过滤器:在组件中定义过滤器,只能在这个组件中使用
-
全局过滤器:定义全局对象中,任何一个组件都可以使用
局部过滤器
直接定义组件内部,只有当前组件能使用
<template> <div> <ul> <li v-for="item in list" :key="item.id"> <a href="#">{{item.name | myToUpperCase}}</a> <a href="#">{{item.bir | formatFilter}}</a> </li> </ul> <p>{{username | myToUpperCase | reaplceFilter}}</p> </div> </template> <script> export default { data(){ return{ list:[ {id:1,name:"xiaowang",bir:new Date()}, {id:2,name:"xiaozhang",bir:new Date()} ], username:"xiaownag" } }, filters:{ // 过滤器名字myToUpperCase myToUpperCase:function(val){ console.log("myToUpperCase",val); return val.toUpperCase() }, // 将指定内容的x换成* reaplceFilter(val){ console.log("reaplceFilter",val); const newStr = val.replace("X","*") return newStr }, formatFilter(val){ return val.getFullYear() } } } </script> <style> </style>
在我们页面上,一个模板里面可以新增很多个过滤器
<p>{{username | myToUpperCase}}</p> <p>{{username | myToUpperCase | reaplceFilter}}</p>
过滤器的执行顺序,根据你定义的来执行的
全局过滤器
全局过滤器将过滤器定义到全局对象中,任何一个组件都可以使用这个过滤器
如果要定义全局过滤器,将过滤器定义Vue对象身上。
在main.js文件中设计如下代码
import Vue from 'vue' import App from './App.vue' // 控制我们开发模式和生成模式 // false为生产模式,true为开发模式 Vue.config.productionTip = true // 将过滤器定义到Vue身上 Vue.filter("changeUpper",function(val){ return val.toUpperCase() }) Vue.filter("reverseFilter",function(val){ return val.split("").reverse().join("") }) const app = new Vue({ // 渲染函数 render: h => h(App), }).$mount('#app')
这个代码里面定义的Vue.filter 代表全局过滤器
在页面上你无需任何引入操作,就可以直接使用全局的过滤器
<template> <div> <h2>Comp2</h2> <p>{{ password | changeUpper | reverseFilter }}</p> </div> </template> <script> export default { data() { return { password: "asd123", }; }, }; </script> <style> </style>
如果本地有同名过滤器,优先找本地的过滤器
时间过滤器,在src/filters/index.js中,可以实现将2021-12-09过滤成距现在多少年(或月,周,天,小时,分钟),即
2021-12-09=>8个月前
import Vue from "vue" Vue.filter("dataComputedFilter", function(hisTime) { if (!arguments.length) return ""; var arg = arguments, now = arg[1] ? arg[1] : new Date().getTime(), diffValue = now - new Date(arg[0].replace(/-/g, "/")).getTime(), result = "", minute = 1000 * 60, hour = minute * 60, day = hour * 24, halfamonth = day * 15, month = day * 30, year = month * 12, _year = diffValue / year, _month = diffValue / month, _week = diffValue / (7 * day), _day = diffValue / day, _hour = diffValue / hour, _min = diffValue / minute; if (_year >= 1) result = parseInt(_year) + "年前"; else if (_month >= 1) result = parseInt(_month) + "个月前"; else if (_week >= 1) result = parseInt(_week) + "周前"; else if (_day >= 1) result = parseInt(_day) + "天前"; else if (_hour >= 1) result = parseInt(_hour) + "个小时前"; else if (_min >= 1) result = parseInt(_min) + "分钟前"; else result = "刚刚"; return result; })
Vue23-Vue全家桶项目
Vue23-Vue全家桶项目
Vue全家桶:Vue基础+VueRouter+Vuex+axios+echarts等等
搭建一个完整的项目。搭建流程跟以前是一样,需要在搭建项目选中我们需要的依赖包。
一、创建项目
vue create my-vue-project
? Please pick a preset: Default ([Vue 2] babel, eslint) Default (Vue 3 Preview) ([Vue 3] babel, eslint) ❯ Manually select features
我们需要在创建项目的时候选择Router和Vuex
? Check the features needed for your project: ◯ Choose Vue version ◉ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◉ Router ◉ Vuex ❯◉ CSS Pre-processors ◯ Linter / Formatter ◯ Unit Testing ◯ E2E Testing
路由的模式
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)
输入Y,设置设置路由模式为history
选择css的预处理器
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys) ❯ Sass/SCSS (with dart-sass) Sass/SCSS (with node-sass) Less Stylus
创建完成项目后我们就可以启动项目
npm run serve
Vue24-路由和SPA
Vue24-路由和SPA
一、什么是SPA
SPA全称(Single Page Application)单页面应用。
目前在项目开发过程中只会存在一个HTML文件,在这个文件中动态引入组件,通过组件的自由搭配,实现页面上不同布局的切换。
在Vue项目,public文件夹存在index.html,这个就是唯一html。以后所有的代码都在vue文件写,动态显示到html中就实现加载效果
单页应用开发实现原理
Login.vue登录组件
Register.vue注册组件
Home.vue主页组件
以后所有的页面显示都在index.html里面div显示
二、路由的区分
路由可以分为前端路由和后端路由
后端路由
router.get("/user/find",function(req,resp){ const {id} = req.query() }) router.post("/user/add",function(req,resp){ const {username,password} = req.body() //调用mongodb的Model执行查询 //将登录结果返回前端 resp.send({ code:1,msg:"登录成功" }) })
后端路由的作用,将你们请求地址和后端函数绑定在一起,形成映射关系。
后端路由核心:将HTTP访问的地址和后端的业务代码建立映射关系。
前端路由
将浏览器输入的URL地址和组件映射在一起。
http://127.0.0.1:8080/my my----My.vue http://127.0.0.1:8080/friend frienD---Friend.vue
在浏览器输入不同的地址,项目就可以切换不同组件来进行渲染。
以后修改历览器地址,也能自动进行切换
路由的出现是因为现在使用SPA的开发模式。
好处:
用户体验好,开发方便。维护方便。效率高
缺点:
不利于SEO优化。SEO(搜索引擎优化)
Vue25-路由基础配置
Vue25-路由基础配置
一、路由搭建流程
在我们项目中创建项目的时候就已经选择了Router,默认在你们项目搭建好路由完整配置
如果你项目在搭建的时候没有选择Router,也可以手动搭建路由。
(1)下载依赖
npm i vue-router
(2)在src目录下面创建router文件夹 index.js 文件
import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' import About from "../views/About.vue" import Register from "../views/Register.vue" // 加载路由插件 Vue.use(VueRouter) // 配置路由映射关系 const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component:About }, { path:"/register", name:"Register", component:Register } ] // 创建VueRouter实例 const router = new VueRouter({ mode: 'history', // 创建项目是否选择路哟history模式 base: process.env.BASE_URL, routes }) export default router
(3)将router配置文件,放在main.js中加载
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' Vue.config.productionTip = false // 将router对象全局挂载到Vue实例。以后任何一个组件都可以获取路由对象 new Vue({ router, store, render: h => h(App) }).$mount('#app')
引入router实例化对象,将这个挂载到Vue实例上面。以后任何一个组件都可以访问路由对象
(4)路由渲染出口配置
<template> <div id="app"> <!-- 路由渲染出口 --> <div class="header"></div> <div> <!-- 路由匹配的组件,放在这个位置显示 --> <router-view /> </div> <div> footer </div> </div> </template> <style lang="scss"> .header { width: 100%; height: 80px; background-color: red; } </style>
router-view代表路由渲染出口,你将这个标签放在App.vue中指定的位置,路由映射组件在这个位置显示
一级路由:登录页面、注册页面、首页、忘记密码
二级路由:在主页里面,加载其他组件来显示,设计映射关系 员工管理、部门管理、财务管理
二、搭建一级路由
在views文件夹下面创建三个页面
Login.vue登录页面
Register.vue注册页面
Home.vue主页页面
在路由映射文件中设置映射关系
import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' import Login from "../views/Login.vue" import Register from "../views/Register.vue" // 加载路由插件 Vue.use(VueRouter) // 配置路由映射关系 const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/login', name: 'Login', component:Login }, { path:"/register", name:"Register", component:Register } ] // 创建VueRouter实例 const router = new VueRouter({ mode: 'history', // 创建项目是否选择路哟history模式 base: process.env.BASE_URL, routes }) export default router
在浏览器输入不同的路由地址,可以看到页面不同组件渲染
三、路由配置信息
路由模式
在路由配置文件中我们发现有以下代码
const router = new VueRouter({ mode: 'history', // 创建项目是否选择路哟history模式 base: process.env.BASE_URL, routes })
mode代表路由模式。
路由模式有两种:
hash模式:地址栏里面/#/,只要历览器看到/#/ 后面的内容发生变化,自动匹配变化后的名字
histroy模式:历史模式H5模式。底层用的就说H5,history对象。路径比较简洁,没有/#
重定向配置
const routes = [ { path:"/", redirect:"/home" }, { path: '/home', name: 'Home', component: Home }, { path: '/login', name: 'Login', component:Login }, { path:"/register", name:"Register", component:Register } ]
重定向一般用于页面开发过程中,/
路径访问。
redirect代表匹配成功,默认进入你指定路由。重新跳转一次
路由懒加载
路由懒加载也叫路由延迟加载。
前端模块化import来加载模块。只要发现import语句,在项目加载的时候默认执行import加载的内容。
有些组件我们在整个项目很少访问,进来也是马上加载,造成内存浪费。项目越来越慢。
有些不常用的路由组件,当他访问这个路径在动态加载。
先将import引入的组件删除。
const routes = [ { path:"/", redirect:"/home" }, { path: '/home', name: 'Home', component: Home }, { path: '/login', name: 'Login', component:()=>import("../views/Login.vue") }, { path:"/register", name:"Register", component:()=>import("../views/Register.vue") } ]
()=>import(xxx)
当我们访问这个路径的时候,执行component,动态加载一个组件。
浏览器里面动态生成js文件,单独访问这个js文件
当我们访问这个路由的时候。花费更多事件初始化这个组件。
Vue26-路由跳转
Vue26-路由跳转
在Vue路由中要实现跳转我们有两种方案
-
基于router-link进行超链接跳转
-
基于按钮的方式跳转,需要使用它路由对象。
链接跳转
router-link可以实现超链接的跳转
<router-link to="/register">提示信息</router-link>
这个to属性是必须要写,不写会报错,跳转的地址一定是我们路由配置好的地址
router-link被webpack打包后生成的也是a标签。
注意:不要再页面上直接用a标签来跳转。a标签有默认刷新的行为。
router-link虽然最后也是转化为超链接跳转,但是底层处理了默认行为。
跳转的时候,找到路由的名字
<router-link :to="{name:'Register'}">跳转到注册</router-link>
一般来说路由的名字也是唯一的。
你可以再router-link这个标签上面增加的属性如下:
属性名 | 说明 | 举例 |
---|---|---|
tag | 如果想要 `` 渲染成其他标签,可以使用 tag 指定标签。 | tag=”button” |
active-class | 设置链接激活时使用的 class。 | active-class=”setColor” |
exact-active-class | 配置当链接被精确匹配的时候应该激活的 class。 | |
exact | 精确匹配路径。 | exact=”true” |
event | 设置可以用来触发导航的事件,可以是一个字符串。默认为 “click”。 | event=”mouseover” |
replace | 设置 replace 属性的话,跳转效果等同于 $router.replace() | replace=”true” |
append | 设置 append 属性后,则在当前 (相对) 路径前添加基路径。 | append=”t |
按钮跳转
我们在页面上点击按钮过后,会跳转到指定的页面,也是路由跳转
通过绑定事件来触发函数,调用路由的对象来进行页面跳转
获取this对象身上提供$router对象,这个对象是路由对象,可以得到完整路由,以及封装api
进行跳转的时候
this.$router.push("/register")
默认记录跳转的历史记录。
this.$router.replace("/register")
默认替换路径,不会记录历史,无法返回到之前那一页
this.$router.push({name:"Register"})
可以指定跳转时通过路由的名字来进行的。必须指定name属性
Vue27-嵌套路由
Vue27-嵌套路由
一、嵌套路由概念
在实际开发过程中,我们路由会嵌套很多层(两层、三层)
运行过程大概过程:
在外层路由渲染完毕后,进入指定组件,这个组件里面还要动态加载其他组件,会通过路由来进行加载,嵌套的路由就是目前我们需要配置的
目录结构:
App.vue Home.vue --- Student.vue --- Classes.vue Login.vue Register.vue
src下面
—-views:这个目录用于存放页面。只要跟路由由关系的组件我们称为页面
—-components:存放组件,跟路由没有关系组件存放在这里面
二、配置嵌套路由
在Home主页中我们能需要引入外部很多组件,实现动态切换,
优先考虑的使用嵌套路由来实现。暂时没有考虑动态组件来实现
import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' import Main from "../views/subs/Main.vue" import Student from "../views/subs/Student.vue" import Classes from "../views/subs/Classes.vue" import Teacher from "../views/subs/Teacher.vue" // 加载路由插件 Vue.use(VueRouter) // 配置路由映射关系 const routes = [ { path:"/", redirect:"/home" }, { path: '/home', name: 'Home', component: Home, children:[ { path:"/", name:"Main", component:Main }, { path:"student", name:"Student", component:Student }, { path:"classes", name:"Classes", component:Classes }, { path:"teacher", name:"Teacher", component:Teacher } ] }, { path: '/login', name: 'Login', component:()=>import("../views/Login.vue") }, { path:"/register", name:"Register", component:()=>import("../views/Register.vue") } ] // 创建VueRouter实例 const router = new VueRouter({ mode: 'history', // 创建项目是否选择路哟history模式 base: process.env.BASE_URL, routes }) export default router
你要哪个路由下面嵌套就必须加入children数组。里面存放也是路由配置
嵌套路由路径前面不要加/
Home中动态加载路由,必须在Home这个组件内部配置渲染出口
<div class="main"> <div class="leftNav"> <ul> <li @click="changeCheck(item.id)" :class="{check:currentTag==item.path}" v-for="item in menus" :key="item.id"> <i :class="item.icon"></i> <router-link :to="item.path">{{ item.title }}</router-link> </li> </ul> </div> <div class="rightContent"> <!-- 二级路由渲染出口 --> <router-view></router-view> </div> </div>
在我们菜单 里引入了router-link,点击文字就可以实现路由切换。渲染的组件动态加载
Vue28-axios请求工具
Vue28-axios请求工具
axios是一个封装了Promise+ajax的一个请求工具,目前也是我们开发过程中使用最多的一个请求工具,可以直接在你项目中下载这个工具帮你主发送异步请求。
传统的异步请求发送jquery+ajax。简化了发送请求的步骤。并没有解决发送请求遇到的回调地狱问题。
Promise或者await、async的来解决。
axios这个工具,我们发送请求比较完美的一种方案。
开发步骤
-
下载axios的包
npm i axios
-
在你们项目中引入axios
import axios from "axios"
-
使用axios
axios.get() axios.post() axios.delete() axios.put()
你也可以用最原始的方式来发送请求
axios({ url:"", method:"", data:{} })
axios请求方案
get请求
我们可以直接调用axios.get方法来获取后端数据
const result = axios.get("url地址")
result得到的结果为promise。拿到里面成功或者失败数据。
result.then(res=>{ console.log(res) }).catch(err=>{ console.log(err) })
你还可以用await来等待成功或者失败结果
async function fecthData(){ const result = await axios.get("url地址") }
get请求传递参数
async function fecthData(){ const result = await axios.get("url地址?id=1&name=xiaowang") }
官方还可以传递一个对象,默认给拼接地址栏
async function fecthData(){ const result = await axios.get("http://127.0.0.1:8000/user",{params:{id:1}}) }
请求地址栏:http://127.0.0.1:8000/user?id=1
POST请求
请求和get差不多
async function fecthData(){ const result = await axios.post("url地址") }
请求要传递参数
async function fecthData(){ const result = await axios.post("url地址",{id:1,name:"xiaowang"}) }
delete请求参数传递
async function fecthData(){ const result = await axios.delete("url地址",{ params:{ id:1 } }) }
三、文件上传
提交数据给服务器,我们一般会有两种方式:
-
普通文本提交,
提交数据到后端
data:{id:1,name;"xiaowang"} data:"id=1&name=xiaowang"
对于HTTP请求来说,将内容转化为字符串,挡在HTTP的数据包里面,提交给后端。
解析字符串,将内容转化为Object对象。
const {id,name} = req.query() const {id,name} = req.body()
-
二进制数据提交
只要涉及到二进制文件提交到后端,都会采用二进制流来提交。
只要涉及文件提交,音频、视频、图片等等,采用二进制流的方式到后端
借助于前端的而一些api、工具来实现文件转化为二进制传输到后端
上传图片的方法:
async fileUpload(event) { console.log(event); const files = event.target.files; // 创建一个对象,浏览器默认会提供对象formData // JSON.stringify() ES5内置的一个对象 const formData = new FormData(); // 文件会默认被formData进行处理,二进制传输后端 Blob // file代表告诉后端。取文件用file来获取 formData.append("file", files[0]); // 上传文件到后端 const res = await axios.post( "http://47.98.128.191:3000/images/uploadImages", formData ); console.log(res); if (res.data.code) { // 得到上传后图片名字 this.student.imagePath = res.data.data[0]; } else { alert("上传失败"); } },
四、请求封装
目前我们直接在页面中发送请求。axios引入到页面中使用。
优点:简单、方便,代码量比较小的时候,开发更加方便
缺点:页面和请求代码耦合在一起。如果需要修改请求,打开所有有请求的页面,一个一个修改。以后我访问请求需要更换地址,变更所有的页面。
将请求的和页面逻辑解耦(分离)
(1)、在项目src目录下面创建api文件或者request文件
文件夹里面存放是请求的js代码。
(2)、在src目录创建utils文件夹,里面创建axiosUtils.js
// 封装我们请求的一个工具 import axios from "axios"; // 你们可以create来创建一个新的axios const newAxios = axios.create({ baseURL:"http://47.98.128.191:3000", timeout:3000 //发送axios请求,超过3s没有结果,axios主动短开请求 }) export default newAxios
(3)你可以直接在页面中引入你们封装好的newAxios来发送请求(开发不会写步骤,演示代码)
<script> import newAxios from "../../../utils/axiosUtils" export default { data() { return { list: [], }; }, mounted() { this.fetchData(); }, methods: { // 请求一般封装为一个函数,在生命周期里面调用 // jquery ajax axios fetch 都是请求封装工具 async fetchData() { const res = await newAxios.get( "/students/getStudents",{ params:{ currentPage:1, pageSize:20 } }); console.log(res); this.list = res.data.data.rows }, }, }; </script>
(4)将请求全都提取出来,放在request文件夹里
根据不同的业务我们会创建不同的js文件
学生的所有请求:student.js
班级的所有请求:classes.js
比如项目中我们学生信息,提取出我们异步方法,统一维护
// 存放的就是所有学生请求 import axios from "../utils/axiosUtils" // 设计一个请求函数,获取所有学生信息 const student = { // 查询所有学生的方法 asyncFindAllStudent(){ // 返回的结果是一个promise return axios.get("/students/getStudents") }, asyncAddStudent(){ return axios.post("/students/addStudents") }, asyncDeleteStudent(){ } } export default student
在页面上使用这个请求工具
export default { import studentRequest from "../../request/student.js" create(){ this.fecthData() }, methods:{ async fecthData(){ await studentRequest.asyncFindAllStudent() } } }
(5) 目前你每个页面都要引入自己这个请求模块.请求挂载到全局
request文件夹创建一个http.js文件
为了开发方便,我们可以将所有请求模块放在request/modules文件夹里面
import student from "./modules/student" import user from "./modules/user" import classes from "./modules/classes" import teacher from "./modules/teacher" export default { student,user,classes,teacher }
在main.js中引入http中暴露的对象,挂载到Vue的原型上面
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import http from "./request/http" Vue.config.productionTip = false Vue.prototype.$http = http // 将router对象全局挂载到Vue实例。以后任何一个组件都可以获取路由对象 new Vue({ router, store, render: h => h(App) }).$mount('#app')
页面中要使用请求
methods: { async fetchData() { //this对象上面 会获取到$http对象 const res = await this.$http.student.asyncFindAllStudent() console.log(res); this.list = res.data.data.rows }, },
Vue29-路由参数传递
Vue29-路由参数传递
路由参数传递,主要指的在进行路由跳转的时候,需要携带当前页面参数到另外一个页面进行获取查询
组件参数传递,一个组件引入另外一个组件,将父组件的数据传递子组件
回顾JS中页面直接如何传递
window.location.href="/movies?id=1&name=1233"
来到movies页面
const path = window.location.href path.split("?") window.location.search()
路由参数传递
一、router-link的传递
<router-link to="/home/updatestudent?studentId=102">详情</router-link> <router-link :to="{path:'/home/updatestudent',query:{_id:item._id}}">详情</router-link>
你可以在路径后面拼接一个问号,默认在跳转的URL地址里面将拼接的参数传递给下一个页面。
你传递的路径和参数可以用对象的方式来进行传递。
以后使用router-link来进行跳转和参数传递
<router-link :to="{path:'/home/updatestudent',query:{_id:item._id}}">详情</router-link> <router-link :to="{name:'StudentUpdate',query:{_id:item._id}}">详情</router-link>
二、$router来进行参数传递
this.$router.push("/home/updatestudent?id="+id) this.$router.push({ path:"/home/updatestudent", query:{ id } }) this.$router.replace({ path:"", query:{ id:1 } })
你可以直接在路径上拼接传递id参数
你也可以传递一个对象给另外一个页面
三、获取路由参数
Vue路由给我们提供了一个对象$route,里面包含当前路径地址。路由参数传递
this.$route.query ====>{id:22784787232}
一般页面直接传递的更多就是id
Vue30-动态路由
Vue30-动态路由
我们在页面跳转的时候。如果要传递参数,还可以用动态路由来实现
/home/student/001 /home/student/002
路由跳转的时候,路由中有一部分内容可以用户自己来决定,称为动态路由
一、配置
在路由映射文件里面我们需要在路由后新增动态的部分
{ path:"detail/:_id", name:"StudentDetail", component:StudentDetail },
/:id代表的就是就是动态的部分内容。
this.$router.push("/detail/001") this.$router.push("/detail/002")
接受动态参数部分的值
this.$route.params ====>{_id:001}
动态部分命令的名字,就是最后参数接受到的名字
动态参数也可以用来实现路由参数传递,相对路由参数传统的方式,这种用的更少一些
你可以在路由新增多个动态的变量
{ path:"detail/:_id/:name", name:"StudentDetail", component:StudentDetail },
参数传递
this.$router.push("/detail/001/xiaowang")
二、动态部分可选
{ path:"detail/:_id?/:name?", name:"StudentDetail", component:StudentDetail },
在动态参数后面加了?,代表这个部分动态参数可选
this.$router.push("/detail")
可以不传递内容也能跳转
Vue31-拦截器配置
Vue31-拦截器配置
axios这个功能可以实现发送请求。
axios发送请求的时候,如果要将本地token传递到后端如何实现?
请求拦截器
这是axios提供的一个功能。所有的axios请求都必须经过这个请求拦截器。统一在操作请求头。
// 封装我们请求的一个工具 import axios from "axios"; // 你们可以create来创建一个新的axios const newAxios = axios.create({ baseURL:"http://47.98.128.191:3000", timeout:3000, //发送axios请求,超过3s没有结果,axios主动短开请求 }) // 请求拦截器 // use这个函数需要我们提供两个函数 第一个函数请求成功进入,第二函数请求失败进入 newAxios.interceptors.request.use((req)=>{ // 统一给所有的请求 增加请求头 // req.headers.token = // 以后所有的请求都默认添加了token验证 console.log(localStorage.getItem("token")); req.headers.Authorization = localStorage.getItem("token") // 返回了req,代表请求继续发送到后端 return req },(error)=>{ // 很少处理业务 }) export default newAxios
我们在axios的时候,创建axiosUtils.js文件,请求拦截器就写在这个后面
统一在请求拦截器给请求对象新增headers,将你们token存放到headers里后端进行验证
参数两个:
第一个参数:请求成功的回调函数
第二个参数:请求失败的回调函数
响应拦截器
// 封装我们请求的一个工具 import axios from "axios"; // 你们可以create来创建一个新的axios const newAxios = axios.create({ baseURL:"http://47.98.128.191:3000", timeout:3000, //发送axios请求,超过3s没有结果,axios主动短开请求 }) // 响应拦截器 newAxios.interceptors.response.use((resp)=>{ // 响应成功后的结果 console.log("响应拦截器,成功的拦截"); console.log(resp); return resp.data },(error)=>{ // 响应失败的结果 500,401,404,400 const response = error.response if(response){ switch(response.status){ case 500: alert("你的网络开小差了") break; case 401: alert("身份过期。重新认证") localStorage.removeItem("token") location.href = "/login" break; case 404: alert("访问路径有问题") break; } } }) export default newAxios
参数有两个:
第一个参数:响应成功会进入的回调
第二个参数:响应失败会进入的回调
一般我们会在失败的回调函数处理后端各种异常的状态码 400、401、500等等
Vue32-导航守卫
Vue32-导航守卫
导航:代表路由发生变化(路由守卫)
导航守卫:当我们路由发生变化的时候要执行一系列的钩子函数。你可以在钩子函数中进入身份验证。
一、导航守卫分类
导航守卫主要分为分类:
(1)全局导航守卫:作用于所有的路由。任何一个路由发生变化都可以监控
(2)路由独享守卫:指定某个路由要进行监控。
(3)组件内守卫:每个组件内部可以进行守卫监控
二、全局守卫
全局守卫又分为以下几种:
-
全局前置守卫:beforeEach
-
全局解析守卫:beforeResolve
-
全局后置守卫:afterEach
你从登录跳转到主页。进入全局前置守卫,判断你是否有权限进入主页。
当你离开主页的时候,判断是否要你离开
import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' import Main from "../views/subs/Main.vue" import Student from "../views/subs/student/Student.vue" import StudentAdd from "../views/subs/student/StudentAdd.vue" import StudentUpdate from '../views/subs/student/StudentUpdate' import StudentDetail from "../views/subs/student/StudentDetail" import Classes from "../views/subs/Classes.vue" import Teacher from "../views/subs/Teacher.vue" import $http from "../request/http" // 加载路由插件 Vue.use(VueRouter) // 配置路由映射关系 const routes = [ { path:"/", redirect:"/home" }, { path: '/home', name: 'Home', component: Home, children:[ { path:"/", name:"Main", component:Main }, { path:"student", name:"Student", component:Student }, { path:"addstudent", name:"StudentAdd", component:StudentAdd }, { path:"updatestudent", name:"StudentUpdate", component:StudentUpdate }, { path:"detail/:_id?/:name?", name:"StudentDetail", component:StudentDetail }, { path:"classes", name:"Classes", component:Classes }, { path:"teacher", name:"Teacher", component:Teacher } ] }, { path: '/login', name: 'Login', component:()=>import("../views/Login.vue") }, { path:"/register", name:"Register", component:()=>import("../views/Register.vue") } ] // 创建VueRouter实例 const router = new VueRouter({ mode: 'history', // 创建项目是否选择路哟history模式 base: process.env.BASE_URL, routes }) // 配置全局前置守卫 // to代表你要访问哪个路径 from:你从哪个路由来的 next:允许你进入你想要的路由 router.beforeEach(async (to,from,next)=>{ // 设置一个白名单 const paths = ["/login","/register"] if(paths.includes(to.path)){ next() }else{ if(localStorage.token){ // 任意发送一个请求。验证token // 发送一个请求,获取用户信息 const res = await $http.user.getUserInfo() // code等于1代表身份验证通过 code:0 当前用户被禁用 if(res.data.code==1){ next() }else{ next("/login") } }else{ next("/login") } } }) export default router
三、路由独享
指定在某个路由上面添加守卫,你只有访问这个路由地址才会触发守卫。
我们项目中主页是需要身份认证过后才能访问的,将路由守卫放在/home这个路由上面
{ path: '/home', name: 'Home', component: Home, // 路由独享 beforeEnter:async (to,from,next)=>{ // 判断本地是否有token if(localStorage.token){ const res = await $http.user.getUserInfo() if(res.code){ next() }else{ next("/login") } }else{ next("/login") } }, children:[ { path:"/", name:"Main", component:Main }, ... ] },
to: 目标地址。要进入的页面
from:来自于哪个页面
next:函数控制是否能够进行跳转
四、组件内守卫(扩展)
每个组件在你进入的时候可以执行守卫代码,在你离开的时候也可以执行守卫代码
他和生命周期函数有差不多的效果,但是也有差异。
组件的守卫检测路由是否发生变化,组件生命周期函数判断组件创建销毁更新的过程
<script> import axios from "../../utils/axiosUtils" export default { methods:{ async getUserInfo(){ const result = await axios.get("/users/getUserInfo") console.log(result); } }, beforeRouteLeave(to,from,next){ console.log("正在计划离开这个组件"); // 离开这个组件之前,你可以询问用户是否要离开页面,数据还没有保存 alert("当前还没编辑完成,是否离开") next() } } </script>
这个守卫更多在于页面的提示信息,当用户离开这个组件的时候,提醒用户状态还没有保存
五、路由元信息
动态组件保存组件状态
<component :is="aliveComp"></component>
aliveComp名字等于组件的名字,就能实现组件切换
缓存组件的状态,可以增加keep-alive
<keep-alive include="Login"> <component :is="aliveComp"></component> </keep-alive>
在路由开发过程中我们有也可以使用keep-alive来缓存组件
<keep-alive> <router-view></router-view> </keep-alive>
上面代码将路由渲染出口包裹起来,缓存当前所有组件。我们可能需要指定缓存哪些组件
元信息
在配置路由的时候,你可以给路由配置对象添加额外的信息,meta来定义
{ path: '/login', name: 'Login', meta:{ //用户自定义数据结构 keep:true }, component:()=>import("../views/Login.vue") },
获取路由元信息的代码如下:
this.$route.meta ===》{keep:true}
在Vue中我们可以使用元信息来控制路由组件是否被缓存
在App.vue组件中,引入以下的代码:
<template> <div id="app"> <keep-alive> <router-view v-if="$route.meta.keep"></router-view> </keep-alive> <router-view v-if="!$route.meta.keep"></router-view> </div> </template>
当你的meta里面设置的keep=true,默认进入keep-alive渲染路由组件,当你的值false的时候,不进入keep-alive,不被keep-alive包裹,不会被缓存
Vue33-ElementUI组件库
Vue33-ElementUI组件库
一、概念
ElementUI是UI组件库,将你们平时经常要用到组件封装成了组件库,需要用的直接引用。
由饿了么团队研发出来开源出来的。可以快速的搭建我们前端项目。一般这种UI组件库适合于中后台系统。
官方地址:Element - The world's most popular Vue UI framework
二、搭建环境
(1)vue脚手架提供了安装elementui的命令
vue add element
会将我们elementui的很多环境搭建好。
(2)安装过程中提示我们采用什么方式来安装:
✔ Successfully installed plugin: vue-cli-plugin-element ? How do you want to import Element? (Use arrow keys) ❯ Fully import Import on demand
Fully import
: 代表完整引入,引入一次,以后再任何一个组件中都可以直接使用ElementUI组件库
Import on demand
:代表按需引入,后续开发过程中,我们需要用哪个组件,手动引入一次。
(3)配置scss,默认elementui底层采用scss来编程
? Do you wish to overwrite Element's SCSS variables? (y/N)
输入Y就可以了。
(4)选择区域,语言
? Choose the locale you want to load (Use arrow keys) ❯ zh-CN zh-TW af-ZA ar bg ca cs-CZ
默认选择zh-cn这个选项就可以了
node-sass镜像配置
npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
(5)启动项目sass报错
启动项目过程中日志里面抛出sass的
margin-bottom:#{$--tooltip-arrow-size/3}
sass版本的问题。默认项目中sass版本 1.2x
需要升级以下sass的版本
npm i sass@1.32.13
在启动,报错就会消失
三、表单组件
我们需要关注的有几个细节:
表单验证规则
rules: { name: [ {pattern:/[a-zA-Z0-9]{6,}/,message:"必须输入6位字母数字"} ], password:[ { required: true, message: "必须输入密码", trigger: "blur", }, { min: 6, max: 10, message: "长度在 6 到 10 个字符", trigger: "blur", }, ] },
rule里面写的变量就是双向绑定的变量。
官方推出了常用的验证规范
min: 6,max: 10,required: true,
你们也可以自己写正则表达式来验证
{pattern:/[a-zA-Z0-9]{6,}/,message:"必须输入6位字母数字"}
四、ajax原生代码和封装
jquery项目中发送异步请求
jquery给你封装好的请求代码。
$.ajax({ url:"", method:"", data:{}, success(res){ } })
const p = new Promise((resolve,reject)=>{ $.ajax({ url:"", method:"", data:{}, success(res){ resolve(res) }, error(error){ reject(error) } }) }) p.then(res=>{ }).catch(err=>{ })
原生AJAX
// (1)ajax这项技术最核心的对象.异步请求对象 const xmlhttp = new XMLHttpRequest() // (2)通过前端浏览器和后端服务器建立连接。 TCP url地址就说你们访问服务器地址,method,请求的方式 xmlhttp.open("url",method) // (3)发送请求,发送请求接受到结果 xmlhttp.send(data) // (4)前端要判断成功还是失败 // x 100-200 200 xmlhttp.onreadystatechange = function(resp){ //状态码必须200,并且对象的状态位4 才能获取服务器数据 if(resp.status==200 && resp.readyState==4){ const result = resp.responseText }else{ // } }
自己封装jquery ajax函数
const myajax = (params)=>{ // (1)ajax这项技术最核心的对象.异步请求对象 const xmlhttp = new XMLHttpRequest() // (2)通过前端浏览器和后端服务器建立连接。 TCP url地址就说你们访问服务器地址,method,请求的方式 xmlhttp.open(params.url,params.method) // (3)发送请求,发送请求接受到结果 xmlhttp.send(params.data) // (4)前端要判断成功还是失败 // x 100-200 200 xmlhttp.onreadystatechange = function(resp){ //状态码必须200,并且对象的状态位4 才能获取服务器数据 if(resp.status==200 && resp.readyState==4){ const result = resp.responseText params.success(result) }else{ //获取失败结果 params.error(result2) } } }
调用自己的ajax
myajax({ url:"", method:"", data:{}, success(){ }, error(){ } })
ElementUI修改学生,头像回显问题解决:
<!-- 头像 --> <el-form-item label="学生头像:"> <el-upload :limit="1" // 用于控制上传图片数量 :on-exceed="limit" // 超过上传数量后的回调函数 :file-list="fileList" // 用于图片回显 action="http://47.98.128.191:3000/images/uploadImages" // 上传文件的接口路径 list-type="picture-card" // 类型:照片墙 :on-preview="handlePictureCardPreview" :on-remove="handleRemove" name="file" // 上传的名字 :on-success="sendUpload" //上传成功后的回调函数 > <i class="el-icon-plus"></i> </el-upload> <el-dialog :visible.sync="dialogVisible"> <img width="100%" :src="form.imagePath" alt="" /> </el-dialog> </el-form-item>
export default { data() { return { form: { _id: "", name: "", age: "", gender: "", subjectsId: "", classesId: "", imagePath: "", // 图片路径 }, fileList: [{url: ""}], // 用于回显图片的状态,arr类型 // 文件上传 dialogVisible: false, }; }, methods: { // 获取学生信息 async getStudentInfoById() { console.log(this.form._id); const res = await this.$http.student.getStudentInfoById(this.form._id); this.form = res.data; this.fileList[0].url = res.data.imagePath // 将data中的fileList的url赋值 }, handleRemove(file, fileList) { console.log(file, fileList); }, handlePictureCardPreview(file) { this.form.imagePath = file.url; this.dialogVisible = true; }, sendUpload(response, file, fileList) { this.form.imagePath = response.data[0]; // 将上传成功后的图片地址赋值给form的imagePath }, // 上传超过一张图片时的回调函数 limit(){ this.$message({ message: '只能上传一个图片', type: 'error' }); } }, };
ElementUI,table组件添加class样式
<el-table :data="tableData" border style="width: 100%"> <el-table-column prop="id" label="编号" width="240"> </el-table-column> <el-table-column prop="name" label="姓名" width="220"> <template slot-scope="scoped"> <span @click="changeEdit(scoped.row)" v-if="!scoped.row.edit">{{scoped.row.name}}</span> <el-input @blur="changeName(scoped.row)" v-model="username" v-else placeholder="请输入用户名"></el-input> </template> </el-table-column> <el-table-column prop="age" label="年龄" width="120"> <template slot-scope="scoped"> <span v-if="scoped.row.age<=30">青年</span> <span v-else-if="scoped.row.age<=60">中年</span> <span v-else>老年</span> </template> </el-table-column> <el-table-column prop="dept" label="部门" width="120"> </el-table-column> <el-table-column prop="bir" label="学科"> // 在table中添加一个插槽 <template slot-scope="scoped"> <span :class="{mycolor:true}">{{scoped.row.bir}}</span> </template> </el-table-column> <el-table-column prop="time" label="入职月份"> <template slot-scope="scoped"> {{scoped.row.time | dataComputedFilter}} // 过滤器具体代码见下方代码 </template> </el-table-column> </el-table>
过滤器:将时间转化成距现在,n年前,n个月前,n周前,n天前,n个小时前,n分钟前,刚刚
// src/filters/index.js import Vue from "vue" Vue.filter("dataComputedFilter", function(hisTime) { if (!arguments.length) return ""; var arg = arguments, now = arg[1] ? arg[1] : new Date().getTime(), diffValue = now - new Date(arg[0].replace(/-/g, "/")).getTime(), result = "", minute = 1000 * 60, hour = minute * 60, day = hour * 24, halfamonth = day * 15, month = day * 30, year = month * 12, _year = diffValue / year, _month = diffValue / month, _week = diffValue / (7 * day), _day = diffValue / day, _hour = diffValue / hour, _min = diffValue / minute; if (_year >= 1) result = parseInt(_year) + "年前"; else if (_month >= 1) result = parseInt(_month) + "个月前"; else if (_week >= 1) result = parseInt(_week) + "周前"; else if (_day >= 1) result = parseInt(_day) + "天前"; else if (_hour >= 1) result = parseInt(_hour) + "个小时前"; else if (_min >= 1) result = parseInt(_min) + "分钟前"; else result = "刚刚"; return result; })
table组件文本居中css样式
::v-deep .el-table th, ::v-deep .el-table td { text-align: center !important; }
Vue34-自定义指令
Vue34-自定义指令
一、官方提供指令
v-for、v-model、v-if、v-bind、v-on等等。官方已经封装好的指令
v-model底层是上input事件+动态value
有些场景官方指令无法满足要求,我们需要自己定义指令。封装我们业务。在页面用封装好的指令完成开发
二、自定义指令
全局指令:一旦定义全局指令,任何一个组件中都可以使用。
局部指令:在指定的组件中定义这个指令,只能在当前组件中使用
过滤器:也分为全局过滤器和局部过滤器。
要求:在Vue中所有的指令(官方和自定指令),必须v-xxx开头
局部指令
在组件内部定义指令,只能组件中使用
directives:{ //属性就是指令的名字 focus:{ // 提供多个生命周期函数,你已经将v-foucs绑定到某个标签身上 bind:function(){ console.log("bind"); }, // 当前使用的这个节点,已经加入到父节点中 inserted:function(el){ console.log(el); el.focus() } }, bgcolor:{ inserted(el){ console.log(el); el.style.backgroundColor="red" } } }
focus和bgcolor都是指令名字,这个名字由你们自己来设计。页面上使用的时候v-mingzi
<input v-focus type="text"> <p v-bgcolor>这是测试文本</p>
自定义指令会提供生命周期函数:
*属性名* | *含义* |
---|---|
bind | 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 |
inserted | 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 |
update | 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有 |
componentUpdated | 指令所在组件的 VNode 及其子 VNode 全部更新后调用。 |
unbind | 只调用一次,指令与元素解绑时调用 |
重点了解bind和inserted函数
全局指令
首先全局指令肯定要单独提出来
src/directives文件夹/index.js
import Vue from "vue" // 将指令绑定Vue对象身上,以后所有页面都可以使用 Vue.directive("focus",{ inserted(el){ console.log(el); // focus() JS代码中方法 el.focus() } }) Vue.directive("bgcolor",{ inserted(el){ el.style.backgroundColor="green" } })
这个indexjs文件需要在main.js中加载一次
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import './plugins/element.js' import $http from "./apis/http" //自定义指令 import "./directives" Vue.config.productionTip = false Vue.prototype.$http = $http new Vue({ router, store, render: h => h(App) }).$mount('#app')
指令赋值操作
<p v-bgcolor="`pink`">蜗牛21期</p> <p v-bgcolor="`blue`">web21</p>
使用指令的时候我们可以传递指令的值过去.
需要到这个值获取值来进行业务换算
Vue.directive("bgcolor",{ inserted(el,obj){ console.log(obj.value); el.style.backgroundColor=obj.value } })
第一个参数el,代表绑定的节点
第二个参数obj,是一个对象,包含当前自定义指令名字\传递的值
应用场景:
你们可以使用自定义指令完成按钮级别的权限.
不同身份在项目能够操作的按钮是不一样.可以将逻辑封装到自定义指令中.
以后页面直接用封装好的指令就完成按钮权限
Vue35-Vuex状态机
Vue35-Vuex状态机
一状态机概念
遇到组件通信
父子通信:
-
props和回调函数
-
props和$listeners
-
$parent和$children
-
props和$emit
-
provider和inject等等
-
$attrs
兄弟通信:
eventbus技术,事件总线.在一个全局对象中创建事件,在另外一个组件触发这个事件.
爷孙节点如何参数?
兄弟组件之间通信?
目前我们要解决上述的问题,需要频繁的操作组件,包括建立他们关系.
代码在后期会非常那以维护,数据流非常混乱,有可能一个数据要经过10几个组件才能达到你目标组件.
引入的状态机(公共数据仓库)
状态指的就说数据,专门存放数据的仓库
在你项目比较小的情况的情况,没有必要用状态机;
当你项目业务比较多,业务比较复杂的情况,没有状态机,维护更新会非常的麻烦
二vuex的概念
vuex是vue提供的一种状态机,专门提供给vuejs来进行状态管理的方案.你也将状态机理解为一种集中数据管理仓库.
当你的项目业务繁多\数据通信比较繁琐的,建议使用状态机管理
一般存放到状态机里面的数据,为了让数据流更加清晰\要么就是为了共享数据
三状态机环境
创建全家桶项目的时候,你们已经在项目中添加了vuex选项
创建好项目后.src目录下面新增一个store文件夹/index.js
(1)以后index.js中写的代码就是仓库中业务
import Vue from 'vue' import Vuex from 'vuex' // 挂载插件 Vue.use(Vuex) export default new Vuex.Store({ state: { }, getters:{ }, mutations: { }, actions: { }, modules: { } })
state
:里面存放的就是你们仓库数据
getters
:相当于组件中计算属性
mutations
:存放的函数,相当于组件中methods,修改仓库数据的唯一方案,通过mutations来进行
actions
:存放的也是函数,存放的基本都是异步函数,所有的请求都可以交给actions来管理
modules
:模块化,目前看到的index.js称为主仓库.以后拆分很多子仓库
(2)将store仓库注入Vue对象中,以后任何一个页面都可以使用this调用仓库
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' Vue.config.productionTip = false Vue.prototype.$http = $http // store仓库注入到Vue中,以后任何一个组件都可以直接调用仓库 new Vue({ router, store, render: h => h(App) }).$mount('#app')
Vue36-Vuex五大核心对象
Vue36-Vuex五大核心对象
state
:里面存放的就是你们仓库数据
getters
:相当于组件中计算属性
mutations
:存放的函数,相当于组件中methods,修改仓库数据的唯一方案,通过mutations来进行
actions
:存放的也是函数,存放的基本都是异步函数,所有的请求都可以交给actions来管理
modules
:模块化,目前看到的index.js称为主仓库.以后拆分很多子仓库
一.state对象开始
存放仓库中的数据,你们可以将需要共享,需要被仓库管理的数据全都放在state中
state: { username:"xiaowang", users:[ {id:1,name:"xiaowang"}, {id:2,name:"xiaofeifei"} ], student:{ id:1,age:20,gender:"男",name:"王二麻子" } },
可以存放各种数据类型,state里面存放的数据主仓库数据
state相互之间的数据是不能访问的,
state:{ username:"xiaowang", newName:username //错误的案列 }
如果一个数据需要依赖于另外一个数据,你们用getters来执行计算
二.getters计算属性
export default new Vuex.Store({ state: { username:"xiaowang", users:[ {id:1,name:"xiaowang"}, {id:2,name:"xiaofeifei"} ], student:{ id:1,age:20,gender:"男",name:"王二麻子" } }, getters:{ // 所有计算属性,都默认会接受state fullName(state){ return state.username + "8" } } })
在我们store仓库中每一个计算属性都可以接受到一个state数据.,切记不要再仓库中用this来相互调用
三. mutations存放函数
export default new Vuex.Store({ state: { username:"xiaowang", users:[ {id:1,name:"xiaowang"}, {id:2,name:"xiaofeifei"} ], student:{ id:1,age:20,gender:"男",name:"王二麻子" }, count:10 }, getters:{ // 所有计算属性,都默认会接受state fullName(state){ return state.username + "8" } }, mutations: { // mutations里面每个函数都可以默认接受state仓库数据 increment(state){ state.count++ }, decrement(state){ state.count-- } } })
mutations里面存放的都是同步函数,一般不要写异步函数.
每个函数都可以默认接受state仓库数据,修改仓库数据操作state参数
修改state数据的唯一方案就是通过mutations来修改
四.actions异步函数
actions里存放都是异步函数,一般用于管理异步请求.
actions里定义的代码
actions: { asyncChangeCount(context,val){ // context表示整个仓库 context.state console.log(val); setTimeout(() => { context.state.count+=val }, 2000); } },
接受到的context属性属于整个仓库,context.state 才能获取到仓库数据.这就是mutations不一样的地方
也可以接受第二个参数,传递进来用户自定义参数
this.$store.dispatch("asyncChangeCount",10)
页面执行actions里面函数需要调用dispatch
五.modules将仓库拆分成很多子仓库
Vuex37-组件中操作仓库数据
Vuex37-组件中操作仓库数据
一.$store来操作仓库
获取仓库数据
只要你再main.js中store仓库注入到Vue对象中,再组件this里面默认多一个$store对象.
这个对象就是我们仓库对象.可以获取仓亏数据,也可以修改仓库
我们仓库中定义好数据
state: { username:"xiaowang", users:[ {id:1,name:"xiaowang"}, {id:2,name:"xiaofeifei"} ], student:{ id:1,age:20,gender:"男",name:"王二麻子" }, count:10 }, getters:{ // 所有计算属性,都默认会接受state fullName(state){ return state.username + "8" } },
再组件中使用
<template> <div> <h2>Teacher</h2> <p>{{$store.state.count}}</p> </div> </template> <script> export default { created(){ console.log(this.$store.state.count); } } </script> <style> </style>
获取state通过$store.state.属性
getters里面的内容要获取
<h3>getters的数据</h3> <p>{{$store.getters.fullName}}</p>
修改仓库数据
修改仓库数据的唯一方案,调用mutations里面函数
import Vue from 'vue' import Vuex from 'vuex' // 挂载插件 Vue.use(Vuex) export default new Vuex.Store({ state: { username:"xiaowang", users:[ {id:1,name:"xiaowang"}, {id:2,name:"xiaofeifei"} ], student:{ id:1,age:20,gender:"男",name:"王二麻子" }, count:10 }, getters:{ // 所有计算属性,都默认会接受state fullName(state){ return state.username + "8" } }, mutations: { // mutations里面每个函数都可以默认接受state仓库数据 increment(state){ state.count++ }, decrement(state){ state.count-- } } })
仓库mutations要提供修改的函数,再页面中commit来调用函数
methods:{ incrementData(){ console.log(this.$store); // commit调用mutations里面函数 this.$store.commit("increment") }, decrmentData(){ this.$store.commit("decrement") } }
调用参数要传递参数
mutations: { // mutations里面每个函数都可以默认接受state仓库数据 increment(state,val){ console.log(val2); state.count+=val }, decrement(state,val){ state.count-=val } },
mutations函数可以接受第二个参数,这个参数童工commit来传递
this.$store.commit("increment",8)
mutations里面函数,只能接受外部一个参数.
actions里面发送异步请求获取数据
import $http from "../apis/http" actions: { async asyncFindClasses(context){ // 发送异步请求 const res = await $http.classes.findAllClasses() // context.state.classesData = res.data.rows context.commit("changeClassesData",res.data.rows) } },
修改state的数据,可以童工context.state来进行修改,这样直接操作state并不会记录日志,不规范的数据流.
context.commit("mutations函数",参数)
实现修改数据,记录日志,完善数据流
在JS文件中使用状态机
// 使用子仓库userInfo中的getUserInfo异步方法,子仓库的异步方法中return过来的结果是一个promise对象,需要用async,await接收 import $store from "@/store/index"; const res = await $store.dispatch("userInfo/getUserInfo") console.log(res);
二.辅助函数来操作仓库
Vuex官方在进行数据操作的时候提供$store对象来获取和操作.这样操作有点麻烦.
如果在一个页面中获取很多仓库数据,多次调用$store.state.xxxx
为了简化我们对仓库的操作.Vuex底层封装了一些辅助函数可以帮助你们更快获取数据.
Vuex提供了以下几个辅助函数.
-
mapState()用于获取仓库state数据
-
mapGetters() 用于获取仓库getters属性
-
mapMutations()获取仓库中mutations的函数
-
mapActions()获取仓库中actions的函数
(1)如何在你的页面中获取辅助函数
import {mapState,mapMutations,mapActions,mapGetters} from "vuex"
mapState
<template> <div> <ul> <li v-for="item in logs" :key="item.date.getTime()"> <span >{{ item.user }}操作了{{ item.title }},在{{ item.date | dateFilter }}</span > </li> </ul> <p>{{count}}</p> <p>{{newCount}}</p> </div> </template> <script> import {mapState} from "vuex" export default { filters: { dateFilter(val) { console.log(val); // return val.getFullYear() +"-"+ (val.getMonth()+1) +"-"+ val.getDate() return val.getTime(); }, }, computed:{ ...mapState(["logs","count"]), newCount(){ return this.count+10 } } }; </script> <style> </style>
mapState这个辅助函数我们需要在computed中使用,vuex会将mapState里面传递数组变量,变成计算属性的语法来使用.
computed:{ ...mapState(["logs"]) }
底层转化过后的结果为
computed:{ logs(){ return $store.state.logs } }
mapGetters
获取仓库中getters数据.在页面中引入这个辅助函数
import {mapState,mapGetters} from "vuex" computed:{ ...mapState(["logs","count"]), ...mapGetters(["fullName"]), newCount(){ return this.count+10 } },
页面中可以直接使用
<p>{{fullName}}</p>
mapMutations
用于获取mutations里面的函数
import {mapMutations} from "vuex"
调用mapMuations里面函数
methods: { ...mapMutations(["deleteLogs"]), // deleteLogs(){} deleteLog() { this.deleteLogs(123) }, },
mapMutations必须放在methods中,因为获取都是函数.
mapActions
获取异步函数,执行异步任务
import {mapActions} from "vuex" methods: { ...mapMutations(["deleteLogs"]), ...mapActions(["asyncChangeCount"]), // deleteLogs(){} deleteLog() { this.deleteLogs(123) }, },
页面上调用
<button @click="asyncChangeCount(12)">点击异步执行修改count</button>
小程序01-环境搭建
小程序01-环境搭建
一、小程序来源
小程序的开发方式是国内非常流行的一种方式。
小程序是2017年1月9号,腾讯张小龙,是一种无需下载安装就可以直接使用的程序。
依托于微信庞大的用户数量,能够快速的建立起小程序流量
缺点:随取随用的一种程序,无需下载。小程序业务必须比app更小更少
移动端常见的一些开发模式:
-
原生APP开发。主要就是android和ios这两大系统。原生App开发,利用原生android和ios来进行项目开发。
用户体验最好的,功能是最完善。成本太高了。
-
H5端,说白了就是手机网页端。在手机里面打开浏览器访问我们H5网页。优点,开发非常的方便,成本非常低。只需要一个浏览器就可以随时访问。 用户体验比较差。
-
小程序端:小程序出现时微信采用了特殊一种开发技术,可以在没有下载程序的情况下,我们就可以动态访问项目。随去随用。当你不用的时候,不会占用手机空间
-
混合开发(hybird)原生开发和h5结合起来开发。写代码可以用h5代码来写,也可以调用原生的一些api,最后你的项目可以打包为一个app安装包
混合开发的原理,就是将h5的代码 外面套一个壳子(浏览器)。打包为app安装包。
二、小程序环境搭建
(1)开发小程序必须先注册一个账号
地址: https://mp.weixin.qq.com/cgi-bin/registermidpage?action=index&lang=zh_CN&token=
选中小程序—-接下来输入没有注册过邮箱
(2)进入公众平台后台管理系统
微信给你们开发和运营提供的一个后台管理系统。
可以管理你们项目版本,也可以管理运营数据以及开发者的配置
开发者必须要关注
AppID(小程序ID) wx36e04*****766d
服务器域名配置
你们小程序要访问的后端接口,以后都会在这个地方配置一下。
小程序访问后端接口报错
(3)下载开发者工具
开发微信小程序腾讯官方提供了一个开发者工具。
这个开发者工具我们最需要使用模拟器。
当然你可以vscode写代码。在微信开发者工具运行代码
三、vscode开发小程序
vscode开发小程序我们需要下载一些插件来支持你们代码提示,格式化等
-
小程序助手
v0.0.6
-
vscode wxml
v0.1.9
-
wechat-snippet:小程序代码格式化插件
-
WXML - Language Service
v2.4.8
小程序02-项目目录结构
小程序02-项目目录结构
小程序中的文件结构
HTML文件—->WXML
CSS文件—->WXSS
Javascript—-JavaScript文件
没有配置——JSON配置
小程序开发过程中,一个页面会包含四个文件。
小程序目录结构
pages:存放的就是小程序开发的页面
——文件夹的名字就是页面的名字。每个文件夹里都包含4个文件
utils:存放的开发过程中封装的工具
config:项目中需要用的配置项
apis:代表项目请求封装
app.js:全局的js配置代码。
app.wxss:全局的样式文件
app.json全局的配置文件
全局app.json
小程序开发文档:小程序配置 | 微信开放文档
全局配置功能:你们可以在全局里面配置页面的标题、页面的导航颜色、页面下拉刷新等
(1)路由配置
小程序的路由和vue不一样,我们无需搭建环境,直接配置就可以了。
"pages":[ "pages/index/index", "pages/logs/logs" ],
在pages里面写的页面的路径。pages前面不要加/
默认按照顺序来加载你的页面。放在最前面页面优先第一次加载
(2)创建一个页面
我们一般创建页面还在小程序的开发者工具里面,你可以自己新建一个page,默认生成4个文件。
也可以直接路由配置里面,新增一个路径。默认生成文件系统
(3)window配置
window配置主要是用于设置小程序的导航窗口样式
"window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "蜗牛", "navigationBarTextStyle": "black" },
-
navigationBarBackgroundColor:设置导航栏的背景颜色,要求必须是16进制颜色
-
backgroundTextStyle:设置下拉刷新的动画颜色,默认light和dark这两种
-
enablePullDownRefresh:true。开启全局的下拉刷新
-
navigationBarTitleText:设置导航栏标题文本
-
navigationBarTextStyle:导航栏的字体样式
(4)tabbar配置
页面的底层导航栏。基本上每个小程序都有tabbar
"tabBar":{ "list":[ { "pagePath":"pages/index/index", "text":"首页", "iconPath":"assets/images/home.png", "selectedIconPath":"assets/images/home-o.png" }, { "pagePath":"pages/home/home", "text":"产品", "iconPath":"assets/images/category.png", "selectedIconPath":"assets/images/category-o.png" } ] },
list里面的配置:
——pagePath:导航的路径
——text:导航的标题
——iconPath:默认图片的路径
——seletedIconPath:选中某个tabbar图片
小程序03-设计页面
小程序03-设计页面
一、WXML
以前网页html被wx进行了封装了。wxml在文件中要写的网页布局代码
现在在微信中已经不存在标签的说法。都说是组件。
常用组件:
View组件:代表布局组件,类似于以前HTML中div标签。
Text组件:这个代表文本组件,类似于HTML总span标签
Swiper组件
这个组件是轮播图组件。Swiper整个滑动模块-Swiper-item,就是每一个滑块
<swiper indicator-dots="true" indicator-color="blue" indicator-active-color="black" autoplay="true" interval="2000" duration="1000" circular="true" current="2" > <swiper-item class="item item1" item-id=""> <image src="//m15.360buyimg.com/mobilecms/jfs/t1/188272/10/27011/63337/62cefc29E4549bd1a/88bed5140abaa031.jpg!cr_1125x449_0_166!q70.jpg"></image> </swiper-item> <swiper-item class="item item2" item-id=""> <image src="//m15.360buyimg.com/mobilecms/s1062x420_jfs/t1/212225/6/11238/61871/61e50ff9E7f02c060/8d0f3065b0c27182.jpg!cr_1053x420_4_0!q70.jpg"></image> </swiper-item> <swiper-item class="item item3" item-id=""> <image src="//imgcps.jd.com/ling4/10043517686339/5Lqs6YCJ5aW96LSn/5L2g5YC85b6X5oul5pyJ/p-61a89cdfa05e88e039948562/e25e4ec8/cr_1125x449_0_166/s/q70.jpg"> </image> </swiper-item> </swiper>
swiper这个组件默认是有高度的:150px
在开发过程中我们有时候需要手动修改这个高度
image
代表图片资源。图片资源默认也是有高度
默认会给图片设置一个尺寸为:320px*240px就会导致默认图片变形
一般我们都会自己设置图片的大小
如果你要自适应图片,我们目前不支持auto属性。需要动态计算我们的图片高度
原图:1125px 449px 划算成 屏幕尺寸 / 高度
calc()
swiper{ // swiper 默认有150px像素的高度 // 实际图片在页面117 图片理论1125 * 352 计算出swiper 高度 height: calc( 100vw * 352 /1125); image{ width:100%; } }
图片还有一些属性
<image class="pic" src="//m15.360buyimg.com/mobilecms/jfs/t1/219283/25/642/202084/616af571E98042565/aea9a971a1c7dc9e.jpg!cr_1125x449_0_166!q70.jpg" mode="widthFix" > </image>
mode可以设置图片以什么方式来进行渲染。如果没写的默认按照320px * 240px来显示
widthFix:保持图片原始比例,那一边显示出来,另外等比列缩放
lazy-load:代表图片懒加载。默认情况下图片没有在可视区域。默认懒加载,滚动到可是区域才会加载出来
暂无图片网址(默认图片):https://ww1.sinaimg.cn/large/007rAy9hgy1g24by9t530j30i20i2glm.jpg
text组件
<text class="" decode="{{true}}"> 这是一段文本 消息! > < </text>
decode:代表是否对内容进行解码,true代表解码,false代表不解码
scroll-view
wxml代码
<view class="page-section"> <view class="page-section-title"> <text>Horizontal Scroll\n横向滚动</text> </view> <view class="page-section-spacing"> <scroll-view class="scroll-view_H" scroll-x="true" bindscroll="scroll" style="width: 100%"> <view id="demo1" class="scroll-view-item_H demo-text-1">1</view> <view id="demo2" class="scroll-view-item_H demo-text-2">2</view> <view id="demo3" class="scroll-view-item_H demo-text-3">3</view> </scroll-view> </view> </view>
wxss
.page-section-spacing{ margin-top: 60rpx; } .scroll-view_H{ white-space: nowrap; } .scroll-view-item{ height: 300rpx; } .scroll-view-item_H{ display: inline-block; width: 100%; height: 300rpx; }
二、WXSS
在HTML中写的所有CSS代码在wxss里面都做了筛选和封装。
并不是所有css样式都能在wxss中用
特点:
-
新增了尺寸单位 rpx。是一个相对单位。开发者不用考虑屏幕的大小,用rpx这个单位自适应
-
小程序中提供了全局样式和局部样式。app.wxss属于全局样式。每个页面wxss属于局部样式
-
wxss里面只能支持一部分选择器。
rpx相对单位
可以根据屏幕来自己计算我们元素的尺寸。规定所有的屏幕默认参考的值750px
iphone6这个屏幕的大小:375px 1px = 2rpx 一个真实的像素 等于2个相对单位
iphone6plus
1px = 1.81.rpx
参考主流:
iphone5 320px
1px = 2.34rpx
全局样式和布局样式
全局样式写在app.wxss里面
局部样式写在页面wxss里面
局部样式优先级比全局样式优先级更高。就近原则
常用选择器
wxss在封装css样式的时候,只能支持一部分选择器
选择器 | 样列 | 样列描述 |
---|---|---|
.class | .show | 类选择器,class=show这种属性 |
#id | .idCard | id选择,唯一性 |
element | text、view | 元素选择器 |
element,element | text,view | 选择器分组 |
nth-child(n) | view:nth-child(4){} | 结构选择器 |
:after | view::after | 在 view 组件后边插⼊内容 |
:before | view::before | 在 view 组件前边插⼊内容 |
还有些特殊的样式不支持
*{ //小程序不支持通配符 } .box{ color:red !important //也不支持 }
less的支持
(1)你可以在页面下面创建同名的less文件
.box{ width: 100px; height: 100px; .te{ color:red; border: 1px solid red; } }
(2)需要在插件市场里面搜素下载插件 easy less
(3)在配置项,加入以下代码
搜素easy less 会出现settings.json文件
"less.compile": { "outExt":".wxss" },
三、JS(模板语法)
在小程序中我们模板语法将业务提取js文件中。页面中也可以中{{}}
组件中间要渲染数据还是跟vue一样,动态属性绑定跟vue不一样
数据绑定
(1)数据绑定
在js文件中定义data数据
Page({ data: { message:"hello", user:{ id:1,name:"xiaowang" }, students:[1,2,3] }, })
页面使用数据
<!--pages/cart/cart.wxml--> <text>{{message}}</text> <!-- 字符串输出 --> <text>{{user.name}}</text> <!-- 数组输出 --> <text>{{students}}</text>
(2) 执行简单的运算
<text>{{1+1}}</text> <text>{{1-"1"}}</text> <text>{{age>=18?"成年":"未成年"}}</text>
(3)动态属性
<text class="as-{{index}}">{{index}}</text>
在属性上面那一部分是动态,{{}}
进行动态控制
列表渲染
在页面中进行数据动态渲染循环的方式
最简单的语法
<view wx:for="{{classes}}" class=""> <text>{{item.id}}</text>--<text>{{item.name}}</text>--<text>{{index}}</text> </view>
wx:for进行遍历的时候,默认在循环的这个模块里面,产生一个变量item、index
关于key的绑定
如果你用的item、index这个两个变量来作为key,默认可以不用动态绑定。
如果你用item对象里面属性来作为key,需要动态找到你们属性名字
<view wx:for="{{classes}}" wx:key="index | item | {{item.id}}"> <text>{{item.id}}</text>--<text>{{item.name}}</text>--<text>{{index}}</text> </view>
你可与自定义循环产生的变量
<view wx:for="{{classes}}" wx:for-item="element" wx:for-index="idx" wx:key="idx" > <text>{{element.id}}</text>-- <text>{{element.name}}</text>-- <text>{{idx}}</text> </view>
wx:for-item:可以自己定义循环变量名字
wx 可以自己定义循环下标名字
扩展:
如果你确实不找到用什么来作为key,提供了一个保留关键字 *this
<view class=""> <text wx:for="{{students}}" wx:key="*this"></text> </view>
*this代表当前遍历出来的结果。
block的使用
<block wx:for="{{students}}" wx:key="*this"> <text>{{item}}</text> <text>{{index}}</text> </block>
block相当于vue代码中的template,渲染过程中不会再页面进行加载。空标签。
block身上可以加wx:key
条件渲染
<view wx:if="{{flag==0}}">成都</view> <view wx:elif="{{flag==1}}">重庆</view> <view wx:else>西安</view> <view class="mo" hidden="{{true}}"> 蜗牛孵化园 </view>
hidden属性可以给任何一个组件添加,表示隐藏的意思
默认是给原生新增display属性来决定显示和隐藏。类似于Vue中v-show指令
四、事件绑定
基础语法
小程序中事件绑定通过bind、catch关键字来实现绑定
一般语法:
点击事件 bindtap \ catchtap
-
如何绑定事件,bind和catch区别
-
绑定事件要绑定事件函数,并传递值
基础语法
<button bindtap="check">按钮</button> <button catchtap="check">按钮</button> 在js文件中定义事件函数 Page({ /** * 页面的初始数据 */ data: { }, check(){ console.log(123); }, })
事件传播对象
在页面上绑定事件的时候一定不要在函数后面加括号传递参数
<button bindtap="check(1,2)">按钮</button> // 错误写法
如何要传递参数
<button data-params="123" bindtap="check">按钮</button> check(event){ console.log(event.currentTarget.dataset.params); },
获取页面传递的事件参数,我们需要通过event对象来获取参数
你如果要传递多个参数
<button data-params="xiaowang" data-index="123" bindtap="check">按钮</button>
可以通过event来获取params变量和index变量
事件类型
绑定事件有两种方式bind、catch
bind来绑定事件相当于以前on来绑定事件
catch来绑定事件,默认阻止事件往父节点传递。v-on:click.stop = “”
事件分类:
-
冒泡型事件:当一个组件事件被触发,默认传递给父组件
-
非冒泡型事件:当一个组件事件被触发,默认不会传递给父组件
冒泡型事件(bind):
事件类型 | 事件描述 | 备注 |
---|---|---|
tap | 手指触摸后马上离开 | 相当于click |
longpress | 手指触摸后,超过 350ms 再离开 | 该事件触发后 tap 事件不再触发 |
touchstart | 手指触摸动作开始 | |
touchmove | 手指触摸后移动 | |
touchend | 手指触摸动作结束 | |
touchcancel | 手指触摸动作被打断 | 例如来电提醒、弹窗等 |
非冒泡型事件:
表单事件
事件类型 | 事件描述 | 备注 |
---|---|---|
input | 键盘输入时触发 | event.detail = {value, cursor, keyCode} |
focus | 输入框聚焦时触发 | event.detail = { value, height } |
blur | 输入框失去焦点时触发 | event.detail = {value: value} |
表单获取到内容
<input class="inputborder" bindinput="dataChange" bindfocus="getData" type="text"/>
通过event来获取文本框的值
dataChange(event){ event.detail.value }
小程序04-组件开发
小程序04-组件开发
小程序中组件开发有两类:
-
官方的组件:view、text、image、input等等
-
自定义组件
自定义组件
在项目下面创建components文件夹。里面先创建组件的文件夹
每个组件文件夹下面都要包含4个文件
一定要确保组件Tabs.json文件要设置一个配置
{ "component": true, "usingComponents": {} }
在其他页面中引入这个组件
需要指定的这个页面中注册这个组件
比如cart这个页面中我们引入Tabs组件
{ "usingComponents": { "tabs":"../../components/Tabs/Tabs" } }
引入的组件名字由你们自己来决定。
这句话相当于Vue中
import Tabs from "../../com" components:{ tabs:Tabs }
在页面中可以直接使用tabs来渲染
<tabs></tabs>
更新数据
在小程序中我们如果要进行数据修改,我们需要使用setData来完成
直接修改data中的数据,小程序默认检测不到。小程序底层和Vue还是不一样。
this.setData({ selectedId:id, list:[ { id: 1, name: "热门1" }, { id: 2, name: "排行2" }, { id: 3, name: "热搜3" }, ] })
key一定是data中原始数据。后面提供要修改的数据
父子组件通信
小程序中我们也需要将某些数据定义在父组件,通过传递数据给子组件进行动态更新
父传子
父组件中定义数据,并动态传递给子组件
data:{ mytabs:[ {id:1,name:"WEB"}, {id:2,name:"Java"} ] } <search mytabs="{{mytabs}}"></search>
子组件接受外部数据
子组件js文件中由一个properties属性,相当于VUE中props属性
properties: { mytabs:{ type:Array, value:[] } },
type:接受的数据类型,value代表默认值,当你没有传递内容,使用默认值
子组件页面就渲染
<view>搜素组件</view> <view wx:for="{{mytabs}}"> <text>{{item.id}}---{{item.name}}</text> </view>
子传父
在父组件自定义一个事件,事件名字一定自己定义,不要官方的事件名字
<search mytabs="{{mytabs}}" bindgetchildrendata="getChildrenData"></search> getChildrenData(event){ console.log("parent",event); console.log(event.detail.id); },
父组件要获取到子组件传递过来的数据,默认得到event对象,你需要通过
event.detail.属性
在子组件那边触发自定义事件
methods: { getValue(event){ const id = event.currentTarget.dataset.params // 将这个id传递给父组件。父组件那边需要提供自定义事件 // getchildrendata this.triggerEvent("getchildrendata",{id},{}) } }
triggerEvent
就是我们子组件触发自定义事件的函数。
需要三个参数:
参数1:自定义事件名字
参数2:传递给父组件的数据,一般都是对象。
参数3:参数在传递给过程中修饰内容。主要定义自定义事件是否冒泡或者是否捕获,一般开发都没有设置,默认值。
生命周期
应用的生命周期(整个程序的生命周期)
页面生命周期(pages里面定义的所有wxml都是我们页面
组件生命周期(目前写在components文件夹里面的内容)
应用生命周期
指的就是我们整个小程序的创建和销毁。
App({ // 在生命周期过程中执行一次。 onLaunch() { console.log("小程序初始化"); }, // 每次从后台切换进来 onShow(){ console.log("监听小程序启动"); }, // 每次切换到后台就执行一次 onHide(){ console.log("监听小程序切换到后台"); }, // 全局对象,每个页面都共享,类似于Vuex globalData: { userInfo: null } })
App.js这个文件是我们启动项目马上要加载的一个文件,应用生命周期一般都在这个文件中定义出来
页面生命周期
生命周期 | 参数 | 描述 | 最低版本 |
---|---|---|---|
data | 无 | ⻚⾯的初始数据 | 1.6.3 |
onLoad | 无 | ⽣命周期回调—监听⻚⾯加载 | 1.6.3 |
onShow | 无 | ⽣命周期回调—只要页面切换回来显示就会执行 | 1.6.3 |
onReady | 无 | ⽣命周期回调—监听⻚⾯初次渲染完成 | 1.6.3 |
onHide | 无 | ⽣命周期回调—监听⻚⾯隐藏 | 1.6.3 |
onUnload | ⽣命周期回调—监听⻚⾯卸载 | ||
onPullDownRefresh | 监听⽤⼾下拉动作 | ||
onReachBottom | ⻚⾯上拉触底事件的处理函数 | ||
onShareAppMessage | ⽤⼾点击右上⻆转发 | ||
onResize | ⻚⾯尺⼨改变时触发,详⻅ 响应显⽰区域变化 | ||
onTabItemTap | 当前是 tab ⻚时,点击 tab 时触发 |
页面生命周期存放在我们每个页面js文件中
页面生命周期分为三类:
-
页面从创建显示销毁的过程
-
上拉加载、下拉刷新
-
分享功能
/** * 生命周期函数--监听页面加载 */ onLoad: function (options) { console.log("onLoad页面加载"); }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: function () { console.log("onReady页面初次渲染完成"); }, /** * 生命周期函数--监听页面显示 */ onShow: function () { console.log("onShow页面被加载显示"); }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () { console.log("onHide页面被加载隐藏"); }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { console.log("onUnload页面被卸载"); },
onLoad只有在初始小程序加载才会执行。程序启动了。在你程序没有重新启动的时候,不会重复执行
onShow:用户进入这个页面,至于这个页面挂载完毕没有。这个生命周期无法控制
onReady:页面挂载完毕,如果要操作节点,可以在这个函数中执行
onHide:代表离开了这个页面,tabbar切换,默认是不会销毁页面。
onUnload:代表页面销毁,一般要销毁页面我们可以使用
goto(){ console.log(123); // 路由跳转,redirectTo代表重定向 wx.redirectTo({ url: '/pages/logs/logs', }); },
组件生命周期
组件生命周期函数
生命周期 | 参数 | 描述 | 最低版本 |
---|---|---|---|
created | 无 | 在组件实例刚刚被创建时执行 | 1.6.3 |
attached | 无 | 在组件实例进入页面节点树时执行 | 1.6.3 |
ready | 无 | 在组件在视图层布局完成后执行 | 1.6.3 |
moved | 无 | 在组件实例被移动到节点树另一个位置时执行 | 1.6.3 |
detached | 无 | 在组件实例被从页面节点树移除时执行 | 1.6.3 |
error | Object Error | 每当组件方法抛出错误时执行 | 2.4.1 |
组件常用的三个生命周期
created(){ console.log("created组件正在初始化"); }, ready(){ console.log("ready组件加载完毕"); }, // 一般在页面销毁的时候,组件才会跟着一起销毁 detached(){ console.log("detached"); }
数据监听
在Vue中我们有计算属性和watch。
小程序也可以让我们执行数据监听
observers,就是我们之前用的watch
基础语法
data:{ numberA:10, sum:0 }, observers:{ // 接受到的值默认会以字符串的形式赋值 numberA:function(num1){ this.setData({ sum:num }) } },
你可以指定监控多个
data: { numberA:10, numberB:20, sum:0 }, observers:{ // 接受到的值默认会以字符串的形式赋值 "numberA,numberB":function(num1,num2){ this.setData({ sum:num1+num2 }) } },
还可以监控对象
observers:{ // 接受到的值默认会以字符串的形式赋值 "numberA,numberB":function(num1,num2){ this.setData({ sum:num1+num2 }) }, user:function(){ }, "user.id":function(obj){ // 输出的结果修改过后的值 console.log(obj); }, "user.**":function(){ console.log("user中任何一个属性发生变化都要执行") } },
这个observers只能在组件中使用,页面时没有这个监听器
小程序05-常用的api介绍
小程序05-常用的api介绍
基础
getSystemInfo
这个api主要获取当前小程序运行的系统环境。拿到手机、平台设别名字型号
getSystemInfoData(){ wx.getSystemInfo({ success: (result)=>{ console.log(result); }, fail: ()=>{ console.log("获取失败"); }, complete: ()=>{ console.log("complete"); } }); },
路由
路由跳转是我们开发过程中使用比较多的api
switchTab跳转:
特点:跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。只要你的页面是tabbar页面。只能用这个api
wx.switchTab({ url: '/pages/logs/logs', success: (result)=>{ console.log("result",result); }, fail: ()=>{ console.log("444"); }, complete: ()=>{} });
switchTab只能用于跳转tabBar页面,必须用switchTab来跳转tabbar页面
来跳转
navigateTo跳转:
特点:进行页面跳转的时候,之前的页面继续保留。不能跳转tabbar页面 ,有历史记录
wx.navigateTo({ url: '/pages/logs/logs' // url:"/pages/cart/cart" });
不能使用navigateTo跳转tabbar页面,不然报错。页面跳转最多调10此,10次执行栈
redirectTo跳转:
特点:进行跳转的时候,之前的页面会默认被销毁,跳转后不能返回,没有记录
wx.redirectTo({ url: '/pages/logs/logs' });
默认销毁你当前页面,不会记录历史,不能跳转tabbar页面
请求
微信小程序开发无需再自己下载请求工具,官方已经将请求的数据封装wx的api
fetchData(){ var reqTask = wx.request({ url: 'https://api-hmugo-web.itheima.net/api/public/v1/categories', data: { id:1 }, header: {'content-type':'application/json'}, method: 'GET', dataType: 'json', responseType: 'text', success: (result)=>{ }, fail: ()=>{}, complete: ()=>{} }); },
请求的api就按照官方文档提供的内容来写
如果你是在开发过程中遇到控制台报错:
你的域名并不是合法域名
你要通过小程序发送任何一个异步请求,小程序都会验证这个域名是否能够使用。
在开发过程中只需要勾选,不校验合法域名。
以后项目上线域名必须备案,请求必须https协议
一旦你们项目要打包上线,你们必须将合法的域名配置到小程序后台系统,不需要上线,不用管这个配置
要配置三个地方。多个域名用分号隔开
小程序06-搭建项目
小程序06-搭建项目
一、创建项目目录
components:文件夹代表组件
utils:代表工具包
apis:代表请求
libs:存放第三方的插件,比如地图
pages:存放页面
搭建tabbar样式
"tabBar": { "list": [ { "pagePath": "pages/home/home", "text": "首页", "iconPath": "assets/images/home.png", "selectedIconPath": "assets/images/home-o.png" }, { "pagePath": "pages/category/category", "text": "分类", "iconPath": "assets/images/category.png", "selectedIconPath": "assets/images/category-o.png" }, { "pagePath": "pages/cart/cart", "text": "购物车", "iconPath": "assets/images/cart.png", "selectedIconPath": "assets/images/cart-o.png" }, { "pagePath": "pages/user/user", "text": "我的", "iconPath": "assets/images/my.png", "selectedIconPath": "assets/images/my-o.png" } ] },
二、配置项目的公共样式
app.wxss中可以设置全局样式,任何一个页面都可以使用全局样式
我们可以配置公共样式
/* 给常用的标签设置公共样式 */ page,view,text,navigator,swiper,swiper-item{ margin: 0px; padding: 0px; box-sizing: border-box; } /* 整个页面我们需要设置一个主题色;一旦在这个变化,整个页面中用到这个主主题色的地方都变化 */ page{ /* 在移动端一般默认14px就足够 */ font-size: 28rpx; /* 设置一个主题色,这个颜色做成一个变量 */ --themeColor:#ff7159 }
--themeColor
目前在app.wxss 中只是代表设置了一个变量,并不会马上生效。
三、首页搜索
首页搜索页面并不是真实文本框,默认点击后需要跳转到专门的搜索页面。
需要用到组件
<navigator url="../../pages/user/user">请输入搜索内容</navigator>
navigator在小程序中就相当于超链接a标签。
url地址我们需要默认填写普通页面地址
如果你要跳转进入到tabbar页面
<navigator url="../../pages/user/user" opne-type="navigate">请输入搜索内容</navigator>
open-type代表跳转类型
navigate:跳转采用wx.navigateTo来进行跳转
switchTab:跳转wx.switchTab()
四、搭建后端环境
后端代码目录下面db2文件夹,里面存放的就是数据库的所有数据
保证mongodb服务器已经启动
我的电脑—-右键—-服务
打开navicat工具
创建一个数据库 WNMallWechat。
每个js文件就是一个文档,找到db2这个目录下面所有js,一个一个运行
启动 项目
node app.js
五、请求封装
在项目下面创建apis文件夹,里面创建index.js文件
// 封装我们的请求 const BASEURL = "http://127.0.0.1:4000"; const fecthData = (url,data={},method="GET")=>{ new Promise((resolve,reject)=>{ var reqTask = wx.request({ url: BASEURL + url, data: data, method: method, success: (result)=>{ resolve(result) }, fail: (error)=>{ reject(error) }, }); }) } export default fecthData
上面的代码在封装一个请求方法,以后任何请求都可以通过这个方法来发送
在apis目录下面创建页面对应的请求封装文件,比如homeApi.js
import fecthData from "../index"; // 获取轮播图 export const bannerRequest = (data,method)=>fecthData("/home/swiperdata",data,method) // 获取ICON楼层数据 export const iconRequest = (data,method)=>fecthData("/home/catitems") // 热门数据 export const recommendRequest = ()=>fetchData("/home/floordata")
以后调用fetchData的时候,只需要传递三个参数:
url地址
请求数据
请求方法
六、插槽功能
小程序插槽功能有默认插槽、还有命名插槽。
如果你是默认插槽,可以直接使用,如果你是命名插槽,必须要在组件中开启插槽功能
步骤:
(1)定义一个Tabs组件
<view class="tabs" hover-class="none" hover-stop-propagation="false"> <view wx:for="{{tabs}}" bindtap="changeChoose" data-index="{{index}}" class="item {{item.isActive?'active':''}}"> <text>{{item.title}}</text> </view> </view> <!-- 用于显示我们页面上的动态数据 --> <view class="content"> <!-- 占位 --> <slot></slot> </view>
slot标签就是插槽的占位符号。代表默认插槽。父组件如果传递插槽内容,默认接受不需要任何配置
父组件那边调用
<!-- 商品列表页面需要使用Tabs组件来进行切换 --> <tabs tabs="{{tabs}}" bindchangeIndexTabs="changeIndexTabs"> <view>slot插槽传递销售商品</view> </tabs>
这样就可以将默认view标签传递到tabs组件中使用
(2)命名插槽使用
如果插槽增加了名字,必须在父组件那边指令插槽名字才能实现页面传递
<view class="tabs" hover-class="none" hover-stop-propagation="false"> <view wx:for="{{tabs}}" bindtap="changeChoose" data-index="{{index}}" class="item {{item.isActive?'active':''}}"> <text>{{item.title}}</text> </view> </view> <!-- 用于显示我们页面上的动态数据 --> <view class="content"> <!-- 占位 --> <slot name="myslot"></slot> </view>
slot标签上面name属性就是我们插槽名字。必须指定了这个名字,内容才能在这个插槽显示
父组件
<!-- 商品列表页面需要使用Tabs组件来进行切换 --> <tabs tabs="{{tabs}}" bindchangeIndexTabs="changeIndexTabs"> <view slot="myslot">slot插槽传递销售商品1</view> </tabs>
我们命名插槽默认不生效。必须在Tabs组件中配置开启插槽(在组件中设置)
Component({ // 开启插槽功能 options:{ multipleSlots:true }, /** * 组件的属性列表 */ properties: { tabs:{ type:Array, value:[] } },
options里面设置multipleSlots代表开启插槽。命名插槽才能正确的渲染
七、文本溢出的解决方案
在移动端文本太长需要控制显示内容,可以使用省略号来进行优化
// 这一行文本高度必须设置 height: 66rpx; // 设置BFC超过的部分被隐藏 overflow: hidden; // 设置文本超出过后显示效果 text-overflow: ellipsis; display: -webkit-box; // 文本默认两行,超出两行就会显示。。。 -webkit-line-clamp: 2; -webkit-box-orient: vertical;
八、上拉加载更多下拉刷新页面
生命周期页面提供了两个函数。
-
触底加载
-
下拉触发
/** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { },
上拉加载更多
我们需要在项目中进行分页,默认情况第一次进来获取第一页数据
data: { tabs: [ { id: 1, title: "销售商品", isActive: true }, { id: 2, title: "热门商品", isActive: false } ], productList: [], currentPage: 1, pageSize: 6, totalPage: 0 }, async fecthProductList() { const { currentPage, pageSize } = this.data const res = await goodsRequest({ currentPage, pageSize }) console.log(res); this.setData({ // 将两个数组合并成一个数组 productList: res.data.message, // PC端需要总页码显示出来。不需要显示总页码 3.5 totalPage:res.data.total/this.data.pageSize }) }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { this.fecthProductList() },
默认第一次发送请求currentPage=1,pageSize=6
当我们向下滚动的时候,我们需要触发onReachBottom。
onReachBottom: function () { // 判断当前你的页码 是否已经是最大页码 if(this.data.currentPage >= this.data.totalPage){ // 提示已经到底了,无法加载更多 // duration代表2s后自动消失 wx.showToast({ title: '到底了!', icon: 'success', duration: 2000 }) }else{ this.setData({ currentPage:++this.data.currentPage }) this.fecthProductList() } },
当前页码比总页码要小的时候,就可以发送请求。
拿到下一页的数据,要和之前的数据进行组合,不能覆盖
async fecthProductList() { const { currentPage, pageSize } = this.data const res = await goodsRequest({ currentPage, pageSize }) console.log(res); this.setData({ // 将两个数组合并成一个数组 productList: [...this.data.productList,...res.data.message], // PC端需要总页码显示出来。不需要显示总页码 3.5 totalPage:res.data.total/this.data.pageSize }) },
productList这个数组需要进行合并操作
优化过程,在发送请求的时候,有可能会很慢,我们可以在页面显示一个加载动画
// 封装我们的请求 const BASEURL = "http://127.0.0.1:4000"; const fecthData = (url,data={},method="GET")=>{ wx.showLoading({ title: "加载中..", mask: true, }); return new Promise((resolve,reject)=>{ var reqTask = wx.request({ url: BASEURL + url, data: data, method: method, success: (result)=>{ resolve(result) }, fail: (error)=>{ reject(error) }, complete:()=>{ // 关闭加载效果 wx.hideLoading(); } }); }) } export default fecthData
下拉刷新
onPullDownRefresh: function () { console.log(123); // 如果下拉刷新想要换一批商品,需要后端接口来动态给你们生成新的商品 this.fecthProductList() },
九、加入字体图标
微信官方给我们提供了大量的图标库,但是有些场景可能无法满足我们要求
我们可以引入aliba iconfont字体图标库。
你们自己搜索一个图标,加入购物车,需要在购物车添加到指定项目
如果你没有项目,可以创建一个项目
进入项目中,我们可以查看自己的项目里面有哪些图标
选中font-class,默认生成一个连接。将连接里面的代码复制到app.wxss 中
/* 给常用的标签设置公共样式 */ page,view,text,navigator,swiper,swiper-item{ margin: 0px; padding: 0px; box-sizing: border-box; } /* 整个页面我们需要设置一个主题色;一旦在这个变化,整个页面中用到这个主主题色的地方都变化 */ page{ /* 在移动端一般默认14px就足够 */ font-size: 28rpx; /* 设置一个主题色,这个颜色做成一个变量 */ --themeColor:#ff7159 } /* 从iconfont获取当前项目的在线地址 */ @font-face {font-family: "iconfont"; src: url('//at.alicdn.com/t/font_2262551_tucv4zfh0z.eot?t=1607676372760'); /* IE9 */ src: url('//at.alicdn.com/t/font_2262551_tucv4zfh0z.eot?t=1607676372760#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAgQAAsAAAAAD4gAAAfAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCELgqQOI02ATYCJAMsCxgABCAFhG0HgRgbJg1RlFBSJtnPArvFOUQZ41nKszQ9PY9eZ+j93odVbNqDx2/ZfiaZuruU1iW+2qpO4ei6OvSheAiHcjg3yLt37r0PqKZQ5fmy/GpECn2aXtqUrqEPz8/pKTUhN+HG8//e7t92Agwp4pIksiDgGF+QwH4eN+39pO2uhNSMigs9OvNATSxsDpuDT5R2TJ0ydexEFbZNv5+rQz0Unb+2kDhq+bb5YyaKiDfSCI0jJDFJsMRR7bqURK0sutkEsyJu18bNEFAsCYy2u5d3WDJUIJByqpLDMiVnNXUidYSSEwdy81L7ao8Bvv2fl5+sEji+Qw+83m8LuAj6Pjo9aTcuCTGeFuA3AwUHAOPhWyr5BUJRGyZKZ/MKmHCEl7VIxuMRVnipy4YMvhmcE7WdsaGpVkyYbY+3B0yiPCfuWZjn/OctMMIWivqBgio2gKGOASTUcQBFnQHwqPMADrUHEFHvAAS1DxBQBwAZ/x8Y2IHGPWEKsA/cA72AwnNfH6QbhGofkthqm6eZ7S7ZTdsyD1ghfZx2cyfH7Ybb8WBXYpvszLrWckeu3F5lZ02J4gqDIZimjxCzi+WXQ0jpUb6aSgeER/eqmUEsX92oDmWKGihqKT83pPAqq1DZoPuS0n/FNX5TaPh6yGDgGY2Fej1XpxtliA65UHatnuBqXyTTJjMPXQkskagOyVyL9+u55D1jUCkxZlSbYNoFxMNLEEqQCBnAXMe4I4makhxyoaddJCq3Ln9ZfDnl4lV3qaLu3HP6hfAV/yUlUPOPCo/Q07W5TsPZIoEb7zG1mC0ZiNgbxUCky1UjsarObK5Y7tKg01EJdKFo7Us1kMBE1bezmYiEucjOitTnMwUaL2vlTJj94euemETgMvrKiFoRPbxC40queNmd3Hjhj+fpGAZayjfKhPqhV1Gn2KN8dvk1naAymtZlo2qe5EBFrv07o1KMQPyZMGKWRwCBNxSqBeF1RKNU6FpPjIrc6sVTSoVbw/Rr1Ylk8sPuw5F3Kbb7eko/GilBCMXguzjjhy9ueskiWuTCQApNKOPQNQ9gSCQIr60ePgupRDqhyUXUsRgU0/PFIjeNYQ6byfeVTLnuBlpHrY2Y7NqbkGnQ0tBD0APQ6WgvnGIAUWtLW4QnI2eqauUEeeVC6vrxSs6FjHCMGZX0PfKPn/sgvTg91jONDPAOIKviDen+544sp1SuU2NQg2nABrOD2KDGbYdTtKvL4+xWQfCFlNCS4hGdJmQupgpN1v7+1iYTcVGHy2SQOy9ftzKbrcBz3Wy5rqbWYsG9d2IxQ6azD+cU6WXHW6F1YDlo5yZ4llVvH+QvMpYkZpeuOx9dnLA9cxEHG6QXFXw0VxJBOrIcON/aIaYfE7EcST/S8WuSCFnZR2ZuUvWPIIdfP7VHLWvLC1u3zAA829JS6IS6OMsKkIkTNkI6ei3TPKq8KeR0zede8VPLZV0F0IRt32zdcIN9v5v8LM/FwYcKTGjBduzAmmBuuFRydb56xDwPVVehedi8xsb5KG6AechymXGTjZ4bm/f+WuTnzN58kzPu6DLnGy4riWEhEvdxTiUS6LeA3yCFXR6ZtpPCNRbzddMcTr7mgRqldqYdqK6Seyd5yw/11UR9w/aYsxgE6W3tGQKl1djWJshQXri9LV0Ai2G2O3viDHjfYqvwTz6xmrJSuXwVh4AHV+FTn8cH4F93fdQXt7o/rrQsvm+1hTBIpFrdJ329jwHdr6i6FiALfFJRqQ6UBD4G+HJR7Fm5lwNdSoOt/GwsKzqqLiApcO2u+TVDidaJtUPzwf8W4+LFPYvWLf88ZN3uRVOe0j0rB4M/X74uma7nSkM+2+xkNvax/32TzaBNlf26pvv+H2Pvnli3/LOQXSnMN/t+9pP12iptPbl/+743zGb/H/2/gTQH3CrQYuE9rrO85vECZ32Q1RJNrYxqGbIQD4kukJUPvcn753zAnWiL1/ZhSiXm8Po4FPC4R1CXh3PY6pDshp3g+xYgkljaE0zTspJORZ5CDwHNeVoqa55xBM1pCBUbP+RASQlwgFNSzEGmtLiY8XKKS+7ktvrltjNd9csblvOWc0cd9VibQ96nAABvP9MbuI/ftE4/xyckWjUhG7rnAwDgZ7Hf09obeC74X+skHlFm5mEGIL5TO4FPyxqHSvvsv94h/ruyPvhvhQ63fksb4LQcIHGoQfBVIlpOMI3D4GOMiY33bGl8WnodKOYC+BqZx/yNDydbEx9AOBmTnWiRrJKNPCs6IHuNQ+wnDI+AXOy7P70xzpiQkYE9X4QsRnyTnSF/fC0r+uEbFP2SvSn/vABVpeSiV84XbGzV85pjkDssWH5gSlAvLZVbB6mvWPqWG+Swe32isYp6lsbGG6F77NGMcYjdlplzkklDHbsjB2DbEhsM1ShcVEFw2MRxDeKhkaAO1s4YiHMufYHJHVgVgfTkys469/2vUMlrcaZmSJX1EzIstX4mFUla5N0L+1ZDrqWztVXKONRKzO5tkA5zh1rUmn7CDOXb1ZDgRKoe3sFGjFPJtsJoftg9b87/3DD02ZtyomLiJUiUJFmKNNKSDvI7Lypc9FbkD5jObyve8D7ZiNyIaoMX8tbTeau8lcH78uD7QXk7Oq6czxWnJLPpJJo39k6q23Zpa0KpHBkAAAAA') format('woff2'), url('//at.alicdn.com/t/font_2262551_tucv4zfh0z.woff?t=1607676372760') format('woff'), url('//at.alicdn.com/t/font_2262551_tucv4zfh0z.ttf?t=1607676372760') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ url('//at.alicdn.com/t/font_2262551_tucv4zfh0z.svg?t=1607676372760#iconfont') format('svg'); /* iOS 4.1- */ } .iconfont { font-family: "iconfont" !important; font-size: 16px; font-style: normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-gouwuche:before { content: "\e664"; } .icon-shouye1:before { content: "\e61b"; } .icon-chakan:before { content: "\e613"; } .icon-search1:before { content: "\e601"; } .icon-sousuo:before { content: "\e618"; } .icon-liuliangyunpingtaitubiao02:before { content: "\e688"; } .icon-shouye:before { content: "\e656"; } .icon-search:before { content: "\e612"; } .icon-all:before { content: "\e6ef"; } .icon-all-fill:before { content: "\e777"; }
以后再页面中就使用你刚刚的图标代码
<text class="iconfont icon-gouwuche"></text>
十、微信路由参数传递
再我们小程序中,我们会涉及到页面之间的参数传递
再前一个页面在地址栏加入参数。在后一个页面获取参数
在前一个页面中传递字符串参数
<navigator url="/pages/goods_list/index?cid=123&name=xiaowang" open-type="navigate"> </navigator>
在goods_list页面中获取参数
onLoad(options){ console.log(options) //{cid:123,name:"xiaownag"} }
每个页面onLoad生命周期默认可以接受一个参数对象
十一、本地存储
微信官方对于本地存储功能,提供两种方案
// wx.setStorage({ // key: 'key', // data: "123", // success:()=>{ // console.log(345); // } // }); wx.setStorageSync("key", "123");
setStorage:默认异步的方式来本地存储
setStorageSync:采用同步方式
getStorage:异步获取数据
getStorageSync:同步获取数据
十二、小程序表单组件
复选框
<checkbox-group bindchange="checkChange"> <checkbox value="A" checked="{{false}}" color="var(--themeColor)"/> <checkbox value="B" checked="{{false}}" color="var(--themeColor)"/> </checkbox-group>
在小程序中我们的复选框要放在一个chexkbox-group组件中
一旦里面的原始被选中,checkbox-group有一个change事件,可以获取到你选中的内容
你写过小程序购物车业务:请把小程序购物车业务完整描述一下。
-
我们购物车是在页面中实现的业务。原生小程序默认没有提供watch监听。自己设计了计算总价函数来调用
-
小程序的业务放在components组件中,observers属性,这个属性可以监控购物车数据的变化,动态计算总价
十三、微信授权登录
在移动端我们登录有三种:
-
用户和密码登录:在App端表现
-
微信授权登录:微信特有的一种登录方式。
-
第三方登录。一般也在app端表现
因为微信小程序运行微信端。微信已经授权登录过了,小程序可以直接使用微信授权过后的结果来进行登录。
免去输入用户名和密码。直接用微信的身份来认证。
用户体验比较好。无需输入用户名和密码,比较安全
登录后要得到token身份信息,才能传递token获取数据
实现步骤:
-
在页面布局,把头像显示出来(默认头像),登录成功显示另外一个板块
<view class="container"> <view class="userInfo"> <!-- 没有登录成功的时候,默认的布局样式 --> <block wx:if="{{!hasUserInfo}}"> <image bindtap="login" class="" src="https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210801185911.png" mode="widthFix"> </image> <text>点击头像登录</text> </block> <!-- 登录成功后的布局样式 --> <block wx:else> <image class="" src="" mode="widthFix"> </image> <text>登录后名字</text> </block> </view> </view>
-
给头像绑定点击事件,点击过后,调用微信的api来完成弹框授权
login(){ // 推荐我们使用的api。这个api可以弹框让用户授权 // 在以前微信版本里面 wx.getUserInfo() wx.getUserProfile({ desc:"获取信息用户身份认证", success:(res)=>{ console.log(res); // 保存临时变量,后面登录成功后,需要将userInfo发送你们自己node服务器 // 现在需要分清楚,需要微信服务器,还需要我们自己的后端服务器 const userInfo = res.userInfo }, fail:error=>{ console.log(error); } }) },
-
授权完成后需要发送请求到微信服务器生成一个临时身份凭证(code)(验证微信用户可用)
login(){ // 推荐我们使用的api。这个api可以弹框让用户授权 // 在以前微信版本里面 wx.getUserInfo() wx.getUserProfile({ desc:"获取信息用户身份认证", success:(res)=>{ console.log(res); // 保存临时变量,后面登录成功后,需要将userInfo发送你们自己node服务器 // 现在需要分清楚,需要微信服务器,还需要我们自己的后端服务器 const userInfo = res.userInfo // 沿着给当前微信用户是否能够进行授权登录 wx.login({ success:res=>{ console.log(res); }, fail:error=>{ console.log("当前用户身份无法进行登录"); } }) }, fail:error=>{ console.log(error); } }) },
-
得到code后我们调用自己的服务器(nodejs搭建后台)接口。进行身份验证。生成token
login(){ // 推荐我们使用的api。这个api可以弹框让用户授权 // 在以前微信版本里面 wx.getUserInfo() let _this = this; wx.getUserProfile({ desc:"获取信息用户身份认证", success:(res1)=>{ // 保存临时变量,后面登录成功后,需要将userInfo发送你们自己node服务器 // 现在需要分清楚,需要微信服务器,还需要我们自己的后端服务器 const userInfo = res1.userInfo // 沿着给当前微信用户是否能够进行授权登录 wx.login({ success:res2=>{ wx.request({ url: 'http://47.98.128.191:3001/users/wxLogin', data: { code:res2.code, appId:"wx5c0777d1adf58b52", appSecret:"b3749b028cfb1fe86f3e94de189005fd", userInfo:userInfo }, method: 'POST', success: (result)=>{ //代表登录成功 }, fail: ()=>{ console.log("登录失败"); }, }); }, fail:error=>{ console.log("当前用户身份无法进行登录"); } }) }, fail:error=>{ console.log(error); } }) },
在调用Nodejs(Java)后端接口,我们需要传递参数给服务器。
data:{ code:授权后得到身份凭证, appId:你自己的appId appSecret:开发者自己的appSecret }
查看自己的appId和appSecret
-
前端获取token,保存token。将用户完整信息显示到页面上
wx.request({ url: 'http://47.98.128.191:3001/users/wxLogin', data: { code:res2.code, appId:"wx36e047cbd8d6766d", appSecret:"c5afb12e7ab702332a04fa25c0658b84", userInfo:userInfo }, method: 'POST', success: (result)=>{ console.log(result); wx.setStorageSync("token", result.data.token); // 才会将头像和微信名字显示出来 _this.setData({ userInfo, hasUserInfo:true }) }, fail: ()=>{ console.log("登录失败"); },
success代表成功回调。能够得到后端返回的token信息
将data中userInfo进行赋值操作。
页面上就可以回显用户的头像和名字
每次登录生成code都是不一样
-
以后发送请求从本地存储获取token,接口身份认证
微信小程序授权登录就是利用微信已经登录过的状态,直接进行身份认证。避免了输入用户名和密码
后端接受到登录请求,不需要自己验证这个用户身份,code凭证去微信服务器验证身份。
十四、优化流程
如果用户退出小程序,在token没有过期的情况,又进入到我们系统。
小程序可能会重新加载一遍,之前的登录状态已经消失。token没过期
提高用户体验,保持登录状态。
在token没删除、没有过期的情况,我们应该自动登录
自动登录流程
在App.js 中执行获取userInfo
// 应用生命周期,用户访问小程序的时候,一进来就执行onLaunch App({ onLaunch() { // 验证身份,判断token是否过期,如果token没过期,从服务器获取用户的userInfo const token = wx.getStorageSync("token"); wx.request({ url: 'http://47.98.128.191:3001/users/getUserInfo', // 这个请求头是后端要求我们必须写的名字 header: {'Authorization':token}, method: 'GET', success: (result)=>{ console.log(result); if(result.data.code){ // 将userInfo保存起来 const userInfo = result.data.userInfo // 在进入程序的时候,马上获取userInfo,保存全局对象 this.globalData.userInfo = userInfo } }, fail: (error)=>{ console.log(error); } }); }, // 原生小程序有没有状态机,但是提供globalData globalData: { userInfo: null } })
应用一进来加载的时候,如果获取userInfo成功,就可以不用在登录,如果获取失败用户在重新登录
globalData: { userInfo: null }
存放用户信息,任何一个页面都可以使用globalData
在user.js 中
// getApp这个函数无需引入,直接调用 const app = getApp() /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { const {userInfo} = app.globalData if(userInfo){ this.setData({ userInfo, // 身份已经存在 hasUserInfo:true }) } },
进入页面就获取全局userInfo,获取成功就显示到页面
十五、微信分享
我们在很多小程序都可以点击右上角按钮,进行微信小程序分享。
-
你分享的当前你停留的页面。当你点击分享到好友的时候,将当前页面截图出来,作为分享界面
-
分享功能是否能使用,取决于你页面中是否有onShareAppMessage。通过配置这个函数来决定页面是否能分享
页面分享有两种模式:
-
通过右上角的按钮,弹出分享页面选项,在分享
-
在页面中提供一个按钮,这个按钮可以默认触发分享钩子函数
按钮分享
<button open-type="share">点击按钮分享</button>
点击这个按钮,就可以触发分享函数
onShareAppMessage: function () { console.log(123); }
右上角分享
点击右上角按钮,弹出选项,分享朋友圈(开发模式下暂时无法使用)和分享微信好友。
我们可以自定义分享的信息
onShareAppMessage: function () { return { title:"蜗牛商城", // 分享图片,如果不自己填写图片,默认当前页面截图 path:"/pages/home/home?id=1" } }
还可以自己设置封面
封面可以是本地图片,也可以是网络图片,支持的图片格式 jpg、png
onShareAppMessage: function () { return { title:"蜗牛商城", // 分享图片,如果不自己填写图片,默认当前页面截图 imageUrl:"https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210801234622.jpg", path:"/pages/home/home?id=1" } }
在分享的时候,还可以提供一个promise对象。
onShareAppMessage: function () { const promise = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve({ title:"蜗牛商城2", // 分享图片,如果不自己填写图片,默认当前页面截图 path:"/pages/home/home?id=1" }) },2000) }) return { title:"蜗牛商城", // 分享图片,如果不自己填写图片,默认当前页面截图 imageUrl:"https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210801234622.jpg", path:"/pages/home/home?id=1", promise } }
当你分享的时候,还需要发送网络请求获取后端数据,小程序提供promise对象
请求封装到promise对象,在return里面加载。
只要提供promise。默认的return模板就失效。promise在3s内必须resolve,否则默认用return中的模板
十六、支付流程
微信支付在小程序中使用比较多的功能。
小程序肯定无法使用支付宝。
微信支付要求比较严格,必须要以企业或者个体工商户来进行注册才能使用,个人是无法使用。
开发、测试要求比较严格,支付过程中对于异常信息,监控比较严格。
开发微信支付流程:
(1)打开微信的开发文档,里面有详细的流程
地址为:wx.requestPayment(Object object) | 微信开放文档
(2)打开微信公众平台,先注册企业信息获取到商户号
需要提供官方发布的资料。营业执照、企业组织机构代码证、对公银行账户、法人身份信息
(3)准备开发流程
微信支付需要前端和后端一起配合来实现,有一个完善的流程。这个流程讲解前端和后端需要做的工作
(4)前端的工作流程
-
在购物车页面去结算,生成一个订单,调用商户的服务器(JAVA\PHP)来进行订单的创建,这个时候创建的订单状态为未支付。
-
创建完成了订单后,前端能够接受到后端传递回来的订单信息(后端往微信支付系统进行通信,生成预订单)肯定是加密的信息。
-
调用支付的api(wx.requestPayment)传递后端需要的参数。实现支付功能
wx.requestPayment({ timeStamp: '', nonceStr: '', package: '', signType: 'MD5', paySign: '', success (res) { }, fail (res) { } })
(5)后端的工作流程
-
前端发送请求到后端生成一个订单,这个订单默认保存到后端数据库中,这个时候后端数据库订单状态为(未支付)
-
商家后端服务器马上会发起一个请求-到微信支付系统(这个请求必须商户号)。将这个订单信息传递微信支付系统,微信支付系统会生成预订单信息。返回给商家后端服务器。拿到这个信息后,会进行加密返回给前端。
-
前端就会发起支付,后端来调起微信支付。(前端微信会弹出支付)
-
接下来的公共完全是微信应用和微信支付系统在验证
-
前端输入密码,也是微信和微信支付系统进行验证。
-
支付成功后,微信支付系统会主动调用你们商户后端服务器,告诉后端支付的结果。后端可以更新订单的状态;
-
微信支付状态,用户能够看到支付成功页面(微信默认内置的页面)
完整的流程图
nginx打包配置视频:http://old.woniuxy.com/studentcourse/4046 ( 前端安全面试题也在里面。 )
uniapp01-环境搭建
uniapp01-环境搭建
uniapp是一个跨端开发框架。
一套代码可以在多端运行:编译为小程序、直接小程序中运行。还可以编译App\ 还可以直接在H5端运行
很多时候会使用uniapp这个框架来开发小程序。
一、基本介绍
地址:uni-app官网
uniapp特点:
-
uniapp这个框架核心的语法是vuejs的语法。里面还包含了跟小程序相同很多组件。
-
uniapp底层用到weex框架,这个框架早期是vue推出的移动端框架。
-
一套代码可以运行到很多端(微信小程序、APP、H5端)
-
生态比较完善,有很多完善的UI组件库支持我们做移动端开发
使用uniapp来开发移动端或者小程序比较方便。尤其vue工程师可以无缝切换过来
微信小程序原生开发,目前在企业中也有,还有很大一部分企业选中用uniapp来开发小程序
uniapp也是小程序的框架。最后将uniapp代码自动编译为小程序代码结构
hBuilderX工具也是前端主流的开发者工具。这个工具已经将各种编译环境配置完善。
写好代码,立即可以将代码编译到小程序中运行。也可以直接连接android手机、模拟器进行运行
二、项目创建
借助于HBuilderX工具创建一个uniapp项目
创建项目选中uniapp项目,选中vue2的版本
项目结构:
pages存放页面
static:存放项目静态资源文件
App.vue项目跟组件
main.js项目入口文件
pages.json这个项目的配置项,全局配置
uni.scss全局样式文件
项目中采用vuejs的语法,但是组件和api都是移动端(移动端都是view text代表组件)的这种语法。
创建完成项目后,你可以可以进行编译,运行到指定的平台
运行到浏览器端,就代表我们要将uniapp的代码编译到H5端(手机web端)显示
三、编译到各个平台
uniapp是跨端开发框架,一套代码可以运行到各个平台
H5:手机浏览器,目前我们默认用chrome的模拟器来演示代码。
小程序端:可以直接运行到小程序开发者工具模拟器。
APP端:可以android模拟器,真机。IOS系统
H5端
默认启动项目,打包后打开chrome(默认浏览器)
小程序端
(1)打开项目中的manifest文件,找到微信小程序配置
manifest文件专门配置打包的配置项。里面有各个平台配置
配置了后,我们在小程序开发工具里面才不会是测试账号。
(2)需要打开微信开发者工具,设置我们代理模式
配置服务
等会你们在hbuilderx工具里面打包到小程序运行,自动启动小程序开发者工具。
项目打包运行的时候,要选中微信小程序开发者工具
接下来hbuilder工具就会将我们代码打包为小程序的代码,放在小程序模拟器里面运行
APP端
要将代码打包成app安装包,有两种,一种android的安装,ios的安装包。
上课更多还是android为准。
开发过程中测试你的代码是否能够正常运行,
-
安装android模拟器,夜神模拟器
-
真机测试
(1)模拟器环境:
你们先要在电脑上面安装模拟器。目前我的电脑支持夜神模拟器
在电脑上面启动android模拟器
(2)打包运行代码
运行android系统:
运行到ios系统
基座:相当于在android或ios系统中创建一个壳子,这个壳子里面可以运行你们的前端代码。手机里面的应用程序安装后有图标,这就称为基座/
打开ADB配置,将模拟器端口号配置进去(如果能默认找到62001,可以不用配置)
夜神模拟器,默认就是62001端口。等会自己找62001这个夜神程序
你们直接运行,代码就会打包到夜神模拟器运行。
不是所有同学的电脑都能用夜神模拟器。如果你用不了这个模拟器。换一个模拟器
真机环境
android调试
-
手机要用数据线连接电脑。驱动要安装成功。推荐要用原配的数据线
-
在手机上开启调试模式,之前以华为为例子
找到版本号:连续点击多次 ,至少5次 提示你已经打开了开发者模式 3. 在设置页面中,搜索“开发人员选项” 4. 打开开发者选项,USB调试必须打开 <img src="https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210804214211.png" alt="image-2" style="zoom:33%;" /> 5. 手机连接电脑的时候,提示你连接方式 选中传输文件。(仅充电) ios环境 有些通过是苹果手机 我们需要在电脑上面下载iTunes工具,手机接入数据线连接itunes。 你们在hbuilder工具里面。 运行代码的时候,寻找一下设备
uniapp02-基础语法
uniapp02-基础语法
一、uniapp中我们基础语法
uniapp底层是基于vue的很多内容来开发的,所以我们基础语法全是vue的语法
目前模板语法和vue是一样的用法
<script> export default { data() { return { title: 'Hello', username:"小王" } }, onLoad() { }, methods: { check(){ console.log(123) } } } </script>
页面获取
<template> <text>{{username}}</text> </template>
vue中我们能够使用的语法,目前在uniapp中基本上都可以直接照搬过来
二、uniapp样式
(1)rpx单位
尺寸范围:提供了rpx这个相对单位
比iphone6为例子。1px = 2rpx
按照屏幕宽度750px来作为参考
App 端,在 pages.json 里的 titleNView 或页面里写的 plus api 中涉及的单位,只支持 px。注意此时不支持 rpx
App端还是可以用rpx,但是在某些环境下他不支持rpx,在配置文件下面pages.json
(2)在uniapp中要使用scss
<style lang="scss" scoped> </style>
要在uniapp中能够使用scss来开发,我们需要在工具中安装费scss插件
(3)引入外部样式
<style> @import "../../assets/styles/demo.css"; </style>
(4)uniapp里面能够支持的选择器
选择器 | 样例 | 样例描述 |
---|---|---|
.class | .intro | 选择所有拥有 class=”intro” 的组件 |
#id | #firstname | 选择拥有 id=”firstname” 的组件 |
element | view | 选择所有 view 组件 |
element, element | view, checkbox | 选择所有文档的 view 组件和所有的 checkbox 组件 |
::after | view::after | 在 view 组件后边插入内容,仅 vue 页面生效 |
::before | view::before | 在 view 组件前边插入内容,仅 vue 页面生效 |
uniapp也不支持*通配符。也不能!important
以后你要给页面设置样式
page{ backgroud-color:"red" }
(5)全局样式和局部样式
uni.scss这是一个全局的scss文件,任何一个页面都可以直接使用里面的css代码
局部样式就每个页面自己的style标签
uniapp03-组件的使用
uniapp03-组件的使用
uniapp中组件分为两类
-
官方定义好的组件,直接用于布局
-
自定义组件。用户自己封装的组件
一、官方组件
:文本标签,可以存放项目中文字
: 布局标签,可以用于布局我们页面
:轮播图标签
:滑动模块标签
:文本框标签
: 复选框标签
:单选框标签
二、自定义组件
自定义组件规则
(1)需要项目中创建components文件夹
在里面创建对应的组件。
(2)再指定的页面中,可以直接使用我们组件
项目中无需引入,也无需注册可以直接使用components目录下面的组件
<template> <view> <text>这是我们页面</text> <Header></Header> </view> </template> <script> // import Header from "../../components/Header/Header" export default { // components:{ // Header // }, data() { return { }; } } </script> <style lang="scss"> </style>
uniapp内部已经全局引入的components文件夹里面的内容,无需再自己引入
elementUI这个框架,全局引入。任何一个页面el-table直接使用
自定义组件通信
父组件传递值给组件
<Header total="12" :username="username" @changeUserNameByParent="changeUserNameByParent"></Header>
子组件接受内容并调用自定义事件
<template> <view> Header--{{username}}---{{total}} <button @click="changeUsername">修改username</button> </view> </template> <script> export default { name:"Header", props:["username","total"], data() { return { }; }, methods:{ changeUsername(){ this.$emit("changeUserNameByParent","xiaofeifei") } } } </script> <style lang="scss"> </style>
uniapp04-全局配置和局部配置
uniapp04-全局配置和局部配置
只要是移动端开发,我们都会涉及到全局的样式配置、全局的公共代码配置
全局配置
基础配置
项目目录下面page.json文件。这个文件就是全局配置文件
{ "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages { "path" : "pages/home/home", "style" : { "navigationBarTitleText": "", "enablePullDownRefresh": false } }, { "path": "pages/index/index", "style": { "navigationBarTitleText": "uni-app" } } ], "globalStyle": { "navigationBarTextStyle": "black", "navigationBarTitleText": "uni-app", "navigationBarBackgroundColor": "#F8F8F8", "backgroundColor": "#F8F8F8" }, "uniIdRouter": {} }
pages就是路由:路由地址都需要注册到这里面才能跳转
globalStyle:全局的样式配置
tabbar配置
{ "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages { "path" : "pages/home/home", "style" : { "navigationBarTitleText": "首页", "navigationBarBackgroundColor": "#F7F7F7", "navigationBarTextStyle": "black", "enablePullDownRefresh": false } }, { "path": "pages/index/index", "style": { "navigationBarTitleText": "日志" } } ], "globalStyle": { "navigationBarTextStyle": "white", "navigationBarTitleText": "uni-app", "navigationBarBackgroundColor": "#000", "backgroundColor": "#F8F8F8" }, "tabBar":{ "list":[ { "pagePath":"pages/home/home", "text":"首页", "iconPath":"./static/home.png", "selectedIconPath":"./static/home-o.png" }, { "pagePath":"pages/index/index", "text":"日志", "iconPath":"./static/logo.png", "selectedIconPath":"./static/logo.png" } ] }, "uniIdRouter": {} }
分包加载
关于小程序开发的时候,我们对开发项目打包过后的文件大小有限制
小程序:每个包开发完成打包压缩,不能超过2M,超过2M无法上线
小程序提供和uniapp提供了分包机制。可以将你们代码分为10个包,每个包最多2M
整个小程序项目可以支持20M大小。
一般都要求,项目中静态资源文件,尽量不要放在本地,尤其图片
app端无需分包,app端对项目大小没有限制,你们写多少打包多少。
分包流程:
(1)在项目中创建pagesA代表第一个分包,pagesB代表第二个分包
默认pages目录代表主包,一般我们tabbar对应的页面必须放在主包中。
其他页面可以放在对应子包
┌─pages │ ├─index │ │ └─index.vue │ └─login │ └─login.vue ├─pagesA │ ├─static │ └─list │ └─list.vue ├─pagesB │ ├─static │ └─detail │ └─detail.vue ├─static ├─main.js ├─App.vue ├─manifest.json └─pages.json
每个子包都可以由自己的静态资源。
保证最外面static存放的主包要用资源
分包的好处: 以后我们小程序初始化的时候,默认只加载主包。跟tabbar相关的页面。子包默认不会加载。
以后如果你跳转到子包的页面。加载这个子包。子包加载过一次,下次就直接使用
子包的路由
"subPackages": [{ "root": "pagesA", "pages": [{ "path": "list/list", "style": { "navigationBarTitleText": "产品" } }] }, { "root": "pagesB", "pages": [{ "path": "detail/detail", "style": { "navigationBarTitleText": "详情" } }] }],
分包过后,子包加载问题
分包后,小程序默认加载主包的内容,子包内容需要访问的时候才会加载
子包预加载:主包加载完毕后,子包可以先加载(不影响我们主包的速度)
"preloadRule": { "pagesA/list/list": { "network": "all", "packages": ["__APP__"] }, "pagesB/detail/detail": { "network": "wifi", "packages": ["pagesA"] } },
network:代表网络,指定当前这个包在那种网络环境下进行加载
packages:当前这个包在哪个时机进行加载。一般我们可以配置root
或 name
。__APP__
表示主包。
__APP__
代表主包加载完毕
pagesA:代表pagesA这个包加载完毕后再加载当前这个包
uniapp05-生命周期函数
uniapp05-生命周期函数
应用的生命周期
我们主要常用的就三个
onLaunch:小程序应用启动的时候我们就可以执行的生命周期函数
onShow:进入小程序的默认执行一次
onHide:退出小程序,开启后台运行
页面生命周期
onLoad(){ console.log("onLoad"); }, onShow(){ console.log("page onShow"); }, onReady(){ console.log("onReady"); }, onHide(){ console.log("page onHide"); }, onUnload(){ },
生命周期函数再uniapp中和原生小城一模一样
onLoad页面加载
onShow页面显示,每次进来都会执行一次
onReady代表页面初次加载,如果页面没有销毁,这个方法只会执行一次
onHide:页面隐藏,离开这个页面,去到其他页面
onUnload页面卸载的收会执行
组件生命周期
组件生命周期就是采用vue的生命周期函数
created:初始化完毕
mouted:挂载完毕
updated:更新完毕
destoryed:卸载完毕等
uniapp06-网络请求
uniapp06-网络请求
一、基础请求
网络请求需要通过uni来进行发送
基本代码如下
methods: { sendMessage(){ console.log(13); uni.request({ url:"https://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata", method:"GET", success(res){ console.log("succes",res); }, fail(error){ console.log("error",error); } }) } }
二、请求封装
(1)在项目下面创建apis文件夹,里面创建http.js 文件
const BASEURL = "https://api-hmugo-web.itheima.net/api/public/v1/" export default function fetchData(url,data={},method="GET"){ return new Promise((resolve,reject)=>{ uni.request({ url:BASEURL + url, method, data, success(res){ resolve(res) }, fail(error){ reject(error) } }) }) }
fetchData函数就是我们发送请求的公共方法
(2)在apis文件夹下面创建对应js文件来管理不同请求
比如个人中。userApi.js文件
import fecthData from "./http" // 发送请求获取个人信息 // 微信登录的请求 /user/login const wxLogin = ()=>{ return fecthData("/home/swiperdata") } // 获取用户信息 getUserInfo const getUserInfo = ()=>{ retrn fecthData("/home/swiperdata") } export default { wxLogin,getUserInfo }
定义多个函数,多个函数可以放在一个对象中,统一暴露出去。
以后可以引入这个文件,得到一个对象,对象里面包含所有的请求方法
(3)将每个模块的请求,合并为一个文件
我们项目会用很多模块,每个模块都有自己请求文件,比如userApi.js\productApi.js等等
在apis文件夹下面创建apis.js文件
import user from "./userApi" import product from "./productApi" export default { user,product }
暴露一个统一的对象,这个对象user就代表user模块,product代表productApi模块
(4)在main.js中引入你们请求,全局挂载
import App from './App' // 条件编译:uniapp可以支持多端运行,有可能某一端无法运行这段代码。针对不同的端、不同版本,提供不同的代码 // #ifndef VUE3 import Vue from 'vue' import $apis from "./apis/apis" Vue.config.productionTip = false // 将$apis挂载到Vue的原型上面,以后任何一个页面都可以使用 Vue.prototype.$api = $apis App.mpType = 'app' const app = new Vue({ ...App }) app.$mount() // #endif
(5)页面使用这个全局挂载的请求
<script> export default { data() { return { } }, methods: { async sendMessage(){ console.log(this.$api); const res = await this.$api.user.getUserInfo() console.log(res) } } } </script>
以后任何页面、任何组件无需引入$api.可以通过this直接调用
思考:每次新增了模块,创建api文件。每个api文件都需要合并在同一暴露,能否自动合并
uniapp07-vuex搭建
uniapp07-vuex搭建
在原生小程序开发过程中,我们没有提供状态机。但是默认提供glabalData,全局对象
在globalData定义数据,在任何一个页面或者组件中要使用globalData
const app = getApp()
getApp是小程序内置的方法,可以得到app.js里面所有的内容包括global
app.globalData
在uniapp中我们不会globalData,vuex也是默认支持的
uniapp数据管理会更加完善
uniapp中vuex无需下载,默认已经内置了vuex
需要搭建vuex的运行环境
开发步骤:
(1)在项目中创建store文件夹,里面index.js文件,这个就是vuex仓库
import Vue from "vue" import Vuex from "vuex" // 挂载vuex Vue.use(Vuex) import userModule from "./modules/userModule" export default new Vuex.Store({ state:{ count:10 }, modules:{ userModule } })
你需要自己在store文件夹里面创建modules文件夹,里面涉及userModule文件
export default { namespaced:true, state:{ user:{ id:1,name:"xiaowang" } }, mutations:{ changeUsername(state,newName){ state.user.name = newName } }, actions:{ asyncChangeusername(context,newName){ setTimeout(()=>{ // context.state.user.name = newName // 修改数据的唯一方案,mutations函数来修改 context.commit("changeUsername",newName) },1000) } } }
(2)我们就需要在main.js文件中加载store仓库
import App from './App' // 条件编译:uniapp可以支持多端运行,有可能某一端无法运行这段代码。针对不同的端、不同版本,提供不同的代码 // #ifndef VUE3 import Vue from 'vue' import $apis from "./apis/apis" import store from "./store/index.js" Vue.config.productionTip = false // 将$apis挂载到Vue的原型上面,以后任何一个页面都可以使用 Vue.prototype.$api = $apis App.mpType = 'app' const app = new Vue({ ...App, store }) app.$mount() // #endif
(3)页面中使用仓库
<script> // import {mapState} from "vuex" import {createNamespacedHelpers} from "vuex" const {mapState,mapMutations} = createNamespacedHelpers("userModule") export default { data() { return { } }, computed:{ ...mapState(["user"]) }, methods: { ...mapMutations(["changeUsername"]), } } </script>
主仓库的数据引入vuex,们获取mapState
createNamespacedHelpers获取子仓库
uniapp 中授权,是哪个api ?
wx.getUserProfile()
uni.getUserProfile()
uniapp08-UI组件库
uniapp08-UI组件库-uview
在开发uniapp的时候,可以使用官方提供的组件,包括文本框、轮播图等等。
但是如果需要一些特殊的组件,我们可以选中去插件实现下载对应的组件。
Vue+ElementUI进行页面
uniapp也可以结合其他的UI组件库来帮助我们。vantui、uview
目前在我们uniapp中使用比较多的组件库uview
一、uview使用加载步骤
在插件市场里面找到uview这个ui组件库
找到uview的插件地址
绿色按钮,代表可以直接uview插件导入到目前存在的项目中。
蓝色按钮,将官方的demo代码在本地创建一份。你可以参考官方的代码
安装成功你们项目中会多出来uni_modules文件夹,uview-ui这样一个插件
如果通过插件市场,无法将uview下载安装到项目中。手动将umi_modules到项目中
二、配置入口文件
在项目main.js文件中引入对应的组件,启动项目就加载这uview
// main.js import uView from '@/uni_modules/uview-ui' Vue.use(uView)
三、配置样式
在uni.scss文件中引入我们全局样式
/* uni.scss */ @import '@/uni_modules/uview-ui/theme.scss';
四、在App.vue文件中引入样式
<style lang="scss"> /* 注意要写在第一行,同时给style标签加入lang="scss"属性 */ @import "@/uni_modules/uview-ui/index.scss"; </style>
uniapp08-UI组件库-vant ui( uni 项目)
-
下载 vantui 的 ui 库:https://github.com/youzan/vant-weapp
-
将 ui 库中的 dist 目录下的组件,拷贝到项目根目录下
/wxcomponents
-
组件的注册:
uniapp 注册第三方组件是在 pages.json
文件中的 globalStyle
属性下完成的,我们只需要在这里面添加 usingComponent
来注册就可以了,注册组件的路径应该是刚刚导入的那个路径;
//pages.json中 { "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages { "path": "pages/movies/movies", "style": { "navigationBarTitleText": "", "enablePullDownRefresh": false } }, { "path": "pages/hello/hello", "style": { "navigationBarTitleText": "", "enablePullDownRefresh": false } }, { "path": "pages/index/index", "style": { "navigationBarTitleText": "uni-app" } }, { "path": "pages/studios/studios", "style": { "navigationBarTitleText": "", "enablePullDownRefresh": false } }, { "path": "pages/mine/mine", "style": { "navigationBarTitleText": "", "enablePullDownRefresh": false } }, { "path": "pages/more/more", "style": { "navigationBarTitleText": "", "enablePullDownRefresh": false } } ], "globalStyle": { "navigationBarTextStyle": "black", "navigationBarTitleText": "uni-app", "navigationBarBackgroundColor": "#F8F8F8", "backgroundColor": "#F8F8F8", // 组件的注册 "usingComponents": { "van-button": "/wxcomponents/vant/button/index", "van-grid": "/wxcomponents/vant/grid/index", "van-grid-item": "/wxcomponents/vant/grid-item/index" } }, "tabBar": { "borderStyle": "white", "list": [{ "pagePath": "pages/movies/movies", "text": "电影", "iconPath": "/static/tab/_movie.png", "selectedIconPath": "/static/tab/movie.png" }, { "pagePath": "pages/studios/studios", "text": "影院", "iconPath": "/static/tab/_studio.png", "selectedIconPath": "/static/tab/studio.png" }, { "pagePath": "pages/mine/mine", "text": "我的", "iconPath": "/static/tab/_mine.png", "selectedIconPath": "/static/tab/mine.png" } ] } }
-
组件的使用:
vantui 组件的使用方法应该遵循 vue 的语法规则,uniapp 会自动将这些语法,转换成原生小程序的语法;
<template> <van-grid column-num="3" :border="true"> <van-grid-item use-slot v-for="(item, index) in rows" :key="index" > <movieItem :movie="item"></movieItem> </van-grid-item> </van-grid> </template> <script> import {mapGetters} from "vuex" export default { data() { return { } }, methods: { }, computed: mapGetters(["rows"]) } </script>
Vue响应式原理
Vue响应式原理
一、研究Vue对象本身
创建一个文件夹(项目),初始化这个文件夹
npm init -y
执行了项目初始化,我们才能npm下载我们第三方的包。
必须保证我们项目中有package.json这个文件,我们才能在这个项目中下载第三方包。
下载vue
npm install vue@2.6.10
需要了解到一些特定:
$el:获取到当前这个vue对象根节点
$data:获取到vue中的data数据
$parents:当前这个组件的父组件
$children:当前这个组件的所有子组件
_vnode:抽象出来的虚拟dom对象
打印出来的内容
Vue $attrs: (...) $children: [] $createElement: ƒ (a, b, c, d) $el: div#app $listeners: (...) $options: {components: {…}, directives: {…}, filters: {…}, el: "#app", _base: ƒ, …} $parent: undefined $refs: {} $root: Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …} $scopedSlots: {} $slots: {} $vnode: undefined password: (...) username: (...) _renderProxy: Proxy {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …} _self: Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …} _staticTrees: null _uid: 0 _vnode: VNode {tag: "div", data: {…}, children: Array(1), text: undefined, elm: div#app, …} _watcher: Watcher {vm: Vue, deep: false, user: false, lazy: false, sync: false, …} _watchers: [Watcher] $data: (...) $isServer: (...) $props: (...)
二、创建vuejs文件实现数据劫持
ES5里面JS提供了一个Object.defineProperty方法
可以对你指定的对象进行一个数据劫持。
当你操作指定对象的时候,我能检测到修改的内容,
当你在页面使用我的对象属性的时候。我能检测到被使用了
数据劫持
<script> const user = { username:"xiaowang", password:123 } let username = user.username // 好好研究一下Object这个对象里有哪些方法 Object.defineProperty(user,"username",{ // 监控是否使用 get(){ console.log("使用了user.username"); return username }, // 监控是否修改 set(val){ console.log("设置user.username的值"); console.log(val); } }) console.log(user.username) user.username = "xiaofeifei" console.log(user) </script>
Object.defineProperty接受三个参数
-
监控的对象
-
监控属性
-
执行get和set
Vue底层默认就是采用数据劫持的方式来上实现数据变化,驱动dom的变化
三、对象属性Reactive化
我们上面的代码可以实现数据劫持,但是仅仅只能针对一个属性。
<script> const user = { username: "xiaowang", password: 123 } // 封装一个函数,这个函数负责数据劫持 function defineReactive(data, key, value) { Object.defineProperty(data, key, { // 监控是否使用 get() { console.log(`${data}的${key}被使用`); return value }, // 监控是否修val改 set(val) { console.log(`${data}的${key}被修改`); value = val } }) } // 以后只要有一个属性要被接触,调用一下这个函数 // defineReactive(user,"username",user.username) // console.log(user.username); // user.username = "xiaofeifei" // 根据user的key来进行循环。 Object.keys(user).forEach(key=>{ defineReactive(user,key,user[key]) }) console.log(user.password); console.log(user.username); </script>
我们需要用到一个Object.,keys来获取所有对象的key。进行遍历。
四、Vue的数据劫持
定义Observer类完成data数据劫持
在自己的vuejs文件中进行数据劫持的类定义
满足一个单一职责:一个类做一件事,一个函数实现一个业务
// Vue提供了一个Observer类来进行数据劫持 // 这个类主要就是针对我们页面Vue对象中data进行数据劫持 class Observer { constructor(data) { this.data = data this.walk() } // 这个方法就是针对你传递进来的data进行数据劫持 defineReactive(data,key,value) { Object.defineProperty(data, key, { get() { console.log(`${data}对象的${key}属性被调用`); return value }, set(val) { console.log(`${data}对象的${key}属性被赋值`); value = val } }) } walk(){ Object.keys(this.data).forEach(key => { this.defineReactive(this.data,key,this.data[key]) }); } } const user = { username:"xiaowang" } new Observer(user) console.log(user.username)
五、Vue对象创建
我们在页面中要使用vue,需要引入vue对象,创建这个对象
const app = new Vue({ el:"#app", data(){ return{ username:"xiaowang" } } })
接下来要定义好Vue类
class Vue{ // 创建Vue对象的时候,传递进来的对象 constructor(options){ this.$options = options this.$data = options.data() this.$el = options.el // 针对$data这个对象里所有属性进行数据劫持 new Observer(this.$data) // 针对Vue对象上面的属性进行劫持 this.proxy() } // 需要将传递进来$data数据,绑定到Vue对象身上 // this.username // this.$data.username // Vue对象身上默认会有属性,Vue里$data也会有属性 proxy(){ Object.keys(this.$data).forEach(key=>{ Object.defineProperty(this,key,{ get(){ return this.$data[key] }, set(val){ this.$data[key] = val } }) }) } }
核心思想,接受创建Vue的时候传递进来的对象。
针对$data进行数据劫持
针对 Vue身上的属性进行数据劫持
六、模板编译
将你们Vue对象中定义好的数据,渲染到页面模板上
默认Vue采用的mastache语法{{}}
// 模板编译代码 // 专门用于模板编译 class Complier{ // 获取到Vue根节点,data // $el:"#app" constructor(el,data){ this.$el = document.querySelector(el) this.$data = data this.complier() } complier(){ // this.$el.children.forEach(item=>{ // console.log(item) // }) // 遍历所有的子标签,寻找子标签中间有{{}} [...this.$el.children].forEach(item=>{ if(/\{\{([a-zA-Z0-9]+)\}\}/.test(item.innerHTML)){ // RegExp.$1 代表获取到 正则表达式第一个 ()里面文本 const key = RegExp.$1.trim() console.log(key); item.innerHTML = this.$data[key] } }) } }
完整代码
// Vue提供了一个Observer类来进行数据劫持 // 这个类主要就是针对我们页面Vue对象中data进行数据劫持 class Observer { constructor(data) { this.data = data this.walk() } // 这个方法就是针对你传递进来的data进行数据劫持 defineReactive(data,key,value) { Object.defineProperty(data, key, { get() { console.log(`${data}对象的${key}属性被调用`); return value }, set(val) { console.log(`${data}对象的${key}属性被赋值`); value = val } }) } walk(){ Object.keys(this.data).forEach(key => { this.defineReactive(this.data,key,this.data[key]) }); } } class Vue{ // 创建Vue对象的时候,传递进来的对象 constructor(options){ this.$options = options this.$data = options.data() this.$el = options.el // 针对$data这个对象里所有属性进行数据劫持 new Observer(this.$data) // 针对Vue对象上面的属性进行劫持 this.proxy() // 实现模板编译,显示数据 new Complier(this.$el,this.$data) } // 需要将传递进来$data数据,绑定到Vue对象身上 // this.username // this.$data.username // Vue对象身上默认会有属性,Vue里$data也会有属性 proxy(){ Object.keys(this.$data).forEach(key=>{ Object.defineProperty(this,key,{ get(){ return this.$data[key] }, set(val){ this.$data[key] = val } }) }) } } // 模板编译代码 // 专门用于模板编译 class Complier{ // 获取到Vue根节点,data // $el:"#app" constructor(el,data){ this.$el = document.querySelector(el) this.$data = data this.complier() } complier(){ // this.$el.children.forEach(item=>{ // console.log(item) // }) // 遍历所有的子标签,寻找子标签中间有{{}} [...this.$el.children].forEach(item=>{ if(/\{\{([a-zA-Z0-9]+)\}\}/.test(item.innerHTML)){ // RegExp.$1 代表获取到 正则表达式第一个 ()里面文本 const key = RegExp.$1.trim() console.log(key); item.innerHTML = this.$data[key] } }) } }
Vue响应式原理(二)
Vue响应式原理
一、研究Vue对象本身
创建一个文件夹(项目),初始化这个文件夹
npm init -y
执行了项目初始化,我们才能npm下载我们第三方的包。
必须保证我们项目中有package.json这个文件,我们才能在这个项目中下载第三方包。
下载vue
npm install vue@2.6.10
需要了解到一些特定:
$el:获取到当前这个vue对象根节点
$data:获取到vue中的data数据
$parent:当前这个组件的父组件
$children:当前这个组件的所有子组件
_vnode:抽象出来的虚拟dom对象
打印出来的内容
Vue $attrs: (...) $children: [] $createElement: ƒ (a, b, c, d) $el: div#app $listeners: (...) $options: {components: {…}, directives: {…}, filters: {…}, el: "#app", _base: ƒ, …} $parent: undefined $refs: {} $root: Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …} $scopedSlots: {} $slots: {} $vnode: undefined password: (...) username: (...) _renderProxy: Proxy {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …} _self: Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …} _staticTrees: null _uid: 0 _vnode: VNode {tag: "div", data: {…}, children: Array(1), text: undefined, elm: div#app, …} _watcher: Watcher {vm: Vue, deep: false, user: false, lazy: false, sync: false, …} _watchers: [Watcher] $data: (...) $isServer: (...) $props: (...)
二、创建vuejs文件实现数据劫持
ES5里面JS提供了一个Object.defineProperty方法
可以对你指定的对象进行一个数据劫持。
当你操作指定对象的时候,我能检测到修改的内容,
当你在页面使用我的对象属性的时候。我能检测到被使用了
数据劫持
<script> const user = { username:"xiaowang", password:123 } let username = user.username // 好好研究一下Object这个对象里有哪些方法 Object.defineProperty(user,"username",{ // 监控是否使用 get(){ console.log("使用了user.username"); return username }, // 监控是否修改 set(val){ console.log("设置user.username的值"); console.log(val); } }) console.log(user.username) user.username = "xiaofeifei" console.log(user) </script>
Object.defineProperty接受三个参数
-
监控的对象
-
监控属性
-
执行get和set
Vue底层默认就是采用数据劫持的方式来上实现数据变化,驱动dom的变化
三、对象属性Reactive化
我们上面的代码可以实现数据劫持,但是仅仅只能针对一个属性。
<script> const user = { username: "xiaowang", password: 123 } // 封装一个函数,这个函数负责数据劫持 function defineReactive(data, key, value) { Object.defineProperty(data, key, { // 监控是否使用 get() { console.log(`${data}的${key}被使用`); return value }, // 监控是否修val改 set(val) { console.log(`${data}的${key}被修改`); value = val } }) } // 以后只要有一个属性要被接触,调用一下这个函数 // defineReactive(user,"username",user.username) // console.log(user.username); // user.username = "xiaofeifei" // 根据user的key来进行循环。 Object.keys(user).forEach(key=>{ defineReactive(user,key,user[key]) }) console.log(user.password); console.log(user.username); </script>
我们需要用到一个Object.,keys来获取所有对象的key。进行遍历。
四、Vue的数据劫持
定义Observer类完成data数据劫持
在自己的vuejs文件中进行数据劫持的类定义
满足一个单一职责:一个类做一件事,一个函数实现一个业务
// Vue提供了一个Observer类来进行数据劫持 // 这个类主要就是针对我们页面Vue对象中data进行数据劫持 class Observer { constructor(data) { this.data = data this.walk() } // 这个方法就是针对你传递进来的data进行数据劫持 defineReactive(data,key,value) { Object.defineProperty(data, key, { get() { console.log(`${data}对象的${key}属性被调用`); return value }, set(val) { console.log(`${data}对象的${key}属性被赋值`); value = val } }) } walk(){ Object.keys(this.data).forEach(key => { this.defineReactive(this.data,key,this.data[key]) }); } } const user = { username:"xiaowang" } new Observer(user) console.log(user.username)
五、Vue对象创建
我们在页面中要使用vue,需要引入vue对象,创建这个对象
const app = new Vue({ el:"#app", data(){ return{ username:"xiaowang" } } })
接下来要定义好Vue类
class Vue{ // 创建Vue对象的时候,传递进来的对象 constructor(options){ this.$options = options this.$data = options.data() this.$el = options.el // 针对$data这个对象里所有属性进行数据劫持 new Observer(this.$data) // 针对Vue对象上面的属性进行劫持 this.proxy() } // 需要将传递进来$data数据,绑定到Vue对象身上 // this.username // this.$data.username // Vue对象身上默认会有属性,Vue里$data也会有属性 proxy(){ Object.keys(this.$data).forEach(key=>{ Object.defineProperty(this,key,{ get(){ return this.$data[key] }, set(val){ this.$data[key] = val } }) }) } }
核心思想,接受创建Vue的时候传递进来的对象。
针对$data进行数据劫持
针对 Vue身上的属性进行数据劫持
六、模板编译
将你们Vue对象中定义好的数据,渲染到页面模板上
默认Vue采用的mastache语法{{}}
// 模板编译代码 // 专门用于模板编译 class Complier{ // 获取到Vue根节点,data // $el:"#app" constructor(el,data){ // #app document.querySelector("#app") this.$el = document.querySelector(el) this.$data = data this.complier() } complier(){ // this.$el.children.forEach(item=>{ // console.log(item) // }) // 遍历所有的子标签,寻找子标签中间有{{}} [...this.$el.children].forEach(item=>{ if(/\{\{([a-zA-Z0-9]+)\}\}/.test(item.innerHTML)){ // RegExp.$1 代表获取到 正则表达式第一个 ()里面文本 const key = RegExp.$1.trim() console.log(key); item.innerHTML = this.$data[key] } }) } }
完整代码
// Vue提供了一个Observer类来进行数据劫持 // 这个类主要就是针对我们页面Vue对象中data进行数据劫持 class Observer { constructor(data) { this.data = data this.walk() } // 这个方法就是针对你传递进来的data进行数据劫持 defineReactive(data,key,value) { Object.defineProperty(data, key, { get() { console.log(`${data}对象的${key}属性被调用`); return value }, set(val) { console.log(`${data}对象的${key}属性被赋值`); value = val } }) } walk(){ Object.keys(this.data).forEach(key => { this.defineReactive(this.data,key,this.data[key]) }); } } class Vue{ // 创建Vue对象的时候,传递进来的对象 constructor(options){ this.$options = options this.$data = options.data() this.$el = options.el // 针对$data这个对象里所有属性进行数据劫持 new Observer(this.$data) // 针对Vue对象上面的属性进行劫持 this.proxy() // 实现模板编译,显示数据 new Complier(this.$el,this.$data) } // 需要将传递进来$data数据,绑定到Vue对象身上 // this.username // this.$data.username // Vue对象身上默认会有属性,Vue里$data也会有属性 proxy(){ Object.keys(this.$data).forEach(key=>{ Object.defineProperty(this,key,{ get(){ return this.$data[key] }, set(val){ this.$data[key] = val } }) }) } } // 模板编译代码 // 专门用于模板编译 class Complier{ // 获取到Vue根节点,data // $el:"#app" constructor(el,data){ this.$el = document.querySelector(el) this.$data = data this.complier() } complier(){ // this.$el.children.forEach(item=>{ // console.log(item) // }) // 遍历所有的子标签,寻找子标签中间有{{}} [...this.$el.children].forEach(item=>{ if(/\{\{([a-zA-Z0-9]+)\}\}/.test(item.innerHTML)){ // RegExp.$1 代表获取到 正则表达式第一个 ()里面文本 const key = RegExp.$1.trim() console.log(key); item.innerHTML = this.$data[key] } }) } }
七、发布订阅(观察者模式)
Vue底层引入了观察者模式(发布订阅)
因为我们在实际开发过程中,页面上会有很不多节点使用了data数据。
订阅者:食客就是订阅者,订阅了干拌抄手。
发布者:发布了干拌抄手的消息就会接受到通知
Vue发布订阅模式流程
草图:
完整代码
// Vue提供了一个Observer类来进行数据劫持 // 这个类主要就是针对我们页面Vue对象中data进行数据劫持 class Observer { constructor(data) { this.data = data this.walk() } // 这个方法就是针对你传递进来的data进行数据劫持 defineReactive(data,key,value) { const dep = new Dep() Object.defineProperty(data, key, { get() { // 依赖收集 // 将wathcer存放到dep对象 // 页面console.log执行get 页面{{username}} if(Dep.target){ dep.subs.push(Dep.target) } console.log(`${data}对象的${key}属性被调用`); return value }, set(val) { // 检测到页面修改的指定的属性 // 调用dep通知所有的watcher进行页面更新 console.log(`${data}对象的${key}属性被赋值`); value = val // 让dep来通知所有的Wathcer进行页面更新 dep.notify() } }) } walk(){ Object.keys(this.data).forEach(key => { this.defineReactive(this.data,key,this.data[key]) }); } } class Vue{ // 创建Vue对象的时候,传递进来的对象 constructor(options){ this.$options = options this.$data = options.data() this.$el = options.el // 针对$data这个对象里所有属性进行数据劫持 new Observer(this.$data) // 针对Vue对象上面的属性进行劫持 this.proxy() // 实现模板编译,显示数据 new Complier(this.$el,this.$data) } // 需要将传递进来$data数据,绑定到Vue对象身上 // this.username // this.$data.username // Vue对象身上默认会有属性,Vue里$data也会有属性 proxy(){ Object.keys(this.$data).forEach(key=>{ Object.defineProperty(this,key,{ get(){ return this.$data[key] }, set(val){ this.$data[key] = val } }) }) } } // 模板编译代码 // 专门用于模板编译 class Complier{ // 获取到Vue根节点,data // $el:"#app" constructor(el,data){ this.$el = document.querySelector(el) this.$data = data this.complier() } complier(){ // this.$el.children.forEach(item=>{ // console.log(item) // }) // 遍历所有的子标签,寻找子标签中间有{{}} [...this.$el.children].forEach(item=>{ if(/\{\{([a-zA-Z0-9]+)\}\}/.test(item.innerHTML)){ // RegExp.$1 代表获取到 正则表达式第一个 ()里面文本 const key = RegExp.$1.trim() console.log(key); // 这个代码是直接渲染到页面上。底层不是直接操作 // render方法就是页面上渲染方法 const render = ()=>item.innerHTML = this.$data[key] render() // 给页面的元素创建Watcher对象 new Watcher(render) } }) } } // 创建订阅者(Watcher) class Watcher{ // 接受render方法,完成页面渲染 constructor(callback){ // Dep类新增了一个静态属性,this代表当前watcher Dep.target = this this.callback = callback this.update() Dep.target = null } update(){ this.callback() } } // 创建一个发布者 class Dep{ constructor(){ // 存放所有我需要管理Watcher this.subs = [] } notify(){ // 通知所有watcher进行页面修改 this.subs.forEach(watcher=>{ watcher.update() }) } }
八、抽象语法树AST
AST称为抽象语法树(Abstract Syntanx Tree)
简称:语法树
将你们源代码抽象为JavaScript对象,用对象的形式来表示我们的源代码
Vue的源代码
<div id="app"> <p :class="{active:true}">{{username}}</p> <span v-bind:index="active">{{password}}</span> <span v-on:click="check">{{password}}</span> </div>
这个源代码是无法直接在浏览器里面进行加载。
Vue为了解决这个问题,将Vue模板转化为HTML代码,页面能直接识别的代码
使用抽象语法树来进行中间转换
Vue模板代码转化为抽象语法树
<div id="app"> <p :class="{active:true}" index="1">{{username}}</p> <span v-bind:index="active">{{password}}</span> <span v-on:click="check">{{password}}</span> </div>
我们会将模板代码编译为字符串,innerHTML
<div id="app"> <p :class="{active:true}">{{username}}</p> <span v-bind:index="active">{{password}}</span> <span v-on:click="check">{{password}}</span> </div>
将字符串解析为JavaScript对象
[ { tag:"div", attrs:[ {id:"app"} ], children:[ { tag:"p", attrs:[ { name:"class", value:"active" }, { name:"index", value:"1" } ], children:[ ] type:0 }, { tag:"span", attrs:[ { name:"index", value:"1" } ], type:1 } ] } ]
抽象语法树:本质就是一个JavaScript对象,针对原来的属性代码进行了抽象后结果
Vue模板如何变成AST,这个过程:链表、递归等等很多算法
总结:
面试题1:请你说一下你了解Vue响应式原理?
Vue2的响应式原理,底层默认采用的数据劫持+发布订阅模式来实现的。
-
数据劫持使用Object.defineProperty进行data里面所有数据的接触。包括对Vue对象身上的属性接触和$data对象的属性进行劫持。
-
在Object.defineProperty里面会有get和set、get主要用于收集依赖(收集watcher和dep关系),set方法主要执行Dep里卖弄notify方法进行通知wacther进行页面更新
-
Dep类属于发布者、Wacther属于订阅者,一旦Dep调用notify我们就会执行Watcher更新,更新执行render渲染
面试题2:Vue语法如何最终被浏览器识别?这个过程是什么?
Vue模板代码不好直接编译为HTML。里面包含特殊语法太多了
Vue底层默认会将Vue、template模板代码抽象为语法树AST,目的就是将Vue模板抽象为JavaScript方便我们后续解析,遍历里面每一个节点。
AST抽象语法树,使用编译函数、转化为虚拟DOM。虚拟dom里面包含变化的内容。
通过diff算法里卖弄patch函数实现页面的更新
页面渲染就是普通HTML代码
面试题3:Vue的效率相对于JS来说,谁高谁低?
根据情况来决定,Vue为了让我们开发方便,数据驱动。封装了很多底层代码。DOM操作。数据更新。封装的越多效率越低。
但是JS里对于JS的大量操作、频繁更新,这个无法进行很好优化,在这种情况下,用Vue,在这些方便提升性能
JS代码本身就很简单,没有复杂的操作。原生JS代码效率肯定比Vue更高