有2种方法 :
1、setList(newList);前对比手动对比list,如果一样不执行setList
2、checked这个状态单独维护一个变量
没有足够的数据
(゚∀゚ )
暂时没有任何数据
仅此而已 回答了问题 · 2020-10-20
有2种方法 :
1、setList(newList);前对比手动对比list,如果一样不执行setList
2、checked这个状态单独维护一个变量
有2种方法 :1、setList(newList);前对比手动对比list,如果一样不执行setList2、checked这个状态单独维护一个变量
关注 3 回答 2
仅此而已 赞了文章 · 2020-07-02
js
压缩js
压缩对前端开发者来说是一门必修课。
一般来说,压缩 js
主要出于以下两个目的:
压缩 js
使用的工具库:
es5
es6+
google
的 js
压缩、优化工具压缩 js
的主要过程:
去掉所有对解析引擎来说无用的字符,包括空格、注释、换行、没有用的变量声明、函数声明等。
把一些局部变量名称、函数名称等用 a, b, ...
、$1, $2, ...
、_1, _2, ...
之类的简略字符进行替换,达到混淆的目的。
源代码
(function () {
var hello = 'hi';
var print = function (str) {
console.log(str);
};
print(hello);
})();
压缩后的代码(仅演示混淆功能)
(function () {
var a = 'hi';
var b = function (c) {
console.log(c);
};
b(a);
})();
把不依赖外部环境的逻辑提前进行运算,并把运算结果替换到相应的源码处,然后从源码中移除这段逻辑。
源代码
(function () {
var hello = 'hi' + ' everyone, ';
var count = 3 * 5;
console.log(hello + count + ' girls');
})();
压缩后的代码(仅演示预编译功能)
(function () {
var hello = 'hi everyone, ';
var count = 15;
console.log(hello + count + ' girls');
})();
对于 js
来说,嵌套越深,执行越慢,对代码进行扁平化处理也是优化代码的一种方式。
源代码
(function () {
var say = {
hello: function (str) {
console.log('hello ' + str);
}
};
say.hello('everyone');
})();
压缩后的代码(仅演示扁平化结构功能)
!function(str){console.log("hello "+str)}("everyone");
源代码
(function () {
var say = {
hello: function (str) {
console.log('hello ' + str);
}
};
say.hello('everyone');
})();
压缩后的代码
!function(l){console.log("hello "+l)}("50 girls");
sourcemap
通常 js
压缩后只有一行代码,并且里面的变量名与函数名等都是混淆了的,这在实际运行中会有一个问题,就是 js
的报错信息将会失真,无法追踪到是在源代码哪一行哪一列报的错。
sourcemap
便是为了解决这个问题而生的。
sourcemap
文件就是记录了从源代码文件到压缩文件的一个代码对应关系记录表,通过压缩文件和 sourcemap
文件可以原原本本找出源代码文件。
查看阮一峰老师的 JavaScript Source Map 详解 了解 sourcemap
的原理与格式。
一般在压缩 js
的过程中,会生成相应的 sourcemap
文件,并且在压缩的 js
文件末尾追加 sourcemap
文件的链接 //# sourceMappingURL=bundle-file-name.js.map
。这样,浏览器在加载这个压缩 js
的时候,就知道还有一个相应的 sourcemap
文件,也一并加载下来,运行的过程中如果 js
报错,也会给出相应源代码的行号与列号,而非压缩文件的。
比如,对下面的源码进行压缩:
(function () {
var say = {
hi: function () {
console.log('hi');
}
};
say.hello();
return say;
})();
未加 sourcemap
文件时,报错信息是:
加上 sourcemap
文件时,报错信息是:
sourcemap
扩展webpack 对 sourcemap
做了扩展,定义在 devtool
配置项中:
eval
: 每个模块都使用 eval()
执行,并且都有 //@ sourceURL
,构建很快,但无法正确显示行号eval-source-map
: 每个模块使用 eval()
执行,并且 source map
转换为 DataUrl
后添加到 eval()
中,一般开发模式中使用这种方式cheap-eval-source-map
: 类似 eval-source-map
,但只映射行,不映射列,并忽略源自 loader
的 source map
,仅显示转译后的代码cheap-module-eval-source-map
: 类似 cheap-eval-source-map
,但会保留源自 loader
的 source map
inline-source-map
: source map
转换为 DataUrl
后添加到 bundle
中cheap-source-map
: 只映射行,不映射列,并忽略源自 loader
的 source map
,仅显示转译后的代码inline-cheap-source-map
: inline-source-map
与 cheap-source-map
的结合cheap-module-source-map
: 类似 cheap-module-eval-source-map
,但不使用 eval()
执行inline-cheap-module-source-map
: inline-source-map
与 cheap-module-source-map
的结合source-map
: 整个 source map
作为一个单独的文件生成,产品环境一般使用这种模式hidden-source-map
: 类似 source-map
,但不会把 //# sourceMappingURL=bundle-file-name.js.map
追加到压缩文件后面nosources-source-map
: 类似 source-map
,但只有堆栈信息,没有源码信息更详细信息可以参考:
对于使用 webpack 来构建项目,建议在开发时使用 eval-source-map
,产品环境使用 source-map
。
因为用压缩文件与 sourcemap
文件是可以原原本本的找到源代码的,所以,为了保护源代码,可以这样隐藏 sourcemap
文件:
web
服务器设置外部不能访问 sourcemap
文件,只能内部访问sourcemap
文件存放到其他地方sourcemap
查找原始报错信息一般而言,在产品阶段,我们会用 window.onerror
来捕获 js
报错,然后上报到服务器,以此来收集用户使用时发生的 bug
:
window.onerror = function(message, source, lineno, colno, error) {
// message: 错误信息
// source: 报错脚本的 url 地址
// lineno: 行号
// colno: 列号
// error: 错误对象
// 上报必要的信息到服务器
}
但产品环境的代码都是压缩的,行号和列号都是失真的,所以就需要用 sourcemap
文件来找到错误对应源代码的行号与列号,以及其他的信息。
使用工具: mozilla/source-map
源代码
(function () {
var say = {
hi: function () {
console.log('hi');
}
};
say.hello();
return say;
})();
压缩后报错信息
window.onerror = function(message, source, lineno, colno, error) {
console.log(`message: ${message}`);
console.log(`source: ${source}`);
console.log(`lineno: ${lineno}`);
console.log(`colno: ${colno}`);
console.log(`error: ${error}`);
}
// message: Uncaught TypeError: e.hello is not a function
// source: url/to/bundle.min.js
// lineno: 1
// colno: 982
// error: TypeError: e.hello is not a function
通过 source-map
查找原始报错信息
const fs = require('fs');
const SourceMap = require('source-map');
const { readFileSync } = fs;
const { SourceMapConsumer } = SourceMap;
const rawSourceMap = JSON.parse(readFileSync('path/to/js/map/file', 'utf8'));
SourceMapConsumer.with(rawSourceMap, null, consumer => {
const pos = consumer.originalPositionFor({
line: 1,
column: 982
});
console.log(pos);
});
查找到的原始信息
{
source: 'path/to/index.js',
line: 8,
column: 7,
name: 'hello'
}
这样,便找到了原始报错信息:
path/to/index.js
hello
如此,便能一下子就找到错误在哪里了。
更多用法,参考 mozilla/source-map
更多博客,查看 https://github.com/senntyou/blogs
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
查看原文细说 js 压缩、sourcemap、通过 sourcemap 查找原始报错信息 1. js 压缩 js 压缩对前端开发者来说是一门必修课。 一般来说,压缩 js 主要出于以下两个目的: 减小代码体积,加快前端资源加载速度 保护源代码不被别人获取 压缩 js 使用的工具库: UglifyJS2: 压缩 es5...
赞 49 收藏 33 评论 5
仅此而已 赞了文章 · 2019-12-05
在团队的项目开发过程中,代码维护所占的时间比重往往大于新功能的开发。因此编写符合团队编码规范的代码是至关重要的,这样做不仅可以很大程度地避免基本语法错误,也保证了代码的可读性,毕竟:
程序是写给人读的,只是偶尔让计算机执行一下。—— Donald Knuth
本文将讲解如何在 VSCode 中配合 ESLint 扩展来实践团队内部的前端编码规范。
ESLint(中文站点)是一个开源的 JavaScript 代码检查工具,使用 Node.js 编写,由 Nicholas C. Zakas 于 2013 年 6 月创建。ESLint 的初衷是为了让程序员可以创建自己的检测规则,使其可以在编码的过程中发现问题而不是在执行的过程中。ESLint 的所有规则都被设计成可插入的,为了方便使用,ESLint 内置了一些规则,在这基础上也可以增加自定义规则。
Windows 10
首先,打开 VSCode 扩展面板并搜索 ESLint 扩展,然后点击安装
安装完毕之后点击 重新加载
以激活扩展,但想要让扩展进行工作,我们还需要先进行 ESLint 的安装配置。
如果你仅仅想让 ESLint 成为你项目构建系统的一部分,我们可以在项目根目录进行本地安装:
$ npm install eslint --save-dev
如果想使 ESLint 适用于你所有的项目,我们建议使用全局安装,使用全局安装 ESLint 后,你使用的任何 ESLint 插件或可分享的配置也都必须在全局安装。
这里我们使用全局安装:
$ npm install -g eslint
安装完毕后,我们使用 eslint --init
命令在用户目录中生成一个配置文件(也可以在任何你喜欢的位置进行生成)
我们在第一个选项中选择自定义代码风格,之后根据需要自行选择。
设置完成后我们会得到一份文件名为 .eslintrc.js
的配置文件:
module.exports = {
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"windows"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"never"
]
}
};
配置文件生成之后,我们接着可以进行自定义修改,这里我们只粗略讲解常用的配置项,完整的可配置项可访问官方文档
在上文生成的配置文件中可以使用 env
属性来指定要启用的环境,将其设置为 true
,以保证在进行代码检测时不会把这些环境预定义的全局变量识别成未定义的变量而报错:
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"jquery": true
}
默认情况下,ESLint 支持 ECMAScript 5 语法,如果你想启用对 ECMAScript 其它版本和 JSX 等的支持,ESLint 允许你使用 parserOptions
属性进行指定想要支持的 JavaScript 语言选项,不过你可能需要自行安装 eslint-plugin-react
等插件。
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
}
在上文的配置文件中, "extends": "eslint:recommended"
选项表示启用推荐规则,在推荐规则的基础上我们还可以根据需要使用 rules
新增自定义规则,每个规则的第一个值都是代表该规则检测后显示的错误级别:
"off"
或 0
- 关闭规则
"warn"
或 1
- 将规则视为一个警告
"error"
或 2
- 将规则视为一个错误
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"windows"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"never"
]
}
完整的可配置规则列表可访问:http://eslint.cn/docs/rules/
其中带 √
标记的表示该规则为推荐规则。
安装并配置完成 ESLint 后,我们继续回到 VSCode 进行扩展设置,依次点击 文件 > 首选项 > 设置
打开 VSCode 配置文件
从左侧系统设置中可以看到,ESLint 扩展默认已经启用,我们现在只需在右侧用户设置中添加配置来指定我们创建的 .eslintrc.js
配置文件路径即可启用自定义规则检测,ESLint 会查找并自动读取它们:
"eslint.options": {
"configFile": "E:/git/github/styleguide/eslint/.eslintrc.js"
},
至此,我们已经可以使用 ESLint 扩展来检测我们的 js 文件了。
由于 ESLint 默认只支持 js 文件的脚本检测,如果我们需要支持类 html
文件(如 vue
)的内联脚本检测,还需要安装 eslint-plugin-html
插件。
因为我们使用全局安装了 ESLint,所以 eslint-plugin-html
插件也必须进行全局安装:
$ npm install -g eslint-plugin-html
安装完成后,我们再次打开 文件 > 首选项 > 设置
,在右侧用户设置中修改 ESLint 的相关配置并保存:
"eslint.options": {
"configFile": "E:/git/github/styleguide/eslint/.eslintrc.js",
"plugins": ["html"]
},
"eslint.validate": [
"javascript",
"javascriptreact",
"html",
"vue"
]
最后,我们打开一个 vue
文件,可以发现 ESLint 扩展已经正常工作了,嗯,enjoy yourself (●ˇ∀ˇ●)
在团队的项目开发过程中,代码维护所占的时间比重往往大于新功能的开发。因此编写符合团队编码规范的代码是至关重要的,这样做不仅可以很大程度地避免基本语法错误,也保证了代码的可读性,毕竟:
赞 118 收藏 153 评论 15
仅此而已 赞了文章 · 2019-11-19
微前端架构是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。
由此带来的变化是,这些前端应用可以独立运行、独立开发、独立部署。以及,它们应该可以在共享组件的同时进行并行开发——这些组件可以通过 NPM 或者 Git Tag、Git Submodule 来管理。
注意:这里的前端应用指的是前后端分离的单应用页面,在这基础才谈论微前端才有意义。
结合我最近半年在微前端方面的实践和研究来看,微前端架构一般可以由以下几种方式进行:
不同的方式适用于不同的使用场景,当然也可以组合一起使用。那么,就让我们来一一了解一下,为以后的架构演进做一些技术铺垫。
在一个单体前端、单体后端应用中,有一个典型的特征,即路由是由框架来分发的,框架将路由指定到对应的组件或者内部服务中。微服务在这个过程中做的事情是,将调用由函数调用变成了远程调用,诸如远程 HTTP 调用。而微前端呢,也是类似的,它是将应用内的组件调用变成了更细粒度的应用间组件调用,即原先我们只是将路由分发到应用的组件执行,现在则需要根据路由来找到对应的应用,再由应用分发到对应的组件上。
在大多数的 CRUD 类型的 Web 应用中,也都存在一些极为相似的模式,即:首页 -> 列表 -> 详情:
如下是一个 Spring 框架,用于返回首页的示例:
@RequestMapping(value="/")
public ModelAndView homePage(){
return new ModelAndView("/WEB-INF/jsp/index.jsp");
}
对于某个详情页面来说,它可能是这样的:
@RequestMapping(value="/detail/{detailId}")
public ModelAndView detail(HttpServletRequest request, ModelMap model){
....
return new ModelAndView("/WEB-INF/jsp/detail.jsp", "detail", detail);
}
那么,在微服务的情况下,它则会变成这样子:
@RequestMapping("/name")
public String name(){
String name = restTemplate.getForObject("http://account/name", String.class);
return Name" + name;
}
而后端在这个过程中,多了一个服务发现的服务,来管理不同微服务的关系。
在形式上来说,单体前端框架的路由和单体后端应用,并没有太大的区别:依据不同的路由,来返回不同页面的模板。
const appRoutes: Routes = [
{ path: 'index', component: IndexComponent },
{ path: 'detail/:id', component: DetailComponent },
];
而当我们将之微服务化后,则可能变成应用 A 的路由:
const appRoutes: Routes = [
{ path: 'index', component: IndexComponent },
];
外加之应用 B 的路由:
const appRoutes: Routes = [
{ path: 'detail/:id', component: DetailComponent },
];
而问题的关键就在于:怎么将路由分发到这些不同的应用中去。与此同时,还要负责管理不同的前端应用。
路由分发式微前端,即通过路由将不同的业务分发到不同的、独立前端应用上。其通常可以通过 HTTP 服务器的反向代理来实现,又或者是应用框架自带的路由来解决。
就当前而言,通过路由分发式的微前端架构应该是采用最多、最易采用的 “微前端” 方案。但是这种方式看上去更像是多个前端应用的聚合,即我们只是将这些不同的前端应用拼凑到一起,使他们看起来像是一个完整的整体。但是它们并不是,每次用户从 A 应用到 B 应用的时候,往往需要刷新一下页面。
在几年前的一个项目里,我们当时正在进行遗留系统重写。我们制定了一个迁移计划:
整个系统并不是一次性迁移过去,而是一步步往下进行。因此在完成不同的步骤时,我们就需要上线这个功能,于是就需要使用 Nginx 来进行路由分发。
如下是一个基于路由分发的 Nginx 配置示例:
http {
server {
listen 80;
server_name www.phodal.com;
location /api/ {
proxy_pass http://http://172.31.25.15:8000/api;
}
location /web/admin {
proxy_pass http://172.31.25.29/web/admin;
}
location /web/notifications {
proxy_pass http://172.31.25.27/web/notifications;
}
location / {
proxy_pass /;
}
}
}
在这个示例里,不同的页面的请求被分发到不同的服务器上。
随后,我们在别的项目上也使用了类似的方式,其主要原因是:跨团队的协作。当团队达到一定规模的时候,我们不得不面对这个问题。除此,还有 Angluar 跳崖式升级的问题。于是,在这种情况下,用户前台使用 Angular 重写,后台继续使用 Angular.js 等保持再有的技术栈。在不同的场景下,都有一些相似的技术决策。
因此在这种情况下,它适用于以下场景:
而在满足上面场景的情况下,如果为了更好的用户体验,还可以采用 iframe 的方式来解决。
iFrame 作为一个非常古老的,人人都觉得普通的技术,却一直很管用。
HTML 内联框架元素<iframe>
表示嵌套的正在浏览的上下文,能有效地将另一个 HTML 页面嵌入到当前页面中。
iframe 可以创建一个全新的独立的宿主环境,这意味着我们的前端应用之间可以相互独立运行。采用 iframe 有几个重要的前提:
如果我们做的是一个应用平台,会在我们的系统中集成第三方系统,或者多个不同部门团队下的系统,显然这是一个不错的方案。一些典型的场景,如传统的 Desktop 应用迁移到 Web 应用:
如果这一类应用过于复杂,那么它必然是要进行微服务化的拆分。因此,在采用 iframe 的时候,我们需要做这么两件事:
加载机制。在什么情况下,我们会去加载、卸载这些应用;在这个过程中,采用怎样的动画过渡,让用户看起来更加自然。
通讯机制。直接在每个应用中创建 postMessage
事件并监听,并不是一个友好的事情。其本身对于应用的侵入性太强,因此通过 iframeEl.contentWindow
去获取 iFrame 元素的 Window 对象是一个更简化的做法。随后,就需要定义一套通讯规范:事件名采用什么格式、什么时候开始监听事件等等。
有兴趣的读者,可以看看笔者之前写的微前端框架:Mooa。
不管怎样,iframe 对于我们今年的 KPI 怕是带不来一丝的好处,那么我们就去造个轮子吧。
不论是基于 Web Components 的 Angular,或者是 VirtualDOM 的 React 等,现有的前端框架都离不开基本的 HTML 元素 DOM。
那么,我们只需要:
第一个问题,创建 DOM 是一个容易解决的问题。而第二个问题,则一点儿不容易,特别是移除 DOM 和相应应用的监听。当我们拥有一个不同的技术栈时,我们就需要有针对性设计出一套这样的逻辑。
尽管 Single-SPA 已经拥有了大部分框架(如 React、Angular、Vue 等框架)的启动和卸载处理,但是它仍然不是适合于生产用途。当我基于 Single-SPA 为 Angular 框架设计一个微前端架构的应用时,我最后选择重写一个自己的框架,即 Mooa。
虽然,这种方式的上手难度相对比较高,但是后期订制及可维护性比较方便。在不考虑每次加载应用带来的用户体验问题,其唯一存在的风险可能是:第三方库不兼容。
但是,不论怎样,与 iFrame 相比,其在技术上更具有可吹牛逼性,更有看点。同样的,与 iframe 类似,我们仍然面对着一系列的不大不小的问题:
而我们即又要拆分应用,又想 blabla……,我们还能怎么做?
组合式集成,即通过软件工程的方式在构建前、构建时、构建后等步骤中,对应用进行一步的拆分,并重新组合。
从这种定义上来看,它可能算不上并不是一种微前端——它可以满足了微前端的三个要素,即:独立运行、独立开发、独立部署。但是,配合上前端框架的组件 Lazyload 功能——即在需要的时候,才加载对应的业务组件或应用,它看上去就是一个微前端应用。
与此同时,由于所有的依赖、Pollyfill 已经尽可能地在首次加载了,CSS 样式也不需要重复加载。
常见的方式有:
应用间的关系如下图所示(其忽略图中的 “前端微服务化”):
这种方式看上去相当的理想,即能满足多个团队并行开发,又能构建出适合的交付物。
但是,首先它有一个严重的限制:必须使用同一个框架。对于多数团队来说,这并不是问题。采用微服务的团队里,也不会因为微服务这一个前端,来使用不同的语言和技术来开发。当然了,如果要使用别的框架,也不是问题,我们只需要结合上一步中的自制框架兼容应用就可以满足我们的需求。
其次,采用这种方式还有一个限制,那就是:规范!规范!规范!。在采用这种方案时,我们需要:
因此,这种方式看起来更像是一个软件工程问题。
现在,我们已经有了四种方案,每个方案都有自己的利弊。显然,结合起来会是一种更理想的做法。
考虑到现有及常用的技术的局限性问题,让我们再次将目光放得长远一些。
在学习 Web Components 开发微前端架构的过程中,我尝试去写了我自己的 Web Components 框架:oan。在添加了一些基本的 Web 前端框架的功能之后,我发现这项技术特别适合于作为微前端的基石。
Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的 Web 应用中使用它们。
它主要由四项技术组件:
<template>
和 <slot>
元素,用于编写不在页面中显示的标记模板。每个组件由 link
标签引入:
<link rel="import" href="components/di-li.html">
<link rel="import" href="components/d-header.html">
随后,在各自的 HTML 文件里,创建相应的组件元素,编写相应的组件逻辑。一个典型的 Web Components 应用架构如下图所示:
可以看到这边方式与我们上面使用 iframe 的方式很相似,组件拥有自己独立的 Scripts
和 Styles
,以及对应的用于单独部署组件的域名。然而它并没有想象中的那么美好,要直接使用纯 Web Components 来构建前端应用的难度有:
Web Components 中的 ShadowDOM 更像是新一代的前端 DOM 容器。而遗憾的是并不是所有的浏览器,都可以完全支持 Web Components。
Web Components 离现在的我们太远,可是结合 Web Components 来构建前端应用,则更是一种面向未来演进的架构。或者说在未来的时候,我们可以开始采用这种方式来构建我们的应用。好在,已经有框架在打造这种可能性。
就当前而言,有两种方式可以结合 Web Components 来构建微前端应用:
前者是一种组件式的方式,或者则像是在迁移未来的 “遗留系统” 到未来的架构上。
现有的 Web 框架已经有一些可以支持 Web Components 的形式,诸如 Angular 支持的 createCustomElement,就可以实现一个 Web Components 形式的组件:
platformBrowser()
.bootstrapModuleFactory(MyPopupModuleNgFactory)
.then(({injector}) => {
const MyPopupElement = createCustomElement(MyPopup, {injector});
customElements.define(‘my-popup’, MyPopupElement);
});
在未来,将有更多的框架可以使用类似这样的形式,集成到 Web Components 应用中。
另外一种方式,则是类似于 Stencil 的形式,将组件直接构建成 Web Components 形式的组件,随后在对应的诸如,如 React 或者 Angular 中直接引用。
如下是一个在 React 中引用 Stencil 生成的 Web Components 的例子:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import 'test-components/testcomponents';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
在这种情况之下,我们就可以构建出独立于框架的组件。
同样的 Stencil 仍然也只是支持最近的一些浏览器,比如:Chrome、Safari、Firefox、Edge 和 IE11
复合型,对就是上面的几个类别中,随便挑几种组合到一起。
我就不废话了~~。
那么,我们应该用哪种微前端方案呢?答案见下一篇《微前端快速选型指南》
相关资料:
查看原文微前端架构是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。
赞 158 收藏 191 评论 5
仅此而已 回答了问题 · 2019-10-10
redux 只是存在浏览器当前窗口的内存中,当你刷新窗口的时候,浏览器内存重制了,所以是空白没有内容,一般的操作是history传递详情页id,在详情页初始化请求详情接口
redux 只是存在浏览器当前窗口的内存中,当你刷新窗口的时候,浏览器内存重制了,所以是空白没有内容,一般的操作是history传递详情页id,在详情页初始化请求详情接口
关注 4 回答 5
仅此而已 回答了问题 · 2019-10-10
series:[{axisLine:{lineStyle:{color:['#000']}}}]
希望能帮到你
{代码...} 希望能帮到你
关注 2 回答 1
仅此而已 回答了问题 · 2019-09-06
typescript只是类型检查,不会影响js实际运行
typescript只是类型检查,不会影响js实际运行
关注 4 回答 3
仅此而已 发布了文章 · 2019-07-17
Hooks
是 React v16.7.0-alpha
中加入的新特性。它能够让函数组件拥有自己的state
。react 16.8.0
稳定版本支持Hooks
,本文就是演示 Hooks
在项目中的使用示例,对于内部的原理这里就不做详细说明。
import React, { useState } from 'react';
function Example() {
// 声明一个名为“count”的新状态变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Example;
import React, { useEffect } from 'react';
function Example() {
//生命周期中的componentDidMount
useEffect(() => {
console.log('componentDidMount')
},[]);
//生命周期中的componentDidMount
useEffect(() => {
console.log('componentDidMount')
return ()=>{ //componentWillUnmount
console.log('componentWillUnmount')
}
},[]);
//生命周期中的componentDidMount 和 componentDidUpdate
useEffect(() => {
console.log('类似于 componentDidMount 和 componentDidUpdate:')
});
return (
<div></div>
);
}
export default Example;
componentDidMount、componentDidUpdate、componentWillUnmount
的使用方法
import React, { useMemo } from 'react';
export default ({a}) => {
const exampleA = useMemo(() => <div>{a}</div>, [a]); //当a的值 发生变化时候才会渲染
return exampleA
}
import React, { useRef } from 'react';
export default ({a}) => {
const inputEl = useRef(null);
return <input ref={ inputEl } type="text" />
}
import React from 'react';
import { withRouter } from 'react-router-dom';
export default withRouter((props) => {
return <div>{props.match.params.id}</div>
})
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
const mapStateToProps = (state) => {
return {
list:state.list
};
};
const mapDispatchToProps = (dispatch) => {
return {
getList:()=>{},//只是实例使用方式
};
};
const useAddField = (props:Props) => {
useEffect(()=>{
console.log('----------第一次渲染')
this.props.list();
return ()=>{
console.log('-------退出')
}
},[]) //componentDidMount
console.log(props.list) //redux里面的值
return <div></div>
};
export default connect(mapStateToProps, mapDispatchToProps)(useAddField);
参考博客查看原文
Hooks 是 React v16.7.0-alpha 中加入的新特性。它能够让函数组件拥有自己的state。react 16.8.0稳定版本支持Hooks,本文就是演示 Hooks 在项目中的使用示例,对于内部的原理这里就不做详细说明。
赞 2 收藏 1 评论 1
仅此而已 赞了文章 · 2019-07-10
近日看到一篇文章99%的程序都没有考虑的网络异常,开篇提到:
绝大多数程序只考虑了接口正常工作的场景,而用户在使用我们的产品时遇到的各类异常,全都丢在看似 ok 的 try catch 中。如果没有做好异常的兼容和兜底处理,会极大的影响用户体验,严重的还会带来安全和资损风险。
于是,笔者分析了 GitHub 上的一些开源微信小程序,发现大多数的代码异常处理确实是不够的。
//调用登录接口
wx.login({
success: function() {
wx.getUserInfo({
success: function(res) {
that.globalData.userInfo = res.userInfo;
typeof cb == "function" && cb(that.globalData.userInfo);
}
});
}
});
网络请求只考虑then
不考虑catch
util.getData(index_api).then(function(data) {
//this.setData({
//
//});
console.log(data);
});
考虑了异常情况但是没有做妥善的处理
db.collection("config")
.where({})
.get()
.then(res => {
console.log(res);
if (res.data.length > 0) {
Taro.setStorage({
key: "config_gitter",
data: res.data[0]
});
}
})
.catch(err => {
console.error(err);
});
也许 99%的情况下接口都是正常返回的,只有 1%的情况会失败。看起来好像不是一件严重的事情,但是考虑到用户的量级,这个事情就不那么简单了。假设有 100 万用户,那么就有 1 万用户遇到异常情况,而且如果用户的使用频次很高,影响的何止 1 万用户。并且,如今产品都是体验至上,如果遇到这样的问题,用户极大可能就弃你而去,流失了客户就等于流失了收入。
如何妥善地处理接口异常的情况是一件严肃的事情,应当被重视起来。
那么,应当如何做呢?首先要定义请求异常的处理代码,比如微信开放接口的参数中有fail
(“接口调用失败的回调函数”)、Promise 的catch
部分;其次,根据异常可能导致的后果,在函数中做相应的处理。如果会导致后续操作失败、或则界面无反馈,那么应当在 fail 回调中正确处理;如果你真的认为基本不可能出问题,那么至少写个异常上报。即使出错了,也知道具体的情况。
下图是微信支付接口的参数列表,其中包含了接口调用失败的回调函数(fail
)。
而且官方也给出了示例:
wx.requestPayment({
timeStamp: "",
nonceStr: "",
package: "",
signType: "MD5",
paySign: "",
success(res) {},
fail(res) {}
});
fail
中上报异常为了确保完全掌握小程序的运行状况,我们将异常上报。Fundebug 的微信小程序插件除了可以自动捕获异常外,还支持通过API 接口主动上报异常。
根据其官方文档:
使用 fundebug.notify(),可以将自定义的错误信息发送到 Fundebug
name: 错误名称,参数类型为字符串
message: 错误信息,参数类型为字符串
option: 可选对象,参数类型为对象,用于发送一些额外信息
示例:
fundebug.notify("Test", "Hello, Fundebug!", { metaData: { company: "云麒", location: "厦门" } });
首先在 Fundebug 创建一个小程序监控项目,并按照指示接入插件,然后在app.js
的onLaunch
函数下面调用wx.requestPayment
来进行测试。
Fundebug 的微信小程序插件捕获并上报了异常:
在metaData
标签还可以看到我们配置的 metaData,也就是fail
回调函数的res
参数。
因此,我们可以知道失败的原因是订单过期。
另外,如果在二维码页面停留时间过久,也会触发报错:
通过简单的加入几行代码,就可以将小程序的异常情况了如指掌。而且 Fundebug 的微信小程序插件还可以监控线上 JavaScript 执行异常、自动捕获wx.request
请求错误、监控慢 HTTP 请求,推荐大家接入试用!
Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用!
转载时请注明作者 Fundebug以及本文地址:
https://blog.fundebug.com/2019/07/08/report-http-error-by-fundebug-notify/
绝大多数程序只考虑了接口正常工作的场景,而用户在使用我们的产品时遇到的各类异常,全都丢在看似 ok 的 try catch 中。如果没有做好异常的兼容和兜底处理,会极大的影响用户体验,严重的还会带来安全和资损风险。
赞 12 收藏 8 评论 0
仅此而已 发布了文章 · 2019-06-03
配置 router.js
import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom';
const router = [{
path: '/',
exact: true,
component:importPath({
loader: () => import(/* webpackChunkName:"home" */ "pages/home/index.js"),
}),
},]
const Routers = () => (
<main>
<Switch>
{
router.map(({component,path,exact},index)=>{
return <Route exact={exact} path={path} component={component} key={path} />
})
}
</Switch>
</main>
);
export default Routers;
入口 index.js
import {HashRouter} from 'react-router-dom';
import React from 'react';
import ReactDOM from 'react-dom';
import Routers from './router';
ReactDOM.render (
<HashRouter>
<Routers />
</HashRouter>,
document.getElementById ('App')
);
home.js
import { withRouter } from "react-router-dom";
@withRouter
class Home extends React.Component<PropsType, stateType> {
constructor(props: PropsType) {
super(props);
this.state = {};
}
goPath=()=>{
this.props.history.push('/home')
}
render() {
return (
<div onClick={this.goPath}>home</div>
);
}
export default Home;
下面代码中会移除部分的类型检查和提醒代码,突出重点代码
react-router
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
if(call&&(typeof call === "object" || typeof call === "function") ){
return call
}else {
return self
}
}
var Switch = function (_React$Component) {
function Switch() {
//使用传递进来的组件覆盖本身
return _possibleConstructorReturn(this, _React$Component.apply(this, arguments));
}
Switch.prototype.render = function render() {
var route = this.context.router.route;
var children = this.props.children;
var location = this.props.location || route.location;
var match = void 0,child = void 0;
//检查element是否是react组件,初始match为null,
React.Children.forEach(children, function (element) {
//如果match符合,forEach不会进入该if
if (match == null && React.isValidElement(element)) {
var _element$props = element.props,
pathProp = _element$props.path,
exact = _element$props.exact,
strict = _element$props.strict,
sensitive = _element$props.sensitive,
from = _element$props.from;
var path = pathProp || from;
child = element;
//检查当前配置是否符合,
match = matchPath(location.pathname, { path: path, exact: exact, strict: strict, sensitive: sensitive }, route.match);
}
});
//如果有匹配元素,则返回克隆child
return match ? React.cloneElement(child, { location: location, computedMatch: match }) : null;
};
return Switch;
}(React.Component);
总结:switch
根据location.pathname,path,exact,strict,sensitive
获取元素并返回element
react-router
var Route = function (_React$Component) {
function Route() {
var _temp, _this, _ret;
//获取参数
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
//修改this
return _ret = (
_temp = (_this = _possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this),
//检查当前元素是否符合match
_this.state = {match: _this.computeMatch(_this.props,_this.context.router)},_temp),
//这里是真正return
_possibleConstructorReturn(_this, _ret);
}
// 设置content
Route.prototype.getChildContext = function getChildContext() {
return {
router: _extends({}, this.context.router, {
route: {
location: this.props.location || this.context.router.route.location,
match: this.state.match
}
})
};
};
// 根据参数检查当前元素是否符合匹配规则
Route.prototype.computeMatch = function computeMatch(_ref, router) {
var computedMatch = _ref.computedMatch,
location = _ref.location,
path = _ref.path,
strict = _ref.strict,
exact = _ref.exact,
sensitive = _ref.sensitive;
if (computedMatch) return computedMatch;
var route = router.route;
var pathname = (location || route.location).pathname;
return matchPath(pathname, { path: path, strict: strict, exact: exact, sensitive: sensitive }, route.match);
};
// 设置match
Route.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps, nextContext) {
this.setState({
match: this.computeMatch(nextProps, nextContext.router)
});
};
Route.prototype.render = function render() {
var match = this.state.match;
var _props = this.props,
children = _props.children,
component = _props.component,
render = _props.render;
var _context$router = this.context.router,
history = _context$router.history,
route = _context$router.route,
staticContext = _context$router.staticContext;
var location = this.props.location || route.location;
var props = { match: match, location: location, history: history, staticContext: staticContext };
//检查route 是否有component组
if (component) return match ? React.createElement(component, props) : null;
// 检查是否包含render 组件
if (render) return match ? render(props) : null;
// withRouter 使用的方式
if (typeof children === "function") return children(props);
if (children && !isEmptyChildren(children)) return React.Children.only(children);
return null;
};
return Route;
}(React.Component);
总结:route
渲染的方式: component
render
children
,代码示例用的是component
,route
是检查当前组件是否符合路由匹配规则并执行创建过程
react-router-dom
import Router from './Router'
import {createHistory} from 'history'
var HashRouter = function (_React$Component) {
function HashRouter() {
var _temp, _this, _ret;
//参数转换为数组
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (
_temp = (_this = _possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this),
_this.history = createHistory(_this.props), _temp), //创建history
_possibleConstructorReturn(_this, _ret); //真正返回的东西 返回this
}
HashRouter.prototype.render = function render() {
// 返回一个Router,并且把history,children传递给Router
return React.createElement(Router, { history: this.history, children: this.props.children });
};
return HashRouter;
}(React.Component);
总结 通过 history
库里面 createHistory
创建路由系统
react-router
var Router = function (_React$Component) {
function Router() {
var _temp, _this, _ret;
//获取参数,和其他组件一样
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = _possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.state = {
match: _this.computeMatch(_this.props.history.location.pathname) //返回路由对象
}, _temp), _possibleConstructorReturn(_this, _ret); //返回this
}
// 返回context
Router.prototype.getChildContext = function getChildContext() {
return {
router: _extends({}, this.context.router, {
history: this.props.history,
route: {
location: this.props.history.location,
match: this.state.match
}
})
};
};
Router.prototype.computeMatch = function computeMatch(pathname) {
return {
path: "/",
url: "/",
params: {},
isExact: pathname === "/"
};
};
Router.prototype.componentWillMount = function componentWillMount() {
var _this2 = this;
var _props = this.props,
children = _props.children,
history = _props.history;
// 启动监听 当hash 改变是做一次检查,并返回unlisten 取消事件
this.unlisten = history.listen(function () {
_this2.setState({
match: _this2.computeMatch(history.location.pathname)
});
});
};
//销毁前取消监听
Router.prototype.componentWillUnmount = function componentWillUnmount() {
this.unlisten();
};
// children是HashRouter 传递进来的
Router.prototype.render = function render() {
var children = this.props.children;
return children ? React.Children.only(children) : null;
};
return Router;
}(React.Component);
总结 history
是一个JavaScript
库,可让您在JavaScript
运行的任何地方轻松管理会话历史记录。history
抽象出各种环境中的差异,并提供最小的API
,使您可以管理历史堆栈,导航,确认导航以及在会话之间保持状态。
var withRouter = function withRouter(Component) {
var C = function C(props) {
//获取props
var wrappedComponentRef = props.wrappedComponentRef,
remainingProps = _objectWithoutProperties(props, ["wrappedComponentRef"]);
// Route 组件 children方式
return React.createElement(Route, {
children: function children(routeComponentProps) {
// 这里使用的是route 组件 children(props)
//routeComponentProps 实际等于 { match: match, location: location, history: history, staticContext: staticContext };
return React.createElement(Component, _extends({}, remainingProps, routeComponentProps, {
ref: wrappedComponentRef
}));
}
});
};
C.displayName = "withRouter(" + (Component.displayName || Component.name) + ")";
C.WrappedComponent = Component;
// 该类似于object.assign(C,Component),得到的结果是C
return hoistStatics(C, Component);
};
到这里真个流程基本结束了,这只是react-router
的一种使用方式的解析,本文的目的是理解react-router
的运行机制,如果有什么错误还望指出,谢谢🙏
react-router-dom@4.3.0 || react-router@4.4.1 react-router 使用方法 配置 router.js {代码...} 入口 index.js {代码...} home.js {代码...} react-router 源码解析 下面代码中会移除部分的类型检查和提醒代码,突出重点代码 第一步 Switch react-router {代码...}...
赞 1 收藏 1 评论 0
查看全部 个人动态 →
(゚∀゚ )
暂时没有
注册于 2017-04-28
个人主页被 1.4k 人浏览
推荐关注