Vue01-框架和库的区别

Vue01-框架和库的区别

概念的区分

库的概念:就是某些特定方法的集合,比如JQuery、extjs、easyui。都是基于JS封装而来的库。

框架:有一套完善代码解决方案,我们可以基于框架的一些规范就能实现页面的交互。

框架带来的好处:

  1. 减少大家开发的差异性。比如再学习期间用vue来写代码,工作中也是这一套标准。规范性

  2. 性能考虑,使用jquery的方式来开发项目。能用很原生的一些代码实现我们业务。性能不好优化,性能瓶颈。dom操作非常消耗性能,而且不好维护的。事件委托。

  3. 开发效率。框架提供了很多api可以帮助你们直接进行业务设计,减少很多比必要代码。

核心思想

  1. 模块化思想:前端模块化和后端模块化。

      import xxx from ""
      export default {}

    后端模块化

      const index = require("./index.js")
      module.exports = 暴露内容

    模块化的标准不一样。

  2. 组件化思想:将页面拆分为一个个独立的模块,来实现代码复用

    目前而言,我们如果想要将页面中公共的部分提取出来。

    css、js,但是html代码能否提取出来

  3. 工程化思想:提供完整的项目开发标准,提供了完善的工具链让我们开发变得更加的易于维护,以及性能考虑

    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的两种模式

  1. 在老项目中直接引入Vue(很少用)

  2. 使用脚手架的方式,搭建前端工程。完善的代码

老项目

(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中的数据来决定。实现动态切换页面元素的样式

动态样式绑定主要两种方案:

  1. 动态class绑定

  2. 动态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的区别(面试题)

  1. v-if如果不满足条件,页面不会加载这个节点。

  2. 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对象{}

总结:

  1. this永远指向一个对象

  2. 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的时候,实际访问父级作用域。

作用域:

  1. 全局作用域

  2. 局部作用域(函数作用域)

  3. 块作用域(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

  1. computed默认会执行缓存。当你页面要频繁重复使用某一个结果,推荐计算属性。

  2. 如果我们需要执行比较复杂的业务逻辑(你如异步请求、本地操作),推荐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组件才用。

每个组件数据来源:

  1. 内部数据:data定义的数据

  2. 外部数据:父组件调用你的时候,传递给你的数据

父传子

父组件引入子组件,我们可以在引入子组件身上增加自定义属性。子组件那边可以通过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的生命周期流程四个阶段:

  1. 初始化阶段:在执行钩子函数初始化,data数据底层数据劫持初始化。

  2. 挂载阶段:将数据放在页面上显示,组件的渲染内容,挂载到页面中渲染。看到页面

  3. 更新阶段:生命周期时间最长的阶段,组件数据发生变化,检测到这个数据变化。

  4. 销毁阶段:组件被销毁的执行,我们会执行资源回收

初始阶段

<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这种设计目的是为什么?

  1. 保证了父子组件之间的数据传递更加规范,约束了标准的数据流向。父到子。将数据传递父组件,更新完毕流向子组件。

  2. 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>

注意事项:

  1. component必须要提供一个is属性,这个is动态绑定

  2. 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>

目前要解决我们动态组件一旦不加载就会默认销毁这个组件问题

  1. 销毁之前将数据保存一次,下次进来的时候,得到数据显示出来

  2. 完全不销毁这个组件,借助于Vue提供了一个组件keep-alive来完成

Vue20-keepAlive

Vue20-keepAlive

一、基本用法

目前遇到的问题,就是动态组件切换的时候,组件会默认销毁。

Vue提供的keepalive是一个组件,这个组件可以用于包含其他组件,被keepalive包裹的组件可以实现不销毁的动作。

基本语法

<keep-alive>
    <component :is="selectedComp“></component>
</keep-alive>

当动态组件被keepalive组件包裹的时候,所有组件的状态都会被缓存起来。当你切换组件的时候,组件不会被销毁,数据会一直保留,除非,你指定某个组件不缓存。

好处:减少组件的频繁创建和销毁,可以提升用户体验。

二、常用api

keep-alive这个组件常用的属性介绍:

  1. include:值为字符串、数组以及正则表达式。只有名字被匹配了才会缓存当前这个组件,白名单。设置设置了内容在白名单,运行缓存

  2. exclude:值为字符串、数组以及正则表达式,只有名字被匹配了才会不缓存指定组件,代表黑名单,只要名字被包含,不会被缓存

  3. 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布局目前我们无法动态变化。

一个组件内部布局也可以动态改变,我们更加灵活的使用这个组件。

例子:我们封装一个表格,表格的数据外部数据动态变化,表头或者表格的按钮,每次调用过程中都会出现差异,这种情况下我们需要利用插槽的方式来动态模板的变化。

插槽就可以实现这个效果

二、插槽的类型

  1. 匿名插槽

  2. 具名插槽

  3. 作用域插槽

匿名插槽(后备插槽)

你在子组件那边设置一个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中可以针对你们模板数据进行处理

提供两种方式:

  1. 局部过滤器:在组件中定义过滤器,只能在这个组件中使用

  2. 全局过滤器:定义全局对象中,任何一个组件都可以使用

局部过滤器

直接定义组件内部,只有当前组件能使用

<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路由中要实现跳转我们有两种方案

  1. 基于router-link进行超链接跳转

  2. 基于按钮的方式跳转,需要使用它路由对象。

链接跳转

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这个工具,我们发送请求比较完美的一种方案。

开发步骤

  1. 下载axios的包

     npm i axios
    
  2. 在你们项目中引入axios

     import axios from "axios"
    
  3. 使用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
        }
    })
}

