首页
归档
关于
友人帐
Search
1
Mac OS 终端利器 iTerm2 + Oh My Zsh
13,901 阅读
2
前端 History 路由及 API 反向代理的 Nginx 配置
10,595 阅读
3
解决 Xcode Command Line Tools 错误或无法安装问题
6,253 阅读
4
Mac下右键使用VSCode打开项目
3,945 阅读
5
BrowserHistory刷新页面404问题
3,430 阅读
码上世界
内容分享
生活印记
其他
登录
Search
小小孩
累计撰写
95
篇文章
累计收到
181
条评论
首页
栏目
码上世界
内容分享
生活印记
其他
页面
归档
关于
友人帐
搜索到
95
篇与
小小孩
的结果
2019-02-13
2019前端的展望与目标
前端发展依旧迅速 各种技术,我快学不动啦> 前端发展依旧迅速 各种技术,我快学不动啦?????React Hooksreact 都核心思想就是,将一个页面拆成一堆独立的,可复用的组件,并且用自上而下的单向数据流的形式将这些组件串联起来。但假如你在大型的工作项目中用 react,你会发现你的项目中实际上很多 react 组件冗长且难以复用。尤其是那些写成 class 的组件,它们本身包含了状态(state),所以复用这类组件就变得很麻烦。React Hooks 要解决的问题是状态共享,是继 render-props 和 higher-order components 之后的第三种状态共享方案,不会产生 JSX 嵌套地狱问题。React Hooks 带来的好处不仅是 “更 FP,更新粒度更细,代码更清晰”,还有如下三个特性:多个状态不会产生嵌套,写法还是平铺的(renderProps 可以通过 compose 解决,可不但使用略为繁琐,而且因为强制封装一个新对象而增加了实体数量)。Hooks 可以引用其他 Hooks。更容易将组件的 UI 与状态分离。文章:一篇看懂 React Hooks基于 Redux 的状态管理从 2013 年 React 发布至今已近 6 个年头,前端框架逐渐形成 React/Vue/Angular 三足鼎立之势。几年前还在争论单向绑定和双向绑定孰优孰劣,现在三大框架已经不约而同选择单向绑定,双向绑定沦为单纯的语法糖。框架间的差异越来越小,加上 Ant-Design/Fusion-Design/NG-ZORRO/ElementUI 组件库的成熟,选择任一你熟悉的框架都能高效完成业务。那接下来核心问题是什么?我们认为是 状态管理。简单应用使用组件内 State 方便快捷,但随着应用复杂度上升,会发现数据散落在不同的组件,组件通信会变得异常复杂。我们先后尝试过原生 Redux、分形 Fractal 的思路、自研类 Mobx 框架、Angular Service,最终认为 Redux 依旧是复杂应用数据流处理最佳选项之一。庆幸的是除了 React 社区,Vue 社区有类似的 Vuex,Angular 社区有 NgRx 也提供了几乎同样的能力,甚至 NgRx 还可以无缝使用 redux-devtools 来调试状态变化。原则方法引发的问题Single source of truth组件 Stateless,数据来源于 Store如何组织 StoreState is read-only只能通过触发 action 来改变 Stateaction 数量膨胀,大量样板代码Changes are made with pure functionsReducer 是纯函数副作用如何处理,大量样板代码如何组织 Action?action type 需要全局惟一,因此我们给 action type 添加了 prefix,其实就是 namespace 的概念为了追求体验,请求(Fetch)场景需要处理 3 种状态,对应 LOADING/SUCCESS/ERROR 这 3 个 action,我们通过 FetchTypes 类型来自动生成对应到 3 个 action如何组织 Store/Reducer?reducer 和 view 不必一一对应,应用中同时存在组件树和状态树,按照各自需要去组织,通过 connect 来绑定状态树的一个或多个分支到组件树通过构造一些预设数据类型来减少样板代码。对于 Fetch 返回的数据我们定义了 AsyncTuple 这种类型,减少了样板代码明确的组织结构,第 1 层是 ROOT,第 2 层是各个页面,第 3 层是页面内的卡片,第 4 层是卡片的数据,这样划分最深处基本不会超过 5 层hapi.jsHapi(简称 HAPP API,发音为“happy”) 是一个容易使用的以配置为中心的框架,内置支持输入验证,缓存,身份验证以及用于构建 Web 和服务应用程序的其他基本工具。hapi 使开发人员能够专注于以高度模块化和规范性的方式编写可重用的应用程序逻辑。Hapi 是一个抽象了现有 Node API 的新框架。Express 更老,更成熟。Express 代码看起来更像本地 Node。 hapi.js 还有个优点可以用过插件 hapi-swagger,自动生成 swagger 文档免除了 api 与文档不同步的困扰GraphQLGraphQL 已被 GitHub 等技术引领者采用。 然而,它并没有像一些预测的那样快速地飞涨。 据 JS 状态调查 显示,只有 1/5 的前端开发人员使用过 GraphQL ,但是有 62.5% 的开发人员已经听说过它并希望使用它,相当惊人。TypeScriptTypeScript 可能是 JavaScript 的未来(但对于 Flow 来说就不一定了)。事实上,在 Stack Overflow 调查中,TypeScript 的评分高于 JavaScript 本身,为 67% ,而最受喜爱的语言为 61.9% 。 根据 JS 的状态调查,超过 80% 的开发人员希望使用 TS 或已经使用它并享受它。 对于 Flow,只有 34% 的开发人员正在使用它或想要使用它。根据所有迹象,TypeScript 是 JS 中静态类型的首选解决方案,许多人选择使用普通的 JavaScript 。 在 2018 年,TS 的 npm 下载数量大幅增长,而 Flow 保持不变。 TypeScript 看起来正在从狂热追随者转向广泛采用。Jest 也正在从 Flow 切换到 TS,Vue 3.0 也开始用 TS 重写。如果你还没有使用,可以考虑切换,绝对能给项目带来很大提升。TS 最大的优势是它提供了强大的静态分析能力,结合 TSLint 能对代码做到更加严格的检查约束。传统的 EcmaScript 由于没有静态类型,即使有了 ESLint 也只能做到很基本的检查,一些 type 问题可能线上出了 Bug 后才被发现。学习网址:TypeScript 中文网WebpackWebpack 3 发布仅 8 个月后,版本 4 发布了。 Webpack 4 继续推动简化和更快的构建,声称高达 98% 的改进。 它选择合理的默认值,在没有插件的情况下处理更多功能,并且不再需要使用配置文件。 Webpack 现在还支持 WebAssembly,并允许您直接导入 WebAssembly 文件。而且 Webpack@5.0.0-alpha.10 也已发布。推荐阅读:Webpack 4 教程:从零配置到生产发布(2018)展望 2019 年TypeScript 开始成为标准 JavaScript 的默认选择。随着基础的建立和不断推动的 Web 体验改进,WebAssembly 将开始看到更多的生命力。React 保持领先,但 Vue 和 Angular 用户会继续增长。CSS-in-js 可能会成为默认的样式化方法,而不是普通的 CSS。毫不奇怪,性能仍然是关注的焦点,诸如 PWAs 和代码分割之类的事情成为每个应用程序的标准。在采用 PWA 的基础上,web 变得更加本地化,具有离线功能和无缝的桌面/移动体验。我们继续看到 CLI 工具和框架的增长,以继续抽象出构建应用程序的繁琐方面,允许开发人员专注于生成功能。更多的公司采用具有统一代码库的移动解决方案,如 React Native 或 Flutter 。Containerization 的影响(即 Docker, Kubernetes)在前端过程中变得更为普遍。GraphQL 在采用方面会有大的飞跃,并在更多公司中得到应用。虚拟现实使用 A-Frame,React VR 和 Google VR 等库向前迈进。具体计划:学习 Typescript GraphQL深入 Node.js坚持更新博客,更新基础知识、技术总结和项目开发中遇到的问题更加深入的去理解 Vue 和 Node,同时实际尝试自己写一下服务端渲染至少写一个 React Reduce 相关的项目看看 deno 这种未来可能替代 Node 的新东西
2019年02月13日
2,270 阅读
0 评论
0 点赞
2019-01-23
JS的防抖与节流
防抖 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo</title> <style type="text/css"> body{ height:3000px; } </style> </head> <body> <script> function fn(){ console.log("invoke fn function"); }; document.body.onscroll = fn; </script> </body> </html>我们打开浏览器,然后打开控制台,滚动鼠标的时候,控制台频繁的打印“invoke fn function”。我们这里为了显示,所以没有涉及到相关dom操作,但是实际开发过程中,更多场景是操作dom,那么将会使你的浏览器瞬间卡卡的感觉,有没有法子来限制一下fn的调用频率呢,答案是可以的,对此,我们将上面的代码改变如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo</title> <style type="text/css"> body{ height:3000px; } </style> </head> <body> <script> function fn(){ console.log("invoke fn function"); }; document.body.onscroll = avoidShak(fn,300); // 限制调用频率 function avoidShak(fn,time){ //设置定时器 let timer; return function(...args){ //清空上一次的定时器 clearTimeout(timer); //获取执行环境的上下文 let context = this; let _arguments = args; timer = setTimeout(()=>{ fn.apply(context,_arguments); },time); }; }; </script> </body> </html>我们现在发现打开浏览器,滚动的时候不会那么频繁的调用fn函数了,只有当我们滚动的间隙稍微停顿300毫秒的时候才会调用一次,这样我们就做到了降低函数调用的频率了。原理它的原理其实很简单:1 用闭包实现一个timer变量,用来保存上一次调用函数的定时器id;2 我们不是直接调用函数,而是中间需要一个间隔,如果两次调用之间的时间差小于我们传递的值,那么清空上一次的调用值;3 我们每一次调用的时候都清除一下上一次的调用定时器id,这样就保证了,如果间隔时间小于我们设置的值,那么上一次函数一定不会调用,从而达到了降低调用频率的效果。上面这种通过设置定时器保证一段时间内事件回调函数只能执行一次的做法在javascript业界有一个专业的术语称谓——防抖!上面的防抖操作,我们发现减少了回调函数调用的频率,但是它有一点点瑕疵:如果我们一直触发事件,回调函数只会在我们停止触发事件并达到了设置的时间间隔之后才会调用一次,也就是说在我们触发事件的过程中,回调函数一直没有执行,这在某些情况下,会跟实际业务需求不符。实际业务需求可能是,1 减少触发频率;2 但不能中间很大一段时间一直不执行。ok,那么此时我们就需要通过函数节流来实现!节流把下面的代码在浏览器中打开,并水平缩放浏览器,看效果:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo</title> <style type="text/css"> *{ margin:0px; padding:0px; vertical-align:baseline; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; } .box{ width:1000px; height: 500px; background-color:#ccc; color:#fff; font-size:20px; padding:15px 20px; text-align:left; } </style> </head> <body> <div class="box" id="box">i am a div box,please resize the browser horizontally!</div> <script type="text/javascript"> let dom = document.getElementById('box'); function setWidth(){ let windowWidth = window.innerWidth; if(windowWidth>=1000)return; dom.style.width = windowWidth + 'px'; }; //采用防抖实现限制回调函数调用频率 function avoidShak(fn,time){ let timer; return function(...args){ clearTimeout(timer); let context = this; let _arguments = args; timer = setTimeout(()=>{ fn.apply(context,_arguments); },time); }; }; window.onresize = avoidShak(setWidth,300); </script> </body> </html>先来说下上面的页面需求:打开页面,在浏览器水平缩放的过程中,如果浏览器宽度不小于1000,那么不做任何事,否则设置dom的宽度为当前浏览器的宽度。但是我们发现,我们在缩放的过程中,dom的尺寸并未做相应的更新,只有在停止缩放一段时间后,dom的宽度才更新到浏览器的宽度,这跟业务需求不符,于是我们代码改变如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo</title> <style type="text/css"> *{ margin:0px; padding:0px; vertical-align:baseline; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; } .box{ width:1000px; height: 500px; background-color:#ccc; color:#fff; font-size:20px; padding:15px 20px; text-align:left; } </style> </head> <body> <div class="box" id="box">i am a div box,please resize the browser horizontally!</div> <script type="text/javascript"> let dom = document.getElementById('box'); function setWidth(){ let windowWidth = window.innerWidth; if(windowWidth>=1000)return; dom.style.width = windowWidth + 'px'; }; //采用节流实现限制回调函数调用频率 function ttrottle(fn,time){ let isNeedInvoke = true; return function(...args){ if(!isNeedInvoke)return; let context = this; let _arguments = args; isNeedInvoke = false; setTimeout(()=>{ fn.apply(context,_arguments); isNeedInvoke = true; },time); }; }; window.onresize = ttrottle(setWidth,300); </script> </body> </html>我们发现经过这样改过之后,dom的宽度变成在我们缩放的过程中也会更新了,满足了我们业务需求。好了,我们来简单介绍下什么是节流!节流其实从名字上就知道它的含义——就是限制函数调用频率。主要有两种方式实现:* 法一:时间差,原理无非就是两次调用之间的时间差小于设置时,那么不调用,反之调用。代码如下:function ttrottle(fn,time){ //上一次调用时间 let lastInvokeTime = new Date().getTime(); //当前调用时间 let currentInvokeTime; return function(...args){ currentInvokeTime = new Date().getTime(); if(currentInvokeTime - lastInvokeTime <= time)return; let context = this; let _arguments = args; lastInvokeTime = currentInvokeTime; fn.apply(context,_arguments); }; };* 法二:定时器实现,原理就是设置时间间隔,如果达不到时间间隔,就清除上一次调用回调定时器id。代码如下:function ttrottle(fn,time){ let isNeedInvoke = true; return function(...args){ if(!isNeedInvoke)return; let context = this; let _arguments = args; isNeedInvoke = false; setTimeout(()=>{ fn.apply(context,_arguments); isNeedInvoke = true; },time); }; };ok,到这里大家应该知道节流的是干嘛以及原理了吧。最后,再来总结一下防抖和节流的区别:防抖和节流的相同点就是限制回调函数调用频率;防抖在一段时间内,回调函数只会调用一次,即触发事件的最后一次;节流在一段时间内,会每隔一段时间调用一次;下面是具体函数这只是简单的实现方法函数防抖函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。如下,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件function debounce(fn, wait) { var timeout = null; return function() { if(timeout !== null) clearTimeout(timeout); timeout = setTimeout(fn, wait); } } // 处理函数 function handle() { console.log(Math.random()); } // 滚动事件 window.addEventListener('scroll', debounce(handle, 1000));函数节流函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。如下,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。var throttle = function(func, delay) { var prev = Date.now(); return function() { var context = this; var args = arguments; var now = Date.now(); if (now - prev >= delay) { func.apply(context, args); prev = Date.now(); } } } function handle() { console.log(Math.random()); } window.addEventListener('scroll', throttle(handle, 1000));总结函数防抖将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。函数节流使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。区别函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
2019年01月23日
1,828 阅读
0 评论
0 点赞
2019-01-20
BrowserHistory刷新页面404问题
使用 React 开发新项目时,遇见了刷新页面,直接访问二级或三级路由时,访问失败,出现 404 或资源加载异常的情况,本篇针对此问题进行分析并总结解决方案。发现问题使用webpack-dev-server做本地开发服务器时,正常情况只需要简单使用webpack-dev-server指令启动即可,但是当项目处于以下两种情况时,往往需要有嵌套路由和异步加载路由:我们使用 react-router 这种路由库构建单页面应用路由;使用 html-webpack-plugin 插件动态将加载 js 的script标签注入 html 文档;这时,访问首页是可以正常加载页面和 js 等文件的,但是当我们需要访问二级甚至三级路由或者刷新页面时,如/page1时,可能会出现两种情况:页面加载失败,返回 Cannot Get(404);服务响应,但是没有返回 webpack 处理输出的 html 文件,导致无法加载 js 资源;分析解决问题发现问题后,我们就要开始分析,我们判断这个问题一般是两方面原因造成:react-router路前端由配置;webpack-dev-server服务配置;发现文档中提到了使用browserHistory时,会创建真实的URL,处理初始/请求没有问题,但是对于跳转路由后,刷新页面或者直接访问该 URL 时,会发现无法正确相应.一下是几种服务器配置解决方式:nodeconst express = require('express') const path = require('path') const port = process.env.PORT || 8080 const app = express() // 通常用于加载静态资源 app.use(express.static(__dirname + '/public')) // 在你应用 JavaScript 文件中包含了一个 script 标签 // 的 index.html 中处理任何一个 route app.get('*', function (request, response){ response.sendFile(path.resolve(__dirname, 'public', 'index.html')) }) app.listen(port) console.log(\"server started on port \" + port);Nginxserver { ... location / { try_files $uri /index.html } }ApacheRewriteBase / RewriteRule ^index\\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L]以下都是针对服务器的配置,本地调试,只是使用了webpack-dev-server的内置服务,但是我们已经找到问题所在了,就是路由请求无法匹配返回 html 文档,所以接下来就该去webpack-dev-server文档查找解决方式了.这里也分两种情况:没有修改 output.publicPath,即 webpack 配置文件中没有声明值,属于默认情况;设置了 output.publicPath 为自定义值;webpack 如下配置:module.exports = { //... devServer: { historyApiFallback: true } };
2019年01月20日
3,430 阅读
0 评论
0 点赞
2019-01-18
Vue中的eventBus使用
使用 eventBus在项目入口文件main.js中 加入如下代码// EventBus window.eventBus = new Vue();这是引入全局变量eventBus准备两个需要通信的 vue 组件About.vue<template> <div class="about"> <h1>This is an {{msg}} page</h1> <h2>other :{{other}}</h2> </div> </template> <script> export default { data() { return { msg: "about", other: "old other" }; }, created() { eventBus.$on("postData", data => { this.other = data; }); } }; </script>代码中 data 里的 other 值一开始是 old other,这个组件需要另一个组件返回过来的值来进行更新。如下代码则是取回 键值是postData 的数据eventBus.$on("postData", data => { this.other = data; });Test.vue<template> <div class="test"> <h1>This is an {{msg}} page</h1> </div> </template> <script> export default { data() { return { msg: "test" }; }, methods: {}, destroyed() { eventBus.$emit("postData", "new other"); } }; </script>以下代码是在组件销毁之前给键值postData 注入值 new othereventBus.$emit("postData", "new other");效果所以效果就是一开始进入 about 页面时 other 值为 old other 进入 test 页面 再返回时 other 值则变成了 new other
2019年01月18日
293 阅读
0 评论
0 点赞
2019-01-16
CSS小技巧小汇
一、flex 项中子元素文本截断 text-overflow:ellipsis 失效,给你一个完美的解决方案。flexbox 布局时 flex 项子元素中的文本溢出后想显示省略号(…),结果设置 text-overflow:ellipsis 后没效果,我们来看代码:HTML 代码:<div class="flex"> <div class="col1">图标</div> <div class="col2"> <div class="item-con"> flexbox 布局旨在提供一个更有效地布局、对齐方式,并且能够使容器中的子元素大小未知或动态变化情况下仍然能够分配好子元素之间的空间。 </div> </div> </div> CSS 代码:.flex { display: flex; } .flex .col1 { margin-right: 6px; } .flex .col2 { flex: 1; } .flex .col2 .item-con { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }完整的 DEMO :我们看到 .col2 设置 flex: 1; , .item-con 设置了 text-overflow:ellipsis ,但是文本还是溢出了。这种布局在移动端页面开发时候经常遇到,我们不能为 .item-con 元素设置个宽度,这样就无法适应不同屏幕尺寸的终端设备。解决方案:在 flex项中设置 min-width: 0;解决方案是在 flex 项(上例子中 .col2 元素)中设置 min-width: 0;,当然你也可以设置其他合适的 min-width值。二、css 文本多行显示,超出省略号表示.text { width: 200px; word-break: break-all; text-overflow: ellipsis; display: -webkit-box; /** 对象作为伸缩盒子模型显示 **/ -webkit-box-orient: vertical; /** 设置或检索伸缩盒对象的子元素的排列方式 **/ -webkit-line-clamp: 3; /** 显示的行数 **/ overflow: hidden; /** 隐藏超出的内容 **/ }三、进度条动画<style> .process { width: 200px; height: 20px; border-radius: 10px; background: repeating-linear-gradient( -45deg, #fafafa 25%, #f93e3e 0, #f93e3e 50%, #fafafa 0, #fafafa 75%, #f93e3e 0 ); background-size: 15px 15px; animation: panoramic 8s linear infinite; } @keyframes panoramic { to { background-position: 200%; } } </style> <div class="process"> <div></div> </div>
2019年01月16日
2,318 阅读
0 评论
0 点赞
2019-01-12
一篇看懂 React Hooks
什么是 React HooksReact Hooks 是 React 16.7.0-alpha 版本推出的新特性,想尝试的同学安装此版本即可。__React Hooks 要解决的问题是状态共享__,是继 render-props 和 higher-order components 之后的第三种状态共享方案,不会产生 JSX 嵌套地狱问题。这个状态指的是状态逻辑,所以称为__状态逻辑复用__会更恰当,因为只共享数据处理逻辑,不会共享数据本身。不久前精读分享过的一篇 Epitath 源码 - renderProps 新用法 就是解决 JSX 嵌套问题,有了 React Hooks 之后,这个问题就被官方正式解决了。为了更快理解 React Hooks 是什么,先看笔者引用的下面一段 renderProps 代码:function App() { return ( <Toggle initial={false}> {({ on, toggle }) => ( <Button type="primary" onClick={toggle}> Open Modal </Button> <Modal visible={on} onOk={toggle} onCancel={toggle} /> )} </Toggle> ) }恰巧,React Hooks 解决的也是这个问题:function App() { const [open, setOpen] = useState(false); return ( <> <Button type="primary" onClick={() => setOpen(true)}> Open Modal </Button> <Modal visible={open} onOk={() => setOpen(false)} onCancel={() => setOpen(false)} /> </> ); }可以看到,React Hooks 就像一个内置的打平 renderProps 库,我们可以随时创建一个值,与修改这个值的方法。看上去像 function 形式的 setState,其实这等价于依赖注入,与使用 setState 相比,__这个组件是没有状态的__。React Hooks 的特点React Hooks 带来的好处不仅是 “更 FP,更新粒度更细,代码更清晰”,还有如下三个特性:多个状态不会产生嵌套,写法还是平铺的(renderProps 可以通过 compose 解决,可不但使用略为繁琐,而且因为强制封装一个新对象而增加了实体数量)。Hooks 可以引用其他 Hooks。更容易将组件的 UI 与状态分离。第二点展开说一下:Hooks 可以引用其他 Hooks,我们可以这么做:import { useState, useEffect } from "react"; // 底层 Hooks, 返回布尔值:是否在线 function useFriendStatusBoolean(friendID) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; } // 上层 Hooks,根据在线状态返回字符串:Loading... or Online or Offline function useFriendStatusString(props) { const isOnline = useFriendStatusBoolean(props.friend.id); if (isOnline === null) { return "Loading..."; } return isOnline ? "Online" : "Offline"; } // 使用了底层 Hooks 的 UI function FriendListItem(props) { const isOnline = useFriendStatusBoolean(props.friend.id); return ( <li style={{ color: isOnline ? "green" : "black" }}>{props.friend.name}</li> ); } // 使用了上层 Hooks 的 UI function FriendListStatus(props) { const statu = useFriendStatusString(props.friend.id); return <li>{statu}</li>; }这个例子中,有两个 Hooks:useFriendStatusBoolean 与 useFriendStatusString, useFriendStatusString是利用 useFriendStatusBoolean 生成的新 Hook,这两个 Hook 可以给不同的 UI:FriendListItem、FriendListStatus 使用,而因为两个 Hooks 数据是联动的,因此两个 UI 的状态也是联动的。顺带一提,这个例子也可以用来理解 对 React Hooks 的一些思考 一文的那句话:__“有状态的组件没有渲染,有渲染的组件没有状态”__:useFriendStatusBoolean 与 useFriendStatusString 是有状态的组件(使用 useState),没有渲染(返回非 UI 的值),这样就可以作为 __Custom Hooks__ 被任何 UI 组件调用。FriendListItem 与 FriendListStatus 是有渲染的组件(返回了 JSX),没有状态(没有使用 useState),这就是一个纯函数 UI 组件,利用 useState 创建 ReduxRedux 的精髓就是 Reducer,而利用 React Hooks 可以轻松创建一个 Redux 机制:// 这就是 Redux function useReducer(reducer, initialState) { const [state, setState] = useState(initialState); function dispatch(action) { const nextState = reducer(state, action); setState(nextState); } return [state, dispatch]; }这个自定义 Hook 的 value 部分当作 redux 的 state,setValue 部分当作 redux 的 dispatch,合起来就是一个 redux。而 react-redux 的 connect 部分做的事情与 Hook 调用一样:// 一个 Action function useTodos() { const [todos, dispatch] = useReducer(todosReducer, []); function handleAddClick(text) { dispatch({ type: "add", text }); } return [todos, { handleAddClick }]; } // 绑定 Todos 的 UI function TodosUI() { const [todos, actions] = useTodos(); return ( <> {todos.map((todo, index) => ( <div>{todo.text}</div> ))} <button onClick={actions.handleAddClick}>Add Todo</button> </> ); }useReducer 已经作为一个内置 Hooks 了,在这里可以查阅所有 内置 Hooks。不过这里需要注意的是,每次 useReducer 或者自己的 Custom Hooks 都不会持久化数据,所以比如我们创建两个 App,App1 与 App2:function App1() { const [todos, actions] = useTodos(); return <span>todo count: {todos.length}</span>; } function App2() { const [todos, actions] = useTodos(); return <span>todo count: {todos.length}</span>; } function All() { return ( <> <App1 /> <App2 /> </> ); }这两个实例同时渲染时,并不是共享一个 todos 列表,而是分别存在两个独立 todos 列表。也就是 React Hooks 只提供状态处理方法,不会持久化状态。如果要真正实现一个 Redux 功能,也就是全局维持一个状态,任何组件 useReducer 都会访问到同一份数据,可以和 useContext 一起使用。大体思路是利用 useContext 共享一份数据,作为 Custom Hooks 的数据源。具体实现可以参考 redux-react-hook。利用 useEffect 代替一些生命周期在 useState 位置附近,可以使用 useEffect 处理副作用:useEffect(() => { const subscription = props.source.subscribe(); return () => { // Clean up the subscription subscription.unsubscribe(); }; });useEffect 的代码既会在初始化时候执行,也会在后续每次 rerender 时执行,而返回值在析构时执行。这个更多带来的是便利,对比一下 React 版 G2 调用流程:class Component extends React.PureComponent<Props, State> { private chart: G2.Chart = null; private rootDomRef: React.ReactInstance = null; componentDidMount() { this.rootDom = ReactDOM.findDOMNode(this.rootDomRef) as HTMLDivElement; this.chart = new G2.Chart({ container: document.getElementById("chart"), forceFit: true, height: 300 }); this.freshChart(this.props); } componentWillReceiveProps(nextProps: Props) { this.freshChart(nextProps); } componentWillUnmount() { this.chart.destroy(); } freshChart(props: Props) { // do something this.chart.render(); } render() { return <div ref={ref => (this.rootDomRef = ref)} />; } }用 React Hooks 可以这么做:function App() { const ref = React.useRef(null); let chart: G2.Chart = null; React.useEffect(() => { if (!chart) { chart = new G2.Chart({ container: ReactDOM.findDOMNode(ref.current) as HTMLDivElement, width: 500, height: 500 }); } // do something chart.render(); return () => chart.destroy(); }); return <div ref={ref} />; }可以看到将细碎的代码片段结合成了一个完整的代码块,更维护。现在介绍了 useState useContext useEffect useRef 等常用 hooks,更多可以查阅:内置 Hooks,相信不久的未来,这些 API 又会成为一套新的前端规范。React Hooks 将带来什么变化Hooks 带来的约定Hook 函数必须以 "use" 命名开头,因为这样才方便 eslint 做检查,防止用 condition 判断包裹 useHook 语句。为什么不能用 condition 包裹 useHook 语句,详情可以见 官方文档,这里简单介绍一下。React Hooks 并不是通过 Proxy 或者 getters 实现的(具体可以看这篇文章 React hooks: not magic, just arrays),而是通过数组实现的,每次 useState 都会改变下标,如果 useState 被包裹在 condition 中,那每次执行的下标就可能对不上,导致 useState 导出的 setter 更新错数据。虽然有 eslint-plugin-react-hooks 插件保驾护航,但这第一次将 “约定优先” 理念引入了 React 框架中,带来了前所未有的__代码命名和顺序限制__(函数命名遭到官方限制,JS 自由主义者也许会暴跳如雷),但带来的便利也是前所未有的(没有比 React Hooks 更好的状态共享方案了,约定带来提效,自由的代价就是回到 renderProps or HOC,各团队可以自行评估)。笔者认为,React Hooks 的诞生,也许来自于这个灵感:“不如通过增加一些约定,彻底解决状态共享问题吧!”React 约定大于配置脚手架 nextjs umi 以及笔者的 pri 都通过有 “约定路由” 的功能,大大降低了路由配置复杂度,__那么 React Hooks 就像代码级别的约定__,大大降低了代码复杂度。状态与 UI 的界限会越来越清晰因为 React Hooks 的特性,如果一个 Hook 不产生 UI,那么它可以永远被其他 Hook 封装,虽然允许有副作用,但是被包裹在 useEffect 里,总体来说还是挺函数式的。而 Hooks 要集中在 UI 函数顶部写,也很容易养成书写无状态 UI 组件的好习惯,践行 “状态与 UI 分开” 这个理念会更容易。不过这个理念稍微有点蹩脚的地方,那就是 “状态” 到底是什么。function App() { const [count, setCount] = useCount(); return <span>{count}</span>; }我们知道 useCount 算是无状态的,因为 React Hooks 本质就是 renderProps 或者 HOC 的另一种写法,换成 renderProps 就好理解了:<Count>{(count, setCount) => <App count={count} setCount={setCount} />}</Count>; function App(props) { return <span>{props.count}</span>; }可以看到 App 组件是无状态的,输出完全由输入(Props)决定。那么有状态无 UI 的组件就是 useCount 了:function useCount() { const [count, setCount] = useState(0); return [count, setCount]; }有状态的地方应该指 useState(0) 这句,不过这句和无状态 UI 组件 App 的 useCount() 很像,既然 React 把 useCount 成为自定义 Hook,那么 useState 就是官方 Hook,具有一样的定义,因此可以认为 useCount 是无状态的,useState 也是一层 renderProps,最终的状态其实是 useState 这个 React 内置的组件。我们看 renderProps 嵌套的表达:<UseState> {(count, setCount) => ( <UseCount> {" "} {/**虽然是透传,但给 count 做了去重,不可谓没有作用 */} {(count, setCount) => <App count={count} setCount={setCount} />} </UseCount> )} </UseState>能确定的是,App 一定有 UI,而上面两层父级组件一定没有 UI。为了最佳实践,我们尽量避免 App 自己维护状态,而其父级的 RenderProps 组件可以维护状态(也可以不维护状态,做个二传手)。因此可以考虑在 “有状态的组件没有渲染,有渲染的组件没有状态” 这句话后面加一句:没渲染的组件也可以没状态。React Hooks 实践通过上面的理解,你已经对 React Hooks 有了基本理解,也许你也看了 React Hooks 基本实现剖析(就是数组),但理解实现原理就可以用好了吗?学的是知识,而用的是技能,看别人的用法就像刷抖音一样(哇,饭还可以这样吃?),你总会有新的收获。首先,站在使用角度,要理解 React Hooks 的特点是 “非常方便的 Connect 一切”,所以无论是数据流、Network,或者是定时器都可以监听,有一点 RXJS 的意味,也就是你可以利用 React Hooks,将 React 组件打造成:任何事物的变化都是输入源,当这些源变化时会重新触发 React 组件的 render,你只需要挑选组件绑定哪些数据源(use 哪些 Hooks),然后只管写 render 函数就行了!DOM 副作用修改 / 监听做一个网页,总有一些看上去和组件关系不大的麻烦事,比如修改页面标题(切换页面记得改成默认标题)、监听页面大小变化(组件销毁记得取消监听)、断网时提示(一层层装饰器要堆成小山了)。而 React Hooks 特别擅长做这些事,造这种轮子,大小皆宜。由于 React Hooks 降低了高阶组件使用成本,那么一套生命周期才能完成的 “杂耍” 将变得非常简单。下面举几个例子:修改页面 title效果:在组件里调用 useDocumentTitle 函数即可设置页面标题,且切换页面时,页面标题重置为默认标题 “前端精读”。useDocumentTitle("个人中心");实现:直接用 document.title 赋值,不能再简单。在销毁时再次给一个默认标题即可,这个简单的函数可以抽象在项目工具函数里,每个页面组件都需要调用。function useDocumentTitle(title) { useEffect( () => { document.title = title; return () => (document.title = "前端精读"); }, [title] ); }在线 Demo监听页面大小变化,网络是否断开效果:在组件调用 useWindowSize 时,可以拿到页面大小,并且在浏览器缩放时自动触发组件更新。const windowSize = useWindowSize(); return <div>页面高度:{windowSize.innerWidth}</div> ;实现:和标题思路基本一致,这次从 window.innerHeight 等 API 直接拿到页面宽高即可,注意此时可以用 window.addEventListener('resize') 监听页面大小变化,此时调用 setValue 将会触发调用自身的 UI 组件 rerender,就是这么简单!最后注意在销毁时,removeEventListener 注销监听。function getSize() { return { innerHeight: window.innerHeight, innerWidth: window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth }; } function useWindowSize() { let [windowSize, setWindowSize] = useState(getSize()); function handleResize() { setWindowSize(getSize()); } useEffect(() => { window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, []); return windowSize; }在线 Demo动态注入 css效果:在页面注入一段 class,并且当组件销毁时,移除这个 class。const className = useCss({ color: "red" }); return <div className={className}>Text.</div> ;实现:可以看到,Hooks 方便的地方是在组件销毁时移除副作用,所以我们可以安心的利用 Hooks 做一些副作用。注入 css 自然不必说了,而销毁 css 只要找到注入的那段引用进行销毁即可,具体可以看这个 代码片段。DOM 副作用修改 / 监听场景有一些现成的库了,从名字上就能看出来用法:document-visibility、network-status、online-status、window-scroll-position、window-size、document-title。组件辅助Hooks 还可以增强组件能力,比如拿到并监听组件运行时宽高等。获取组件宽高效果:通过调用 useComponentSize 拿到某个组件 ref 实例的宽高,并且在宽高变化时,rerender 并拿到最新的宽高。const ref = useRef(null); let componentSize = useComponentSize(ref); return ( <> {componentSize.width} <textArea ref={ref} /> </> );实现:和 DOM 监听类似,这次换成了利用 ResizeObserver 对组件 ref 进行监听,同时在组件销毁时,销毁监听。其本质还是监听一些副作用,但通过 ref 的传递,我们可以对组件粒度进行监听和操作了。useLayoutEffect(() => { handleResize(); let resizeObserver = new ResizeObserver(() => handleResize()); resizeObserver.observe(ref.current); return () => { resizeObserver.disconnect(ref.current); resizeObserver = null; }; }, []);在线 Demo,对应组件 component-size。拿到组件 onChange 抛出的值效果:通过 useInputValue() 拿到 Input 框当前用户输入的值,而不是手动监听 onChange 再腾一个 otherInputValue 和一个回调函数把这一堆逻辑写在无关的地方。let name = useInputValue("Jamie"); // name = { value: 'Jamie', onChange: [Function] } return <input {...name} />;可以看到,这样不仅没有占用组件自己的 state,也不需要手写 onChange 回调函数进行处理,这些处理都压缩成了一行 use hook。实现:读到这里应该大致可以猜到了,利用 useState 存储组件的值,并抛出 value 与 onChange,监听 onChange 并通过 setValue 修改 value, 就可以在每次 onChange 时触发调用组件的 rerender 了。function useInputValue(initialValue) { let [value, setValue] = useState(initialValue); let onChange = useCallback(function(event) { setValue(event.currentTarget.value); }, []); return { value, onChange }; }这里要注意的是,我们对组件增强时,__组件的回调一般不需要销毁监听,而且仅需监听一次,这与 DOM 监听不同__,因此大部分场景,我们需要利用 useCallback 包裹,并传一个空数组,来保证永远只监听一次,而且不需要在组件销毁时注销这个 callback。在线 Demo,对应组件 input-value。做动画利用 React Hooks 做动画,一般是拿到一些具有弹性变化的值,我们可以将值赋给进度条之类的组件,这样其进度变化就符合某种动画曲线。在某个时间段内获取 0-1 之间的值这个是动画最基本的概念,某个时间内拿到一个线性增长的值。效果:通过 useRaf(t) 拿到 t 毫秒内不断刷新的 0-1 之间的数字,期间组件会不断刷新,但刷新频率由 requestAnimationFrame 控制(不会卡顿 UI)。const value = useRaf(1000);实现:写起来比较冗长,这里简单描述一下。利用 requestAnimationFrame 在给定时间内给出 0-1 之间的值,那每次刷新时,只要判断当前刷新的时间点占总时间的比例是多少,然后做分母,分子是 1 即可。在线 Demo,对应组件 use-raf。弹性动画效果:通过 useSpring 拿到动画值,组件以固定频率刷新,而这个动画值以弹性函数进行增减。实际调用方式一般是,先通过 useState 拿到一个值,再通过动画函数包住这个值,这样组件就会从原本的刷新一次,变成刷新 N 次,拿到的值也随着动画函数的规则变化,最后这个值会稳定到最终的输入值(如例子中的 50)。const [target, setTarget] = useState(50); const value = useSpring(target); return <div onClick={() => setTarget(100)}>{value}</div> ;实现:为了实现动画效果,需要依赖 rebound 库,它可以实现将一个目标值拆解为符合弹性动画函数过程的功能,那我们需要利用 React Hooks 做的就是在第一次接收到目标值是,调用 spring.setEndValue 来触发动画事件,并在 useEffect 里做一次性监听,再值变时重新 setValue 即可。最神奇的 setTarget 联动 useSpring 重新计算弹性动画部分,是通过 useEffect 第二个参数实现的:useEffect( () => { if (spring) { spring.setEndValue(targetValue); } }, [targetValue] );也就是当目标值变化后,才会进行新的一轮 rerender,所以 useSpring 并不需要监听调用处的 setTarget,它只需要监听 target 的变化即可,而巧妙利用 useEffect 的第二个参数可以事半功倍。在线 DemoTween 动画明白了弹性动画原理,Tween 动画就更简单了。效果:通过 useTween 拿到一个从 0 变化到 1 的值,这个值的动画曲线是 tween。可以看到,由于取值范围是固定的,所以我们不需要给初始值了。const value = useTween();实现:通过 useRaf 拿到一个线性增长的值(区间也是 0 ~ 1),再通过 easing 库将其映射到 0 ~ 1 到值即可。这里用到了 hook 调用 hook 的联动(通过 useRaf 驱动 useTween),还可以在其他地方举一反三。const fn: Easing = easing[easingName]; const t = useRaf(ms, delay); return fn(t);发请求利用 Hooks,可以将任意请求 Promise 封装为带有标准状态的对象:loading、error、result。通用 Http 封装效果:通过 useAsync 将一个 Promise 拆解为 loading、error、result 三个对象。const { loading, error, result } = useAsync(fetchUser, [id]);实现:在 Promise 的初期设置 loading,结束后设置 result,如果出错则设置 error,这里可以将请求对象包装成 useAsyncState 来处理,这里就不放出来了。export function useAsync(asyncFunction) { const asyncState = useAsyncState(options); useEffect(() => { const promise = asyncFunction(); asyncState.setLoading(); promise.then( result => asyncState.setResult(result);, error => asyncState.setError(error); ); }, params); }具体代码可以参考 react-async-hook,这个功能建议仅了解原理,具体实现因为有一些边界情况需要考虑,比如组件 isMounted 后才能相应请求结果。Request Service业务层一般会抽象一个 request service 做统一取数的抽象(比如统一 url,或者可以统一换 socket 实现等等)。假如以前比较 low 的做法是:async componentDidMount() { // setState: 改 isLoading state try { const data = await fetchUser() // setState: 改 isLoading、error、data } catch (error) { // setState: 改 isLoading、error } }后来把请求放在 redux 里,通过 connect 注入的方式会稍微有些改观:@Connect(...) class App extends React.PureComponent { public componentDidMount() { this.props.fetchUser() } public render() { // this.props.userData.isLoading | error | data } }最后会发现还是 Hooks 简洁明了:function App() { const { isLoading, error, data } = useFetchUser(); }而 useFetchUser 利用上面封装的 useAsync 可以很容易编写:const fetchUser = id => fetch(`xxx`).then(result => { if (result.status !== 200) { throw new Error("bad status = " + result.status); } return result.json(); }); function useFetchUser(id) { const asyncFetchUser = useAsync(fetchUser, id); return asyncUser; }填表单React Hooks 特别适合做表单,尤其是 antd form 如果支持 Hooks 版,那用起来会方便许多:function App() { const { getFieldDecorator } = useAntdForm(); return ( <Form onSubmit={this.handleSubmit} className="login-form"> <FormItem> {getFieldDecorator("userName", { rules: [{ required: true, message: "Please input your username!" }] })( <Input prefix={<Icon type="user" style={{ color: "rgba(0,0,0,.25)" }} />} placeholder="Username" /> )} </FormItem> <FormItem> <Button type="primary" htmlType="submit" className="login-form-button"> Log in </Button> Or <a href="">register now!</a> </FormItem> </Form> ); }不过虽然如此,getFieldDecorator 还是基于 RenderProps 思路的,彻底的 Hooks 思路是利用之前说的 __组件辅助方式,提供一个组件方法集,用解构方式传给组件__。Hooks 思维的表单组件效果:通过 useFormState 拿到表单值,并且提供一系列 __组件辅助__ 方法控制组件状态。const [formState, { text, password }] = useFormState(); return ( <form> <input {...text("username")} required /> <input {...password("password")} required minLength={8} /> </form> );上面可以通过 formState 随时拿到表单值,和一些校验信息,通过 password("pwd") 传给 input 组件,让这个组件达到受控状态,且输入类型是 password 类型,表单 key 是 pwd。而且可以看到使用的 form 是原生标签,这种表单增强是相当解耦的。实现:仔细观察一下结构,不难发现,我们只要结合 组件辅助 小节说的 “拿到组件 onChange 抛出的值” 一节的思路,就能轻松理解 text、password 是如何作用于 input 组件,并拿到其输入状态。往简单的来说,只要把这些状态 Merge 起来,通过 useReducer 聚合到 formState 就可以实现了。为了简化,我们只考虑对 input 的增强,源码仅需 30 几行:export function useFormState(initialState) { const [state, setState] = useReducer(stateReducer, initialState || {}); const createPropsGetter = type => (name, ownValue) => { const hasOwnValue = !!ownValue; const hasValueInState = state[name] !== undefined; function setInitialValue() { let value = ""; setState({ [name]: value }); } const inputProps = { name, // 给 input 添加 type: text or password get value() { if (!hasValueInState) { setInitialValue(); // 给初始化值 } return hasValueInState ? state[name] : ""; // 赋值 }, onChange(e) { let { value } = e.target; setState({ [name]: value }); // 修改对应 Key 的值 } }; return inputProps; }; const inputPropsCreators = ["text", "password"].reduce( (methods, type) => ({ ...methods, [type]: createPropsGetter(type) }), {} ); return [ { values: state }, // formState inputPropsCreators ]; }上面 30 行代码实现了对 input 标签类型的设置,监听 value onChange,最终聚合到大的 values 作为 formState 返回。读到这里应该发现对 React Hooks 的应用都是万变不离其宗的,特别是对组件信息的获取,通过解构方式来做,Hooks 内部再做一下聚合,就完成表单组件基本功能了。实际上一个完整的轮子还需要考虑 checkbox radio 的兼容,以及校验问题,这些思路大同小异,具体源码可以看 react-use-form-state。模拟生命周期有的时候 React15 的 API 还是挺有用的,利用 React Hooks 几乎可以模拟出全套。componentDidMount效果:通过 useMount 拿到 mount 周期才执行的回调函数。useMount(() => { // quite similar to `componentDidMount` });实现:componentDidMount 等价于 useEffect 的回调(仅执行一次时),因此直接把回调函数抛出来即可。useEffect(() => void fn(), []);componentWillUnmount效果:通过 useUnmount 拿到 unmount 周期才执行的回调函数。useUnmount(() => { // quite similar to `componentWillUnmount` });实现:componentWillUnmount 等价于 useEffect 的回调函数返回值(仅执行一次时),因此直接把回调函数返回值抛出来即可。useEffect(() => fn, []);componentDidUpdate效果:通过 useUpdate 拿到 didUpdate 周期才执行的回调函数。useUpdate(() => { // quite similar to `componentDidUpdate` });实现:componentDidUpdate 等价于 useMount 的逻辑每次执行,除了初始化第一次。因此采用 mouting flag(判断初始状态)+ 不加限制参数确保每次 rerender 都会执行即可。const mounting = useRef(true); useEffect(() => { if (mounting.current) { mounting.current = false; } else { fn(); } });Force Update效果:这个最有意思了,我希望拿到一个函数 update,每次调用就强制刷新当前组件。const update = useUpdate();实现:我们知道 useState 下标为 1 的项是用来更新数据的,而且就算数据没有变化,调用了也会刷新组件,所以我们可以把返回一个没有修改数值的 setValue,这样它的功能就仅剩下刷新组件了。const useUpdate = () => useState(0)[1];对于 getSnapshotBeforeUpdate, getDerivedStateFromError, componentDidCatch 目前 Hooks 是无法模拟的。isMounted很久以前 React 是提供过这个 API 的,后来移除了,原因是可以通过 componentWillMount 和 componentWillUnmount 推导。自从有了 React Hooks,支持 isMount 简直是分分钟的事。效果:通过 useIsMounted 拿到 isMounted 状态。const isMounted = useIsMounted();实现:看到这里的话,应该已经很熟悉这个套路了,useEffect 第一次调用时赋值为 true,组件销毁时返回 false,注意这里可以加第二个参数为空数组来优化性能。const [isMount, setIsMount] = useState(false); useEffect(() => { if (!isMount) { setIsMount(true); } return () => setIsMount(false); }, []); return isMount;在线 Demo存数据上一篇提到过 React Hooks 内置的 useReducer 可以模拟 Redux 的 reducer 行为,那唯一需要补充的就是将数据持久化。我们考虑最小实现,也就是全局 Store + Provider 部分。全局 Store效果:通过 createStore 创建一个全局 Store,再通过 StoreProvider 将 store 注入到子组件的 context中,最终通过两个 Hooks 进行获取与操作:useStore 与 useAction:const store = createStore({ user: { name: "小明", setName: (state, payload) => { state.name = payload; } } }); const App = () => ( <StoreProvider store={store}> <YourApp /> </StoreProvider> ); function YourApp() { const userName = useStore(state => state.user.name); const setName = userAction(dispatch => dispatch.user.setName); }实现:这个例子的实现可以单独拎出一篇文章了,所以笔者从存数据的角度剖析一下 StoreProvider 的实现。对,Hooks 并不解决 Provider 的问题,所以全局状态必须有 Provider,但这个 Provider 可以利用 React 内置的 createContext 简单搞定:const StoreContext = createContext(); const StoreProvider = ({ children, store }) => ( <StoreContext.Provider value={store}>{children}</StoreContext.Provider> );剩下就是 useStore 怎么取到持久化 Store 的问题了,这里利用 useContext 和刚才创建的 Context 对象:const store = useContext(StoreContext); return store;更多源码可以参考 easy-peasy,这个库基于 redux 编写,提供了一套 Hooks API。封装原有库是不是 React Hooks 出现后,所有的库都要重写一次?当然不是,我们看看其他库如何做改造。RenderProps to Hooks这里拿 react-powerplug 举例。比如有一个 renderProps 库,希望改造成 Hooks 的用法:import { Toggle } from 'react-powerplug' function App() { return ( <Toggle initial={true}> {({ on, toggle }) => ( <Checkbox checked={on} onChange={toggle} /> )} </Toggle> ) } ↓ ↓ ↓ ↓ ↓ ↓ import { useToggle } from 'react-powerhooks' function App() { const [on, toggle] = useToggle() return <Checkbox checked={on} onChange={toggle} /> }效果:假如我是 react-powerplug 的维护者,怎么样最小成本支持 React Hook? 说实话这个没办法一步做到,但可以通过两步实现。export function Toggle() { // 这是 Toggle 的源码 // balabalabala.. } const App = wrap(() => { // 第一步:包 wrap const [on, toggle] = useRenderProps(Toggle); // 第二步:包 useRenderProps });实现:首先解释一下为什么要包两层,首先 Hooks 必须遵循 React 的规范,我们必须写一个 useRenderProps 函数以符合 Hooks 的格式,**那问题是如何拿到 Toggle 给 render 的 on 与 toggle?**正常方式应该拿不到,所以退而求其次,将 useRenderProps 拿到的 Toggle 传给 wrap,让 wrap 构造 RenderProps 执行环境拿到 on 与 toggle 后,调用 useRenderProps 内部的 setArgs 函数,让 const [on, toggle] = useRenderProps(Toggle) 实现曲线救国。const wrappers = []; // 全局存储 wrappers export const useRenderProps = (WrapperComponent, wrapperProps) => { const [args, setArgs] = useState([]); const ref = useRef({}); if (!ref.current.initialized) { wrappers.push({ WrapperComponent, wrapperProps, setArgs }); } useEffect(() => { ref.current.initialized = true; }, []); return args; // 通过下面 wrap 调用 setArgs 获取值。 };由于 useRenderProps 会先于 wrap 执行,所以 wrappers 会先拿到 Toggle,wrap 执行时直接调用 wrappers.pop() 即可拿到 Toggle 对象。然后构造出 RenderProps 的执行环境即可:export const wrap = FunctionComponent => props => { const element = FunctionComponent(props); const ref = useRef({ wrapper: wrappers.pop() }); // 拿到 useRenderProps 提供的 Toggle const { WrapperComponent, wrapperProps } = ref.current.wrapper; return createElement(WrapperComponent, wrapperProps, (...args) => { // WrapperComponent => Toggle,这一步是在构造 RenderProps 执行环境 if (!ref.current.processed) { ref.current.wrapper.setArgs(args); // 拿到 on、toggle 后,通过 setArgs 传给上面的 args。 ref.current.processed = true; } else { ref.current.processed = false; } return element; }); };以上实现方案参考 react-hooks-render-props,有需求要可以拿过来直接用,不过实现思路可以参考,作者的脑洞挺大。Hooks to RenderProps好吧,如果希望 Hooks 支持 RenderProps,那一定是希望同时支持这两套语法。效果:一套代码同时支持 Hooks 和 RenderProps。实现:其实 Hooks 封装为 RenderProps 最方便,因此我们使用 Hooks 写核心的代码,假设我们写一个最简单的 Toggle:const useToggle = initialValue => { const [on, setOn] = useState(initialValue); return { on, toggle: () => setOn(!on) }; };在线 Demo然后通过 render-props 这个库可以轻松封装出 RenderProps 组件:const Toggle = ({ initialValue, children, render = children }) => renderProps(render, useToggle(initialValue));在线 Demo其实 renderProps 这个组件的第二个参数,在 Class 形式 React 组件时,接收的是 this.state,现在我们改成 useToggle 返回的对象,也可以理解为 state,利用 Hooks 机制驱动 Toggle 组件 rerender,从而让子组件 rerender。封装原本对 setState 增强的库Hooks 也特别适合封装原本就作用于 setState 的库,比如 immer。useState 虽然不是 setState,但却可以理解为控制高阶组件的 setState,我们完全可以封装一个自定义的 useState,然后内置对 setState 的优化。比如 immer 的语法是通过 produce 包装,将 mutable 代码通过 Proxy 代理为 immutable:const nextState = produce(baseState, draftState => { draftState.push({ todo: "Tweet about it" }); draftState[1].done = true; });那这个 produce 就可以通过封装一个 useImmer 来隐藏掉:function useImmer(initialValue) { const [val, updateValue] = React.useState(initialValue); return [ val, updater => { updateValue(produce(updater)); } ]; }使用方式:const [value, setValue] = useImmer({ a: 1 }); value(obj => (obj.a = 2)); // immutable总结把 React Hooks 当作更便捷的 RenderProps 去用吧,虽然写法看上去是内部维护了一个状态,但其实等价于注入、Connect、HOC、或者 renderProps,那么如此一来,使用 renderProps 的门槛会大大降低,因为 Hooks 用起来实在是太方便了,我们可以抽象大量 Custom Hooks,让代码更加 FP,同时也不会增加嵌套层级。那么看了这么多使用实例,你准备怎么用呢?原文:https://github.com/ascoders/blog/issues/29
2019年01月12日
753 阅读
0 评论
0 点赞
2019-01-08
有哪些鲜为人知,但是很有意思的网站?
网站之最第一个网站(世界上第一个网站):http://info.cern.ch/水滴(世界上最小的网站):http://www.guimp.com/世界最高(世界上最高的网站):https://worlds-highest-website.com/世界最长(世界上最长,增长最快的网站):http://www.worldslongestwebsite.com/世界之邮(世界上最长的邮箱):世界上最长的邮箱工具类看图识花(上传图片识别花的种类):http://stu.iplant.cn/webGridzzly(在线制作自己的网格纸):http://www.gridzzly.com/在线电子书转换器(电子书格式在线转换):http://cn.epubee.com/字体转换器(字体在线转换):http://www.akuziti.com/证件照换底色(一键换底色):https://www.bgconverter.com/OPEN GPS(高精度 IP 定位):https://www.opengps.cn/Default.aspxWindy(在线气象观测):https://www.windy.com/RAMMB(全球实时卫星云图):http://rammb-slider.cira.colostate.eduASD 商品历史价格查询(商品价格曲线):http://asd-price.com/铁路信息查询(全国铁路车站车次信息查询):https://moerail.ml/DesignCap(免费海报在线制作):https://www.designcap.com/app/在线工具箱(各种实用工具聚合):http://tool.mkblog.cn/在线工具箱(各种实用工具聚合):http://www.nicetool.net/长链接生成器:长链接生成器 v2.2图片/视频工具Photopea(网页版 PS):https://www.photopea.com/PHOTOMOSH(给图片视频加特效):https://photomosh.com/Algorithmia(AI 给黑白照片上色):https://demos.algorithmia.com/colorize-photos/Remove.bg(在线一键抠图):https://www.remove.bg/Nod to the Rhythm(让你的照片张嘴唱歌):http://nodtotherhythm.com/makeGfycat(在线制作并托管高清 GIF):https://gfycat.com/应景图(GIF 图片添加字幕水印):http://www.yingjingtu.com/indexwebsiteplanet(图片压缩):https://www.websiteplanet.com/zh-hans/webtools/imagecompressor/极速瘦图(图片压缩):http://www.jsysuo.com/Picdiet(图片压缩):https://www.picdiet.com/zh-cnTinyPNG(图片压缩):https://tinypng.com/Squoosh(图片压缩):https://squoosh.app/Needs More JPEG(图片超级压缩):http://needsmorejpeg.com/Waifu2x(图片在线无损放大):http://waifu2x.udp.jp/Bigjpg(图片放大):http://bigjpg.com/ILOVEIMG(在线图片编辑器):https://www.iloveimg.com视频解析网(微博,秒拍,快手,抖音):https://www.parsevideo.com/IT/AI/工具类王斌给您对对联(AI 在线对对联):https://ai.binwang.me/couplet/玄派网 (武侠生成器):http://www.xuanpai.com/Grabient(CSS 代码渐变颜色生成工具):https://www.grabient.com/多彩的颜色(图片色彩分析):https://woshizja.github.io/colorful-color/Emoji 短网址(把网址变成 Emoji 表情):https://e.mezw.com/表情符号生成器(Emoji 表情自定义创建生成):https://phlntn.com/emojibuilder/万象智能鉴黄系统(图片分析):https://cloud.tencent.com/act/event/ci_demo给小动物加光剑(趣味加工):https://giphy.com/search/lightsaber-catsLyrebird(克隆自己的声音,需登录):https://lyrebird.ai/signupWindows93(网页版 windows93 系统):http://www.windows93.net/百度镜子(百度的镜子网站):https://baidujingzi.com/bilibili 镜子(bilibili 的镜子网站)http://www.ilidilid.com/ertdfgcvb(代码特效展示):https://ertdfgcvb.xyz/Silk(互动生成艺术画):http://weavesilk.com/ASCIIFlow Infinity(可视化字符图像绘制):http://asciiflow.com/我知道你下载了什么(BT 下载内容监测):https://iknowwhatyoudownload.com/en/peer/Foxmiguel(游戏直播聚合站):http://www.foxmiguel.com/逗比拯救世界(表情包分享):http://www.bee-ji.com/装逼大全(表情包制作):https://www.zhuangbi.info/音乐/影视类穿帮网(影视剧穿帮镜头赏析):http://www.bug.cn/The Movie title stills collection(电影标题剧照集):http://annyas.com/screenshots/音乐搜索器(音乐聚合搜索下载生成外链):http://tool.mkblog.cn/music/资源帝(在线音乐聚合)http://music.ziyuandi.cn/SUKIER(冷门歌曲推荐):http://www.sukier.com/中国摇滚(中国摇滚年代史):http://www.yaogun.com/Youtube 字幕下载(字幕下载):http://downsub.com/胖鸟电影(最新影视剧蓝光下载站):http://www.pniao.com/爱追剧(电视剧电影追剧下载):http://www.aizhuiju.com/Mov电影天堂(小体积影视剧下载):https://www.dygod.net/高清 MP4ba(最新影视剧下载:复活了):http://www.mp4ba.com/RARBG(美国影视 BT 站):https://rarbgprx.org/torrents.php动漫花园资源网(动漫作品 BT 下载站):https://share.dmhy.org/爱恋动漫(动漫作品 BT 下载站):http://www.kisssub.org/迷你 MP4(最新影视剧下载):http://www.minimp4.com二次元/动漫类萌娘百科:https://zh.moegirl.org/Mainpage神奇宝贝百科:http://wiki.52poke.com/小鸡词典(互联网流行词汇百科网站):https://jikipedia.com/AnimeShot (把动画字幕用于吐槽生活):https://as2.bitinn.net/萌兔本地漫画阅读器(漫画本子在线阅读):http://wusagi.pw/猫耳 FM(二次元声站):https://www.missevan.com/Bilibili 工具箱(弹幕内容查用户名):https://biliquery.typcn.com/PaintsChainer(AI 为你的画自动上色):AI 涂色绘画文艺类时间胶囊(封存自己的记忆):http://p.timepill.net/时光邮局(给未来的自己写一封信):https://www.hi2future.com/I Remember(我记得,一个记忆碎片网站,头脑特工队):http://i-remember.fr/en/海の見える駅(能看见海的车站):https://seaside-station.com/网盘/搜索类快速创建收件夹(百度网盘匿名收件箱):http://xzc.cn/Firefox Send(临时网盘):https://send.firefox.com/Ecosia(搜索引擎,搜索使用的广告收入用于种植树木):https://www.ecosia.org/爱搜资源(百度网盘密码破解分享):https://www.aisouziyuan.com/鸠摩搜书(电子书搜索下载):https://www.jiumodiary.com/GIPHY(GIF 动图搜索网站):https://giphy.com/Similar Site Search(相似网站搜索):https://www.similarsitesearch.com/cn/文学/百科类武侠世界(歪果仁翻译中国小说):https://www.wuxiaworld.com/维基大典(国学百科):維基大典Gallerix(世界名画档案馆):https://gallerix.ru/Internet Archive(互联网档案博物馆):https://archive.org/世界护照大全(领略全球护照风采):https://www.passportindex.org/cn/中国海报(中国历年海报存档):https://chineseposters.net/美丽的化学(化学之美):https://www.beautifulchemistry.net/乡音苑(中国方言活地图):http://phonemica.net/湿在人为(两性博客):http://www.idashi.org/汉典(汉字典书):http://www.zdic.net/书格(传统书籍):https://shuge.org/古诗文网(中国传统古诗文):https://www.gushiwen.org/中少快乐阅读平台(怀旧少儿老杂志):少年儿童杂志全集实用/行政类国家邮政局申诉网站(快递问题投诉专用):http://sswz.spb.gov.cn/无人认领尸体在线查询(慎入):http://www.gzbz.com.cn/dead_men/index.asp药物临床试验登记与信息公示平台(人体实验):http://www.chinadrugtrials.org.cn/德州大学电子图书馆(人体骨骼 X 光标本):http://www.digimorph.org/index.phtml趣味/无聊类Spray.training(FPS 游戏压枪训练工具):http://spray.training/一键六学(网络梗生成器):http://bog.ac/tool/6/#今天中午吃什么?(世纪难题):https://www.zwcsm.com/你好污啊(撩妹金句):https://www.nihaowua.com/Clash(用歌声说出你想说的话,想起了《大黄蜂》):https://clash.me/Windows Update Prank(假装 Windows 升级界面):http://fakeupdate.net/字符跳跃(让你的网址动起来):http://glench.com/hash/#CLICKhappy happy hardcore(治愈小表情):https://happyhappyhardcore.com/经典 DVD(无聊网站):http://itneverhitsthecorner.com/Neave.TV(稀奇古怪的电视频道):https://neave.tv/无聊网站大全(点击进入随机无聊站):https://theuselessweb.com/声音/太空类雨季情绪(下雨的声音):https://rainymood.com/VirtOcean(海洋的声音):http://virtocean.com/Purrli(猫打呼噜的声音):https://purrli.com/这里有猫(Purrli 中文版):https://m.niucodata.com/cat/cat.php?from=wbMeteor showers(太空视角观看流星雨):https://www.meteorshowers.org/怀旧类秘密花园(中文网站考古):http://www.yini.org/Inspirograph(怀旧在线万花尺):https://nathanfriend.io/inspirograph/日本传统色(古典传统配色):http://nipponcolors.com/中国色(日本传统色的中文版):http://zhongguose.com/四大名著小说(名著地图):http://www.sdmz.net/水浒 108 将形象大全(怀旧图片):http://ls.ganquancun.com/shuihuzhuan108/游戏/测试类Am I pretty or ugly?(在线颜值分析):https://www.prettyscale.com/MyAccent(测试你的口音是英式还是美式):口音测试扫雷(网页版扫雷游戏):https://www.saolei.org/信任的进化(人性小游戏):https://www.sekai.co/trust/太鼓达人(日本经典音乐游戏网页版):https://taiko.bui.pm/一画换一画(互动绘画):http://www.sketchswap.com/Lines FRVR(划线小游戏):https://lines.frvr.com/Bad News(虚假新闻是怎样炼成的):https://getbadnews.com/#playLINE RIDER(Flash 小游戏):https://www.linerider.com/QWOP(经典小游戏):http://www.foddy.net/Athletics.htmlMikutap(鼠标音乐游戏):https://aidn.jp/mikutap/Mikutap(鼠标音乐游戏中文版):https://static.hfi.me/mikutap/it’s a(door)able(解锁小游戏):https://ncase.me/door/ScribblerToo(蜘蛛画画)Scribbler TooTexter(字符画):http://tholman.com/texter/Finding Home(音乐解压游戏):http://findingho.me/魔术键盘(解压网站):http://magickeyboard.io/Emojis & Earth Porn(寻找不动的表情):http://emojisandearthporn.com/Staggering Beauty(精神污染):http://www.staggeringbeauty.com/Pica Pic(复古手持游戏合集):http://www.pica-pic.com/在线 DOS 游戏(中文怀旧游戏):https://dos.zczc.cz/中文家用游戏博物馆(中文怀旧游戏):http://www.famicn.com/老男人游戏网(模拟器 ROM 下载网站):http://www.oldmanemu.net/Neave Interactive(互动小游戏合集):https://neave.com/Bestgames(在线小游戏网站):http://www.bestgames.com/IGCD(互联网游戏汽车数据库):http://www.igcd.net/特别推荐福利吧(分享你的福利吧):http://fulibus.net/龙轩导航(可能是最好用的导航网站):http://ilxdh.com/抽屉新热榜(不正经的资讯社区):https://dig.chouti.com有趣网址之家(趣味小站集锦):https://youquhome.com/原文:github
2019年01月08日
413 阅读
0 评论
0 点赞
2019-01-01
小程序全局组件的使用
开发小程序时经常会用到全局组件,不限制页面随时可用的组件,但小程序的全局组件非常鸡肋,小程序并不能动态渲染,需要每个页面都需要引入,虽然可以全局声明 (开发者工具 1.02.1810190 支持),但是像全局提醒组件还是需要每个页面写组件,但是可以把组件方法提到全局 也是很好了 ,接下来开始小程序全局组件的使用吧!编写全局提示组件index.wxml<view class="notice-container {{visible?'notice-show':''}}"> {{ item.content }} </view>index.wxss.notice-container { position: fixed; top: 0; min-height: 30px; line-height: 30px; width: 100%; background: rgba(0, 0, 0, 0.25); color: #fff; display: none; } .notice-container .notice-show { display: block; }index.jsconst default_data = { visible: false, content: "" }; let timmer = null; Component({ data: { ...default_data }, methods: { handleShow(options) { this.setData({ ...options, visible: true }); const d = 2 * 1000; if (timmer) clearTimeout(timmer); if (d !== 0) { timmer = setTimeout(() => { this.handleHide(); timmer = null; }, d); } }, handleHide() { this.setData({ ...default_data }); } } });index.json{ "component": true }全局声明在app.json配置中添加如下配置{ "usingComponents": { "notice": "./components/notice/index" } }页面引入这就麻烦了 需要的页面都需要引入只是不需要声明了页面引入如下<notice id="notice"></notice>这里的id 值很重要后面要用到全局调用接下来很重要新建notice.jsfunction getCtx(selector) { const pages = getCurrentPages(); const ctx = pages[pages.length - 1]; const componentCtx = ctx.selectComponent(selector); if (!componentCtx) { console.error("无法找到对应的组件"); return null; } return componentCtx; } function Notice(options) { const { selector = "#notice" } = options; // notice 为刚才的id值 const ctx = getCtx(selector); ctx.handleShow(options); } const $notice = Notice; export default $notice;每个页面全局调用这个 $notice 方法就可以了最后反正小程序的全局组件还是很难用,如果能 js 动态渲染就好了 期待吧。。
2019年01月01日
2,610 阅读
0 评论
1 点赞
2019-01-01
基于Node.js 的抖音去水印视频下载脚本
文章已删除
2019年01月01日
2,658 阅读
0 评论
0 点赞
2018-12-24
网易云音乐歌词直链
音乐这里需要用到网易云音乐提供的直链 apihttps://music.163.com/song/media/outer/url?id=xxx.mp3这里的 id=xxx.mp3 是根据你需要的歌曲的 ID 进行更改。例如:歌曲:演员-薛之谦https://music.163.com/#/song?id=32507038修改为:https://music.163.com/song/media/outer/url?id=32507038.mp3DEMO 演示 歌词官方接口http://music.163.com/api/song/media?id=xxx这里的 id=xxx 是根据你需要的歌曲的 ID 进行更改。总结以歌曲 2 Days为例网页: https://music.163.com/#/song?id=517918738音乐: https://music.163.com/song/media/outer/url?id=517918738.mp3
2018年12月24日
1,209 阅读
0 评论
0 点赞
1
...
8
9
10