• (一) webpack配置

    • (1) 前端项目构建打包工具介绍
    • (2) 一份webpack配置
    • (3) vue-cli是如何创建项目的
  • (三) vue-cli配置跨域
  • (四) 统一管理请求
  • (五) 路由守卫
  • (六) 用户权限管理
  • (七) 自定义vue指令
  • (八) vue双向数据绑定原理
    • (1) 双向数据绑定核心原理
    • (2) Vue的双向数据绑定原理
  • (九) keep-alive
  • (十) 组件通信方式总结
    • (1) 父子组件通信(略)
    • (2) vuex跨组件通信(略)
    • (3) provide和inject 祖宗和后代通信
    • (4) vue中央事件总线机制(bus)

(一) webpack配置#

(1) 前端项目构建打包工具介绍#

  1. 为什么需要这些项目构建打包工具
  2. 项目构建打包工具有哪些
    • grunt
    • gulp (基于流)
    • fis (百度)
    • rollup
    • webpack (一切皆模块)
    • vite (快)
  3. webpack介绍
    1. 是一款打包构建工具,目前就流行打包构建工具
    2. webpack特点: 一切皆模块, 能把所有资源打包成浏览器能识别的 html,css,js,png
    3. 官网地址: webpack

(2) 一份webpack配置#

核心知识点:

  1. 入口和出口
  2. loader: webpack默认只认识js模块, 其它文件都要配置响应的loader
  3. plugin(插件): 可以给webpack添加额外的功能
  4. resolve: 可以配置一写特殊的功能
  5. devServer 启动服务,也可在它里面配置跨域
const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");module.exports = {entry: "./src/main.js",output: {filename: "app.js",path: path.resolve(__dirname, "dist"),},mode: "development",// 对模块进行配置module: {// 规则rules: [{// 意思是: 遇到.css结尾的文件, 先使用style-loader和css-loader进行处理test: /\.css$/,use: ["style-loader", "css-loader"],},{test: /\.less$/,use: ["style-loader", "css-loader", "less-loader"],},],},plugins: [new htmlWebpackPlugin()],resolve: {alias: {"@": path.resolve(__dirname, "src"),},// 省略后缀名extensions: [".mjs", ".js", ".jsx", ".vue", ".json", ".wasm"],},devServer: { compress: true,open:true, //是否自动打开默认浏览器port: 8080 // 端口号}
};

(3) vue-cli是如何创建项目的#

  1. vue-cli底层使用的就是webpack来构建和打包项目的, 但是它把webpack的配置都隐藏起来了

  2. 查看vue-cli中的webpack配置

    vue inspect > webpack.config.js
    
  3. 若要修改webpack的配置, 在根目录创建vue.config.js, 里面的配置最终会合并到webpack的配置里面

    module.exports = {lintOnSave: false,outputDir: 'nongye.cn',
    }
    

(三) vue-cli配置跨域#

配置跨域: 在vue.config.js里加入以下代码

