整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Vue 3 进阶用法:Provide / Inject 机制

、属性下钻

属性可用于父组件向子组件传递参数,但仅限于相邻父子组件。如果一个组件要给它的子组件的子组件(即孙组件)传递参数,不得不拆成两个步骤:先传给它的儿子,再由儿子传给孙子。

祖孙三代之间的属性透传

尽管可以实现目的,但是中间层组件需要定义自身用不到的属性。如果嵌套层级更深,需要传递的属性更多,可以想象,这条链路的每个中间层组件都会变得不幸。这种为了透传而声明属性造成的不幸,Vue 叫它属性下钻(Prop Drilling)。

二、Provide 和 Inject

为了解决属性下钻的问题,Vue 提供了 Provide/Inject 解决方案。这个方案定义了两个角色:ProviderInjector,Provider 是数据的提供方,Injector 是数据的消费方。

Provider 通过 provide(key, data) 把数据存放在特定区域,Injector 通过 inject(key) 从特定区域获取数据。其中的 key 用于区分不同用途的数据。

数据的提供和获取

这套机制跟智能取餐柜有些类似。送餐员是食物(数据)的 Provider,你是食物的 Injector,而 key 是取餐柜号。送餐员通过 provide(key, food) 把食物放到特定柜子,你通过 inject(key) 从正确的柜子取走食物并消费它。

和取餐柜不同的是,一个 Provider 可以对应多个 Injector。即父组件提供一条数据后,可以被子子孙孙组件反复消费。

provide() 函数的两个参数,第一个参数叫注入键(injection key),类型可以是字符串或 Symbol。第二个参数是注入的数据,可以是任意类型,包括响应式数据。

使用 provide 和 inject 传递数据

渲染效果:

使用 provide 和 inject 的渲染结果

三、同名覆盖

在一个层级较深的组件树中,如果出现多个 Provider 组件,且提供的 key 是相同的,最终 Injector 取到的数据是怎样的?

Injector 永远获取离它最近的祖先组件提供的数据,再往上的数据会被同名覆盖。用代码验证一下:

同名覆盖

渲染效果:

同名覆盖的渲染结果

如果你在开发一个很大的应用,为了防止同名覆盖,可以在 provide() 中使用 Symbol 类型代替字符串。

四、最顶级的 Provider

在 Vue 3 中,最顶级的 Provider 是 app。通过 app.provide(key, value) 提供数据。数据的消费方法没有什么不同。

顶级 Provider

顶级 Provider 的渲染结果

五、Inject 默认值

如果子组件通过 inject(key) 消费了一个 key,但是所有的上级组件都没提供数据,会让 Vue 恼火,并发出警告信息。

上游没有提供数据,但下游用了

没有注入数据的渲染结果

为了平息 Vue 的警告信息,可以在子组件使用 inject() 时,传入第二个参数当作默认值。

使用默认值

这样,当所有上级都不提供数据时,子组件就会启用默认值。

正常显示默认值

有的时候,默认值需要调用工厂函数计算才能获得。此时,函数本身不是默认值,函数的返回值才是默认值,需要将 inject() 第三个参数设为 true,表示第二个参数的特殊作用。

标记默认的工厂函数

六、处理响应式数据

如果提供的数据是响应式数据,建议把响应式数据变更数据的方法放在一个地方,打包一并提供给下游。谁声明谁治理,这样职责比较清晰。

响应式数据的声明和变更在一个地方

渲染结果:

响应式数据的渲染结果

对于某些重要数据,如果上游组件不希望下游组件改动,可以先用 readonly() 方法保护好,然后再打包发送给下游。

设定为只读数据

下游修改只读数据时,Vue 不仅会阻止操作,还会发出警告:

修改只读数据时的警告信息

参考资料

  • Provide / Inject,https://vuejs.org/guide/components/provide-inject.html

rovide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作。

一、简单示例

向子组件注入数据

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>provide / inject</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
    <div id="js_app">
        <children></children>
    </div>
    <script>
    var Children = {
        inject: ['list'],
        template: '<ul><li v-for="(v,i) in list" :key="i">姓名:{{ v.name }} 分数:{{ v.score }}</li></ul>'
    };
    var app = new Vue({
        el: '#js_app',
        provide: {
            list: [
                { name: 'tom', score: 88 },
                { name: 'jack', score: 75 },
                { name: 'rose', score: 96 }
            ]
        },
        components: {
            children: Children
        }
    });
    </script>
</body>

</html>

二、配合Symbol使用