三、文件上传

提交数据给服务器,我们一般会有两种方式:

  1. 普通文本提交,

    提交数据到后端

     data:{id:1,name;"xiaowang"}
     data:"id=1&name=xiaowang"
    

    对于HTTP请求来说,将内容转化为字符串,挡在HTTP的数据包里面,提交给后端。

    解析字符串,将内容转化为Object对象。

     const {id,name} = req.query()
     const {id,name} = req.body()
    
  2. 二进制数据提交

    只要涉及到二进制文件提交到后端,都会采用二进制流来提交。

    只要涉及文件提交,音频、视频、图片等等,采用二进制流的方式到后端

    借助于前端的而一些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)组件内守卫:每个组件内部可以进行守卫监控

二、全局守卫

全局守卫又分为以下几种:

  1. 全局前置守卫:beforeEach

  2. 全局解析守卫:beforeResolve

  3. 全局后置守卫: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状态机

一状态机概念

遇到组件通信

父子通信:

  1. props和回调函数

  2. props和$listeners

  3. $parent和$children

  4. props和$emit

  5. provider和inject等等

  6. $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提供了以下几个辅助函数.

  1. mapState()用于获取仓库state数据

  2. mapGetters() 用于获取仓库getters属性

  3. mapMutations()获取仓库中mutations的函数

  4. 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更小更少

移动端常见的一些开发模式:

  1. 原生APP开发。主要就是android和ios这两大系统。原生App开发,利用原生android和ios来进行项目开发。

    用户体验最好的,功能是最完善。成本太高了。

  2. H5端,说白了就是手机网页端。在手机里面打开浏览器访问我们H5网页。优点,开发非常的方便,成本非常低。只需要一个浏览器就可以随时访问。 用户体验比较差。

  3. 小程序端:小程序出现时微信采用了特殊一种开发技术,可以在没有下载程序的情况下,我们就可以动态访问项目。随去随用。当你不用的时候,不会占用手机空间

  4. 混合开发(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开发小程序我们需要下载一些插件来支持你们代码提示,格式化等

  1. 小程序助手v0.0.6

  2. vscode wxmlv0.1.9

  3. wechat-snippet:小程序代码格式化插件

  4. WXML - Language Servicev2.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}}">
    这是一段文本        消息!
    &gt;
    &lt;
</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中用

特点:

  1. 新增了尺寸单位 rpx。是一个相对单位。开发者不用考虑屏幕的大小,用rpx这个单位自适应

  2. 小程序中提供了全局样式和局部样式。app.wxss属于全局样式。每个页面wxss属于局部样式

  3. 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.idCardid选择,唯一性