module.exports = {lintOnSave: false,outputDir: "nongye.cn",devServer: {proxy: {// 所有包含有'api'字符串的请求地址都会转发到target指向的地址"/api": {// 要访问的目标target: "http://106.55.50.108:3007",ws: true,// 允许跨域changeOrigin: true,pathRewrite: {"^/api": "", //通过pathRewrite重写地址,将前缀/api转为/},},},},
};

本地接口配置: src/utils/request.js的配置

if (isDev) {baseURL = "/api";  // 请求的时候,会在/api前面拼接本地服务器地址, 完整地址是  http://localhost:8080/api
} else {baseURL = "http://106.55.50.108:3007";
}

转发详情

假设本地服务器接口为, http://localhost:8080/api/sensor/count,

最终访问的接口为 http://81.71.65.4:3003/sensor/count

注意:

  1. 访问的接口必须包含api
  2. 访问的接口地址必须跟本地服务器保持一致, 本例是http://localhost:8080
  3. 配置完之后要重启

(四) 统一管理请求#

/src/api/index.js

import $axios from '../utils/request';// account模块
export const $accountAdd = (data)=> {return $axios.post('/account/add',data);
}
export const $accountList = ()=> {return $axios.get('/account/list');
}
export const $accountLogin = (params)=> {return $axios.post('/account/login',params);
}
export const $accountDel = (data={})=> {return $axios.post('/account/del',data);
}

使用

<script>
import * as api from "../../api/index";
export default {data() {return {list: [],};},created() {this.getList();},methods: {  getList() { api.$feeUsageList().then((res) => {this.list = res.data; });},}
};
</script>

(五) 路由守卫#

常常用来鉴权(鉴察权限)

// 路由白名单
const whiteList = ["/login"];
/*** 路由守卫* to 要前往的路由* from 当前路由* next 下一步操作*/
router.beforeEach((to, from, next) => {if (store.state.token) {//已经登录: 如果路由是登录页,默认跳转到首页if (to.path == "/login") {next({path: "/dashboard/index",});} else {next();}} else {//没有登录,路由是否在白名单中if (whiteList.includes(to.path)) {//放行next();} else {//没在白名单next("/login");}}
});export default router;

(六) 用户权限管理#

  1. 根据不同角色配置不同的路由数组

    注: 有些公司由后台配置, 但由前端来配置更灵活更方便

  2. 登录获取token

  3. 根据用户角色通过router.addRoutes动态添加用户角色对应的路由数组, 并把数组保存到store里

  4. 从store里获取路由数组, 动态渲染侧边栏

(七) 自定义vue指令#

一个简单的vue指令: 让输入框自动获得焦点

<template><div><p>页面载入时,input 元素自动获取焦点:</p><input v-focus/></div>
</template><script>
export default {  // 局部指令, 也可以做成全局指令directives: {focus: {inserted: function (el) {// 聚焦元素el.focus();},},},
};
</script>

vue指令的三个构造函数:

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用

(八) vue双向数据绑定原理#

vue原理之-双向绑定实现原理 - 知乎

此问题回答的关键点:

  1. 双向数据绑定核心原理
  2. vue双向数据绑定的实现过程

(1) 双向数据绑定核心原理#

vue数据双向绑定是通过es5的一个新增的一个特性Object.defineProperty来对数据进行劫持, 然后结合发布者-订阅者模式的方式来实现的,其中比较关键的是数据劫持(Object.defineProperty),下面咱们看一个例子。

var obj = {}
Object.defineProperty(obj, 'name', {get: function () {console.log("获取了");},set: function () {console.log('修改了');}
})
// 修改属性
obj.name = 'fei';
// 读取属性
var name = obj.name;

有了Object.defineProperty, 我们就可以在获取和修改属性的时候做一些响应的操作, 从而实现数据的双向绑定, 下面是一个简单的版的双向数据绑定

<!DOCTYPE html>
<html lang="en"> <body><input type="text" id="inp" oninput="handleInput()"><p id="text"></p><script>// 另一种添加属性的方法var obj = {};var $inp = document.getElementById('inp');var $text = document.getElementById('text');// es5新增特性Object.defineProperty(obj, 'username', {get: function () { console.log('get方法被调用');return $inp.value;},set: function (value) { console.log('set方法被调用');$inp.value = value;$text.innerText = value;}});// 给属性赋值obj.username = '张三';// 获取属性的值var username = obj.username;function handleInput() {obj.username = $inp.value;} </script>
</body>
</html>

(2) Vue的双向数据绑定原理#

实现过程

​ 我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:

  1. 实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

  2. 实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

  3. 实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

手写实现vue双向数据绑定

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>vue双向数据绑定</title>
</head><body><div id="app">{{username}} <br><input type="text" v-model="msg"> <br>{{ msg }}</div><script>/*** 第1步: 实现一个监听器Observer* 用来劫持并监听所有属性* 如果属性有变动的,就通知订阅者。*/function Observe(obj, vm) {Object.keys(obj).forEach(function (key) {defineReactive(vm, key, obj[key]);})}function defineReactive(obj, key, val) {var dep = new Dep();// 劫持数据Object.defineProperty(obj, key, {get: function () {// 添加订阅者 watcher 到主题对象 Depif (Dep.target) dep.addSub(Dep.target);return val},set: function (newVal) {// 新数据和旧数据相同, 不做处理if (newVal === val) return;val = newVal;// 作为发布者发出通知dep.notify();}});}function nodeToFragment(node, vm) {var flag = document.createDocumentFragment();var child;// 许多同学反应看不懂这一段,这里有必要解释一下// 首先,所有表达式必然会返回一个值,赋值表达式亦不例外// 理解了上面这一点,就能理解 while (child = node.firstChild) 这种用法// 其次,appendChild 方法有个隐蔽的地方,就是调用以后 child 会从原来 DOM 中移除// 所以,第二次循环时,node.firstChild 已经不再是之前的第一个子元素了while (child = node.firstChild) {compile(child, vm);flag.appendChild(child); // 将子节点劫持到文档片段中}return flag}/*** 第2步: 实现一个订阅者Watcher* 可以收到属性的变化通知并执行相应的函数,从而更新视图。*/function Watcher(vm, node, name, nodeType) {Dep.target = this;this.name = name;this.node = node;this.vm = vm;this.nodeType = nodeType;// 调用更新视图的方法去更新视图this.update();Dep.target = null;}Watcher.prototype = {update: function () {this.get();if (this.nodeType == 'text') {this.node.nodeValue = this.value;}if (this.nodeType == 'input') {this.node.value = this.value;}},// 获取 data 中的属性值get: function () {this.value = this.vm[this.name]; // 触发相应属性的 get}}// 因为订阅者可能有很多个, 所以需要一个消息收集器function Dep() {this.subs = []}Dep.prototype = {addSub: function (sub) {this.subs.push(sub);},notify: function () {this.subs.forEach(function (sub) {sub.update();});}}/*** 第3步: 实现一个解析器Compile* 可以扫描和解析每个节点的相关指令* 并根据初始化模板数据以及初始化相应的订阅器。*/function compile(node, vm) {var reg = /\{\{(.*)\}\}/;// 节点类型为元素if (node.nodeType === 1) {var attr = node.attributes;// 解析属性for (var i = 0; i < attr.length; i++) {if (attr[i].nodeName == 'v-model') {var name = attr[i].nodeValue; // 获取 v-model 绑定的属性名node.addEventListener('input', function (e) {// 给相应的 data 属性赋值,进而触发该属性的 set 方法vm[name] = e.target.value;});node.value = vm[name]; // 将 data 的值赋给该 nodenode.removeAttribute('v-model');}};new Watcher(vm, node, name, 'input');}// 节点类型为 textif (node.nodeType === 3) {if (reg.test(node.nodeValue)) {var name = RegExp.$1; // 获取匹配到的字符串name = name.trim();new Watcher(vm, node, name, 'text');}}}function Vue(options) {this.data = options.data;var data = this.data;Observe(data, this);var id = options.el;var dom = nodeToFragment(document.getElementById(id), this);// 编译完成后,将 dom 返回到 app 中document.getElementById(id).appendChild(dom);}var vm = new Vue({el: 'app',data: {username: '张三',msg: 'hello world'}})</script>
</body>
</html>

(九) keep-alive#

  1. 用户在某个列表页面选择筛选条件过滤出一份数据列表,由列表页面进入数据详情页面,再返回该列表页面,我们希望:列表页面可以保留用户的筛选(或选中)状态。

  2. keep-alive就是用来解决这种场景。当然keep-alive不仅仅是能够保存页面/组件的状态这么简单,它还可以避免组件反复创建和渲染,有效提升系统性能。总的来说,keep-alive用于保存组件的渲染状态。

  3. keep-alive的生命周期

    1. 初次进入时:created > mounted > activated;退出后触发 deactivated
    2. 再次进入:会触发 activated;事件挂载的方法等,只执行一次的放在 mounted 中;组件每次进去执行的方法放在 activated 中
  4. 应用实例

    注意: 要把keep-alive放在父路由组件上, 不然不生效

    // 例子1: 缓存所有组件

    <keep-alive><router-view/>
    </keep-alive>
    

    // 例子2: 缓存部分组件

    // router.js
    {path: "/demo",name: "demo",meta: {title: "demo",icon: "icon-d-right-arrow",},hidden: isDev ? false : true,component: Layout,redirect: "/demo/index",children: [{path: "index",name: "demo-index", meta: {keepAlive: true}  // true代表需要缓存,必须放入meta里, 不然访问不到,component: () => import("@/views/demo/index.vue"),},],},<!-- 父路由组件(在这里是Layout)添加一下keep-alive -->
    <!-- 对keepAlive为true的路由使用keep-alive包裹 -->
    <keep-alive><router-view v-if="$route.meta.keepAlive" />
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive" />
    

总结:

  1. 被缓存的组件, created和mounted只会调用一次, 第二次进入组件不再调用(强制刷新除外)
  2. 被缓存的组件有额外的两个生命周期activated和deactivated, 如果你进入或离开组件需要做一些操作, 可以在这两个生命周期里进行

(十) 组件通信方式总结#

(1) 父子组件通信(略)#

(2) vuex跨组件通信(略)#

(3) provide和inject 祖宗和后代通信#

  1. 祖宗使用provide定义数据或方法
  2. 后代(任何层级)都可以使用inject获取数据
// 祖宗组件, App.vue
<script>
export default {provide: {username: '张三',say() {console.log(this.username+'是狂徒');}}
}
</script>// 后代组件
<template><div>provide和inject</div>
</template><script>
export default {inject: ["username", "say"],created() {console.log(this.username);this.say();},
};
</script>

(4) vue中央事件总线机制(bus)#

  1. vue中非父子组件之间通信除了使用vuex,也可以通过bus总线,两者适用场景不同。

  2. vuex适用中大型项目、数据在多组件之间公用的情况。

  3. bus的本质是创建了一个空的vue实例用来存放数据, 适合小项目、数据被更少组件使用的项目,对于中大型项目 数据在很多组件之间使用的情况 bus就不太适用了。bus其实就是一个发布订阅模式,利用vue的自定义事件机制,在触发的地方通过$emit向外发布一个事件,在需要监听的页面,通过$on监听事件(订阅)

    注意:

    • 需要先订阅, 发布的时候才能收到
    • 比较适合兄弟组件通信, 也就是一个组件有很多子组件, 这些子组件之间的通信
    • 也适合孙子组件发送消息个祖宗组件, 因为祖宗组件会比后代组件先加载

概念: 发布订阅模式, 就好像以前一个家庭跟一个报社订报纸, 报社是发布者, 有了新的报纸就派人去送报纸, 家庭是订阅者, 订了的家庭就能收到新报纸, 一个发布者可以对应多个订阅者

应用:

(1) main.js 导入事件中线插件, 需要先安装插件

import VueBus from 'vue-bus';
Vue.use(VueBus);

(2) demo1 子传父(孙传爷也一样), 其实对组件没有限制, 可以进行跨组件通信

  • 父组件订阅事件
  • 子组件发布事件
<template><div><h3>父组件 {{ username }}</h3><hr><Son/></div>
</template><script>
import Son from "./Son.vue";
export default {components: {Son,},data() {return {username: "张三",};},created() {this.$bus.on("aaa", (data) => {this.username = data;});},
};
</script>
<template><div><button @click="sendEvent">发布事件</button></div>
</template><script>
import Son from './Son.vue'
export default {components: {Son,}, methods: {sendEvent() { // 发布事件,名称为listEventthis.$bus.emit("aaa", '李四');},},
};
</script>

(2) demo2 跨组件通信(非直属亲属关系)

  • demo.vue订阅aaa事件
  • demo2.vue发布aaa事件

其实不是太适合这种情况, 因为订阅一方得先运行, 而且还不能被销毁(要使用keep-alive)

<!-- demo.vue -->
<template><div><h3>demo1</h3><button @click="sendEvent">发布事件</button></div>
</template><script>
export default {  methods: {sendEvent() {alert('事件发布成功');this.$bus.emit("aaa", "李四");},},
};
</script>
<!-- demo2.vue -->
<template><div><h3>demo2 {{ username }}</h3></div>
</template><script>
export default {data() {return {username: "张三",};},created() {this.$bus.on("aaa", (data) => {this.username = data;});},
};
</script>

Vue进阶知识(2)相关推荐

  1. Vue基础知识总结(二):进阶篇

    Vue基础知识总结(二):进阶篇 1.0 MVVM模式 MVVM 是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式. MVVM模式将页面,分层了 M .V.和VM , ...

  2. div滚动到指定位置 vue_【Vue 进阶】从 slot 到无渲染组件

    什么是插槽 插槽(slot)通俗的理解就是"占坑",在组件模板中占有位置,当使用该组件的时候,可以指定各个坑的内容.也就是我们常说的内容分发 值得一提的是,插槽这个概念并不是 Vu ...

  3. Vue进阶(幺零二):面试必备:2023 Vue经典面试题整理(含答案)

    组件之间的数据传递 自定义组件 ES6 Promise 用法 VUE输入框事件监听blur与change的差异 Vuex之理解Mutations 与 Vuex 的第一次接触 vue中created.m ...

  4. vue基础知识(day06)

    今日学习目标 能够了解组件进阶知识 能够掌握自定义指令创建和使用 能够完成tabbar案例的开发 组件进阶 1.0 组件进阶 - 动态组件 目标: 多个组件使用同一个挂载点,并动态切换,这就是动态组件 ...

  5. Android进阶知识:绘制流程(上)

    1.前言 之前写过一篇Android进阶知识:事件分发与滑动冲突,主要研究的是关于Android中View事件分发与响应的流程.关于View除了事件传递流程还有一个很重要的就是View的绘制流程.一个 ...

  6. react只停留在表层?五大知识点带你梳理进阶知识

    五大知识点带你梳理react进阶知识 ✉️前言

  7. webpack入门核心知识还看不过瘾?速来围观万字入门进阶知识

    一文了解webpack入门进阶知识

  8. Vue 进阶系列(一)之响应式原理及实现

    Vue进阶系列汇总如下,欢迎阅读. Vue 进阶系列(一)之响应式原理及实现 Vue 进阶系列(二)之插件原理及实现 Vue 进阶系列(三)之Render函数原理及实现 什么是响应式Reactivit ...

  9. vue基础知识之vue-resource/axios

    Vue基础知识之vue-resource和axios(三) vue-resource Vue.js是数据驱动的,这使得我们并不需要直接操作DOM,如果我们不需要使用jQuery的DOM选择器,就没有必 ...

最新文章

  1. docker 连接容器
  2. 利用键盘钩子捕获Windows键盘动作
  3. 为什么将0.1f改为0会使性能降低10倍?
  4. 如何删除webstrom中生成的.idea wrokspace
  5. One question regarding your note Note 1731777 - Debugging background work items
  6. C++中指针与引用的区别
  7. vue 外部方法调用内部_vue 的进阶之路
  8. 漫步数理统计五——条件概率与独立(上)
  9. vue给组件传html,如何将 html 模板作为道具传递给 Vue 组件
  10. ES面试基础知识要点
  11. PHP中类重写方法,php中重写方法有什么规则
  12. P NP NPC NP hard
  13. JQuery获取第几个元素和判断元素在第几个
  14. oracle 自定义表类型赋值,Oracle自定义类型 Record + PL/SQL表
  15. 思科路由器如何导出配置文件_如何备份cisco路由器配置文件
  16. 找不到或无法加载主类怎么办
  17. 《Visual Prompting: Modifying Pixel Space to Adapt Pre-trained Models》论文阅读笔记
  18. python图片保存_Python中读取,显示,保存图片的方法
  19. 从零开始——Emacs 安装配置使用教程 2015
  20. 整理了几个经常访问的Qt知名社区

热门文章

  1. 1146:统计立方数
  2. 可以跑Linux的RISC-V计算机、微型电脑、单板机
  3. 2022年全球市场维生素贴片总体规模、主要生产商、主要地区、产品和应用细分研究报告
  4. SQL Server 没有日志文件(*.ldf) 只有数据文件(*.mdf) 恢复到SQL Server2005
  5. iOS Handle Refunds 处理退款 --- WWDC20
  6. 怎么查看LinkedIn领英号用了多久?
  7. AirPods 3、AirPods Pro 有何差别?
  8. idea连接数据库无法识别sql语句中的表
  9. oracle exfsys 下 rlm$evtcleanup,RLM$SCHDNEGACTION 运行导致负载问题
  10. FMS/AMS5安装后的简单应用和配置注意事项