var symbol = Symbol();
var Children = {
    inject: { // 这里是一个对象,自定义key指定值
        list: symbol
    },
    template: '<ul><li v-for="(v,i) in list" :key="i">姓名:{{ v.name }} 分数:{{ v.score }}</li></ul>'
};
var app = new Vue({
    el: '#js_app',
    provide: function() { // 这里是一个函数,返回一个对象
        return {
            [symbol]: [
                { name: 'tom', score: 88 },
                { name: 'jack', score: 75 },
                { name: 'rose', score: 96 }
            ]
        };
    },
    components: {
        children: Children
    }
});

三、使用默认值

var Children = {
    inject: {
        list: {
            from: 'list',
            default: function() {
                return [
                    { name: 'tom', score: 88 },
                    { name: 'jack', score: 75 },
                    { name: 'rose', score: 96 }
                ];
            }
        }
    },
    template: '<ul><li v-for="(v,i) in list" :key="i">姓名:{{ v.name }} 分数:{{ v.score }}</li></ul>'
};
var app = new Vue({
    el: '#js_app',
    components: {
        children: Children
    }
});

四、配合props使用

浏览器开发者工具在爬虫中常用来进行简单的抓包分析、JS逆向调试,打开方式:

  1. F12;
  2. 快捷键 Ctrl+Shift+I;
  3. 鼠标右键检查或者审查元素;
  4. 浏览器右上角 —> 更多工具 —> 开发者工具

常见禁用开发者工具手段:https://blog.csdn.net/cplvfx/article/details/108518077

官方文档:https://developer.chrome.com/docs/devtools/

  • Elements(元素面板):使用“元素”面板可以通过自由操纵 DOM 和 CSS 来重您网站的布局和设计。
  • Console(控制台面板):在开发期间,可以使用控制台面板记录诊断信息,或者使用它作为 shell,在页面上与 JavaScript 交互。
  • Sources(源代码面板):在源代码面板中设置断点来调试 JavaScript ,或者通过 Workspaces(工作区)连接本地文件来使用开发者工具的实时编辑器。
  • Network(网络面板):从发起网页页面请求 Request 后得到的各个请求资源信息(包括状态、资源类型、大小、所用时间等),并可以根据这个进行网络性能优化。
  • Performance(性能面板):使用时间轴面板,可以通过记录和查看网站生命周期内发生的各种事件来提高页面运行时的性能。
  • Memory(内存面板):分析 web 应用或者页面的执行时间以及内存使用情况。
  • Application(应用面板):记录网站加载的所有资源信息,包括存储数据(Local Storage、Session Storage、IndexedDB、Web SQL、Cookies)、缓存数据、字体、图片、脚本、样式表等。
  • Security(安全面板):使用安全面板调试混合内容问题,证书问题等等。
  • Lighthouse(诊断面板):对当前网页进行网络利用情况、网页性能方面的诊断,并给出一些优化建议。


元素选择

可以直接点击页面的元素,会自动跳转到对应的源代码。

终端模拟

模拟各种终端设备,支持自定义终端。

设置

开发者工具设置,包括一些外观、快捷置、终端设备、地理位置设置等。

自定义

自定义和控制开发者工具,包括调整工具的位置、全局搜索、运行命令、其他工具等。

终端模拟

终端模拟

可以模拟各种终端设备,适合查看手机页面的数据,点击【More tools】—> 【Sensors】可以模拟终端的地理位置、终端朝向等;工具栏可以选择要模拟的终端型号,其中 Responsive 是自适应。

Network 面板

Controls 控制器

  • Preserve log:是否在页面重加载后,清除请求列表。
  • Disable cache:是否启用缓存。

是否开启抓包

清除请求

是否隐藏 Filter(过滤器)窗格

搜索

Network conditions,网络条件,允许在各种网络环境中测试网站,包括 3G,离线等,还可以自定义限制最大下载和上传流量。

Import/Export HAR file,导入导出抓包数据。

Filter 过滤器

  • Hide data URLs:data URLs 指一些嵌入到文档中的小型文件,在请求表里面以 data: 开头的文件就是,如较为常见的 svg 文件。勾选 Hide data URLs复选框即可隐藏此类文件。
  • All:显示所有请求。
  • XHR:全称 XMLHttpRequest,是一种创建 AJAX 请求的 JavaScript API,通常抓取 Ajax 请求可以选择 XHR。
  • WS:全称 WebSocket,是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
  • Manifest 安卓开发文件名,属于 AndroidManifest.xml 文件,在简单的 Android 系统的应用中提出了重要的信息码。
  • Has blocked cookies:仅显示具有阻止响应 cookie 的请求。
  • Blocked Requests:只显示被阻止的请求。

断点调试

常规断点调试