elementtext、view元素选择器
element,elementtext,view选择器分组
nth-child(n)view:nth-child(4){}结构选择器
:afterview::after在 view 组件后边插⼊内容
:beforeview::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
  1. 如何绑定事件,bind和catch区别

  2. 绑定事件要绑定事件函数,并传递值

基础语法

<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 = “”

事件分类:

  1. 冒泡型事件:当一个组件事件被触发,默认传递给父组件

  2. 非冒泡型事件:当一个组件事件被触发,默认不会传递给父组件

冒泡型事件(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-组件开发

小程序中组件开发有两类:

  1. 官方的组件:view、text、image、input等等

  2. 自定义组件

自定义组件

在项目下面创建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
errorObject 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;

八、上拉加载更多下拉刷新页面

生命周期页面提供了两个函数。

  1. 触底加载

  2. 下拉触发

 /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  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字体图标库。

地址: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属性,这个属性可以监控购物车数据的变化,动态计算总价

十三、微信授权登录

在移动端我们登录有三种:

  1. 用户和密码登录:在App端表现

  2. 微信授权登录:微信特有的一种登录方式。

  3. 第三方登录。一般也在app端表现

因为微信小程序运行微信端。微信已经授权登录过了,小程序可以直接使用微信授权过后的结果来进行登录。

免去输入用户名和密码。直接用微信的身份来认证。

用户体验比较好。无需输入用户名和密码,比较安全

登录后要得到token身份信息,才能传递token获取数据

实现步骤:

  1. 在页面布局,把头像显示出来(默认头像),登录成功显示另外一个板块

     <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>
    
  2. 给头像绑定点击事件,点击过后,调用微信的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);
           }
         })
       },
    
  3. 授权完成后需要发送请求到微信服务器生成一个临时身份凭证(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);
           }
         })
       },
    
  4. 得到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

  5. 前端获取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都是不一样

  6. 以后发送请求从本地存储获取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,获取成功就显示到页面

十五、微信分享

我们在很多小程序都可以点击右上角按钮,进行微信小程序分享。

  1. 你分享的当前你停留的页面。当你点击分享到好友的时候,将当前页面截图出来,作为分享界面

  2. 分享功能是否能使用,取决于你页面中是否有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特点:

  1. uniapp这个框架核心的语法是vuejs的语法。里面还包含了跟小程序相同很多组件。

  2. uniapp底层用到weex框架,这个框架早期是vue推出的移动端框架。

  3. 一套代码可以运行到很多端(微信小程序、APP、H5端)

  4. 生态比较完善,有很多完善的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调试

  1. 手机要用数据线连接电脑。驱动要安装成功。推荐要用原配的数据线

  2. 在手机上开启调试模式,之前以华为为例子

            找到版本号:连续点击多次 ,至少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” 的组件
elementview选择所有 view 组件
element, elementview, checkbox选择所有文档的 view 组件和所有的 checkbox 组件
::afterview::after在 view 组件后边插入内容,仅 vue 页面生效
::beforeview::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:当前这个包在哪个时机进行加载。一般我们可以配置rootname__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 项目)

  1. 下载 vantui 的 ui 库:https://github.com/youzan/vant-weapp

  2. 将 ui 库中的 dist 目录下的组件,拷贝到项目根目录下 /wxcomponents

  3. 组件的注册:

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"
            }
        ]
    }
}

  1. 组件的使用:

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接受三个参数

  1. 监控的对象

  2. 监控属性

  3. 执行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接受三个参数

  1. 监控的对象

  2. 监控属性

  3. 执行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的响应式原理,底层默认采用的数据劫持+发布订阅模式来实现的。

  1. 数据劫持使用Object.defineProperty进行data里面所有数据的接触。包括对Vue对象身上的属性接触和$data对象的属性进行劫持。

  2. 在Object.defineProperty里面会有get和set、get主要用于收集依赖(收集watcher和dep关系),set方法主要执行Dep里卖弄notify方法进行通知wacther进行页面更新

  3. 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更高

Logo

前往低代码交流专区

更多推荐