├── README.md ├── images └── chrome-timing.png ├── mvvm ├── observer.html └── observer.js └── src ├── cross-domain.md ├── css-1px.css ├── debounce_throttle.js ├── flatten.js ├── index.html ├── jsonp.js ├── multi-page-webpack.md ├── net-optimize.md ├── promise.js ├── react-update-log.md ├── reset.css ├── skeleton.md ├── umd.js ├── vue-life-cycle.jpg ├── vue-mixins.md └── vue-optimize.md /README.md: -------------------------------------------------------------------------------- 1 | 7 | ### 将一些记录在印象笔记上的零散知识点代码自我实现下汇总过来 8 | 9 | + [移动端真机调试之spy-debugger](https://github.com/lusteng/daily-notes/issues/1) 10 | 11 | + [jsonp 封装](https://github.com/lusteng/daily-notes/blob/master/src/jsonp.md) 12 | 13 | + [取自淘宝的reset css](https://github.com/lusteng/daily-notes/blob/master/src/reset.css) 14 | 15 | + [1px解决方案](https://github.com/lusteng/daily-notes/blob/master/src/css-1px.css) 16 | 17 | + [封装amd、commonJs、window导出](https://github.com/lusteng/daily-notes/blob/master/src/umd.js) 18 | 19 | + [react 组件监听router变化方法](https://github.com/lusteng/daily-notes/issues/2) 20 | 21 | + [节流、防抖函数实现](https://github.com/lusteng/daily-notes/blob/master/src/debounce_throttle.js) 22 | 23 | + [promise简版实现](https://github.com/lusteng/daily-notes/blob/master/src/promise.js) 24 | 25 | + [扁平化数组处理](https://github.com/lusteng/daily-notes/blob/master/src/flatten.js) 26 | 27 | + [分析Chrome调试面板timing来优化用页面性能](https://github.com/lusteng/daily-notes/blob/master/src/net-optimize.md) 28 | 29 | + [vue 生命周期讲解图示](https://github.com/lusteng/daily-notes/blob/master/src/vue-life-cycle.jpg) 30 | 31 | + [vue mixins 提取不同组件的公共处理事务](https://github.com/lusteng/daily-notes/blob/master/src/vue-mixins.md) 32 | 33 | + [骨架屏](https://github.com/lusteng/daily-notes/blob/master/src/skeleton.md) 34 | 35 | + [多页面打包](https://github.com/lusteng/daily-notes/blob/master/src/multi-page-webpack.md) 36 | 37 | + [优化vue打包文件过大](https://github.com/lusteng/daily-notes/blob/master/src/vue-optimize.md) 38 | 39 | + [react项目版本升级踩坑日志](https://github.com/lusteng/daily-notes/blob/master/src/react-update-log.md) 40 | 41 | + [跨域方式实现总结](https://github.com/lusteng/daily-notes/blob/master/src/cross-domain.md) 42 | 43 | + [模拟实现Vue数据双向绑定响应](./mvvm/observer.js) -------------------------------------------------------------------------------- /images/chrome-timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lusteng/daily-notes/0fc652b7e510ea7c3b63865ca6c4e04dcfd07e66/images/chrome-timing.png -------------------------------------------------------------------------------- /mvvm/observer.html: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Document 19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 | 50 | 51 | -------------------------------------------------------------------------------- /mvvm/observer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Demo mvvm/observer.js 3 | * @Description: 模拟vue mvvm 实现流程 4 | */ 5 | 6 | /** 7 | * @description: 创建个vue实例 8 | */ 9 | class Vue{ 10 | constructor(options){ 11 | if(options.data && typeof options.data === 'function'){ 12 | this._data = options.data.call(this) 13 | new Observer(this._data) 14 | }else{ 15 | throw new Error('data must is function') 16 | } 17 | } 18 | // 绑定数据 19 | mounted(){ 20 | let vm = this 21 | new Watcher(vm, this.render) 22 | } 23 | // vue render 触发数据get 24 | render(){ 25 | let vm = this 26 | // 假装访问了该data中属性 27 | for(let key in vm._data){ 28 | vm._data[key] 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * @description: 观察者 将传入的数据改造为可侦测数据 35 | * @param datas vue 数据对象 36 | * @return: 37 | */ 38 | class Observer{ 39 | constructor(datas){ 40 | // 使用Object.defineProperty 转化可监测 41 | this.defReactive(datas) 42 | } 43 | defReactive(datas){ 44 | for(let key in datas){ 45 | let val = datas[key] 46 | // 每个订阅者数据下面建立一个专有dep,负责收集依赖和通知依赖变化 47 | let dep = new Dep() 48 | Object.defineProperty(datas, key, { 49 | enumerable: true, 50 | configurable: true, 51 | get(){ 52 | // 加入当前依赖到dep 53 | dep.depend() 54 | return val 55 | }, 56 | set(newVal){ 57 | if(newVal === val) return 58 | // 当前dep通知变更 59 | dep.notify(key, newVal) 60 | } 61 | }) 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * @description: 依赖收集器,分发变更给订阅者 68 | * @param {type} 69 | * @return: 70 | */ 71 | const dep_id = 0 72 | class Dep{ 73 | constructor(){ 74 | // 订阅者列表 75 | this.subs = [] 76 | // 假装个id查看下当前对象内存 77 | this.id = dep_id 78 | dep_id ++ 79 | } 80 | depend(){ 81 | // 将当前watcher加入订阅者列表 82 | if(Dep.target) Dep.target.addSub(this) 83 | } 84 | notify(key, val){ 85 | // 触发当前dep中的订阅者更新 86 | this.subs.forEach(sub => { 87 | sub.update(key, val) 88 | }) 89 | } 90 | } 91 | 92 | /** 93 | * @description: 订阅者 更新界面 94 | * @param vm vue 对象 95 | * @param fn render 函数 96 | */ 97 | class Watcher{ 98 | constructor(vm, fn){ 99 | this.vm = vm 100 | this.fn = fn 101 | Dep.target = this 102 | fn.call(vm) 103 | Dep.target = null 104 | } 105 | addSub(dep){ 106 | dep.subs.push(this) 107 | } 108 | update(key, val){ 109 | console.log(`data的${key}更新成${val}了~~~`) 110 | // 触发render函数,在render的过程再次触发了对应key的get 111 | this.fn.call(this.vm) 112 | } 113 | 114 | } 115 | 116 | 117 | /** 118 | * @flow: 119 | * 1. Vue 对象初始化阶段(beforeMount之前),将data数据通过(Observer 观察者对象)Object.defineProperty转化成可检测数据模型 120 | * 2. Vue 对象挂载数据阶段(beforeMount之后,mounted之前)初始化Watcher对象(界面使用数据的依赖) 121 | * tips: Watcher初始化在Observer之后,防止数据还没挂载到界面就触发了watcher update,使用Dep.target存储watcher对象 122 | * 3. get 数据,触发Dep对象(负责收集依赖,管理订阅通知)depend将watcher对象存入subs列表(订阅者列表) 123 | * 4. 界面操作数据,触发set ,Dep发送notify,触发watcher的update,更新Vue的render函数,同时又一次触发get,再次将 124 | * 此次的watcher存入Dep的subs列表,以此类推 125 | */ -------------------------------------------------------------------------------- /src/cross-domain.md: -------------------------------------------------------------------------------- 1 | ### 序言 2 | --- 3 | 某天,和某大大交流技术的时候,被问到能列出多少种解决跨域方式时,心里巴拉巴拉了一顿,除了日常工作中用到的JSONP(早期工作)、CORS、代理,突然发现能说上来的很少呀!于是乎感觉来总结一番。。。 4 | 5 | ### 何为跨域? 6 | 跨域源自早期浏览器的[同源策略](https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy),即协议 + 域名 + 端口三者相同,不同源之间的的脚本或文档、http请求不能相互访问。只有以下三个标签允许跨域加载资源: 7 | + \ 8 | + \ 9 | + \ 60 | 61 | // b 页面 4000 62 | // 监听message 63 | window.onmessage = function(e) { 64 | console.log(e.data) // are you ok? 65 | e.source.postMessage('ok', e.origin) 66 | } 67 | ``` 68 | 69 | 5. websocket 70 | > WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了 71 | 72 | ```html 73 | // socket.html 74 | 83 | ``` 84 | 85 | ```js 86 | // node server.js 87 | const Koa = require('koa'), 88 | route = require('koa-route'), 89 | websockify = require('koa-websocket'); 90 | 91 | const app = websockify(new Koa()); 92 | app.ws.use(function (ctx, next) { 93 | return next(ctx); 94 | }); 95 | 96 | // Using routes 97 | app.ws.use(route.all('/test', function (ctx) { 98 | ctx.websocket.on('message', function (message) { 99 | console.log(message); 100 | ctx.websocket.send('yes'); 101 | }); 102 | })); 103 | 104 | app.listen(3000); 105 | 106 | ``` 107 | 6. window.name + iframe 108 | > 巧妙利用window.name(只能window.name属性)这个属性可以通过iframe切换时保留下来,然后可以通过本域页面访问到window.name 这个变量来绕过跨域机制 109 | 110 | ```html 111 | 112 | 113 | 114 | 115 | 116 | 130 | 131 | 132 | 133 | ``` 134 | 135 | ```html 136 | 137 | 138 | 144 | ``` 145 | 146 | 7. document.domain + iframe 147 | > 通过js将两个子域设置相同的主域,可以访问其他二级域名的变量,适合同一主域下的二级域名的情况,比如a.test.com 和 b.test.com 148 | 149 | ```html 150 | 151 | 152 | 153 | 154 | 162 | ``` 163 | ```html 164 | 165 | 166 | 171 | ``` 172 | 173 | -------------------------------------------------------------------------------- /src/css-1px.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @Description: css 移动端1px解决方案 3 | * 1、缩放方案 伪类 + scale 4 | * 2、渐变背景色方案 5 | * 3、阴影方案 (部分设备容易border消失) 6 | * 4、背景图片方案(可维护性差) 7 | * 5、svg方案(代码复杂度高) 8 | * 6、设置viewport的scacle值,dpr2下设置为0.5,dpr2下设置为0.333(侵入性过高,不推荐) 9 | **/ 10 | 11 | /* 参考 https://github.com/dengwb1991/owl-ui/blob/master/src/styles/common/border.less */ 12 | /* 线上实例 http://www.liubaitong.com/1px/index.html */ 13 | div { 14 | width: 80vw; 15 | height: 80px; 16 | line-height: 80px; 17 | margin: 30px auto; 18 | background-color: #f0f0f0; 19 | box-sizing: border-box; 20 | text-indent: 2em; 21 | } 22 | 23 | .dpr::after{ 24 | content: "1" 25 | } 26 | 27 | /*border-top:1px*/ 28 | .border_normal, 29 | /* 缩放方案 */ 30 | .border_scale, 31 | /* 渐变背景色方案 */ 32 | .border_gradient, 33 | /* 阴影方案 */ 34 | .border_boxshadow, 35 | /* 背景图片方案 */ 36 | .border_base64, 37 | /* svg方案 */ 38 | .border_svg{ 39 | border-top: 1px solid #999; 40 | } 41 | 42 | /* dpr2 */ 43 | @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) { 44 | .dpr::after{ 45 | content: "2" 46 | } 47 | .border_gradient { 48 | background-image: linear-gradient(to top, transparent 50%, #999 50%); 49 | background-size: 100% 1px; 50 | background-repeat: no-repeat; 51 | background-position: top center; 52 | border-top: 0 none; 53 | padding-top: 1px; 54 | } 55 | 56 | .border_scale { 57 | position: relative; 58 | padding-top: 1px; 59 | border-top: 0 none; 60 | } 61 | .border_scale::after { 62 | content: ""; 63 | position: absolute; 64 | top: 0; 65 | left: 0; 66 | width: 200%; 67 | height: 200%; 68 | border: 1px solid #999; 69 | border-radius: 10px; 70 | transform: scale(0.5); 71 | transform-origin: 0 0; 72 | box-sizing: border-box; 73 | } 74 | .border_boxshadow{ 75 | border-top: 0 none; 76 | box-shadow: 0 -1px 1px -1px red, 77 | 1px 0 1px -1px red, 78 | 0 1px 1px -1px red, 79 | -1px 0 1px -1px red; 80 | } 81 | .border_base64 { 82 | padding-top: 1px; 83 | border-top: 0 none; 84 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAQSURBVBhXY5g5c+Z/BhAAABRcAsvqBShzAAAAAElFTkSuQmCC); 85 | background-position: 0 0; 86 | background-repeat: repeat-x; 87 | background-size: 1px 1px; 88 | } 89 | 90 | .border_svg { 91 | border-top: 0 none; 92 | background-image: url("data:image/svg+xml;utf8,"); 93 | background-position:0 0; 94 | background-repeat:no-repeat; 95 | } 96 | } 97 | 98 | /* dpr3 */ 99 | @media only screen and (-webkit-min-device-pixel-ratio: 3), only screen and (min-device-pixel-ratio: 3) { 100 | .dpr::after{ 101 | content: "3" 102 | } 103 | .border_gradient { 104 | background-image: linear-gradient(to top, transparent 66.66%, #999 66.66%); 105 | background-size: 100% 1px; 106 | background-repeat: no-repeat; 107 | background-position: top center; 108 | border-top: 0 none; 109 | padding-top: 1px; 110 | } 111 | 112 | .border_scale { 113 | position: relative; 114 | padding-top: 1px; 115 | border-top: 0 none; 116 | } 117 | .border_scale::after { 118 | content: ""; 119 | position: absolute; 120 | top: 0; 121 | left: 0; 122 | width: 300%; 123 | height: 300%; 124 | border: 1px solid #999; 125 | border-radius: 15px; 126 | transform: scale(0.333); 127 | transform-origin: 0 0; 128 | box-sizing: border-box; 129 | } 130 | .border_boxshadow{ 131 | border-top: 0 none; 132 | box-shadow: 0 -1px 1px -1px red, 133 | 1px 0 1px -1px red, 134 | 0 1px 1px -1px red, 135 | -1px 0 1px -1px red; 136 | } 137 | .border_base64 { 138 | padding-top: 1px; 139 | border-top: 0 none; 140 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAQSURBVBhXY5g5c+Z/BhAAABRcAsvqBShzAAAAAElFTkSuQmCC); 141 | background-position: 0 0; 142 | background-repeat: repeat-x; 143 | background-size: 1px 1px; 144 | } 145 | 146 | .border_svg { 147 | border-top: 0 none; 148 | background-image: url("data:image/svg+xml;utf8,"); 149 | background-position:0 0; 150 | background-repeat:no-repeat; 151 | } 152 | } -------------------------------------------------------------------------------- /src/debounce_throttle.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @desc 防抖函数(某段时间内只能执行一次)与节流函数(两次执行的间隔时间,频率) 4 | * @diff 防抖函数:初次调用了setTimeout后,若还继续触发则一直不执行fn函数(每次连续触发段内只能执行一次) 5 | * @diff 节流函数:初次调用了setTimeout后,若还继续触发则再次调用setTimeout(频率) 6 | * / 7 | 8 | /** 9 | * @desc 防抖 10 | * @param fn 调用函数 11 | * @param wait 延迟执行时间 ms 12 | * @param immediate 是否立即执行 bool true 是 false 否 13 | */ 14 | 15 | /** 16 | * @desc 防抖 17 | * @param fn 调用函数 18 | * @param wait 延迟执行时间 ms 19 | * @param immediate 是否立即执行 bool true 是 false 否 20 | */ 21 | 22 | function debounce(fn, wait = 1000, immediate = false){ 23 | let timeout 24 | 25 | return function(){ 26 | let _context = this, 27 | arg = arguments 28 | /********** 29 | * !!!防抖和节流最大区别 !!! 30 | * 防抖只允许连续动作内执行一次,若动作一直触发则该次后续触发的函数不执行,故此处清除timeout 31 | * 节流在连续动作内降低执行频率,一直触发则按wait参数频率触发setTimeout执行,此处节流和防抖最大区别处也是执行代码最大区别处 32 | ************/ 33 | timeout && clearTimeout(timeout) 34 | 35 | if(immediate){ //立即执行 36 | let canRun = !timeout 37 | //wait时间后timeout为空,fn可再次执行 38 | timeout = setTimeout(() => { 39 | timeout = null 40 | }, wait) 41 | 42 | canRun && fn.apply(_context, arg) 43 | }else{ 44 | timeout = setTimeout(() => { 45 | fn.apply(_context, arg) 46 | }, wait) 47 | } 48 | } 49 | } 50 | 51 | // 测试用例 52 | let fun = function(){ 53 | console.log(333333333) 54 | } 55 | let test = debounce(fun, 2000, true) 56 | setInterval(() => { 57 | test() 58 | }, 100) 59 | 60 | /** 61 | * @desc 节流 62 | * @param fn 调用函数 63 | * @param interval 执行间隔时间 ms 64 | * @param immediate 时间段起始执行还是末位执行 bool true 起始 false 末位 65 | */ 66 | 67 | function throttle(fn, interval = 300, immediate = false){ 68 | let 69 | timeout, 70 | st = 0 71 | 72 | return function () { 73 | let _context = this, 74 | args = arguments 75 | 76 | if(immediate){ //时间段开头执行 77 | let nt = + new Date(); 78 | if(nt - st > interval){ 79 | fn.apply(_context, args) 80 | st = nt 81 | } 82 | }else{ //时间段末位执行 83 | if(!timeout){ 84 | timeout = setTimeout(() => { 85 | fn.apply(_context, args) 86 | timeout = null 87 | }, interval) 88 | } 89 | 90 | } 91 | } 92 | } 93 | 94 | 95 | 96 | //工具包 97 | // lodash https://github.com/lodash/lodash 98 | // underscore https://github.com/jashkenas/underscore 99 | -------------------------------------------------------------------------------- /src/flatten.js: -------------------------------------------------------------------------------- 1 | // 扁平化数组,处理数据时常用 2 | // const arr = [1, [[2], 3, 4], [22,12,[22]],5]; 3 | const arr = [1, [[2, [3232, [444]]], 3, 4], [{"aaa": "fff"},12,['ggg']],5]; 4 | 5 | function flatten(arr){ 6 | let res = [] 7 | arr.forEach(item => { 8 | if(Array.isArray(item)){ 9 | res = res.concat(flatten(item)) 10 | }else{ 11 | res.push(item) 12 | } 13 | }) 14 | return res 15 | } 16 | 17 | // 仅针对数组项为number类型 18 | function flatten(arr){ 19 | return arr.toString().split(',').map(item => { 20 | return Number(item) 21 | }) 22 | } 23 | 24 | /** 25 | * 巧妙应用apply 26 | * Array.prototype.concat每次调用apply,将一维数组当参数执行concat连接 27 | * 使用some检测存在是否存在嵌套数组(concat每次只能连接一维数组) 28 | */ 29 | function flatten(arr){ 30 | while(arr.some(item => Array.isArray(item))){ 31 | arr = Array.prototype.concat.apply([], arr) 32 | } 33 | return arr 34 | } 35 | 36 | /** 37 | * 使用Array reduce 方法, reduce 传入两个参数 38 | * @param1 fn 自定义的函数,函数提供四个参数 39 | accumulator 上次循环返回的值,初始为数组第一项或者reduce 的参数2; 40 | currentValue 数组正在处理的元素 41 | currentIndex 数组中正在处理的当前元素的索引 42 | array 调用reduce()的数组 43 | * @param2 initialValue 为第一个函数参数 accumulator的初始值,不提供则以数组第一项为初始值 44 | */ 45 | function flatten(arr){ 46 | return arr.reduce((prev, cur) => prev.concat(Array.isArray(cur) ? flatten(cur) : cur), []) 47 | } 48 | 49 | let res = flatten(arr) 50 | 51 | console.log(res); -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 测试 6 | 7 | 8 | 9 |
fsdfkdsjlfjdsfklj
10 | 23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /src/jsonp.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * 3 | */ 4 | 5 | // jsonp 原理 (利用请求script资源没有跨域限制) 6 | // 1.客户端(前端)注册一个callback函数 7 | function callback(data){ 8 | //data 操作获取到的数据 9 | } 10 | // 2.服务端将需要返回的json数据作为入参到callback函数,返回给客户端 11 | // 请求跨域资源返回的内容 12 | callback(data) 13 | 14 | 15 | /** 16 | * @param { 17 | * url, // jsonp资源url 18 | * data, // jsonp url search参数 19 | * success, // 成功回调 20 | * error // 失败回调 21 | * } opts 22 | */ 23 | 24 | function jsonp(opts = {}){ 25 | let { 26 | url = "", 27 | data = {}, 28 | success = () => {}, 29 | error = () => {} 30 | } = opts 31 | if(!url) throw new Error("please pass in url params") 32 | // 随机名 33 | let radomName = `id_${+ new Date()}_${Math.random().toString().substr(2)}` 34 | // script 标签 35 | let urlParamsMerge = Object.assign({}, data, {callback: radomName}) 36 | let scriptEle = document.createElement('script') 37 | 38 | for(let u in urlParamsMerge){ 39 | url += url.indexOf('?') > -1 ? `&${u}=${ (urlParamsMerge[u])}` : `?${u}=${encodeURIComponent(urlParamsMerge[u])}` 40 | } 41 | 42 | scriptEle.id = radomName 43 | scriptEle.src = url 44 | 45 | // 执行callback回调 46 | window[radomName] = (d) => { 47 | // 清空执行 48 | window[radomName] = undefined 49 | 50 | // 清空script元素 51 | removeNode(document.getElementById(radomName)) 52 | 53 | // 回传数据 54 | success(d) 55 | } 56 | // 错误回调 57 | scriptEle.onerror = error 58 | 59 | // 插入到页面head元素 60 | document.head.appendChild(scriptEle) 61 | 62 | // 移除节点 63 | function removeNode(node){ 64 | let parent = node.parentNode 65 | if(parent && parent.type !== 11){ 66 | parent.removeChild(node) 67 | } 68 | } 69 | } 70 | 71 | 72 | //测试 73 | jsonp({ 74 | url: "https://floor.jd.com/user-v20/hotwords/get", 75 | data: { 76 | source: "pc-home", 77 | pin: "", 78 | uuid: 154512121, 79 | _: 1573462343824 80 | }, 81 | success: function(data){ 82 | console.log(data); 83 | }, 84 | error: function(error){ 85 | console.log(error); 86 | } 87 | }) 88 | 89 | //测试 90 | jsonp({ 91 | url: "https://floor.jd.com/user-v20/hotwords/g", 92 | data: { 93 | source: "pc-home", 94 | pin: "", 95 | uuid: 15451, 96 | _: 1573462343824 97 | }, 98 | success: function(data){ 99 | console.log(data); 100 | }, 101 | error: function(error){ 102 | console.log(error); 103 | } 104 | }) -------------------------------------------------------------------------------- /src/multi-page-webpack.md: -------------------------------------------------------------------------------- 1 | ### 本方案仅用于预研,无实际应用到项目中 2 | > 多页打包主要的两点 3 | 1. entry改为多入口 4 | 2. html-webpack-plugin 插件针对不同入口输出不同页面 5 | 6 | PS:当然还有那些css,js文件的拆分输入 7 | 8 | ### 配置固定多页应用 9 | > 本例中分为front和backend前后两个界面 10 | 11 | 1. 配置入口 12 | ```js 13 | // 改为多入口 14 | entry: { 15 | front: './src/project/front/main.js', 16 | backend: './src/project/backend/main.js', 17 | } 18 | ``` 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/net-optimize.md: -------------------------------------------------------------------------------- 1 | ### 前言 2 | > http加载优化是前端性能优化中的重要一环,有效的优化可以缩短资源加载时间,提升用户体验,那么此处从chrome调试面板的timing切入了解 3 | 4 | ### network timing (资源加载时序) 5 | 6 | 如下图列出一个资源加载的时间线 7 | 8 | 9 | 10 | ### timing 解析 11 | + Queuing(排队) 12 | 13 | 如果一个请求排队,则表明请求被渲染引擎推迟,因为它被认为比关键资源(如脚本/样式)的优先级低。这经常发生在 images(图像) 上 14 | 15 | 请求排队的原因: 16 | 17 | 1. 这个请求被搁置,在等待一个即将被释放的不可用的TCP socket。 18 | 2. 这个请求被搁置,因为浏览器限制。在HTTP 1协议中,每个源上只能有6个TCP连接 19 | 3. 正在生成磁盘缓存条目(通常非常快)。 20 | 21 | + Stalled/Blocking (停止/阻塞) 22 | 23 | 发送请求之前等待的时间。它可能因为进入队列的任意原因而被阻塞。这个时间包括代理协商的时间。 24 | 25 | + Proxy Negotiation (代理协商) 26 | 27 | 与代理服务器连接协商花费的时间 28 | 29 | + DNS Lookup (DNS查找) 30 | 31 | 执行DNS查找所用的时间。 页面上的每个新域都需要完整的往返(roundtrip)才能进行DNS查找。 32 | 33 | + Initial Connection / Connecting (初始连接/连接) 34 | 35 | 建立连接所需的时间, 包括TCP握手/重试和协商SSL。 36 | 37 | + SSL 38 | 39 | 完成SSL握手所用的时间。 40 | 41 | + Request Sent / Sending (请求已发送/正在发送) 42 | 43 | 发出网络请求所花费的时间。 通常是几分之一毫秒。 44 | 45 | + Waiting (TTFB) (等待) 46 | 47 | 等待初始响应所花费的时间,也称为`Time To First Byte`(接收到第一个字节所花费的时间)。这个时间除了等待服务器传递响应所花费的时间之外,还捕获到服务器发送数据的延迟时间。 48 | 49 | + Content Download / Downloading (内容下载/下载) 50 | 51 | 接收响应数据所花费的时间,即从第一个字节开始,到下载完最后一个字节结束所花费的时间。 52 | 53 | 54 | ### 针对timing 产生问题优化 55 | 56 | + Queuing 等待时间过长 57 | 58 | 同域名下的请求数是否过多?发送的http请求数是否过多? 59 | 60 | + Stalled/Blocking 阻塞时间过长 61 | 62 | js性能太差,阻塞页面? 某个请求慢阻塞页面的加载? 63 | 64 | 65 | 66 | + Initial Connection / Connecting 花费时间长 67 | 68 | 69 | + Content Download 内容下载时间过长 70 | 71 | 考虑一些图片、样式资源文件体积过大,考虑优化体积大小 72 | 73 | [分析页面加载慢原因](https://www.jianshu.com/p/24b93b13e5a9) 74 | -------------------------------------------------------------------------------- /src/promise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * promise 核心处理then 3 | * 1.初始触发resolve或者reject函数,改变state,存储函数传参,调用延时队列 4 | * 2.外部调用then方法,存储执行函数队列,返回下一个Promise对象 5 | * 3.异步延时函数调用,处理当前状态下调用的处理函数 触发then的下一步Promise对象的resolve和reject函数调用 6 | */ 7 | 8 | 9 | const PENDING = 'PENDING' 10 | const FULFILLED = 'FULFILLED' 11 | const REJECTED = 'REJECTED' 12 | 13 | class Promise { 14 | constructor(handle){ 15 | if(!Promise._isFunc(handle)){ 16 | throw new Error(`the Promise params must is function, but your params is ${Object.prototype.toString(handle)}`) 17 | return 18 | } 19 | 20 | // 记录状态机 21 | this.status = PENDING 22 | // 记录resolve/reject 传入值 23 | this.data = null 24 | // 状态为pending是置入的回调函数队列 25 | this.cbQueen = [] 26 | 27 | // 首先调用传入promise函数 28 | try { 29 | handle(this.resolve.bind(this), this.reject.bind(this)) 30 | } catch (err) { 31 | // 执行异常,直接切换失败状态 32 | this.reject.call(this, err) 33 | } 34 | } 35 | 36 | static _isFunc(fn){ 37 | return typeof fn === 'function' 38 | } 39 | 40 | resolve(val){ 41 | let _this = this 42 | if(val && (typeof val === 'object' || typeof val === 'function')){ 43 | //返回的是个promise对象 44 | _this.then.call(val, _this.resolve.bind(this), _this.reject.bind(this)) 45 | return 46 | } 47 | _this.data = val 48 | _this.status = FULFILLED 49 | _this._deplayRun() 50 | } 51 | 52 | reject(val){ 53 | let _this = this 54 | _this.data = val 55 | _this.status = REJECTED 56 | _this._deplayRun() 57 | } 58 | 59 | then(resolve, reject){ 60 | let _this = this 61 | return new Promise((nextResolve, nextReject) => { 62 | let cb = { 63 | resolve: resolve ? resolve : (val) => { return val}, 64 | reject: reject ? reject : (val) => { return val }, 65 | nextResolve, 66 | nextReject 67 | } 68 | _this.cbQueen.push(cb) 69 | }) 70 | } 71 | 72 | // 延时:避免promise内部同步代码 73 | _deplayRun(){ 74 | let _this = this 75 | setTimeout(() => { 76 | _this.cbQueen.forEach(cb => { 77 | _this._handleCbQueen(cb) 78 | }) 79 | }) 80 | } 81 | 82 | _handleCbQueen(cb){ 83 | let 84 | _this = this, 85 | data = _this.data, 86 | status = _this.status; 87 | if(status === PENDING){ 88 | return; 89 | } 90 | let curFn = status === FULFILLED ? cb.resolve : cb.reject 91 | let nextFn = status === FULFILLED ? cb.nextResolve : cb.nextReject 92 | if(!curFn){ 93 | //then函数未传递任何东西,直接执行then下一步 94 | nextFn() 95 | return 96 | } 97 | try { 98 | let rt = curFn(data) 99 | cb.nextResolve(rt) 100 | } catch (error) { 101 | cb.nextReject(error) 102 | } 103 | } 104 | 105 | catch(reject){ 106 | return this.then(null, reject) 107 | } 108 | } 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/react-update-log.md: -------------------------------------------------------------------------------- 1 | ### 为什么要升级? 2 | 16版React不仅体积缩小了30%,还新增了碎片(fragments)、错误边界、portals、支持自定义 DOM 属性、Hooks等一些功能强大的新特性啦啦啦 3 | 然鹅公司主业务线由于开展比较早,每当开发新功能却用不上新的强大特性,让人很不服气,这不为了开上新的React的车,组内讨论,决定利用一段时间把主业务线React和架构同时来次大的升级调整 4 | 5 | ### 想法是好的,路是坎坷的,踩坑之路不断~~~ 6 | 升级react、react-dom版本连带产生的问题,落后版本库升级,打包工具升级,解决升级后的打包错误 7 | 8 | + #### webpack2 升级到 webpack4 9 | 1. 增加webpack4 mode development/production 配置项 10 | 11 | 2. loaders 改 rules 12 | 13 | 3. webpack.dll.js 14 | ```js 15 | - new webpack.optimize.OccurenceOrderPlugin() 16 | + new webpack.optimize.OccurrenceOrderPlugin() 17 | 18 | - new webpack.optimize.UglifyJsPlugin() 19 | 20 | + optimization: { 21 | minimizer: [ 22 | new UglifyJsPlugin({ 23 | uglifyOptions: { 24 | compress: false 25 | } 26 | }) 27 | ] 28 | } 29 | 30 | ``` 31 | 4. CSS文件提取插件 32 | mini-css-extract-plugin 替代 extract-text-webpack-plugin 33 | 34 | 5. autoprefixer-loader废弃 35 | 改为 postcss-loader + autoprefxer 36 | ```js 37 | // 根目录新增 postcss.config.js 38 | module.exports= { 39 | plugins: [ 40 | require('autoprefixer')({ 41 | 'browsers': [ 42 | 'defaults', 43 | 'defaults', 44 | 'last 2 versions', 45 | '> 1%', 46 | 'iOS 7', 47 | 'last 3 iOS versions' 48 | ] 49 | }) 50 | ] 51 | } 52 | 53 | // css loader 增加postcss-loader 54 | ``` 55 | 56 | 6. 配置更新 57 | ```js 58 | - resolve: { 59 | root: [ 60 | path.resolve('node_modules') 61 | ] 62 | } 63 | 64 | + 65 | resolve: { 66 | modules: [ 67 | path.resolve('node_modules') 68 | ] 69 | } 70 | ``` 71 | 72 | + ### babel工具 升级 73 | 74 | babel-loader v6 》 v8 75 | 76 | 1. 安装[@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env)插件 77 | > 去掉旧有babel-preset-es2015的babel-preset-esxxxx需要对es6、es7、es8等需要不停安装插件方案 78 | 79 | 2. babel-loader版本搭配错误引发的打包错误 80 | babel-loader@^8 搭配 @babel/x babel7版本工具库 81 | babel-loader@^7及以下 搭配 babel-x babel7版本之前工具库 82 | 83 | 3. 安装 @babel/plugin babel 7以上版本插件 84 | 安装[@babel/plugin-transform-destructuring](https://babeljs.io/docs/en/babel-plugin-transform-destructuring)插件 85 | > 解决 {...this.props} es6解构赋值语法转换 86 | 87 | 最后改后的.babelrc 配置文件 88 | ```js 89 | { 90 | "presets": [ 91 | "@babel/preset-env", 92 | "@babel/preset-react" 93 | ], 94 | "plugins": [ 95 | "react-hot-loader/babel", 96 | "@babel/plugin-proposal-class-properties", 97 | "@babel/plugin-transform-destructuring", 98 | "@babel/plugin-transform-modules-commonjs" 99 | ] 100 | } 101 | 102 | ``` 103 | 104 | 4. babel打包插件报错 105 | ```js 106 | Uncaught TypeError: Cannot assign to read only property 'exports' of object '#' 报错 107 | import 和 module.export不能混用, 安装@babel/plugin-transform-modules-commonjs 插件解决 108 | ``` 109 | 110 | ### react-hot-loader 升级 111 | 1. 打包报错,原因 react-hot-loader 版本过低 112 | ```js 113 | Module not found: Error: Cannot resolve module 'react/lib/ReactMount' 114 | ``` 115 | 116 | ### React 关联包升级 117 | 1. react 与 react-dom对应版本不一致导致报错 118 | ```js 119 | lib.js:formatted:52200 Uncaught TypeError: Cannot read property 'hasOwnProperty' of undefined 120 | at Object. (lib.js:formatted:52200) 121 | ``` 122 | 123 | [stackoverflow解答](https://stackoverflow.com/questions/56003446/uncaught-typeerror-cannot-read-property-hasownproperty-of-undefined-react-dom) 124 | 125 | 2. react-router 与 react对应不一致 导致报错 126 | ```js 127 | "export 'hashHistory' was not found in 'react-router' 128 | ``` 129 | ```js 130 | "export 'Link' was not found in 'react-router' 131 | ``` 132 | 133 | ### React 升级坑解决 134 | 1. 新版不支持prop-types 135 | 解决方案: 136 | + 针对项目代码,引入prop-types第三方包解决 137 | + 针对第三方在更新维护类库,升级到支持16版本react的库 138 | 以antd为例: 升级到v3版本,再针对图标Icon以及其他组件api调整的地方做出修改 139 | + 针对第三方已停止更新类库,使用公司github账号fork版本库,在fork库下面做出修改,再依赖fork的库 140 | 141 | 2. 新版不支持React.createClass 142 | 引入 create-react-class 143 | 144 | 3. 消除不安全生命周期在开发环境的warning 145 | ```js 146 | componentWillMount 147 | componentWillReceiveProps 148 | componentWillUpdate 149 | ``` 150 | 批量添加前缀 UNSAFE_ 151 | 152 | [生命周期更新文档](https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html) 153 | 154 | [react 16 升级指南](https://reactjs.org/blog/2017/09/26/react-v16.0.html) 155 | 156 | 157 | 158 | ### 踩坑之后的收获 159 | > 踩坑之后,整体架构把握更加清晰,加深了对react的一些全局观的认识 160 | 161 | --- 162 | 163 | + ## react-router v4的变迁 164 | react-router v3与v2本质上区别不大,v4版本开始有了本质的变化 165 | 最直观的体现,v4版本的路由使用更加灵活,不再是v3及之前那种僵硬的用法,可以与其他组件和睦相处,不再排它性 166 | ```jsx 167 | import { BrowserRouter, Route } from 'react-router-dom' 168 | 169 | const PrimaryLayout = () => ( 170 |
171 |
172 | Our React Router 4 App 173 |
174 |
175 | 176 | 177 |
178 |
179 | ) 180 | 181 | const HomePage =() =>
Home Page
182 | const UsersPage = () =>
Users Page
183 | 184 | const App = () => ( 185 | 186 | 187 | 188 | ) 189 | 190 | render(, document.getElementById('root')) 191 | ``` 192 | 193 | 1. exact 是否精准匹配(v4 路由可以同时匹配多条路由,而v3只能匹配一条) 194 | 设置exact: 访问/users 只会匹配/users 195 | 不设置exact: 访问/users 只同时匹配/users和/ 196 | 197 | 2. Switch 只能匹配包裹的一条路由 198 | ```jsx 199 | // 增加Switch, 访问/users 只渲染第一个UsersPage组件 200 | // 不增加Switch, 访问/users 同时渲染UsersPage, myPage组件 201 | 202 | import React from 'react' 203 | import ReactDom from 'react-dom' 204 | import { 205 | BrowserRouter, Route, Link, Switch } from 'react-router-dom' 206 | 207 | const PrimaryLayout = () => ( 208 |
209 |
210 | Our React Router 4 App 211 |
212 |
213 | 214 | 215 | 216 |
217 |
218 | ) 219 | 220 | const HomePage =() =>
首页
221 | const UsersPage = () =>
用户页面1
222 | const myPage = () =>
我的页面2
223 | 224 | const App = () => ( 225 | 226 | 227 | 228 | ) 229 | 230 | ReactDom.render(, document.getElementById('app')) 231 | 232 | ``` 233 | 234 | 3. 新增props.match,可以轻松获取路由进入匹配路上后传入的参数 235 | ```jsx 236 | 237 | 238 | //UserProfilePage 组件可以通过 this.props.match.params.userId 获取到进入路由的userId 239 | 240 | ``` 241 | 242 | 4. [借助v4 api 构建具有鉴权的路由](https://css-tricks.com/react-router-4/#article-header-id-8) 243 | 244 | 5. 新增NavLink,可以增加active选中状态 245 | ```jsx 246 | //匹配到/app时增加active class 247 | Home 248 | ``` 249 | 250 | [react-router v4 变更文档](https://css-tricks.com/react-router-4/) 251 | [react-router api 文档](https://www.jianshu.com/p/e3adc9b5f75c) 252 | 253 | + ## react-router-dom 254 | v4 版本只需引入 react-router-dom即可 255 | react-router-dom: 基于react-router,加入了在浏览器运行环境下的一些功能,例如:Link组件,会渲染一个a标签,Link组件源码a标签行; BrowserRouter和HashRouter 组件,前者使用pushState和popState事件构建路由,后者使用window.location.hash和hashchange事件构建路由。 256 | react-router: 实现了路由的核心功能 257 | react-router-native: 基于react-router,类似react-router-dom,加入了react-native运行环境下的一些功能。 258 | 259 | ---- 260 | 261 | + ## redux 架构 262 | > reduce的作用,庞大的单页面应用管理数据困难,数据的变化不可预测,不可溯源,兄弟组件间数据影响困难,reduce构建一个树形store,将数据来源单一性,变化可预测性,适用于大型应用的数据管理 263 | 264 | 结构: Reducer、Action、Store 三部分构成 265 | Store 管理着数据源state, 每个reduce只有一个树状数据源 266 | Action 是改变数据的唯一外在接口 267 | Reducer 根据Action描述的type来改变的数据state,reducer是定义一系列纯函数 268 | 269 | + ## react-reduce 270 | > 连接react和reduce的桥梁 271 | + react 连接 reduce (react-redux) 通过容器组件将store传入到react的展示型组件 272 | + 根组件处 通过的react-redux提供的Provider 高阶组件,将Redux和React绑定在一起 273 | + 通过mapStateToProps 将redux数据中心store state映射到展示组件的props中 274 | ```jsx 275 | const mapStateToProps = state => { 276 | return { 277 | todos: state.todos 278 | } 279 | } 280 | ``` 281 | + 通过mapDispatchToProps 将redux的dispatch方法接入到展示组件的props中 282 | ``` 283 | const mapDispatchToProps = dispatch => { 284 | return { 285 | onTodoClick: id => { 286 | dispatch(toggleTodo(id)) 287 | } 288 | } 289 | } 290 | ``` 291 | + 使用connect 连接容器组件和展示组件 292 | ```jsx 293 | 294 | import { connect } from 'react-redux' 295 | 296 | export default connect( 297 | mapStateToProps, 298 | mapDispatchToProps 299 | )(ScdnBindWidthTraffic) 300 | 301 | ``` 302 | + 展示型组件可以通过this.props.dispatch 调用action 改变reduce的state 303 | 定义action 处理函数不单要定义action,还要有个专门的字典对应不同action的type 304 | 305 | + 使用createStore让所有容器组件可以访问的store,在根组件操作 306 | ```jsx 307 | import React from 'react' 308 | import { render } from 'react-dom' 309 | import { Provider } from 'react-redux' 310 | import { createStore } from 'redux' 311 | import todoApp from './reducers' 312 | import App from './components/App' 313 | 314 | let store = createStore(todoApp) 315 | 316 | render( 317 | 318 | 319 | , 320 | document.getElementById('root') 321 | ) 322 | ``` 323 | 324 | + 将异步请求变成同步改变action 325 | 针对异步请求的三种状态 pedding fulfilled rejected 分别对应三种状态,处理不同的数据流向 XXX_REQUEST XXX_SUCCESS XXX_FAILURE 变成一个同步处理异步改变数据的状态 326 | 327 | [react-redux 文档](https://cn.redux.js.org/docs/advanced/UsageWithReactRouter.html) 328 | [redux介绍](https://www.jianshu.com/p/53faad8a152a) 329 | 330 | ---- 331 | + ## react 16 新特性 332 | 333 | [react 更新日志](https://github.com/facebook/react/blob/master/CHANGELOG.md#1600-september-26-2017) 334 | 335 | 1. render 支持返回数组和字符串 336 | ``` 337 | // 不需要再将元素作为子元素装载到根元素下面 338 | render() { 339 | return [ 340 |
  • 1
  • , 341 |
  • 2
  • , 342 |
  • 3
  • , 343 | ]; 344 | //or 345 | return "render string" 346 | } 347 | 348 | 2. 错误边界,不会出现v15版本出现错误导致整个组件树报错 349 | [错误边界解说](https://zh-hans.reactjs.org/docs/error-boundaries.html) 350 | 351 | 捕获到子组件错误后使用 static getDerivedStateFromError() 渲染备用UI 352 | componentDidCatch() 打印错误信息 353 | 354 | ```js 355 | 356 | class ErrorBoundary extends React.Component { 357 | constructor(props) { 358 | super(props); 359 | this.state = { hasError: false }; 360 | } 361 | 362 | static getDerivedStateFromError(error) { 363 | // 更新 state 使下一次渲染能够显示降级后的 UI 364 | return { hasError: true }; 365 | } 366 | 367 | componentDidCatch(error, errorInfo) { 368 | // 你同样可以将错误日志上报给服务器 369 | logErrorToMyService(error, errorInfo); 370 | } 371 | 372 | render() { 373 | if (this.state.hasError) { 374 | // 你可以自定义降级后的 UI 并渲染 375 | return "Something went wrong."; 376 | } 377 | 378 | return this.props.children; 379 | } 380 | } 381 | 382 | // 控制错误边界捕获的父级组件 383 | 384 | 385 | 386 | 387 | ``` 388 | [错误边界使用代码示例](https://codepen.io/gaearon/pen/wqvxGa?editors=0010) 389 | 390 | 3. createPortal(插槽) 将组件挂载到非根元素的其他dom中,最适合弹窗的场景 391 | 392 | [代码解析](https://zh-hans.reactjs.org/docs/portals.html) 393 | 394 | ```js 395 | render() { 396 | // React 并*没有*创建一个新的 div。它只是把子元素渲染到 `domNode` 中。 397 | // `domNode` 是一个可以在任何位置的有效 DOM 节点。 398 | return ReactDOM.createPortal( 399 | this.props.children, 400 | domNode 401 | ); 402 | } 403 | ``` 404 | 405 | 4. Fiber(分片) 406 | Fiber 是对 React 核心算法的一次重新实现,将原本的同步更新过程碎片化,避免主线程的长时间阻塞,使应用的渲染更加流畅。 407 | 408 | 5. Fragment(空标签) 409 | 改变15版本的,数组子元素需要用个额外标签包裹 410 | 411 | ```html 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 |
    aaabbbcccddd
    425 | 426 | 实际渲染dom: 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 |
    aaabbbcccddd
    437 | 438 | ``` 439 | 440 | 6. createRef / forwardRef (新的定义ref的方式) 441 | ```js 442 | 443 | // before React 16 444 | componentDidMount() { 445 | const el = this.refs.myRef 446 | } 447 | 448 | render() { 449 | return
    450 | } 451 | 452 | // React 16+ 453 | constructor(props) { 454 | super(props) 455 | 456 | this.myRef = React.createRef() 457 | } 458 | 459 | //访问dom this.myRef.current 460 | 461 | render() { 462 | return
    463 | } 464 | ``` 465 | 466 | forwardRef 多使用在高阶组件或者父组件获取子组件dom元素 467 | 468 | ```js 469 | const FancyButton = React.forwardRef((props, ref) => ( 470 | 473 | )); 474 | 475 | class Parent extends React.Component{ 476 | 477 | constructor(){ 478 | super(); 479 | this.divRef = React.createRef() 480 | } 481 | 482 | // this.divRef.current FancyButton组件内容 483 | 484 | render(){ 485 | return
    486 | 489 | aaaaaaaaaaaaaaaaa 490 | 491 |
    492 | } 493 | } 494 | ``` 495 | [参考文档](https://zh-hans.reactjs.org/docs/react-api.html#reactcreateref) 496 | 497 | 7. React.lazy(异步加载组件) 498 | ```js 499 | const OtherComponent = React.lazy(() => import('./OtherComponent')); 500 | ``` 501 | [参考文档](https://zh-hans.reactjs.org/docs/code-splitting.html#reactlazy) 502 | 503 | 504 | 8. 新增生命周期 505 | + getDerivedStateFromProps (从props的值映射state) 506 | 取代 componentWillMount componentWillReceiveProps 507 | 一个静态的纯函数生命周期,根据props返回state的映射 508 | 509 | ```js 510 | //纯函数,不要做什么额外的副作用改变 511 | static getDerivedStateFromProps(props, state){ 512 | if(props.test !== state.testState){ 513 | // 映射改变testState的state值 514 | return { 515 | testState: props.tes + 'a' 516 | } 517 | }else{ 518 | // 当不做任何改变时,返回null 519 | return null 520 | } 521 | } 522 | ``` 523 | 524 | + getSnapshotBeforeUpdate(获取变化更新前的快照) 525 | 取代 componentWillUpdate 526 | 配合componentDidUpdate使用 527 | 528 | ```js 529 | getSnapshotBeforeUpdate(prevProps, prevState) { 530 | if (prevProps.test < this.props.test) { 531 | return prevProps.test + 10; 532 | } 533 | return null; 534 | } 535 | // snapshot获取到getSnapshotBeforeUpdate的返回值 536 | componentDidUpdate(prevProps, prevState, snapshot) { 537 | // 此处可以根据snapshot做一些操作 538 | } 539 | 540 | ``` 541 | [参考文档](https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate) 542 | 543 | 9. Hook 544 | 替代class 使用函数式书写组件 545 | 546 | + useState 为组件添加state值, 第一个参数为state值,第二个参数为设置state值的函数,相当于setState 547 | 548 | ```js 549 | import React, { useState } from 'react' 550 | const HomePage =(props) => { 551 | const [count, setCount] = useState(11) 552 | 553 | return
    554 | 555 |

    展示修改的值{count}

    556 |
    557 | } 558 | ``` 559 | 560 | + useEffect 集合了react 生命周期的作用, 每次组件更新会调用useEffect 561 | class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途 562 | 563 | ```js 564 | const HomePage =(props) => { 565 | const [count, setCount] = useState(11) 566 | const [val, setVal] = useState('my val') 567 | 568 | useEffect(() => { 569 | document.title = val + count 570 | }) 571 | 572 | return
    573 | 574 |

    展示修改的值{count}

    575 |
    576 | } 577 | ``` 578 | 579 | + 自定义Hook 580 | 一种官方约定: 函数名以use 开头,并调用其他hook,则认为是一种自定义hook 581 | 582 | ### --save 与 --save-dev 区别 583 | 踩完坑对这俩有了更准确的理解, dev只安装develop环境需要的插件,一般是打包工具,换言之,该部分插件不会出现在生产环境上,而--save则是生产和开发环境都会用到,通常是react这类的功能代码库 584 | 585 | 更多其他hook,参见官方文档 586 | [hook文档](https://zh-hans.reactjs.org/docs/hooks-overview.html) 587 | 588 | 589 | 590 | 591 | 592 | 593 | -------------------------------------------------------------------------------- /src/reset.css: -------------------------------------------------------------------------------- 1 | 2 | /* 附带另一个比较全的方案 https://meyerweb.com/eric/tools/css/reset/ */ 3 | 4 | /** 清除内外边距 **/ 5 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */ 6 | dl, dt, dd, ul, ol, li, /* list elements 列表元素 */ 7 | pre, /* text formatting elements 文本格式元素 */ 8 | form, fieldset, legend, button, input, textarea, /* form elements 表单元素 */ 9 | th, td /* table elements 表格元素 */ { 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | /** 设置默认字体 **/ 15 | body, 16 | button, input, select, textarea /* for ie */ { 17 | font: 12px/1.5 tahoma, arial, \5b8b\4f53, sans-serif; 18 | } 19 | h1, h2, h3, h4, h5, h6 { font-size: 100%; } 20 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */ 21 | code, kbd, pre, samp { font-family: courier new, courier, monospace; } /* 统一等宽字体 */ 22 | small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */ 23 | 24 | /** 重置列表元素 **/ 25 | ul, ol { list-style: none; } 26 | 27 | /** 重置文本格式元素 **/ 28 | a { text-decoration: none; } 29 | a:hover { text-decoration: underline; } 30 | 31 | 32 | /** 重置表单元素 **/ 33 | legend { color: #000; } /* for ie6 */ 34 | fieldset, img { border: 0; } /* img 搭车:让链接里的 img 无边框 */ 35 | button, input, select, textarea { font-size: 100%; } /* 使得表单元素在 ie 下能继承字体大小 */ 36 | /* 注:optgroup 无法扶正 */ 37 | 38 | /** 重置表格元素 **/ 39 | table { border-collapse: collapse; border-spacing: 0; } 40 | 41 | /* 清除浮动 */ 42 | .ks-clear:after, .clear:after { 43 | content: '\20'; 44 | display: block; 45 | height: 0; 46 | clear: both; 47 | } 48 | 49 | .ks-clear, .clear { 50 | *zoom: 1; 51 | } -------------------------------------------------------------------------------- /src/skeleton.md: -------------------------------------------------------------------------------- 1 | ### 骨架屏原理 2 | > 针对spa项目刷新载入时加载过慢,容易出现白屏,给页面加上一层loading状态,提升用户体验 3 | 4 | 1. 资源载入下来获取页面大致dom结构 5 | 2. 将自定义loading样式(或者图片)写到对应需要展示骨架屏样式的元素上 6 | 3. 资源加载下来移除骨架屏 7 | 8 | 9 | ### 骨架屏分类 10 | ***** 11 | + ### 侵入式 12 | > 提前感知要植入骨架的页面,将提前针对性写好的骨架屏代码植入 13 | ```html 14 | // html 针对已知的页面写好骨架植入 15 |
    16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    22 | 23 | // css 24 | .skeleton { 25 | padding: 10px; 26 | } 27 | 28 | .skeleton .skeleton-head, 29 | .skeleton .skeleton-title, 30 | .skeleton .skeleton-content { 31 | background: rgb(194, 207, 214); 32 | } 33 | 34 | .skeleton-head { 35 | width: 100px; 36 | height: 100px; 37 | float: left; 38 | } 39 | 40 | .skeleton-body { 41 | margin-left: 110px; 42 | } 43 | 44 | .skeleton-title { 45 | width: 500px; 46 | height: 60px; 47 | } 48 | 49 | .skeleton-content { 50 | width: 260px; 51 | height: 30px; 52 | margin-top: 10px; 53 | } 54 | ``` 55 | > 耦合性过高,可扩展性低,每次改动页面的结构,需要对应于改写骨架代码 56 | 57 | #### webpack打包植入(通过SPA框架替换页面根元素的间隙时机,将骨架代码通过webpack植入进去,获得展示骨架屏的机会) 58 | 59 | 编写webpack插件 60 | // MyPlugin.js 61 | ```js 62 | function MyPlugin(options) { 63 | // 个性化定制 64 | this.options = options; 65 | } 66 | 67 | //webpack 执行插件的apply方法 68 | MyPlugin.prototype.apply = function(compiler) { 69 | 70 | // webpack 提供的 compilation 71 | compiler.plugin('compilation', (compilation) => { 72 | // 监听到html-webpack-plugin事件操作html页面 参见下面链接 73 | compilation.plugin('html-webpack-plugin-before-html-processing',(htmlData, callback) => { 74 | // 预先替换骨架屏代码 75 | htmlData.html = htmlData.html.replace('
    ',` 76 |
    77 | // ... 78 | // 写好的骨架html和css 79 |
    80 | `); 92 | // 调用回调函数,将处理好的html注入 93 | callback(null, htmlData); 94 | }); 95 | }); 96 | } 97 | // 导出插件 98 | module.exports = MyPlugin; 99 | ``` 100 | webpack配置文件调用 101 | 102 | ```js 103 | 104 | //省略n行代码 ... 105 | 106 | // 调用插件 107 | const HtmlWebpackPlugin = require('html-webpack-plugin') 108 | const MyPlugin = require('yourpath/MyPlugin') 109 | 110 | //省略n行代码 ... 111 | 112 | //在plugins中执行插件 113 | plugins: [ 114 | new HtmlWebpackPlugin({}), 115 | new MyPlugin(), // 在html-webpack-plugin插件后调用 116 | ] 117 | 118 | ``` 119 | 120 | [html-webpack-plugin 封装webpack插件参考](https://www.npmjs.com/package/html-webpack-plugin) 121 | 122 | [https://css-tricks.com/building-skeleton-screens-css-custom-properties/](https://css-tricks.com/building-skeleton-screens-css-custom-properties/) 123 | 124 | + ### 非侵入式 125 | > 页面是不可预知,通过webpack植入骨架屏代码,感知页面元素,植入对应的骨架loading页面 126 | 127 | + 非侵入式跟侵入式的webpack植入骨架屏代码类似,最大的区别是能感知html的dom结构,获取dom的宽高,写上dom的加载样式,也就是最核心的实现难点感知页面结构 128 | [借鉴饿了么实现骨架屏的原理](https://github.com/ElemeFE/page-skeleton-webpack-plugin) 129 | 130 | 1. 通过node库[puppeteer](https://github.com/GoogleChrome/puppeteer)获取dom结构,puppeteer会连接到一个Chromium实例,然后通过puppeteer.launch或puppeteer.connect创建一个Browser对象。此时就可以获取当前页面的dom结构 131 | 2. 获取到了dom元素,就可以针对需要做骨架屏的元素做样式处理 132 | 3. 同上侵入式,触发vue或者react 数据渲染,将根元素替换掉,展示真实的页面元素 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/umd.js: -------------------------------------------------------------------------------- 1 | // umd规范: 兼容amd、commonJs、浏览器window绑定对象规范 2 | (function( root, window, document, factory, undefined) { 3 | if( typeof define === 'function' && define.amd ) { 4 | // AMD. Register as an anonymous module. 5 | define( function() { 6 | root.XXX = factory(window, document); 7 | return root.XXX; 8 | } ); 9 | } else if( typeof exports === 'object' ) { 10 | // Node. Does not work with strict CommonJS. 11 | module.exports = factory(window, document); 12 | } else { 13 | // Browser globals. 14 | window.XXX = factory(window, document); 15 | } 16 | }(this, window, document, function(window, document){ 17 | 'use strict'; 18 | //do something 19 | })); -------------------------------------------------------------------------------- /src/vue-life-cycle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lusteng/daily-notes/0fc652b7e510ea7c3b63865ca6c4e04dcfd07e66/src/vue-life-cycle.jpg -------------------------------------------------------------------------------- /src/vue-mixins.md: -------------------------------------------------------------------------------- 1 | ### 总结项目中使用mixins场景 2 | > mixins类似对象的继承,n个子类继承公共父类的公共的一些方法,由于vue不像react采用面向对象写法,可以通过es6更好继承父组件的公共方法,vue提供mixins、extends实现多个子类继承公共功能,由于只在项目中使用mixins,这里总结下 3 | 4 | **** 5 | 6 | ### 局部使用 7 | > 需要抽象出多个组件的公共功能然后进行封装,很好的避免公共需求改变时,每个组件都要改一遍的尴尬局面 8 | 9 | 公共文件定义mixins 10 | ```js 11 | // mixins/common.js 12 | const common = { 13 | data(){ 14 | return { 15 | name: "common" 16 | } 17 | }, 18 | created() { 19 | console.log(`${this.name} 混进来了哦`) 20 | }, 21 | methods: { 22 | // 执行多个子组件公共抽象方法,此处仅示例 23 | sayCommon(){ 24 | // do something 25 | console.log(`I am ${this.name}`); 26 | }, 27 | 28 | }, 29 | } 30 | export default common 31 | ``` 32 | 33 | 组件A 引入 34 | ```js 35 | import common from 'yourPath/mixins/common' 36 | export default { 37 | name: "a", 38 | mixins: [common], 39 | created() { 40 | this.say() 41 | }, 42 | methods: { 43 | say(){ 44 | //do mixins thing 45 | this.sayCommon() 46 | console.log('I am A'); 47 | }, 48 | } 49 | } 50 | 51 | // common 混进来了哦 52 | // I am common 53 | // I am A 54 | ``` 55 | 56 | 57 | 组件B 引入 58 | ```js 59 | import common from 'yourPath/mixins/common' 60 | export default { 61 | name: "b", 62 | mixins: [common], 63 | created() { 64 | this.say() 65 | }, 66 | methods: { 67 | say(){ 68 | //do mixins thing 69 | this.sayCommon() 70 | console.log('I am B'); 71 | }, 72 | } 73 | } 74 | 75 | // common 混进来了哦 76 | // I am common 77 | // I am B 78 | ``` 79 | 80 | ### 全局使用 81 | > 针对不同的路由级组件做不同的事物,不必通过路由钩子来实现 82 | 83 | // mixins/globalMixins.js 84 | ```js 85 | const GlobalMixins = { 86 | created() { 87 | const componentName = this.$options.name 88 | // 判断组件名执行不同的一些事务 89 | if(componentName === 'A' || componentName === 'B' || componentName === 'C'){ 90 | //do something 91 | }else if(componentName === 'C' || componentName === 'D'){ 92 | //do something 93 | } 94 | }, 95 | } 96 | 97 | export default GlobalMixins 98 | ``` 99 | // mian.js 100 | ```js 101 | import GlobalMixins from 'yourPath/mixins/globalMixins' 102 | Vue.mixin(GlobalMixins) 103 | ``` 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/vue-optimize.md: -------------------------------------------------------------------------------- 1 | ### 优化vue打包文件过大 2 | 3 | PS: 摘自早期项目优化记录,由于技术更新,有些不太适用了,适量阅读 4 | 5 | #### 1. 采用路由懒加载 6 | > 针对路由组件级组件分片打包,分隔文件大小 7 | 8 | 优化前 9 | ```js 10 | //路由配置引入组件 11 | import BusinessManage from '@PAGE/Business/BusinessManage' 12 | 13 | ``` 14 | 优化后 15 | ```js 16 | // 此处针对() => import封装一层 17 | const BusinessManage = () => import('@PAGE/Business/BusinessManage') 18 | ``` 19 | 20 | 输出分块js文件按文件名输出, 需要webpack 2.6.0以上版本 21 | 22 | ```js 23 | const BusinessManage = () => import(/* webpackChunkName: "BusinessManage" */'@PAGE/Business/BusinessManage') 24 | ``` 25 | 26 | [https://webpack.docschina.org/api/module-methods/#import-](https://webpack.docschina.org/api/module-methods/#import-) 27 | 28 | 29 | #### 2. 引入第三方cdn打包 30 | > 项目中存在较大的第三方库,通过webpack配置将第三方库隔离出打包文件,减少首屏白屏时间 31 | 32 | ```js 33 | // webpack.base.config.js 此处以项目vue-cli2.0为例 34 | module.exports = { 35 | ... 36 | //externals 为 webpack 所依赖的外部资源声明 37 | //键名为 webpack 给外部资源所定义的内部别名alias,键值为外部资源所export暴露到全局的对象名称 38 | externals: { 39 | "echarts": "echarts" 40 | }, 41 | ... 42 | } 43 | 44 | // index.html 引入cdn库 45 | 46 | 47 | // xx component 48 | // 删除 import echarts from 'echarts',通过echarts变量名直接使用echarts 49 | ``` 50 | 51 | #### 3.开启文件gzip(后端同学开启下) 52 | 53 | #### 4.取消map文件生成(线上map文件用于定位报错文件位置,意义不大,可以采用埋点监控) 54 | ```js 55 | // config/index.js 56 | productionSourceMap: false 57 | ``` --------------------------------------------------------------------------------