适用于分析关键函数代码逻辑

  1. Ctrl+Shift+F 或者右上角三个点打开全局搜索,搜索关键字。
  2. 定位到可疑代码,点击行号埋下断点。
  3. 调试代码,分析逻辑,其中 console 模板可以直接编写 JS 代码进行调试。

各个选项功能:

执行到下一个断点

执行下一步,不会进入所调用的函数内部

进入所调用的函数内部

跳出函数内部

一步步执行代码,遇到有函数调用,则进入函数

停用断点

不要在出现异常时暂停。

  • Breakpoints:可以看到已经埋下的断点。
  • Scope:可以看到当前局部或者全局变量的值,可对值进行修改。
  • Call Stack:可以看到当前代码调用的堆栈信息,代码执行顺序为由下至上。

XHR 断点

匹配 url 中关键词,匹配到则跳转到参数生成处,适用 于url 中的加密参数全局搜索搜不到,可采用这种方式拦截。

行为断点

Event Listener Breakpoints,事件侦听器断点,当鼠标点击、移动、键盘按键等行为或者其他事件发生时可以触发断点,比如 Mouse —> click,可快速定位点击按钮后,所执行的 JS。

插入 JS

在 sources —> snippets 下可以新建 JS 脚本。

打印 windows 对象的值

在 console 中输入如下代码,如只打印 `_$` 开头的变量值:

for (var p in window) {
    if (p.substr(0, 2) !== "_$") 
        continue;
    console.log(p + " >>> " + eval(p))
}

无限 debugger 防调试

某些页面打开调试工具会出现无限 debugger 的现象:

中间人拦截替换无限 debug 函数

查看调用栈,点击第二行跳转到原函数:

可以看到 _0x2ba9bc[_0x20b2('0x79')]_0x2ba9bc[_0x20b2('0x7a')] 分别对应 debu 和 gger,连起来就是 debugger,在本地重写这个 JS,直接将这两个值置空:

使用插件 ReRes,编写规则,遇到此 JS,就替换成我们本地经过修改过的 JS,替换后无限 debugger 就不存在了:

方法置空

直接在 Console 中将无限 debugger 的函数重写置空也可以破解无限 debugger,缺点是刷新后失效。

解除定时器

适用于定时器类触发的 debug:

for (var i = 1; i < 99999; i++)window.clearInterval(i);

Hook 钩子

钩子英文 Hook,在 windows 系统中,所有的都是消息,按了一下键盘,就是一个消息,Hook 的意思就是勾住,在消息过去之前先把消息勾住,不让其执行,然后自己优先处理。也就是这个技术提供了一个入口,能够针对不同的消息或者 api 在执行前,先执行我的操作。“我的操作”就是钩子函数。在开发者工具中以 chrome 插件的方式,在匹配到关键词处插入断点。

创建一个文件夹,文件夹中创建一个钩子函数文件 inject.js 以及插件的配置文件 manifest.json :

打开 chrome 的扩展程序, 打开开发者模式,加载已解压的扩展程序,选择创建的文件夹即可:

配置文件 manifest.json

以一个 header 钩子为例,其配置文件如下:

{
   "name": "Injection",
    "version": "1.0",
    "description": "RequestHeader钩子",
    "manifest_version": 1,
    "content_scripts": [
        {
            "matches": [
                "<all_urls>"
            ],
            "js": [
                "inject.js"
            ],
            "all_frames": true,
            "permissions": [
                "tabs"
            ],
            "run_at": "document_start"
        }
    ]
}

header 钩子

header 钩子用于定位 header 中关键参数生成位置,以下代码演示了当 header 中包含 `Authorization` 时,则插入断点

var code = function(){
var org = window.XMLHttpRequest.prototype.setRequestHeader;
window.XMLHttpRequest.prototype.setRequestHeader = function(key,value){
    if(key=='Authorization'){
        debugger;
    }
    return org.apply(this,arguments);
}
}
var script = document.createElement('script');
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);

cookie 钩子

cookie 钩子用于定位 cookie 中关键参数生成位置,以下代码演示了当 cookie 中匹配到了 `abcdefghijk`, 则插入断点:

var code = function(){
    var org = document.cookie.__lookupSetter__('cookie');
    document.__defineSetter__("cookie",function(cookie){
        if(cookie.indexOf('abcdefghijk')>-1){
            debugger;
        }
        org = cookie;
    });
    document.__defineGetter__("cookie",function(){return org;});
}
var script = document.createElement('script');
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);

请求钩子

请求钩子用于定位请求中关键参数生成位置,以下代码演示了当请求的 url 里包含 `AbCdE` 时,则插入断点: