react项目类名动态添加

形式一: className={['permission-row-wrap', opt.childrenList.length ? 'permission-row-no-right' : null]}形式二: className={`pageName-wrap ${opt.childrenList.length ? 'pageName-wrap-no-right' : null}`}形式三: className{[classA,'box',index===this.state.currentState?"active":null].join('')}最终失去: className={'classsA' 'classB'}留神肯定不能如下写法: className={arr.length && 'classsA'}~~~~通过编译之后变成如下:className={false}并不是冀望的className={'classsA'}

July 13, 2020 · 1 min · jiezi

从前端的视角理解数据和缓存

对数据系统的了解数据系统设计是对于数据存储、共享、更新(以及流传更新)、缓存(以及缓存生效)的技术。大部分软件系统都能够从数据系统的角度去了解。 数据系统是如此的广泛,以至于开发者实际上每天都在设计数据系统,却经常没有意识到它们的普适性,将多个实质雷同的问题当作了孤立的问题来了解。利用状态治理、配置管理、用户数据管理问题,实质上都属于数据系统的问题。 本篇文章站在前端的视角上,通过对数据系统的探讨,心愿帮忙开发者在开发的过程中无意识地辨认、设计数据系统: 哪些是数据根源、哪些是缓存?数据根源在哪些组件之间共享?(即作用域多大?)缓存在哪些组件之间共享?(即作用域多大?)缓存的生命周期是多长?客户端中的哪些利用状态能够视为服务端数据库的缓存?本文的大部分例子是前端利用,然而数据系统的规定实用于任何软件系统。 如果你心愿从服务端、数据库的视角来了解数据系统,我理解到ddia是一本很优良的书籍,提供了更残缺、业余的探讨。繁多数据源与层级缓存任何数据系统都须要遵循一个准则:single source of truth,即繁多数据根源。每个数据应该只有一个【数据根源】,其余的数据获取形式都只是缓存。 如果你是一名前端开发者,那么你在学习前端状态治理(比方redux)的时候,应该曾经据说过这个准则,然而你可能会疏忽这个准则的普适性:这个准则并不仅仅实用于前端利用的状态治理,它实用于任何软件系统。状态治理问题并不是特定于前端畛域的问题,而是任何软件系统设计的广泛问题。 意识层级缓存数据系统的设计,很大水平上是【层级缓存零碎】的设计。 从计算机底层的视角来看,缓存层级是这样的: 完整版耗时表。通过这些工夫,能够大抵估算出一个数据系统的性能。缓存层级的特点: 处于越低的层级,越靠近于【数据根源】处于越低的层级,存储容量越大,数据越残缺处于越低的层级,拜访起来越慢当最底层的数据根源产生更新的时候,下层的数据缓存应该及时生效,并且针对旧数据的操作不应该间接利用于新数据上站在理论软件系统的视角,情理也是一样的,只不过利用在了更加宏观的层面: 个别不须要思考计算机底层的缓存退出利用运行时缓存,比方前端利用状态(实质上还是在内存中)对于 服务器/客户端 零碎,客户端中的大部分利用状态能够视为服务端数据库的缓存缓存落后问题任何波及到缓存的中央,就免不了缓存落后的问题。当最底层的数据根源产生更新的时候,上游的数据缓存应该及时生效,并且针对旧数据的操作不应该间接利用于新数据上。一份数据源,可能被内部利用更新。如果缓存无奈在第一工夫晓得【数据根源】的更新,那么它就会落后于理论数据,产生不统一。 不同的数据系统对于缓存不统一的容忍水平不同,缓存生效的策略也不同。 比方DNS零碎,只须要保障用户最终可能读取到最新的IP地址(最终一致性)。批改DNS记录后不会在寰球所有DNS服务节点失效,须要期待DNS服务器缓存过期后向源服务器申请新记录能力实现更新。 从web前端利用的视角来说,很多前端利用状态能够视为服务端数据源的缓存。一般来说前端利用可能在”本人提交更新的时候“更新前端状态。然而如果是一些内部事件造成服务端数据源的扭转,大部分前端利用无奈立即通晓更新。大部分前端利用抉择容忍这种缓存落后,仅在组件挂载时申请数据、更新状态,因为跨客户端/服务器做缓存生效的代价太大了。缓存落后造成的典型问题有:”删除操作时,资源曾经不存在,因而操作失败“。 作用域与生命周期【数据根源】、缓存都须要思考作用域与生命周期。 作用域就是对数据共享范畴的考量;生命周期是对创立、销毁机会的考量。两者往往有很大的相关性。 常见的【数据】作用域划分形式: 跨利用实例级别:多个标签页(利用实例)共享一个【数据】单利用实例级别:每个标签页(利用实例)外部有一个全局【数据】(也叫利用全局数据)利用部分级别:利用部分治理本人的【数据】,一个页面中可能蕴含多个独立的【数据】。比方: 组件实例级别:每个组件实例领有一份本人的【数据】。相似于对象属性。组件类级别:每一个组件类共享一份本人的【数据】。相似于类的动态属性。组件树级别:一颗组件树共享一份本人的【数据】。手动管制:你也能够在组件之间手动传递【数据】对象,更准确地管制【数据】的可见范畴。当没有组件持有【数据】对象的时候,它就会被垃圾回收。这里的【数据】能够指代【数据根源】,也能够指代【缓存】。常见的生命周期划分形式: 长久化,【数据】只能被利用被动删除。个别与“跨利用实例级别”的作用域配合。与页面生命周期同步,页面销毁时这个【数据】也销毁。个别与“单利用实例级别”的作用域配合。与组件生命周期同步,应用程序框架依据以后利用状态和输出,来创立、销毁组件,【数据】也随之创立和泯灭。个别与“组件实例级别”或“组件树级别”的作用域配合。与代码模块的生命周期同步。这种【数据】个别申明在代码模块顶部,或者作为类的动态成员。比方:const sharedCache = new Map();export const Component = class Component { // ... getData(key) { return sharedCache.get(key); }}手动创立、革除。比方第一次执行某种行为的时候创立【数据】,在利用路由到某个性能之外的时候革除。个别与“手动管制”的作用域配合。辨认常见的数据系统在辨认、设计数据系统的时候,对于每一个逻辑上的数据定义,应该先有一个明确的【数据根源】,而后衍生出多级缓存。上面列举一些常见的数据系统类型。 长久化存储作为【数据根源】常见的长久化存储是文件系统、数据库。 举个例子,咱们能够用数据库来存储用户的账号、姓名、邮箱等用户数据,将它作为【数据根源】。 这些中央可能蕴含用户数据的【缓存正本】: - 数据库自身的缓存零碎,由数据库外部实现- 服务端利用内个别会应用**申请级别**的缓存:每次申请读取一次数据源,存到缓存(即变量)中,用来做计算。缓存的作用域和生命周期都是本次申请- 客户端利用向服务端申请某个用户的数据当前,将后果保留在客户端利用状态中。**客户端中的很多利用状态,实质上都是服务端数据源的缓存**。当数据须要更新时,必须提交给服务端的【数据根源】。长久化存储在软件系统在软件敞开时也可能保持数据,个别只能由利用被动删除。 计算公式作为【数据根源】有一类数据,是能够基于其余数据来计算出来的,它的根源无需存储在硬盘或内存中。对于这种计算数据来说,如果在每次须要应用的时候都计算一次,一来可能造成性能问题,二来可能导致前后不统一。因而往往须要应用缓存,并且要明确定义缓存的生命周期(比方软件重启、页面刷新时从新计算)。 举个例子,用户年龄是一种数据,然而并没有哪个会数据库会存储“用户当初多少岁”这个数据,它的【数据根源】是一个计算公式:以后工夫-出世工夫。前端利用个别在须要展现年龄的时候就计算一次,存到利用状态(实质上是内存中的缓存),而后在以后页面始终应用这个后果。 这个缓存的生命周期与页面生命周期统一,页面敞开时缓存也随之销毁。作一个极其的假如,这个页面关上应用超过了一年,那么就会呈现缓存过期的问题(岁数应该增长了一岁),因而须要引入缓存生效的伎俩。最原始的缓存生效伎俩是,重启利用(即刷新页面),下次启动的时候从新计算最新的年龄。 这个缓存的作用域仅限于这个页面,如果有多个标签页同时关上了这个前端利用,那么每个页面都有一份本人的缓存,互相隔离,防止读取到同一个数据的两个缓存。 利用状态作为【数据根源】对于前端利用来说,浏览器url是一种前端利用状态(只不过它由浏览器来治理,并提供操控API给前端利用代码)。前端利用依据不同的url状态来展现不同的性能,服务端不关怀每个客户的url状态,因而url是前端利用的一种【数据根源】。前端利用个别会订阅url的更新,响应url的变动展现不同的页面组件。 在这里,前端url数据并没有显著的缓存的存在。实践上你能够每次须要应用这个数据的时候都拜访数据根源window.location.href。有时候在路由框架中存在一份url缓存正本,只不过因为它订阅了url的更新,所以个别不会呈现缓存落后的问题。比方,前端利用能够辨认这个模式的url来失去region参数:www.my-app.com/${region}/items。如果用户拜访了url:www.my-app.com/cn-hangzhou/items,那么就相当于启动利用,并把region数据初始化为cn-hangzhou。url就是region数据的【数据根源】。如果用户在利用中通过操作按钮切换了region,前端应用逻辑就应用浏览器API来更新url(数据根源),而后,前端利用感知到url的更新,进而更新本人的行为。 这个例子也能够看出,须要更新数据的时候,应该更新【数据根源】,而不应该间接更新缓存。由前端治理的【数据根源】还包含:页面的滚动状态、输入框的focus的状态等UI状态,无需提交给服务端。 因为这种数据根源就在本过程中,访问速度很快,因而个别不须要思考缓存。次要须要思考的是它的初始化形式和作用域。 常见的初始化形式: 能够间接初始化为一个默认值。能够读取利用启动参数来初始化数据根源。比方下面的例子,用户点击怎么的url来关上页面,决定了利用的初始region。对于命令行利用则能够读取命令行参数。能够在利用启动时读取内部状态来初始化数据根源。初始化当前就无需再思考内部状态。当数据须要更新时,间接更新利用中的【数据根源】,这是它与”内部长久化存储作为数据根源“的基本区别。作用域就是对数据共享范畴的考量。常见的作用域划分形式: 多个页面(利用实例)共享一个【数据根源】每个页面(利用实例)外部有一个全局【数据根源】(也叫全局状态)每个利用部分(比方一颗组件树)有【数据根源】,一个页面中可能蕴含多个独立的【数据根源】相干浏览前端React相干: https://twitter.com/kentcdodd...https://twitter.com/dan_abram...

July 13, 2020 · 1 min · jiezi

如何写一个Nx-schematic-plugin

前言玩过Angular的同学都晓得Angular作为一个Framework,领有一套齐备的生态,还集成了弱小的CLI。而React则仅仅是一个轻量级的Library,官网社区只定义了一套组件的周期规定,而周边社区能够基于此规定实现本人的组件,React并不会提供给你一套开箱即用的计划,而须要本人在第三方市场筛选称心的组件造成“全家桶”,这也是React社区沉闷的起因之一。 最近工作中在思考应用monorepo对我的项目进行治理,发现了一套dev toolkit叫做Nx,Nx应用monorepo的形式对我的项目进行治理,其外围开发者vsavkin同时也是Angular我的项目的晚期核心成员之一,他把Angular CLI这套货色拿到Nx,使其不仅能够反对Angular我的项目的开发,当初还反对React我的项目。 Nx反对开发本人的plugin,一个plugin包含schematics和builders(这两个概念也别离来自Angular的schematics以及cli-builders),schematics按字面意思了解就是“大纲”的意思,也就是能够基于一些模板自动化生成所需的文件;而builders就是能够自定义构建流程。 明天要讲的就是如何开发一个属于本人的Nx plugin (蕴含schematics),我会应用它来自动化创立一个页面组件,同时更新router配置,主动将其退出react router的config。 对于Monorepo这篇文章不会具体介绍什么是monorepo,mono有“单个”的意思,也就是单个仓库(所有我的项目放在一个仓库下治理),对应的就是polyrepo,也就是失常一个我的项目一个仓库。如下图所示: 更多对于monorepo的简介,能够浏览以下文章: Advantages of monoreposHow to develop React apps like Facebook, Microsoft, and GoogleMisconceptions about Monorepos: Monorepo != Monolith对于Nx plugin先贴一张脑图,一个一个解说schematic的相干概念: 后面提到Nx plugin包含了builder(自动化构建)和schematic(自动化我的项目代码的增删改查)。一个成型的Nx plugin能够应用Nx内置命令执行。 对于文章要介绍的schematics,能够认为它是自动化代码生成脚本,甚至能够作为脚手架生成整个我的项目构造。 Schematics要实现的指标Schematics的呈现优化了开发者的体验,晋升了效率,次要体现在以下几个方面: 同步式的开发体验,无需晓得外部的异步流程Schematics的开发“感觉”上是同步的,也就是说每个操作输出都是同步的,然而输入则可能是异步的,不过开发者能够不必关注这个,直到上一个操作的后果实现前,下一个操作都不会执行。 开发好的schematics具备高扩展性和高重用性一个schematic由很多操作步骤组成,只有“步骤”划分正当,扩大只须要往里面新增步骤即可,或者删除原来的步骤。同时,一个残缺的schematic也能够看做是一个大步骤,作为另一个schematic的前置或后置步骤,例如要开发一个生成Application的schematic,就能够复用原来的生成Component的schematic,作为其步骤之一。 schematic是原子操作传统的一些脚本,当其中一个步骤产生谬误,因为之前步骤的更改曾经利用到文件系统上,会造成许多“副作用”,须要咱们手动FIX。然而schematic对于每项操作都是记录在运行内存中,当其中一项步骤确认无误后,也只会更新其外部创立的一个虚构文件系统,只有当所有步骤确认无误后,才会一次性更新文件系统,而当其中之一有误时,会撤销之前所做的所有更改,对文件系统不会有“副作用”。 接下来咱们理解下和schematic无关的概念。 Schematics的相干概念在理解相干概念前,先看看Nx生成的初始plugin目录: your-plugin |--.eslintrc |--builders.json |--collection.json |--jest.config.js |--package.json |--tsconfig.json |--tsconfig.lib.json |--tsconfig.spec.json |--README.md |--src |--builders |--schematics |--your-schema |--your-schema.ts |--your-schema.spec.ts |--schema.json |--schema.d.tsCollectionCollection蕴含了一组Schematics,定义在plugin主目录下的collection.json: { "$schema": "../../node_modules/@angular-devkit/schematics/collection-schema.json", "name": "your-plugin", "version": "0.0.1", "schematics": { "your-schema": { "factory": "./src/schematics/your-schema/your-schema", "schema": "./src/schematics/your-schema/schema.json", "aliases": ["schema1"], "description": "Create foo" } }}下面的json文件应用@angular-devkit/schematics下的collection schema来校验格局,其中最重要的是schematics字段,在这外面定义所有本人写的schematics,比方这里定义了一个叫做"your-schema"的schematic,每个schematic下须要申明一个rule factory(对于rule之后介绍),该factory指向一个文件中的默认导出函数,如果不应用默认导出,还能够应用your-schema#foo的格局指定以后文件中导出的foo函数。 ...

July 11, 2020 · 8 min · jiezi

手把手带你入门-Gatsby

Gatsby 介绍什么是 GatsbyGatsby 是一个基于 React ,用于搭建动态站点的开源框架,用于帮忙 开发者构建运行速度极快的网站。能够说它是一个动态站点生成器,Gatsby 次要的利用的技术是 React 和 GraphQL。 官网:https://www.gatsbyjs.org/ 为什么选 GatsbyGatsby 能疾速应用 React 生态系统来生成动态网站,它具备更高的性能,而且 Gatsby 的生态也很弱小。 当你想本人入手搭建集体博客时,思考的是 SEO 要好,而且你不必理睬数据库和服务器等简单的部署设置,Gatsby 构建的网站为动态站点,你能够很轻松的将网站部署在很多服务上。Gatsby 不须要期待申请时生成页面,而是事后生成页面,因而网站的速度会很快。 Gatsby 中使用了 React, react-router, Webpack 以及 GraphQL 等新技术,也追随了技术潮流。 GraphQL 介绍下面咱们说到了 GraphQL,没理解的同学可能不太分明。 GraphQL 是一种用于 API 的查询语言,是 Restful API 的替代品。至于代替 Restful API 一说,集体感觉当初 Restful API 占据相对的主导地位,以后还是很难被撼动的。因为目前来看,GraphQL 也有它的毛病。 看到这里没学过 GraphQL 也不必怕,因为用到的不多而且 Gatsby 内置一个界面不便咱们操作。GraphQL 目前国外比拟火,它作为一门新技术,即便不深刻咱们也能够理解一下。 这里咱们当然要吹捧它的长处,下面的定义你可能会纳闷,API 不就是后端写好的接口吗,怎么还能查问? GraphQL 咱们把这个单词拆开的话是: Graph(图形或图表) + QL(Query Language),它是一种图形化的查询语言,形容客户端如何像服务端申请数据的语法标准。听起来隐隐约约,那它绝对 Restful API 又解决了什么痛点呢? ...

July 10, 2020 · 7 min · jiezi

从0开始手把手教你使用React开发答题App

我的项目演示地址我的项目演示地址 我的项目源码我的项目源码 视频教程视频教程 我的项目代码构造 前言React 框架的优雅显而易见,组件化的编程思维使得React框架开发的我的项目代码简洁,易懂,但晚期 React 类组件的写法略显繁琐。React Hooks 是 React 16.8 公布以来最吸引人的个性之一,她简化了原有代码的编写,是将来 React 利用的支流写法。 本文通过一个实战小我的项目,手把手从零开始率领大家疾速入门React Hooks。本我的项目线上演示地址: 在本我的项目中,会用到以下知识点: React 组件化设计思维React State 和 PropsReact 函数式组件的应用React Hooks useState 的应用React Hooks useEffect 的应用React 应用 Axios 申请近程接口获取问题及答案React 应用Bootstrap丑化界面Hello React(1)装置node.js 官网链接 (2)装置vscode 官网链接 (3)装置 creat-react-app 性能组件,该组件能够用来初始化一个我的项目, 即依照肯定的目录构造,生成一个新我的项目。关上cmd 窗口 输出: npm install --g create-react-app npm install --g yarn(-g 代表全局装置) 如果装置失败或较慢。须要换源,能够应用淘宝NPM镜像,设置办法为:npm config set registry https://registry.npm.taobao.org设置实现后,从新执行 npm install --g create-react-appnpm install --g yarn(4)在你想创立我的项目的目录下 例如 D:/project/ 关上cmd命令 输出 ...

July 10, 2020 · 7 min · jiezi

在-React-中用事件驱动进行状态管理

作者:Abdulazeez Abdulazeez Adeshina翻译:疯狂的技术宅 原文:https://blog.logrocket.com/ev... 未经容许严禁转载 自 Hook 被引入 React 以来,Context API 与 Hook 库在利用状态治理中被一起应用。然而把 Context API 和 Hooks(许多基于 Hooks 的状态治理库建设在其根底上)组合的用法对于大规模利用来说可能效率不高。 因为必须创立一个自定义的 Hook 能力启用对状态及其办法的拜访,而后能力在组件中应用它,所以在理论开发中很繁琐。这违反了 Hook 的真正目标:简略。然而对于较小的利用,Redux 可能会显得太重了。 明天咱们将探讨 Context API 的代替办法:Storeon。 Storeon 是一个微型的、事件驱动的 React 状态治理库,其原理相似于 Redux。用 Redux DevTools 能够查看并可视化状态操作。 Storeon 外部应用 Context API 来治理状态,并采纳事件驱动的办法进行状态操作。 Storestore 是在应用程序状态下存储的数据的汇合。它是通过从 Storeon 库导入的 createStoreon() 函数创立的。 createStoreon() 函数承受模块列表,其中每个模块都是一个承受 store 参数并绑定其事件监听器的函数。这是一个store 的例子: import { createStoreon } from 'storeon/react'// todos moduleconst todos = store => { store.on(event, callback)}export default const store = createStoreon([todos])模块化Storeon 中的 store 是模块化的,也就是说,它们是独立定义的,并且没有被绑定到 Hook 或组件。每个状态及其操作方法均在被称为模块的函数中定义。这些模块被传递到 createStoreon() 函数中,而后将其注册为全局 store。 ...

July 10, 2020 · 4 min · jiezi

React高阶组件HOC三继承方式的高阶组件

继承形式的高阶组件采纳继承关联作为参数的组件和返回的组件,如果传进来的参数组件为WrappedComponent,那么返回中的组件将会继承于如果传进来的参数组件为WrappedComponent。 与代理形式的高阶组件的区别代理形式的高阶组件 继承形式的高阶组件 从以上两张图中能够看出有两点区别 1、继承形式的高阶组件继承于React.Component,而代理形式的高阶组件间接继承于传入的组件参数。2、返回值也是显著不同的。继承形式的高阶组件须要把传入组件返回进来,而代理形式的高阶组件返回的是super.render()。继承形式的高阶组件的利用其有两种操作: 1、操纵props。2、操纵生命周期。1.操纵props首先创立一个继承形式的高阶组件D,通过以下办法实现操纵props。 import React from 'react'const D = (WrappedComponent) => class NewComp extends WrappedComponent { render() { // 获取继承而来的传入组件的元素内容 const element = super.render() // 对元素标签判断增加款式属性 const newStyle = { color:element.type === 'div' ? 'red' : 'yellow', fontSize: '50px' } // 定义新的props const newProps = {...this.props, style: newStyle} // React.cloneElement办法生成新的React对象 return React.cloneElement(element, newProps, element.props.children) } }export default D创立一般组件E传入继承高阶组件D import React, { Component } from 'react'import D from './D'@Dclass E extends Component { render() { return ( <div>我是E中div</div> ) }}export default E创立一般组件F传入继承高阶组件D ...

July 9, 2020 · 1 min · jiezi

手写一个ReactRedux玩转React的Context-API

上一篇文章咱们手写了一个Redux,然而单纯的Redux只是一个状态机,是没有UI出现的,所以个别咱们应用的时候都会配合一个UI库,比方在React中应用Redux就会用到React-Redux这个库。这个库的作用是将Redux的状态机和React的UI出现绑定在一起,当你dispatch action扭转state的时候,会自动更新页面。本文还是从它的根本应用动手来本人写一个React-Redux,而后替换官网的NPM库,并放弃性能统一。 本文全副代码曾经上传GitHub,大家能够拿下来玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/react-redux 根本用法上面这个简略的例子是一个计数器,跑起来成果如下: 要实现这个性能,首先咱们要在我的项目外面增加react-redux库,而后用它提供的Provider包裹整个ReactApp的根组件: import React from 'react';import ReactDOM from 'react-dom';import { Provider } from 'react-redux'import store from './store'import App from './App';ReactDOM.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById('root'));下面代码能够看到咱们还给Provider提供了一个参数store,这个参数就是Redux的createStore生成的store,咱们须要调一下这个办法,而后将返回的store传进去: import { createStore } from 'redux';import reducer from './reducer';let store = createStore(reducer);export default store;下面代码中createStore的参数是一个reducer,所以咱们还要写个reducer: const initState = { count: 0};function reducer(state = initState, action) { switch (action.type) { case 'INCREMENT': return {...state, count: state.count + 1}; case 'DECREMENT': return {...state, count: state.count - 1}; case 'RESET': return {...state, count: 0}; default: return state; }}export default reducer;这里的reduce会有一个初始state,外面的count是0,同时他还能解决三个action,这三个action对应的是UI上的三个按钮,能够对state外面的计数进行加减和重置。到这里其实咱们React-Redux的接入和Redux数据的组织其实曾经实现了,前面如果要用Redux外面的数据的话,只须要用connectAPI将对应的state和办法连贯到组件外面就行了,比方咱们的计数器组件须要count这个状态和加一,减一,重置这三个action,咱们用connect将它连贯进去就是这样: ...

July 9, 2020 · 5 min · jiezi

antd图标库按需加载的插件实现

antd是阿里出品的一款基于antd的UI组件库,应用简略,功能丰富,被广泛应用在中台我的项目开发中,尽管也呈现了彩蛋事变,但不能否定antd自身的优良,而咱们公司在理论工作中也大量应用antd进行开发,应用的版本次要集中在3.x这个大版本中,在理论应用过程中发现了一个比拟显著的问题,那就是antd的图标输入打包体积过大,即使是应用了一个图标,也会将所有图标打包输入,没有按需加载(4.x版本曾经实现了按需加载,但目前公司还没做整体的降级),在这种状况下,就亟需一款能按需加载的插件来减小图标输入的体积。 解决思路定位了问题,接下来就是想方法解决了,后期在网上搜寻到一种解决办法,那就是在webpack中的配置图标文件门路,具体如下: resolve: { alias: { '@ant-design/icons/lib/dist$': './youIcon.js', }}youIcon.js中导出应用到的图标,通过这种形式能极大减小动态资源输入的体积,然而这一过程是手动配置,保护和应用不是很不便,然而借助这种解决形式,加上动静生成本地youIcon.js文件就能够了,确定了解决思路,接下来就入手设计插件。 插件设计1.运行流程 2.功能设计如上图所示,咱们须要的插件须要满足两个性能,第一,动静提取我的项目源代码和应用到的antd组件中的图标,第二,批改webpack的alias配置,接下来将别离讲述设计过程: 1.动静提取图标提取图标,须要对指标文件进行代码剖析,提取图标代码的相干特色,而后整顿输入到本地的指标文件下,过程如下图:1.) 特色匹配利用babel将源代码编译输入,而后失去icon的生成代码,联合astexplorer剖析节点属性,确定出匹配计划,这里还须要留神一点的是,有些组件在生成图标的过程中比拟非凡,比方Button能够通过loading和icon属性设置,在匹配的时候须要非凡解决;2.) 属性提取在提取图标的过程中,失去了图标的属性后,须要和本地node_modules外面的@ant-design/icon/lib/dist的所有官网导出库匹配,找到指标图标,就能确定图标的有效性;3.) 字符串拼接写入将图标名称和寻址门路拼接起来,再、在写入antd-icon-reduce.js文件之前,判断是否曾经存在雷同的图标名称,如果存在则放弃写入,通过上述步骤就能够将我的项目中用到的所有图标全副收集到antd-icon-reduce.js文件中了。 2.动静批改配置动静批改配置依赖于antd-icon-reduce-plugin插件来实现,其中的工作原理如下图:1.) 初始化在插件初始化的时候,插件次要干了两件事,第一,生成antd-icon-reduce.js文件,而后将这个文件门路传递给antd-icon-reduce-loader,第二,增加专门匹配node_modules/antd目录下的所有js文件,确保我的项目中应用到的组件都能被loader解决,通过初始化之后,我的项目就有了寄存导出的图标文件,更重要的是适配了所有可能生成图标的源文件;2.) 配置文件当loader运行实现之后,咱们就失去了一份残缺的图标导出文件(antd-icon-reduce.js),这个时候就须要批改webpack的alias了,这里须要在每次loader运行实现后都从新生成一份文件(内容拷贝至antd-icon-reduce.js文件),文件名须要动静更新,确保每次webpack内存加载的配置文件都是最新的;3.) 文件删除在编译输入实现后,须要革除antd-icon-reduce.js和另一份配置文件,这里都是在webpack相应的hooks外面实现。 具体应用办法和实现可参考我在博客园写的文章,这里就不过多论述了:https://www.cnblogs.com/fulu/...

July 9, 2020 · 1 min · jiezi

ReactSpring-Data-JPAMySQL-增查改删

视频演示:https://www.bilibili.com/video/BV1La4y1a7Rp/ 工程概述:前后端分离,进行简单增查改删(CRUD)前端使用React后端使用Spring Data JPA数据库使用MySQL后台端代码上一节已经展示,这里将不再重复,仅展示React代码既可。 往期内容内容 01 Vue+Spring Boot JPA+MySQL 增查改删 02 Thymeleaf+Spring Boot JPA+MySQL 增查改删 03 Vue+Spring Boot 文件操作,上传、预览和删除 04 Thymeleaf+Spring Boot 文件操作,上传、预览和删除 EmployeeService.jsimport http from "../http-common";class EmployeeService { getAll(pageNumber) { return http.get(`/employee?page=${pageNumber}`); } get(id) { return http.get(`/employee/${id}`); } create(data) { return http.post("/employee", data); } update(data) { return http.put("/employee", data); } delete(id) { return http.delete(`/employee/${id}`); } } export default new EmployeeService();EmployeesCreateComponent.jsimport React, { Component } from "react";import { Redirect } from "react-router-dom";import EmployeeService from "../services/EmployeeService";export default class EmployeesCreateComponent extends Component { constructor(props) { super(props); this.saveEmployee = this.saveEmployee.bind(this); this.onChangeName = this.onChangeName.bind(this); this.onChangeGender = this.onChangeGender.bind(this); this.onChangeAge = this.onChangeAge.bind(this); this.onChangeIntroduce = this.onChangeIntroduce.bind(this); this.state = { id: null, name: "", gender: "MALE", age: 18, introduce: "", ageRange: [], redirect: false }; } componentDidMount() { //初始化年龄下拉列表 var rows = [], i = 17, len = 60; while (++i <= len) rows.push(i); this.setState({ ageRange : rows }) } onChangeName(e) { this.setState({ name: e.target.value }); } onChangeGender(e) { this.setState({ gender: e.target.value }); } onChangeAge(e){ this.setState({ age: e.target.value }); } onChangeIntroduce(e){ this.setState({ introduce: e.target.value }); } //保存 saveEmployee() { var data = { name: this.state.name, gender: this.state.gender, age: this.state.age, introduce: this.state.introduce }; EmployeeService.create(data) .then(response => { this.setState({ redirect: true }); }) .catch(e => { console.log(e); }); } render() { const { redirect } = this.state; return ( <div className="submit-form"> {redirect ? ( <Redirect to='/employees'/> ) : ( <div> <div className="form-group"> <label htmlFor="name">Name</label> <input type="text" className="form-control" id="name" required value={this.state.name} onChange={this.onChangeName} name="name" /> </div> <div className="form-group"> <input type="radio" id="male" name="gender" value="MALE" onChange={this.onChangeGender} /> <label htmlFor="male">Male</label> <input type="radio" id="female" name="gender" value="FEMALE" onChange={this.onChangeGender} /> <label htmlFor="female">Female</label> <div className="form-group"> <label> Age:</label> <select className="form-control" value={this.state.age} name="age" onChange={this.onChangeAge}> {this.state.ageRange.map((a, index) => ( <option value={a} key={index}>{a}</option> ))} </select> </div> <div className="form-group"> <label> Introduce:</label> <textarea className="form-control" value={this.state.introduce} name="introduce" onChange={this.onChangeIntroduce} ></textarea> </div> </div> <button onClick={this.saveEmployee} className="btn btn-success"> Add Employee </button> </div> )} </div> ); }}EmployeesEditComponent.jsimport React, { Component } from "react";import EmployeesService from "../services/EmployeeService";export default class Tutorial extends Component { constructor(props) { super(props); this.getEmployee = this.getEmployee.bind(this); this.onChangeName = this.onChangeName.bind(this); this.onChangeGender = this.onChangeGender.bind(this); this.onChangeAge = this.onChangeAge.bind(this); this.onChangeIntroduce = this.onChangeIntroduce.bind(this); this.updateEmployee = this.updateEmployee.bind(this); this.state = { currentEmployee: { id: null, name: "", gender: "MALE", age: 0, introduce: "" }, ageRange: [] }; } componentDidMount() { //初始化年龄列表 var rows = [], i = 17, len = 60; while (++i <= len) rows.push(i); this.setState({ ageRange : rows }) //根据ID获取员工信息 this.getEmployee(this.props.match.params.id); } onChangeName(e) { const name = e.target.value; this.setState(function(prevState) { return { currentEmployee: { ...prevState.currentEmployee, name: name } }; }); } onChangeGender(e) { const gender = e.target.value; this.setState(function(prevState) { return { currentEmployee: { ...prevState.currentEmployee, gender: gender } }; }); } onChangeAge(e) { const age = e.target.value; this.setState(function(prevState) { return { currentEmployee: { ...prevState.currentEmployee, age: age } }; }); } onChangeIntroduce(e) { const introduce = e.target.value; this.setState(function(prevState) { return { currentEmployee: { ...prevState.currentEmployee, introduce: introduce } }; }); } //根据ID获取员工信息 getEmployee(id) { EmployeesService.get(id) .then(response => { this.setState({ currentEmployee: response.data.content }); console.log(response.data); }) .catch(e => { console.log(e); }); } //更新员工信息 updateEmployee() { EmployeesService.update(this.state.currentEmployee) .then(response => { console.log(response.data); this.props.history.push('/employees'); }) .catch(e => { console.log(e); }); } render() { const { currentEmployee } = this.state; return ( <div> <div className="submit-form"> <form> <div className="form-group"> <label htmlFor="name">Name</label> <input type="text" className="form-control" id="name" value={currentEmployee.name} onChange={this.onChangeName} /> </div> <div className="form-group"> <input type="radio" id="male" name="gender" checked={currentEmployee.gender === "MALE"} value="MALE" onChange={this.onChangeGender} /> <label htmlFor="male">Male</label> <input type="radio" id="female" name="gender" checked={currentEmployee.gender === "FEMALE"} value="FEMALE" onChange={this.onChangeGender} /> <label htmlFor="female">Female</label> <div className="form-group"> <label> Age:</label> <select className="form-control" value={this.state.currentEmployee.age} name="age" onChange={this.onChangeAge}> {this.state.ageRange.map((a, index) => ( <option value={a} key={index}>{a}</option> ))} </select> </div> <div className="form-group"> <label> Introduce:</label> <textarea className="form-control" value={this.state.currentEmployee.introduce} name="introduce" onChange={this.onChangeIntroduce} ></textarea> </div> </div> </form> <button type="submit" className="btn btn-success" onClick={this.updateEmployee} > Update Employee </button> <p>{this.state.message}</p> </div> </div> ); }}EmployeesListComponent.jsimport React, { Component } from "react";import EmployeeService from '../services/EmployeeService';import Pagination from "react-js-pagination";import Table from 'react-bootstrap/Table';import Button from 'react-bootstrap/Button';export default class EmployeesListComponent extends Component { constructor(props) { super(props); this.retrieveEmployee = this.retrieveEmployee.bind(this); this.state = { employees: [], //数据 activePage: 1, //默认首页 itemsCountPerPage: 3, //每页记录数 totalItemsCount: 0, //总记录数 pageRangeDisplayed: 5 //分页栏只显示5个分页 }; } //点击分页 handlePageChange(activePage) { this.setState({activePage: activePage}); this.retrieveEmployee(activePage); } componentDidMount() { //获取首页 this.retrieveEmployee(1); } //根据ID删除员工 deleteEmployee(id){ EmployeeService.delete(id).then(response =>{ //删除成功后,也可以直接调用后台获取当前页面数据 this.retrieveEmployee(this.state.activePage); }); } //编辑页面 editEmployee(id) { this.props.history.push('/employee/'+id) } //获取数据方法 retrieveEmployee(activePage) { //前端页面从1开始,而后台页面从0开始,所以-1 EmployeeService.getAll(activePage-1) .then(response => { this.setState({ employees: response.data.content.content, totalItemsCount: response.data.content.totalElements, itemsCountPerPage: response.data.content.size }); console.log(response.data); }) .catch(e => { console.log(e); }); } render() { const { employees } = this.state; return ( <div> <Table striped bordered hover> <thead > <tr> <th >ID</th> <th >Name</th> <th >Gender</th> <th >Age</th> <th >Introduce</th> <th >Actions</th> </tr> </thead> <tbody> {employees && employees.map((tutorial, index) => ( <tr key={index}> <td>{tutorial.id}</td> <td>{tutorial.name}</td> <td>{tutorial.gender}</td> <td>{tutorial.age}</td> <td>{tutorial.introduce}</td> <td> <Button variant="info" onClick={() => this.editEmployee(tutorial.id) }>编辑</Button> <Button variant="danger" onClick={() => this.deleteEmployee(tutorial.id) }>删除</Button> </td> </tr> ))} </tbody> </Table> { this.state.totalItemsCount > 0 && <div className="col-md-6"> <Pagination activePage={this.state.activePage} itemsCountPerPage={this.state.itemsCountPerPage} totalItemsCount={this.state.totalItemsCount} pageRangeDisplayed={this.state.pageRangeDisplayed} onChange={this.handlePageChange.bind(this)} itemClass="page-item" linkClass="page-link" /> </div> } </div> ); }}

July 7, 2020 · 4 min · jiezi

使用-Create-React-Doc-快速搭建文档博客站点

Create React Doc 是一个基于 React 的 markdown 文档站点生成工具。就像 create-react-app 一样,开发者可以使用 Create React Doc 来开发、部署 markdown 站点或者博客而不用关心站点环境配置信息。 blog 特性零配置书写 markdown 文档站点。markdown 文档支持懒加载以及热加载。基于文件目录自动生成多层级菜单。支持一键发布到 GitHub Pages.快速上手执行如下命令: npx create-react-doc my-docnpm install && cd my-docnpm start然后打开 [http://localhost:3000/]() 就可以看到文档站点。当准备发布到生产环境时,执行 npm run build 就能将文档站点打包压缩。 使用create-react-doc 非常容易上手。开发者不需要额外安装或配置 webpack 或者 Babel 等工具,它们被内置隐藏在脚手架中,因此开发者可以专心于文档的书写。 下面提供三种方式来快速创建文档站点: npxnpx create-react-doc my-docnpmnpm init create-react-doc my-docyarnyarn create create-react-doc my-doc一旦安装执行完毕,执行 npm install 然后进入项目文件夹: npm install && cd my-doc在新创建的项目中, 可以执行内置的一些命令: npm start or yarn start在开发者模式下启动文档项目: ...

July 6, 2020 · 1 min · jiezi

React入门useStateuseRefuseContent等API讲解

useState语法:const [n, setN] = React.useState(0); 0是n的默认值,setN是操作n的函数setN: 我们以为setN会改变n,但是事实往往出乎意料,n并不会变,他是将改变后的数据存到一个数据里面(具体在哪里我们先定为x),而不是直接赋值去改变nsetN一定会触发组件的重新渲染useState: useState肯定会从x那里,读取到n的最新值x: 每个组件都有自己的x,这个x我们命名为stateuseRef语法: const nRef = React.useRef(0);nRef.current(表示当前的值)nRef.current: 当值改变了不会重新renderuseContextconst themeContext = React.createContext(null);function App() { const [theme, setTheme] = React.useState("red"); return ( // themeContext.Provider 创建一个局部作用域 // 其中里面的所有组件都可以使用setTheme这个函数 <themeContext.Provider value={{ theme, setTheme }}> <div className={`App ${theme}`}> <p>{theme}</p> <div> <ChildA /> </div> <div> <ChildB /> </div> </div> </themeContext.Provider> );}function ChildA() { // ChildA这个子组件内部想使用父组件setTheme函数的方式 const { setTheme } = React.useContext(themeContext); return ( <div> <button onClick={() => setTheme("red")}>red</button> </div> );}

July 5, 2020 · 1 min · jiezi

reactantd密码编辑组件

之前的一篇文章里,写过关于忘记密码这部分的业务,今天这个主要是详细写密码编辑这一块功能,需求也很明确,考虑到密码未输入,密码输入错误以及确认密码等情况,代码里面会添加注释.总结一点,写react组件概括就是封装函数,将函数调用到不同的生命周期钩子触发响应事件 `import React from 'react';``import message from 'antd/lib/message';`import { View } from 'core'; //组件继承传值封装import { Log, Toolkit } from 'util'; //工具封装export default class EditPassWordView extends View { constructor(props) { super(props); this.state = { showSuccessWin: false,//密码修改成功提示框显示开关 password: '', //输入密码 showError: false, // 密码输入错误显示 showError1: false, // 新密码未输入错误显示 showError2: false, //新密码和确认密码输入错误显示 errorMsg: '', //错误信息 newpassword: '', //输入新密码 surepassword: '', //输入确认密码 }; this.validateCallBack = this.validateCallBack.bind(this); this.handleCloseWin = this.handleCloseWin.bind(this); } /* *检查旧密码是否正确 */ _checkPassword() { if (!this.state.password) { return false; } this.props.action.checkPassword({ oldPassword: this.state.password, uid: this.props.uid, //找到唯一键值 }).then((respData) => { if (respData.code === 0) { this.setState({ showError: false, }); } else { this.setState({ showError: true, showError1: false, showError2: false, errorMsg: respData.msg, }); } }, (error) => { Log.error(error.reason); }); return false; } /** * 校验密码强度回调函数 * @param {*string} validateFlag 校验结果 * @param {*string} errTips 错误提示语 */ validateCallBack(validateFlag, errTips) { // 校验失败 if (!validateFlag) { this.setState({ showError: false, showError2: false, showError1: true, errorMsg: errTips, }); } } /* *修改密码 */ _editPassword() { const { password, newpassword, surepassword } = this.state; // if (!this.validatePassWord(password)) { // return false; // } if (!password) { this.setState({ showError: true, showError2: false, showError1: false, errorMsg: '请输入密码', }); return false; } if (!Toolkit.validatePassWord(newpassword, this.validateCallBack)) { return false; } if (newpassword !== surepassword) { this.setState({ showError2: true, showError: false, showError1: false, errorMsg: '两次密码输入不一致!', }); return false; } const data = { oldPassword: password, newPassword: newpassword, uid: this.props.uid, }; this.props.action.editPassword(data).then((respData) => { if (respData.code === 0) { this.setState({ showError2: false, showSuccessWin: true, }); } else { message.error(respData.msg || respData.reason); } }, (error) => { message.error(error.msg || error.reason); Log.error(error.reason); }); return false; } /** * 关闭弹出框事件 */ handleCloseWin() { this.setState({ showSuccessWin: false, password: '', newpassword: '', surepassword: '', }); } /** * 渲染密码修改成功的提示弹出框 */ _renderPopWin() { const { showSuccessWin } = this.state; if (showSuccessWin) { return ( <div className="stopPropa dyModal pop-win-box"> <div className="wrap"> <div className="dyModalBg" /> <div className="modalContent"> <div className="header"> <h3>提示</h3> <i className="close-icon" onClick={this.handleCloseWin} /> </div> <div className="content"> <div className="tips"> <p style={{ fontWeight: 'bold' }}>密码修改成功</p> <p className="sub">请使用新密码重新登录一次</p> </div> <div className="btn-box"> <a className="btn-sure" href={this.props.logoutUrl}>确定</a> </div> </div> </div> </div> </div> ); } return ''; } _render() { return ( <div> {this._renderPopWin()} <div className="change_password"> <h1>修改密码</h1> <ul> <li> <span>原密码:</span> <input type="password" name="password" value={this.state.password} onChange={(evt) => { this.setState({ showError: false, showError2: false, }); this.state.password = evt.target.value; }} onKeyUp={ (evt) => { if (evt.keyCode === 13) { this._editPassword(); } } } /> <strong className="password" style={{ display: this.state.showError ? 'block' : 'none' }} >{this.state.errorMsg}</strong> </li> <li> <span>新密码:</span> <input type="password" name="newpassword" value={this.state.newpassword} onChange={(evt) => { this.setState({ showError1: false, showError2: false, }); this.state.newpassword = evt.target.value; }} onKeyUp={ (evt) => { if (evt.keyCode === 13) { this._editPassword(); } } } /> <strong className="password" style={{ display: this.state.showError1 ? 'block' : 'none' }} >{this.state.errorMsg}</strong> </li> <li> <span>确认密码:</span> <input type="password" name="surepassword" value={this.state.surepassword} onChange={(evt) => { this.setState({ showError2: false, }); this.state.surepassword = evt.target.value; }} onKeyUp={ (evt) => { if (evt.keyCode === 13) { this._editPassword(); } } } /> <strong className="password" style={{ display: this.state.showError2 ? 'block' : 'none' }} >{this.state.errorMsg}</strong> </li> </ul> <button onClick={this._editPassword.bind(this)} >修改密码</button> </div> </div> ); }}

July 5, 2020 · 3 min · jiezi

react之性能优化

问题分析我们在本文讨论的是react性能优化问题,核心就一点:渲染。 围绕的内容有: shouldComponent协调PureComponent可变数据 和 不可变数据本文主要解决如下四个问题: 重渲染处于react生命周期的哪个阶段?如何进行重渲染?如何合理使用shouldComponentUpdate控制react重渲染?(包含PureComponent在性能优化中的作用) 一、前置知识1. 生命周期 上图所示为react v16.4开始使用的react生命周期,详细总结请看我另一篇文章: react之生命周期API的总结 组件周期说明Mounting组件初次挂载到页面(初次渲染)Updating组件更新阶段(重渲染)Unmounting组件卸载(卸载)2. 协调 - Reconciliation render return 的 JSX 经 React 转换为 virtual DOMdiff 算法对比新旧 virtual DOM针对差异化进行更新React.createElement( ) 输出 DOM相对协调有更多理解,请点击 协调 - React3. 回答第一个问题:重渲染处于 react 生命周期的哪个阶段? 由生命周期示意图,可以看到大部分的API集中在组件更新(Updating)阶段,这个阶段进行的就重渲染。 二、 分析Updating1. render Updating 阶段的核心是 render 涉及的协调,协调的大致流程如下 对比新旧DOM性能损耗是很大的,尽管 react 使用 diff 算法并进行优化,我们也应该尽可能减少使用协调。为此,需要把调用 render 的权利,握在开发者手里! 2. 梳理Updating流程 为验证是否有控制 render 的方案,需要梳理 Updating 阶段的流程 react 留给我们控制 render 的API —— shouldComponentUpdate ...

July 2, 2020 · 5 min · jiezi

关于react生命周期的总结

写在前边这是一片记录react生命周期的文章。博主最近在重读react官网,想对react有更深度的理解。如果你同样对react这个优秀的框架抱有兴趣,欢迎联系我一同探讨!!文中有描述含混不清和错误的地方,望不吝赐教,直接指出 文末附有验证生命周期API先后顺序的demo QQ: 272473132github: https://github.com/CregskiN1. 基础概念Mount、Update、UnmountMounting:组件初次渲染Updating:因某些原因,组件需要更新Unmounting:组件卸载。即他的父组件的render()函数中不再return他渲染阶段、预提交阶段、提交阶段Render phase:react负责将JSX渲染为DOM对象(仅渲染,不更新页面)一旦state或props改变,Render phase阶段将被强制重新执行(具体次数无法预计)。所以尽量不要Render phase更新state或props。Pre-commit phase:react渲染完DOM,预提交阶段,在这里,可以读取DOM信息Commit phase:react渲染并完成DOM更新官方建议:仅在这个阶段执行副作用、state更新2. Mounting阶段Render phaseconstructor() 初始化state、props 初始化state不要用props.color,可以直接用this.props.color为事件处理 绑定thisstatic getDerivedStateFromProps(props, state) 用处罕见:派生状态组件,即state在任何时候都取决于prop 不建议使用派生组件,会导致受控于非受控含混不清,state混乱return newState 返回对象以更新state,若返回null,不更新任何内容(无法使用this.state修改state)render() 当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:React 元素。通常通过 JSX 创建。例如, 会被 React 渲染为 DOM 节点, 会被 React 渲染为自定义组件,无论是 还是 均为 React 元素。数组或 fragments。 使得 render 方法可以返回多个元素。欲了解更多详细信息,请参阅 fragments 文档。Portals。可以渲染子节点到不同的 DOM 子树中。欲了解更多详细信息,请参阅有关 portals 的文档字符串或数值类型。它们在 DOM 中会被渲染为文本节点布尔类型或 null。什么都不渲染。(主要用于支持返回 test && 的模式,其中 test 为布尔类型。)Pre-commit phaseCommit phasecomponentDidMount() this.setState() 触发额外渲染,但会在浏览器更新屏幕之前副作用(side effect)3. Updating阶段当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:Render phasestatic getDerivedStateFromProps()shouldComponentUpdate(nextProps, nextState) ...

July 1, 2020 · 3 min · jiezi

React高阶组件HOC二代理方式的高阶组件

上文提到react高阶组件的基本概念以及基本使用方式,下面将介绍高阶组件的应用方式。 代理方式的高阶组件返回的新组件直接继承于React.Component类,新组件作为传入组件的一个代理,在新组件中渲染出被包裹组件。除了高阶组件自己要做的事情以外,其余全部由被包裹组件来做。其有四种操作: 1、操纵props。2、访问ref。3、抽取状态。4、包装组件。1.操纵props:我们拿组件B来做演示,给B加一个属性name。 import React from 'react';import B from './components/B'import C from './components/C'import './App.css';class App extends React.Component { render() { return ( <div> <B name='李雷' /> <C /> </div> ); }}在B组件中把name通过props显示出来,但是发现上面并未对sex属性赋值,我们可以在高阶组件中进行传入。 import React, { Component } from 'react'import A from './A'@Aclass B extends Component { render() { console.log(this.props) return ( <div> <div>这是组件B</div> <div>我的名字:{ this.props.name }</div> <div>我的性别:{ this.props.sex }</div> </div> ) }}export default B首先把根节点的props透传入组件B中,在添加一个新的属性sex。 import React, { Component } from 'react'export default function A(WrappedComponent) { return class A extends Component { render() { return ( <div className="box"> <div>我是公共组件A的内容</div> <WrappedComponent {...this.props} sex={'男'} /> </div> ) } }}最终达到我们操作props的要求:2.访问ref:通过ref访问调用C的实例 ...

June 30, 2020 · 2 min · jiezi

React高阶组件HOC一

关于react高阶组件的官方文档https://reactjs.org/docs/high...。 起因之前和几个朋友讨论react的过程中,大家都认为对react已经非常熟悉了,忽然聊到react高阶组件问题,都不能清楚说出个所以然来,表面上大家很少用react高阶组件,但实际上react高阶组件在react应用中非常非常重要,并且也时常用到。如此重要的内容实际上优质资料却很少,今天想就react高阶组件记录下自己的学习理解。 基本概念需要了解react高阶组件需要先了解高阶函数。 高阶函数:1、把函数作为参数传递。setInterval(() => { //需要做的事情}, 3000)2、函数可以作为返回值输出。function test(a) {// 返回一个匿名函数 return function() { return a }}了解高阶函数之后来了解一下高阶组件。 高阶组件:1、高阶组件就是接受组件作为参数,最终返回一个新组件的函数。2、需要明白一点的是高阶组件是一个函数而不是一个组件。高阶组件含义下面写个小的例子解释下高阶组件,我们设置三个组件分别叫做A.js,B.js和C.js。 A.js为高阶组件,WrappedComponent是接受的普通组件参数,最终export的是一个function,即上面提到的概念:最终返回一个新组件的函数 import React, { Component } from 'react'// WrappedComponent为传入的普通组件export default function A(WrappedComponent) { return class A extends Component { render() { return ( <div className="box"> <div>我是公共组件A的内容</div> <WrappedComponent /> </div> ) } }}B.js 调用A高阶组件,最终导出是把B传入A函数中形成新的组件。 import React, { Component } from 'react'import A from './A'class B extends Component { render() { return ( <div>这是组件B</div> ) }}export default A(B)C.js和B.js一个意思 ...

June 30, 2020 · 1 min · jiezi

react组件执行两次问题

无意中发现react渲染执行两次,开始以为是代码写错了,后来去网上查找发现原来并不是一个bug。React 在 开发环境下会刻意执行两次渲染,以防止组件内有什么 side effect 引起 bug,提前预防。这里官方github上有作出解释: 从代码中来看 let a = 0class App extends React.Component { render() { a = a + 1 console.log(`运行${a}次`) return ( <div></div> ) }}控制台显示结果运行了两次。

June 30, 2020 · 1 min · jiezi

createreactapp-架构的项目打包生产环境的代码如何关闭-sourcemap

答案简便的处理方法, 配置环境变量 GENERATE_SOURCEMAP=false。可以在打包的时候加入,如 GENERATE_SOURCEMAP=false npm run build也可以直接设置环境变量然后再打包,如通过.env等文件,或者 ci 配置的 settings/ pipelines/ Secret variables 来设置。 解释sourcemap 是什么? 按照字面量的理解就是源码映射。其作用是将发布的代码和源码关联起来,方便在浏览器或其他运行环境中调试。懂的人都知道,无需过分解释。 参考代码的出处为 <rootDir>/config/webpack.config.prod.js,可以找到这样的一个变量 shouldUseSourceMap。 验证如何校验 sourcemap 在打包中是否已经去掉成功了呢?每个人有自己的办法。 思考关于 sourcemap 更好的处理方法? sourcemap 直接暴露在生产环境中,有一些不雅和安全性上的考量,是否可以将 sourcemap 的路径映射到内网的环境中呢?这样,只有能够访问内网环境的自己人才能加载 sourcemap,便对 sourcemap 有一定的保护作用。

June 30, 2020 · 1 min · jiezi

react之错误边界Error-Boundaries

错误边界(Error boundaries)从react v16开始,引入边界错误概念。 核心API getDerivedStateFromErrorcomponentDidCatch1. 目的:某些UI崩溃,不至于整个webapp崩溃2. 注意⚠️:错误边界无法捕获如下场景的错误事件处理(了解更多)异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)服务端渲染它自身抛出来的错误(并非它的子组件)3.编写错误边界组件import React, { Component } from 'react';interface ErrorBoundaryProps { };interface ErrorBoundaryState { hasError: boolean;};class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> { constructor(props: ErrorBoundaryProps) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error: Error) { // 更新 state 使下一次渲染能够显示降级后的 UI return { hasError: true }; } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { // 你同样可以将错误日志上报给服务器 console.group(); console.log('ErrorBoundary catch a error:'); console.info('error', error); console.info('error info', errorInfo); console.groupEnd() } render() { console.log('component ErrorBoundary render...'); const { children } = this.props; const { hasError } = this.state; // return ( // <> // { // hasError ? 'Something Wrong' : children // } // </> // ) if (hasError) { return 'Something wrong'; } else { return children; } }}export default ErrorBoundary;4.编写一个“错误组件”点击按钮,触发+1,当counter为5时,抛出Error ...

June 30, 2020 · 2 min · jiezi

react定义异常组件

企业开发中,组件众多,甚至组件嵌套组件也很多,所以当成百上千个组件加载起来,难免的会有些意外报错情况,根据现有的开发经验,将react项目里面异常组件定义出来,并给与提示信息而非直接抛出红色错误这个里面有个额外的知识补充,依据于[Error.captureStackTrace],关于这个方面的知识我也是学习的这个大佬的文章,大家也可以看看:https://segmentfault.com/a/11... export default class Exception extends Error { /* Exceptions for Remote START */ static CONNECTION_TIMEOUT = '远端请求超时。'; static NO_CONTAINER = '未找到容器。'; /* Exceptions for Remote END */ static PARAMETER_INVALIDE = '无效的参数。'; static TYPE_INCORRECT = '错误的数据类型。'; static LOG_DRIVER_UNDEFINED = '未定义的日志驱动。'; /** * 构造函数。 */ constructor(message) { super(); if ('captureStackTrace' in Error) { Error.captureStackTrace(this, Exception); } this.name = 'WHException'; this.message = message; }}使用此文件一般可以放在Action里面

June 27, 2020 · 1 min · jiezi

react中使用antd并配置less修改主题

Antd的基本使用进入Antd官方网站查看具体使用文档 安装和初始化在react项目中进行安装yarn add antd或者npm i antd安装 简单使用在App.js文件中引入按钮并使用 注意,这里还需要引入css样式import 'antd/dist/antd.css' import React, { Component } from 'react'import { Button } from 'antd'; //引入按钮import 'antd/dist/antd.css'; //还需要引入css样式export default class App extends Component { render() { return ( <div> <Button>我是antd的按钮哦</Button> </div> ) }}Antd的高级配置按需加载目的是不用引入import 'antd/dist/antd.css'各个组件就可以拥有自己的css样式 前提需要安装react-app-rewired / customize-cra yarn add react-app-rewiredyarn add customize-cra需要配置package.json文件 "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-scripts eject"}在项目根目录下增加一个文件config-overrides.js再去安装 babel-plugin-importyarn add babel-plugin-import找到config-overrides.js文件进行配置 ...

June 27, 2020 · 1 min · jiezi

聊一聊Diff算法ReactVue2xVue3x

场景计算两颗树形结构差异并进行转换 文中, 所提及n均代表节点的个数传统Diff算法处理方案: 循环递归每一个节点传统diff 如上所示, 左侧树a节点依次进行如下对比: a->e、a->d、a->b、a->c、a->a之后左侧树其它节点b、c、d、e亦是与右侧树每个节点对比, 算法复杂度能达到O(n^2) 查找完差异后还需计算最小转换方式,这其中的原理我没仔细去看,最终达到的算法复杂度是O(n^3) 将两颗树中所有的节点一一对比需要O(n²)的复杂度,在对比过程中发现旧节点在新的树中未找到,那么就需要把旧节点删除,删除一棵树的一个节点(找到一个合适的节点放到被删除的位置)的时间复杂度为O(n),同理添加新节点的复杂度也是O(n),合起来diff两个树的复杂度就是O(n³)优化的Diff算法vue和react的虚拟DOM的diff算法大致相同,其核心是基于两个简单的假设: 两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构同一层级的一组节点,他们可以通过唯一的id进行区分(优化的)diff三点策略: web UI中DOM节点跨层级的移动操作特别少,可以忽略不计。拥有相同类型的两个组件将会生成相似的树形结构,拥有不同类型的两个组件将会生成不同树形结构。对于同一层级的一组自节点,他们可以通过唯一id进行区分。即, 比较只会在同层级进行, 不会跨层级比较React优化Diff算法基于以上优化的diff三点策略,react分别进行以下算法优化tree diffcomponent diffelement difftree diffreact对树的算法进行了分层比较。react 通过 updateDepth对Virtual Dom树进行层级控制,只会对相同颜色框内的节点进行比较,即同一个父节点下的所有子节点。当发现节点不存在,则该节点和其子节点都会被删除。这样是需要遍历一次dom树,就完成了整个dom树的对比 分层比较 img 如果是跨层级的移动操作,如图 跨层级操作 img 当根结点发现A消失了,会删除掉A以及他的子节点。当发现D上多了一个A节点,会创建A(包括其子节点)节点作为子节点 所以:当进行跨层级的移动操作,react并不是简单的进行移动,而是进行了删除和创建的操作,这会影响到react性能。所以要尽量避免跨层级的操作。(例如:控制display来达到显示和隐藏,而不是真的添加和删除dom) component diff如果是同类型的组件,则直接对比virtual Dom tree如果不是同类型的组件,会直接替换掉组件下的所有子组件如果类型相同,但是可能virtual DOM 没有变化,这种情况下我们可以使用shouldComponentUpdate() 来判断是否需要进行diffcomponent vs img 如果组件D和组件G,如果类型不同,但是结构类似。这种情况下,因为类型不同,所以react会删除D,创建G。所以我们可以使用shouldComponentUpdate()返回false不进行diff。 针对react15, 16出了新的生命周期 所以:component diff 主要是使用shouldComponentUpdate() 来进行优化 element diffelement diff 涉及三种操作:插入,移动,删除 不使用key的情况 img 不使用key的话,react对新老集合对比,发现新集合中B不等于老集合中的A,于是删除了A,创建了B,依此类推直到删除了老集合中的D,创建了C于新集合。= 酱紫会产生渲染性能瓶颈,于是react允许添加key进行区分 使用key的情况 img react首先对新集合进行遍历,for( name in nextChildren),通过唯一key来判断老集合中是否存在相同的节点,如果没有的话创建,如果有的话,if (preChild === nextChild ) 进行移动操作 ...

June 26, 2020 · 1 min · jiezi

如何搭建个人博客网站附开源代码

前言最近个人博客打算做一次比较大的更新,所以把第一版开源了。顺便写个文章记录一下搭建博客的一些注意项 1.网站模块大概有一下这些(从各处个人网站观察得到) 主页展示个人介绍留言板心情记录博文归档作品展示商业合作书籍分享我目前是只选择了主页综合展示,书籍分享,心情记录,个人介绍4个模块。没有专门做一个博文的归档,是因为我考虑到博文数量不多,于是就把这一部分整合到主页,使用无限滚动的方式来展示博文,如果文章比较多的话还是推荐专门分开一个模块做文章的检索分类。 2.挑选技术栈其实选vue的话,我可以少花很多时间hhh。因为学了react,然后公司的项目又用不上,所以就拿个博客来练手了,写法比较稚嫩,希望各位大佬见谅。使用到的技术大概有这些react,redux,axios,sass 3.需要实现的功能和注意点文章浏览首先最主要的肯定是能看文章嘛,那么如何记录文章,如何排版文章呢?我一开始是想使用富文本编辑器的,直接输出html保存到数据库,然后前台直接读即可。后面考虑到,如果这样的话,我就要sf上所有的文章自己在富文本编辑器上重新排版编辑一遍,那就太浪费时间了。所以我选择了直接使用markdown语法来编辑文章,然后使用react-markdown插件来解析我的文章,最终在前端进行展示。文章评论一开始是想做楼中楼回复,可以进行多级评论, 后面考虑到第一:实现起来会麻烦很多,后台的数据库设计也比较复杂。第二:我这个是个人博客网站,没必要进行多人互相讨论。所以,只做了一级评论,也就是每次我回复别人也算是一条新的评论。访客记录因为是个人网站,所以没有设计用户系统,想想别人发表评论还需要登陆之类的操作是多麻烦。那发表评论怎么记录是谁发表的呢?我还是提供了一个表单来填写发言人的名字的。这个名字仅仅是储存在前台的localstorage里,讲道理也没有人会一直清理这个东西,所以你下次来我的网站,就记住你了。文章阅读和点赞等因为没有用户系统,所以阅读数应该是每次点进来都要发请求记录一次的,这个问题不大。是否已经点赞的话,这些信息都可以储存在前端的localStorage里,记录一下文章id即可,下次点进这个文章就知道是否赞过了订阅|消息推送消息推送的话,我使用的是node.js接入第三方的邮件服务来发送邮件通知订阅者。除了发文章要通知订阅者,有访客评论的时候最好也搞个右键通知我们自己4.页面适配现在手机那么多人用啦,讲道理确实得给手机适配一下,手机端我去掉了很多的模块,主要留下了文章列表,而且是使用媒体查询来进行页面适配的,为什么要使用媒体查询? 因为我要1px,1px地精准调节来达到移动端比较好的展示效果因为我不需要适配太多设备,一种手机,一种pc即可,手机屏幕尺寸不做太多细致的处理,因为现在大部分人的手机尺寸都是全面屏6寸这样,看我博客的人显然不会是用着安卓2.3的70岁的阿伯,基于网站的受众分析,只需要做这两种尺寸的适配页面适配说了,那么页面设计呢?设计这玩意儿见仁见智,如果懂设计,那就自己设计,如果不懂,那就抄,就是这么简单 5.后台管理当我们写好前端的博客以后,并且挂在服务器上的时候,肯定不想每次写文章,用xx笔记写好,然后手动insert进服务器,这就比较傻了。所以我们肯定还需要一个后台管理,来管理我们的博文,留言,评论等等 目前我的博客有的管理项目如下,可以参考下: 标签管理博文管理评论管理订阅管理音乐管理书籍管理心情管理后语说了那么多,不如直接上源码,各位老爷,给个star可好?github博客源码 实际的博客(目前大更新还在路上,所以现在博客的表现和你github拉下来的表现是一致的)leelei的博客

June 23, 2020 · 1 min · jiezi

测试

测试测试测试测试测试

June 22, 2020 · 1 min · jiezi

记umi3的一次实战经历

背景疫情期间远程面试量增加,这其实为 coding 考核提供了比到面更好的基础(候选人可以使用自己的电脑,在自己熟悉的环境里,降低由于不熟练、紧张导致的失误率)。 我们用过codeshare、HackerRank、LeetCode,甚至尝试过用vscode的Live Share。在频繁使用这些工具后突发奇想,既然是前端测验,反正跑的都是 javascript,我们能不能做一个简易版的 web 考核工具,候选人写完题目直接本页面跑测试用例检查结果呢? 答案肯定是『能』,愿意动手就行。于是有了本文涉及的这个小工具。 等不及想看实际效果的,来这里 需求首先我们需要的是明确需求,这点所有研发的朋友都很了解。下面就来先梳理一下要完成的目标: 作为面试官,我需要题目可选,因为面试时间有限、候选人经历不同,可以指定不同的题目要求候选人作答作为面试官,如果现有题目不合我意,我希望增加一个新题目的成本不要太高作为面试官,我希望每个题目能有一个基本的代码结构,并且可以限定候选人在其中作答。(因为之前有遇到几位候选人,修改了题目,并振振有词『你也没说不能改题目啊』)作为面试官,我希望每个题目都能有测试用例,并且测试用例要对候选人可见,方便候选人理解题目作为候选人,我希望测试用例可以在线执行,并且显示每个用例的执行结果,方便我排查错误作为候选人,我希望系统能帮我记录每个题目的作答,这样就不会在题目切换后,之前的作答丢失UI/交互 设计从使用者视角出发,提供一个简单清爽的交互界面,让人一目了然,尽可能降低理解成本,毕竟面试时间有限,例如 之前使用 HackerRank 或者 LeetCode 这类不仅提供代码在线编辑,同时也可以在线检测结果的工具,如果候选人之前没接触过,我们就必须留有一定时间让他熟悉,甚至提供一些引导,以避免候选人因为紧张而操作不当,最终影响面试结果。 所以小工具,力求功能简单粗暴。大致风格如下: 简单的左|中|右布局: 左侧题目选择中间代码编辑区域右侧测试用例执行模块。(可隐藏)程序功能点分析图省事就选择了基于 react 的企业级应用开发框架 umi。熟悉 java 的朋友可以把她简单理解成 前端 react 领域的 srping-boot,她提供了开发一个应用需要的各种技术(诸如:路由管理、权限控制、状态管理、调试、代码拆分等 )的最佳实践以及预配置,并以Convention over configuration作为指导思想提供了一系列便利开发者的接口,以此帮助开发者简化应用开发的成本。 广告结束。。。 接下来为每个需求整理下设计思路。 要完成 UI/交互 设计的样式,其实不复杂,利用 ant-design/layout 就可以轻松实现一个左右布局。 作为面试官,我需要若干题目可选,因为面试时间有限、候选人经历不同,可以指定不同的题目要求候选人作答题目选择设计为路由驱动即可,即:/:examId ,点击左侧不同的题目,切换路由。页面根据路由参数 examId 加载指定的题目到右侧的编辑器,以及测试用例模块初始化。 作为面试官,如果现有题目不合我意,我希望增加一个新题目的成本不要太高目前的设计是从工具本身的源码着手,所以要求整个项目在新增题目的部分具有相对的灵活性和简便性。我现在采用以目录为单位的题目储备形式,如下: ├── src│   └── exams│   ├── exam1│ │ ├── index.ts│ │ ├── question.txt│ │ └── testcase.ts│   ├── exam2│ │ ├── index.ts│ │ ├── question.txt│ │ └── testcase.ts│ ├── ...index.ts 作为每个题目的入口文件,负责整个题目的结构组装question.txt 题目的code basetestcase.ts 测试用例既然要封装一个统一的数据结构在入口文件 (index.ts)里,那么就为她设计一个满足我们需求的数据结构,如下: ...

June 22, 2020 · 4 min · jiezi

React-Hooks-加持下的函数组件设计

有了 React Hooks 的加持,妈妈再也不用担心函数组件记不住状态过去,React 中的函数组件都被称为无状态函数式组件(stateless functional component),这是因为函数组件没有办法拥有自己的状态,只能根据 Props 来渲染 UI ,其性质就相当于是类组件中的 render 函数,虽然结构简单明了,但是作用有限。 但自从 React Hooks 横空出世,函数组件也拥有了保存状态的能力,而且也逐渐能够覆盖到类组件的应用场景,因此可以说 React Hooks 就是未来 React 发展的方向。 React Hooks 解决了什么问题复杂的组件难以分拆我们知道组件化的思想就是将一个复杂的页面/大组件,按照不同层次,逐渐抽象并拆分成功能更纯粹的小组件,这样一方面可以减少代码耦合,另外一方面也可以更好地复用代码;但实际上,在使用 React 的类组件时,往往难以进一步分拆复杂的组件,这是因为逻辑是有状态的,如果强行分拆,会令代码复杂性急剧上升;如使用 HOC 和 Render Props 等设计模式,这会形成“嵌套地狱”,使我们的代码变得晦涩难懂。 状态逻辑复杂,给单元测试造成障碍这其实也是上一点的延续:要给一个拥有众多状态逻辑的组件写单元测试,无疑是一件令人崩溃的事情,因为需要编写大量的测试用例来覆盖代码执行路径。 组件生命周期繁复对于类组件,我们需要在组件提供的生命周期钩子中处理状态的初始化、数据获取、数据更新等操作,处理起来本身逻辑就比较复杂,而且各种“副作用”混在一起也使人头晕目眩,另外还很可能忘记在组件状态变更/组件销毁时消除副作用。 React Hooks 就是来解决以上这些问题的针对状态逻辑分拆复用难的问题:其实并不是 React Hooks 解决的,函数这一形式本身就具有逻辑简单、易复用等特性。针对组件生命周期繁复的问题:React Hooks 屏蔽了生命周期这一概念,一切的逻辑都是由状态驱动,或者说由数据驱动的,那么理解、处理起来就简单多了。利用自定义 Hooks 捆绑封装逻辑与相关 state我认为 React Hooks 的亮点不在于 React 官方提供的那些 API ,那些 API 只是一些基础的能力;其亮点还是在于自定义 Hooks —— 一种封装复用的设计模式。 例如,一个页面上往往有很多状态,这些状态分别有各自的处理逻辑,如果用类组件的话,这些状态和逻辑都会混在一起,不够直观: class Com extends React.Component { state = { a: 1, b: 2, c: 3, } componentDidMount() { handleA() handleB() handleC() }}而使用 React Hooks 后,我们可以把状态和逻辑关联起来,分拆成多个自定义 Hooks ,代码结构就会更清晰: ...

June 22, 2020 · 3 min · jiezi

React-useEffectuseCallbackuseMemo的个人理解

本文记录个人对React的hook中useEffect,useCallback,useMemo的个人理解。三者的定义: useEffect(didUpdate, deps);const memoizedCallback = useCallback(() => { doSomething(params); }, deps);const memoizedValue = useMemo(() => computeExpensiveValue(params), deps);三者的第二个deps是一个数组,表示依赖的参数列表,当依赖列表中任一参数变化时,则重新执行前面的函数。如deps = [a,b],表示a或者b发生变化则执行前面的函数(注意对象和值的变化方式不同)。当deps=[]时,表示只会在组件第一次渲染成功之后执行一次。 useEffectdidUpdate是一个包含命令式、且可能有副作用代码的函数。didUpdate是组件渲染成功且deps依赖参数发生变化时执行的函数,也就是说useEffect会执行didUpdate。didUpdate可以没有返回值,只是执行didUpdate的内容。didUpdate当它有返回值时,返回值必须是个可执行的函数,目的是用于清除didUpdate执行过程中产生的订阅或者计时器等资源。同时如果didUpdate多次触发,则在每次重新执行前都会先执行返回的可执行函数。(官方称之为清除effect)如下: useEffect(() => { const timer = setInterval(()=>{ console.log('effect') }, 10*1000); return () => { // 清除定时器 clearInterval(timer); };});因此对于useEffect来说,didUpdate时deps有变化时就是执行的,didUpdate若有返回值时,则在下次执行didUpdate时会先执行其返回的函数。 useCallback返回一个memoized回调函数,我的理解即返回一个函数的句柄,等同于函数的变量,因此你可以使用memoizedCallback()进行执行该函数或者传递给事件和子组件,这里可以推荐绝大多数事件或者子组件的方法使用useCallback,避免组件更新重复渲染。因此useCallback中的doSomething并不会在定义时就执行,而是需要手动调用返回的memoizedCallback才是真的执行。简单理解为useCallback定义了一个函数,仅在deps发生变化时重新定义该函数,否则该函数的变量不会变化,事件和子组件内容也就不用重新绑定或者渲染。 useMemo返回一个memoized值,useMemo函数每当deps发生变化时都会调用执行computeExpensiveValue的内容,这是与useCallback最大的不同,useCallback不执行doSomething的内容,只是重新刷新函数句柄。因此在官方上有这样的一个等式:useCallback(fn, deps) 相当于 useMemo(() => fn, deps)这应该看懂了吧。deps发生变化时,useCallback返回的是一个可执行fn的句柄,而useMemo则是执行()=>fn,但是因为返回的是fn函数,因此当调用这两种时其实执行的是相同的fn函数内容。 总结我自身对三者的理解是基于hook定义时:首参是否执行和各自返回内容的作用与差异进行理解,如果本文无法准确描述清楚,建议你也可以从这两方面进行入手分析三者之间的区别和用途。当然这样的解释和理解可能无法去解释什么情况用useEffect、useCallback、useMemo。但是作为开发者而言,理解hook其定义的差异,亦可理解其不同的用途目的,也就能区分清楚三者各自应在什么情况下进行使用。

June 21, 2020 · 1 min · jiezi

一直以来useCallback的使用姿势都不对

整理自gitHub笔记 一、误区 :useCallback是解决函数组件过多内部函数导致的性能问题使用函数组件时经常定义一些内部函数,总觉得这会影响函数组件性能。也以为useCallback就是解决这个问题的,其实不然(Are Hooks slow because of creating functions in render?): JS内部函数创建是非常快的,这点性能问题不是个问题;得益于相对于 class 更轻量的函数组件,以及避免了 HOC, renderProps 等等额外层级,函数组件性能差不到那里去;其实使用useCallback会造成额外的性能;因为增加了额外的deps变化判断。useCallback其实也并不是解决内部函数重新创建的问题。仔细看看,其实不管是否使用useCallback,都无法避免重新创建内部函数: export default function Index() { const [clickCount, increaseCount] = useState(0); // 没有使用`useCallback`,每次渲染都会重新创建内部函数 const handleClick = () => { console.log('handleClick'); increaseCount(clickCount + 1); } // 使用`useCallback`,但也每次渲染都会重新创建内部函数作为`useCallback`的实参 const handleClick = useCallback(() => { console.log('handleClick'); increaseCount(clickCount + 1); }, []) return ( <div> <p>{clickCount}</p> <Button handleClick={handleClick}>Click</Button> </div> )}二、useCallback解决的问题useCallback其实是利用memoize减少不必要的子组件重新渲染 import React, { useState, useCallback } from 'react'function Button(props) { const { handleClick, children } = props; console.log('Button -> render'); return ( <button onClick={handleClick}>{children}</button> )}const MemoizedButton = React.memo(Button);export default function Index() { const [clickCount, increaseCount] = useState(0); const handleClick = () => { console.log('handleClick'); increaseCount(clickCount + 1); } return ( <div> <p>{clickCount}</p> <MemoizedButton handleClick={handleClick}>Click</MemoizedButton> </div> )}即使使用了React.memo修饰了Button组件,但是每次点击【Click】btn都会导致Button组件重新渲染,因为: ...

June 21, 2020 · 4 min · jiezi

用hooks写个登录表单

最近尝试用React hooks相关api写一个登陆表单,目的就是加深一下对hooks的理解。本文不会讲解具体api的使用,只是针对要实现的功能,一步一步深入。所以阅读前要对 hooks有基本的认识。最终的样子有点像用hooks写一个简单的类似redux的状态管理模式。 细粒度的state一个简单的登录表单,包含用户名、密码、验证码3个输入项,也代表着表单的3个数据状态,我们简单的针对username、password、capacha分别通过useState建立状态关系,就是所谓的比较细粒度的状态划分。代码也很简单: // LoginForm.jsconst LoginForm = () => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [captcha, setCaptcha] = useState(""); const submit = useCallback(() => { loginService.login({ username, password, captcha, }); }, [username, password, captcha]); return ( <div className="login-form"> <input placeholder="用户名" value={username} onChange={(e) => { setUsername(e.target.value); }} /> <input placeholder="密码" value={password} onChange={(e) => { setPassword(e.target.value); }} /> <input placeholder="验证码" value={captcha} onChange={(e) => { setCaptcha(e.target.value); }} /> <button onClick={submit}>提交</button> </div> );};export default LoginForm;这种细粒度的状态,很简单也很直观,但是状态一多的话,要针对每个状态写相同的逻辑,就挺麻烦的,且太过分散。 ...

June 21, 2020 · 4 min · jiezi

React-Iframe-使用探索

作者后端经验比较丰富,近期要做跨域跨前端框架的前端页面展示,自然联想到了 IFRAME 方法,细致了解下来发现它可以用来解决很多棘手问题,包括: 跨域问题Ajax 前进后退问题异步上传问题跨框架问题父页面基础 React 框架import React, { PureComponent } from 'react';export default class Iframe extends PureComponent { render () { return ( <div> <h1>Parent</h1> <p>Send Message <button id="message_button" onClick={this.handleParentClick.bind(this)}>Hi child</button> </p> <span> Show Message </span> <div id='results'></div> </div> ); }}import 和 export 外部都是 React 基本框架,内部显示了页面布局: Parent Parent 提示当前部分是父页面; Send Message 和 Hi child 按钮用来向 iframe 传递消息,按钮绑定了点击事件 handleParentClick,后文会定义; Show Message 和 results div 用来展示 iframe 传来的消息,后文会做处理。 ...

June 17, 2020 · 3 min · jiezi

关于react.js:细聊Concent-Recoil-探索react数据流的新开发模式

开源不易,感激你的反对,❤ star me if you like concent ^_^ 序言之前发表了一篇文章 redux、mobx、concent个性大比拼, 看后生如何对局前辈,吸引了不少感兴趣的小伙伴入群开始理解和应用 concent,并取得了很多正向的反馈,实实在在的帮忙他们进步了开发体验,群里人数尽管还很少,但大家热情高涨,技术探讨气氛浓重,对很多陈腐技术都有放弃肯定的敏感度,如上个月开始逐步被提及得越来越多的出自facebook的最新状态治理计划 recoil,尽管还处于试验状态,然而相必大家曾经私底下开始欲欲跃试了,毕竟出世名门,有fb背书,肯定会大放异彩。 不过当我体验完recoil后,我对其中标榜的准确更新放弃了狐疑态度,有一些误导的嫌疑,这一点下文会独自剖析,是否属于误导读者在读完本文后天然能够得出结论,总之本文次要是剖析Concent与Recoil的代码格调差异性,并探讨它们对咱们未来的开发模式有何新的影响,以及思维上须要做什么样的转变。 数据流计划之3大流派目前支流的数据流计划按状态都能够划分以下这三类 redux流派redux、和基于redux衍生的其余作品,以及相似redux思路的作品,代表作有dva、rematch等等。 mobx流派借助definePerperty和Proxy实现数据劫持,从而达到响应式编程目标的代表,类mobx的作品也有不少,如dob等。 Context流派这里的Context指的是react自带的Context api,基于Context api打造的数据流计划通常主打轻量、易用、概览少,代表作品有unstated、constate等,大多数作品的外围代码可能不超过500行。 到此咱们看看Recoil应该属于哪一类?很显然按其特色属于Context流派,那么咱们下面说的主打轻量对Recoil并不实用了,关上其源码库发现代码并不是几百行完事的,所以基于Context api做得好用且弱小就未必轻量,由此看出facebook对Recoil是有野心并给予厚望的。 咱们同时也看看Concent属于哪一类呢?Concent在v2版本之后,重构数据追踪机制,启用了defineProperty和Proxy个性,得以让react利用既保留了不可变的谋求,又享受到了运行时依赖收集和ui准确更新的性能晋升福利,既然启用了defineProperty和Proxy,那么看起来Concent应该属于mobx流派? 事实上Concent属于一种全新的流派,不依赖react的Context api,不毁坏react组件自身的状态,放弃谋求不可变的哲学,仅在react本身的渲染调度机制之上建设一层逻辑层状态散发调度机制,defineProperty和Proxy只是用于辅助收集实例和衍生数据对模块数据的依赖,而批改数据入口还是setState(或基于setState封装的dispatch, invoke, sync),让Concent能够0入侵的接入react利用,真正的即插即用和无感知接入。 即插即用的外围原理是,Concent自建了一个平行于react运行时的全局上下文,精心保护这模块与实例之间的归属关系,同时接管了组件实例的更新入口setState,保留原始的setState为reactSetState,所有当用户调用setState时,concent除了调用reactSetState更新以后实例ui,同时智能判断提交的状态是否也还有别的实例关怀其变动,而后一并拿进去顺次执行这些实例的reactSetState,进而达到了状态全副同步的目标。 Recoil初体验咱们以罕用的counter来举例,相熟一下Recoil裸露的四个高频应用的api atom,定义状态selector, 定义派生数据useRecoilState,生产状态useRecoilValue,生产派生数据定义状态内部应用atom接口,定义一个key为num,初始值为0的状态 const numState = atom({ key: "num", default: 0});定义派生数据内部应用selector接口,定义一个key为numx10,初始值是依赖numState再次计算而失去 const numx10Val = selector({ key: "numx10", get: ({ get }) => { const num = get(numState); return num * 10; }});定义异步的派生数据selector的get反对定义异步函数 须要留神的点是,如果有依赖,必须先书写好依赖在开始执行异步逻辑const delay = () => new Promise(r => setTimeout(r, 1000));const asyncNumx10Val = selector({ key: "asyncNumx10", get: async ({ get }) => { // !!!这句话不能放在delay之下, selector须要同步的确定依赖 const num = get(numState); await delay(); return num * 10; }});生产状态组件里应用useRecoilState接口,传入想要获去的状态(由atom创立而得) ...

June 15, 2020 · 5 min · jiezi

使用-Formik-Yup-处理-React-表单验证

前言React 操作表单一直都是比较繁琐的操作,在以前用的是 redux-form, 但现在 formik 这个库设计得更加优雅,Github 的 star 数目已经远远超过 redux-form 了。做表单就是为了收集一些数据,然后进行提交。而 redux-form 理念是把这些数据存放到 reducer 中去,当我们表单多的时候,显然对整个 store 的数据管理不太友好,因为多了很多表单数据。而 formik 解决的其中一个痛点就是这个,它可以在组件内部处理这些表单项而简单易用。 Formik 旨在轻松管理具有复杂验证的表单, Formik 支持同步和异步表单级和字段级验证。 例子例子的 Formik 为 2.1.4 版 先来看个基本的例子: import React from 'react'import { useFormik } from 'formik'import './FormDemo.css'// 检查表单字段const validate = values => { const errors = {} if (!values.name) { errors.name = '不能为空' } else if (values.name.length > 20) { errors.name = '名字太长,输入有误' } if (!values.email) { errors.email = '不能为空' } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) { errors.email = '邮箱格式错误' } return errors}const FormDemo = () => { const formik = useFormik({ initialValues: { name: '', email: '', }, validate, onSubmit: values => { alert(JSON.stringify(values, null, 2)) }, }) return ( <form onSubmit={formik.handleSubmit} className='form-wrap'> <div className='form-group'> <label>名字</label> <input name='name' onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.name} /> {formik.touched.name && formik.errors.name ? ( <div className='error-tip'>{formik.errors.name}</div> ) : null} </div> <div className='form-group'> <label>邮箱</label> <input name='email' onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.email} /> {formik.touched.email && formik.errors.email ? ( <div className='error-tip'>{formik.errors.email}</div> ) : null} </div> <button type='submit'>Submit</button> </form> )}export default FormDemo以上是一个很简单的表单,输入名字与邮箱。 ...

June 13, 2020 · 2 min · jiezi

react组件生命周期函数

生命周期函数图谱 一、常用的生命周期函数1.render()render()注意(1) render() 方法是 class 组件中唯一必须实现的方法(2) render() 函数应该为纯函数 这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。如需与浏览器进行交互,请在 `componentDidMount()` 或其他生命周期方法中执行你的操作。保持 `render()` 为纯函数,可以使组件更容易思考。(3) 如果 shouldComponentUpdate() 返回 false,则不会调用 render() 2.constructor()在 React 组件挂载之前,会调用它的构造函数 constructor(props) { super(props); // 不要在这里调用 this.setState() this.state = { counter: 0 }; this.handleClick = this.handleClick.bind(this);}如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。通常,在 React 中,构造函数仅用于以下两种情况: 通过给 this.state 赋值对象来初始化内部 state。为事件处理函数绑定实例注意:(1) 在 constructor() 函数中不要调用 setState() 方法。如果你的组件需要使用内部 state,请直接在构造函数中为 this.state 赋值初始 state:(2) 要避免在构造函数中引入任何副作用或订阅。如遇到此场景,请将对应的操作放置在 componentDidMount 中(3)避免将 props 的值复制给 state!这是一个常见的错误 constructor(props) { super(props); // 不要这样做 this.state = { color: props.color };}3.componentDidMount()componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用,依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,添加订阅等 ...

June 9, 2020 · 2 min · jiezi

React-小知识

React.StrictMode识别具有不安全生命周期的组件有关旧式字符串ref用法的警告关于已弃用的findDOMNode用法的警告检测意外的副作用检测遗留 context API示例: import React from 'react';function ExampleApplication() { return ( <div> <Header /> <React.StrictMode> <div> <ComponentOne /> <ComponentTwo /> </div> </React.StrictMode> <Footer /> </div> );}React.Lazy允许我们定义动态加载的组件示例: // This component is loaded dynamicallyconst SomeComponent = React.lazy(() => import('./SomeComponent'));React.Suspense延迟加载组件,当组件未准备好时,显示 loading // This component is loaded dynamicallyconst OtherComponent = React.lazy(() => import('./OtherComponent'));function MyComponent() { return ( // Displays <Spinner> until OtherComponent loads <React.Suspense fallback={<Spinner />}> <div> <OtherComponent /> </div> </React.Suspense> );}ReactDOM.createPortal将组件挂载在任意节点 ...

June 9, 2020 · 1 min · jiezi

react基础知识梳理一

最近在自学react,现在想整理整理知识点,希望能帮助到那些正在学react的孩子们 react核心点:组件化,路由,模块化设计,其设计思路跟vue类似,不过react中 数据是单向的只能从父组件流向子组件,子传父需要我们自己做一些处理,redux并非 react中才能用,只是react支持的更好,我们可以在其它框架,如angular,甚至任 何框架都能使用它。 react很多用法习惯可以参照vue,只是很多东西需要自己手动封装这就考验我们的Js 基本功了,个人建议想学好react必须具备扎实的js基础和es6的基础,否则学起来 真心吃力~~ 下面是一些引言,后期持续更新 (1)react 每个模块都需要 import React from 'react'(2)react根节点只有一个 类似于vue的根组件 ReactDOM.render('根节点元素',挂载到根节点) 如:ReactDOM.render('<App/>',document.getElementById("root")) (3)react组件类型:函数组件,类组件(后期更新) (4)jsx语法:javascript+xml语法 注意js语句必须写在{}里,否则会被解析 成普通文本。 (5)react-router react的路由,用法类似于vue,有hash和history模式 (6)react生命周期:Initialization,mounting,updation,unmounting (后期更新) (7)react快速生成模板工具 simple react插件,我们可以背几个常用的指令 如 rc cc等 快速生成功能模块大大提升我们的开发效率.这期分享完毕,感谢阅读

June 7, 2020 · 1 min · jiezi

React-原理一实现-createElement-和-render-方法

前言在 React 中,我们都知道可以写 jsx 代码会被编译成真正的 DOM 插入到要显示的页面上。这具体是怎么实现的,今天我们就自己动手做一下。 实现 createElement 方法这个方法平时开发我们并不会用到,因为它是经 babel 编译后的代码,我们新建一个 React 项目,index.js 最简单的代码结构如下: import React from 'react'import ReactDOM from 'react-dom'ReactDOM.render(<h1 className='title'>Hello React</h1>, document.getElementById('root'))这里就 jsx 会变编译成真正的 DOM ,把 html 代码拿到 babel 官网编译 于是我们就看到了 React.createElement() 方法,但这只是调用这个方法,它具体做了什么返回什么我们还不知道,我们可以打印这个函数运行的结果: console.log( React.createElement( 'h1', { className: 'title', }, 'Hello React' ))返回的这个对象就是虚拟 DOM 了。 我们来分析它返回的对象参数,首先第一个是 $$typeof: REACT_ELEMENT_TYPE这个是 React 元素对象的标识属性 REACT_ELEMENT_TYPE 的值是一个 Symbol 类型,代表了一个独一无二的值。如果浏览器不支持 Symbol类型,值就是一个二进制值。 为什么是 Symbol?主要防止 XSS 攻击伪造一个假的 React 组件。因为 JSON 中是不会存在 Symbol 类型的。key:这个比如循环中会用到这个key值props:传入的属性值,比如 id, className, style, children 等ref: DOM 的引用剩下的是私有属性(本篇不展开讨论)在本篇我们会用自己简单的方式实现这两个方法,而不是根据源码,所以实现上的方法只要能实现它的基本功能即可;有个基本概念在,以后再循序渐进学习源码。 ...

June 6, 2020 · 2 min · jiezi

使用createreactapp快速构建React项目

从学习的角度来说,react没有vue那么容易上手。万事开头难,然后中间难,结果难......下面是我一步步集成插件,搭建基本框架的过程。 1 使用create-react-app通过官方的脚手架工具创建项目目录 npx create-react-app react-app第一行的 npx 不是拼写错误,它是 npm 5.2+ 附带的 package 运行工具。 稍等一会,安装完成后,使用npm或者yarn启动项目 npm start(yarn start)项目启动后,默认的端口号是3000,可以通过localhost:3000来访问改变一下原本的目录解构,调整成个人比较习惯或者舒适的 2 基本配置2.1 支持Css预处理器create-react-app默认情况下是不暴露配置文件的,执行npm run eject暴露配置文件的操作是不可逆的。 个人喜欢使用react-app-rewired自定义配置(类似于vue-cli3的自定义配置文件) react-app-rewired 1.x 配合 create-react-app 1.x react-app-rewired 2.x 配合 create-react-app 2.x以上 react-app-rewired@^2.0.0+ 版本需要搭配 customize-cra 使用2.1.1 支持Scsscreate-react-app原本就自带sass-loader和sass相关的配置,你只需要安装node-sass就可以使用了 npm i node-sass -D注意 安装node-sass的时候总是会各种不成功,主要因为在安装时会从 github.com 上下载一个 .node 文件 方法一就是直接使用cnpm安装一切包 方法二是使用代理 我比较喜欢使用npm i node-sass --sass_binary_site=https://npm.taobao.org/mirror...2.1.2 支持Less后续因为会集成antd,所以预处理器我推荐使用less比较统一,执行: npm i react-app-rewired customize-cra -D修改package.json "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-scripts eject"}然后在根目录新建config-overrides.js并添加配置: ...

June 5, 2020 · 4 min · jiezi

通过对比class组件来理解React的hooks特性

前言hooks是react16.8新增的特性。关于为什么要新增hooks,是因为class的组件会存在以下的一些问题。 在组件之间复用状态逻辑很难复杂组件变得难以理解难以理解的 class这些点就不详细赘述了,这篇文章的重点是介绍hooks。 useStatefunction useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>]; useState是用来取代class组件中的setState。useState接受一个值作为state的初始值,返回一个数组,数组第一个值是state,第二个值是一个用于修改state的函数。useState可以多次使用。栗子如下: import React, { useState } from 'react';const App = function() { const [count, setCount] = useState(0); // 另一个没有使用的state const [other, setOther] = useState('hello'); return ( <button onClick={() => setCount(count+1)}>{ count }</button> ); }它对应的class组件的代码如下: import React, { Component } from 'react';class App extends Component { constructor(props) { super(props); this.state = { count: 0, other: 'hello' }; } setCount(count: number) { this.setState({ count }); } setOther(other: string) { this.setState({ other }); } render() { return ( <button onClick={() => this.setCount(this.state.count + 1)}> {this.state.count} </button> ); }}useReduceruseReducer和useState的用处是一样的,不同的是useReducer处理的是更加复杂的场景,例如三级联动选择框,需要同时对多个state进行联动处理。可以看做是一个小的redux,使用方式和redux也基本一致。 ...

June 5, 2020 · 3 min · jiezi

一篇文章快速入门React框架

视频教程本文章在B站配有视频教程 课程目标了解最常用的React概念和相关术语,例如JSX,组件,属性(Props),状态(state)。构建一个非常简单的React应用程序,以阐述上述概念。最终效果 创建React应用helloworld(1)安装node.js     官网链接 (2)打开cmd 窗口 输入    npm install --g create-react-app npm install --g yarn(-g 代表全局安装) 如果安装失败或较慢。需要换源,可以使用淘宝NPM镜像,设置方法为:npm config set registry https://registry.npm.taobao.org,设置完成后,重新执行 cnpm install --g create-react-appcnpm install --g yarn安装 creat-react-app 功能组件,该组件可以用来初始化一个项目, 即 按照一定的目录结构,生成一个新项目    (3)在你想创建项目的目录下  例如 D:/project/ 打开cmd命令 输入    create-react-app react-tutorial去使用creat-react-app命令创建名字是react-tutorial的项目 安装完成后,移至新创建的目录并启动项目 cd react-tutorialyarn start一旦运行此命令,localhost:3000新的React应用程序将弹出一个新窗口。 项目目录结构一个/public和/src目录,以及node_modules,.gitignore,README.md,和package.json。 在目录/public中,重要文件是index.html,其中一行代码最重要 <div id="root"></div>该div做为我们整个应用的挂载点 /src目录将包含我们所有的React代码。 要查看环境如何自动编译和更新您的React代码,请找到文件/src/App.js:将其中的 <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a>修改为 <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > 和豆约翰 Learn React </a>保存文件后,您会注意到localhost:3000编译并刷新了新数据。现在删除/src目录中的所有文件,我们将从头开始创建自己的项目文件。 ...

June 4, 2020 · 4 min · jiezi

React列表中实现文案多行收起展开的功能实用小妙招

css实现在我们平时的业务开发中经常会用到文案超出只有收起,点击在展示全部文案;通常的使用时使用css来实现 display: -webkit-box;-webkit-box-orient: vertical;-webkit-line-clamp: 3;overflow: hidden;效果如下: 使用css实现时只能做多行的省略,也没法根据文字去添加定制化的按钮去实现展开收起的功能,这个只是适合特定要求不是很高的场合下使用。 字符串截取另一种方法是使用字符串截取的方案 _renderContent = item => { const { content, id } = item; if (content.length > 69) { return ( <div> <div ref={id} className="content"> {content.slice(0, 69)} </div> <div className="content-btn" ref={id + 'btn'} onClick={() => { this.handleContent(item); }}> 全文 </div> </div> ); } else { return <div className="content">{content}</div>; }};展示效果: 弊端: 数字、中文字符和引文字符的宽度是不一样的,在使用字符串截取是很容易出现如上的偏差,并且每个手机的分辨率不一样,字符渲染的宽度像素也是不同的,这样也会导致误差;所以字符串截取也不是一种很好的方案。 最终实现方案话不多说直接上代码:content组件 js代码: import React from 'react';import cs from 'classnames';import './style.scss';export default class TextContainer extends React.Component { constructor(props) { super(props); this.state = { content: props.content, showAll: false, btnText: '全文', needHidden: false // 文字超出4行 需要隐藏 }; } /** * @description: 处理content文案的点击展开收起 * @return: null */ handleContent = e => { e.stopPropagation(); let { showAll } = this.state; this.setState({ showAll: !showAll }); }; // 判断文本超出行数 isElementCollision = (ele, rowCount = 4, cssStyles, removeChild) => { if (!ele) { return false; } const clonedNode = ele.cloneNode(true); // 给clone的dom增加样式 clonedNode.style.overflow = 'visible'; clonedNode.style.display = 'inline-block'; clonedNode.style.width = 'auto'; clonedNode.style.whiteSpace = 'nowrap'; clonedNode.style.visibility = 'hidden'; // 将传入的css字体样式赋值 if (cssStyles) { Object.keys(cssStyles).forEach(item => { clonedNode.style[item] = cssStyles[item]; }); } // 给clone的dom增加id属性 let _time = new Date().getTime(); const containerID = 'collision_node_id_' + _time; clonedNode.setAttribute('id', containerID); let tmpNode = document.getElementById(containerID); let newNode = clonedNode; if (tmpNode) { document.body.replaceChild(clonedNode, tmpNode); } else { newNode = document.body.appendChild(clonedNode); } // 新增的dom宽度与原dom的宽度*限制行数做对比 const differ = newNode.offsetWidth - ele.offsetWidth * rowCount + 40; // console.log(differ, 'differ'); if (removeChild) { document.body.removeChild(newNode); } return differ > 0; }; componentDidMount = () => { const cssStyles = { fontSize: '0.9375rem', fontWeight: '400', lineHeight: '1.5625rem' }; // console.log(this.isElementCollision(this.refs['content'], 4, cssStyles, true)); let needHidden = this.isElementCollision(this.refs['content'], 4, cssStyles, true); this.setState({ needHidden }); }; render() { let { content, needHidden, showAll } = this.state; let { headerText } = this.props; return ( <div> <div ref={'content'} className={cs('content', { 'hidden-text': !showAll && needHidden })}> {headerText ? headerText() : null} {content} </div> {needHidden && ( <div className="content-btn" onClick={e => { this.handleContent(e); }}> {!showAll ? '全文' : '收起'} </div> )} </div> ); }}css代码: ...

June 4, 2020 · 2 min · jiezi

图片轮播预览

效果 实现js代码: import React, { Component } from 'react';import Carousel from 'nuka-carousel';import './largePreview.scss';const Index = props => { return props.visible ? <LargePriview {...props} /> : null;};/** * @param {Array} pics 图片数组 [ {img: ''}, {img: ''} ] * @param {boolean} visible 是否展示大图预览 * @param {number} currentIndex 当前是第几张图片,数组下标 * @param {function} close 关闭当前图片预览 */class LargePriview extends Component { constructor(props) { super(props); this.state = { screenHeight: '100%', currentIndex: 1, toggleBarHeight: 0, pics: [] }; } componentWillMount = () => { if (navigator.userAgent.indexOf('cheshangtong') > -1) { this.setState({ pics: JSON.parse(WBCST.getParamFromUrl('pic')), currentIndex: Number(WBCST.getParamFromUrl('index')) }); } else { this.setState({ pics: this.props.location.param.pic, currentIndex: this.props.location.param.index }); } WBCST.toggleTitlePanel( { hideNavBar: true, bounces: 0, statusBarStyle: 'light' }, data => { this.setState({ toggleBarHeight: data.toggleBarHeight }); } ); }; componentDidMount() { const screenHeight = (document && document.body.clientHeight) || '100%'; this.setState({ screenHeight }); } screenHeight = () => { const screenHeight = (document && document.body.clientHeight) || '100%'; let clientWidth = document.querySelector('body').offsetWidth; const { toggleBarHeight } = this.state; let height = toggleBarHeight > 50 ? toggleBarHeight : toggleBarHeight + (45 / 375) * clientWidth; return screenHeight - height; }; handleImgClick(show, index) { this.setState({ currentIndex: index }); } handleTop = () => { const { toggleBarHeight } = this.state; let clientWidth = document.querySelector('body').offsetWidth; let top = toggleBarHeight > 50 ? toggleBarHeight - (45 / 375) * clientWidth : toggleBarHeight; return top; }; render() { const { screenHeight, currentIndex, pics } = this.state; return ( <div className="imgs-large-wrapper"> <div style={{ height: this.handleTop() }} className="pre-status"></div> <div className="imgs-top-float"> <div className="close" onClick={() => { if (navigator.userAgent.indexOf('cheshangtong') > -1) { WBCST.closeCurrentPage(); } else { this.props.history.go(-1); } }}></div> <div className="imgs-index-style"> {currentIndex + 1}/{pics.length} </div> <div className="right"></div> </div> <div> <Carousel autoplay={false} slideIndex={currentIndex} defaultControlsConfig={{ nextButtonText: '', prevButtonText: '', nextButtonStyle: { display: 'none' }, prevButtonStyle: { display: 'none' }, pagingDotsStyle: { display: 'none' } }} afterSlide={index => { this.handleImgClick(true, index); }}> {pics.map((imgItem, imgIndex) => { return ( <div className="imgs-carousel-box" style={{ height: this.screenHeight() }} key={imgIndex}> <img src={imgItem} /> </div> ); })} </Carousel> </div> </div> ); }}export default LargePriview;css代码: ...

June 4, 2020 · 2 min · jiezi

33行react简要分析

一、简介前段时间看到一个用33行代码就实现了一个非常基本的react代码。感觉还是蛮有趣的,代码如下: 其主要实现了两大功能:① 生成虚拟DOM;② 根据虚拟DOM渲染出真实的DOM; 无注释版:https://github.com/leontrolsk...有注释版:https://github.com/leontrolsk... 二、代码分析虽然代码总共才33行,但是写的非常简洁,可能不是一下就能看懂,我对此细细研读了一番,用更加明了的方式重新写了一下。主要就是对外暴露了React.createElement()和React.render()两个方法。① 实现代码基本框架 // 定义一个React类并对外暴露class React { // 负责创建虚拟DOM static createElement(...args) { } // 负责将虚拟DOM渲染成真实的DOM static render(parent, v) { }}export default React;② 实现React.createElement()方法createElement()方法主要就是解析传递过来的参数,然后解析出标签名、类名数组、属性对象、子节点数组。 解析标签名: 第一个参数为字符串中包含标签名,这个参数支持点的形式,如 div.redColor.bluebg,所以需要以点号进行分割成数组["div", "redColor", "bluebg"],将数组的第一个元素作为标签名,剩余的元素都作为元素的类名。解析元素的属性对象: 通常传入的第二个参数对象为元素的属性对象,但是也可以不传或直接传入元素的子节点,所以需要对第二个参数进行判断,看一下是否是属性对象。判断的依据就是,如果第二个参数是null、string、number、array、vnode,那么就不是属性对象,而是子节点。class React { // 判断能不能渲染,不能渲染则为属性对象 static isRenderable(v) { return v === null || ['string', 'number'].includes(typeof v) || v.__m || Array.isArray(v); }}解析子节点: 解析完标签名和属性对象后,剩下的参数都是子节点,只不过参数有可能是一个数组,所以需要对其进行判断,如果是数组则需要遍历数组,然后再递归将其添加到children数组中。class React { // 负责创建虚拟DOM static createElement(...args) { // ① 将attrs初始化为一个空对象{},并解构出第一个参数head let attrs = {}; let [head, ...tail] = args; // ② 获取标签名和第一个参数中传递的类名 let [tag, ...classes] = head.split('.'); // ③ 获取传入的attrs对象,首先判断tail中的第一部分能不能渲染,如果不能渲染,说明是attrs对象,否则为子节点 if (tail.length && !this.isRenderable(tail[0])) { // 如果传入的第二个参数不能被渲染,那么第二个参数就是attrs对象 [attrs, ...tail] = tail; // 取出attrs对象 } // ④ 解析attrs对象中包含的类名 if (attrs.class) { // 如果attrs属性对象中有class属性,那么将其中的类名放到classes中 classes = [...classes, ...attrs.class]; } // ⑤ 移除attrs对象中的class属性 attrs = {...attrs}; delete attrs.class; // ⑥ 初始化children为空数组[] const children = []; // ⑦ 定义一个addChildren()方法,将所有子节点加入到children数组中 const addChildren = (v) => { if (v === null) { // 如果是null则直接返回,不将其加入到children数组中 return; } if (Array.isArray(v)) { // 如果是数组则遍历数组并调用addChildren() v.map(addChildren); } else { children.push(v); // 非数组则直接将其添加到children数组中 } } addChildren(tail); // ⑧ 返回虚拟DOM对象 return { __m: true, // 是否是虚拟节点 tag: tag || 'div', attrs, classes, children }; }}③ 实现React.render()方法React.render()方法主要传入一个真实的父节点和对应的虚拟节点,将真实父节点当做旧节点,将虚拟节点作为新节点,然后对新旧节点进行比较,没有则创建,有则更新,同时更新属性,然后进行递归比较其子节点,直到没有子节点为止。 ...

June 2, 2020 · 2 min · jiezi

React-diff-算法

前言很久以前写过一篇了解虚拟DOM 的文章,主要讲解了vue为什么会使用虚拟 DOM 以及 VUE 的 diff 算法。最近技术栈迁移到了 React,就好好研究了一下 React diff 算法的实现。 React Fiber在了解 React diff 算法之前,先了解 React Fiber 相关的知识,将有助于后面对 diff 算法中比对的理解。React 16版本之后推出了 Fiber 的概念,React Fiber是对核心算法的一次重新实现,基本本文主要是讲 diff 算法,因此忽略分片、更新优先级这些概念,可以简单的将 Fiber理解为 DOM 结构的 JS 映射。 例如如下的 React 代码 class App extends React.Component { state = { list: [{ key: 'A', value: '我是 A' }, { key: 'B', value: '我是 B' }, { key: 'C', value: '我是 C' }, { key: 'D', value: '我是 D' }, { key: 'E', value: '我是 E' }] }; btn2Click = () => { // } render () { const { list } = this.state; return ( <div className="App"> <span className="btn-2" onClick={this.btn2Click}> 点击调换顺序 </span> { list.map((item) => (<div key={item.key}>{item.value}</div>)) } </div> )};}映射为 React Fiber 的简略表示为 ...

June 1, 2020 · 3 min · jiezi

React实现类似淘宝tab居中切换效果

效果 DOM布局const label = { lettersort: false, paramname: "label", paramid: 0, title: "车源列表筛选项", option: [{ value: 1, text: "全部" }, { value: 2, text: "本地求购" }, { value: 3, text: "精准收车" }, { value: 4, text: "全国收车" }, { value: 5, text: "同行询价" }, { value: 6, text: "可批可售" }, { value: 7, text: "车抵贷款" }, { value: 8, text: "消费贷款" }, { value: 9, text: "商家库容" }, { value: 10, text: "代理合作" }, { value: 11, text: "过户转籍" }, { value: 12, text: "寻车拖车" }, { value: 13, text: "解压抵押" }, { value: 14, text: "抵押核验" } ]}filterDom = () => { let filterJson = label; let arr = filterJson.option; return ( <div ref="filterBar" className="filter-list"> {arr.map((item, index) => { if (item.value == this.state.filterSelect) { return ( <div ref={item.value} className="filter-item active" key={index} value={item.value}> {item.text} <div className="zhishi"></div> </div> ); } else { return ( <div className="filter-item" onClick={() => { this.filterBarClick(item); }} ref={item.value} key={index} value={item.value}> {item.text} </div> ); } })} </div> );};render(){ return( <div> ... <div className="filter-content" style={{ display: this.state.filterBarShow }}> {this.filterDom()} <div className="shadow"></div> {/* 按钮和占位 */} <div className="filte-btn-content" onClick={() => { this.filterBtnClick(); }}> <div className="filte-btn"></div> </div> </div> ... </div> )}scss样式表 ...

June 1, 2020 · 3 min · jiezi

React-hook-系列

用hook也有半年多了,结合自身使用以及社区,写了如下的文章,欢迎各位大佬沟通交流初识 React hook (一)初识 React hook (二)React hook 使用规则自定义 React hookReact hook 源码学习一个使用 render props 而不是hooks的场景

June 1, 2020 · 1 min · jiezi

RN和React路由详解及对比

前言在平时H5或者RN开发时,我们业务场景中大部分都不是单页面的需求,那这时我们就能使用路由在进行多页面的切换。下面会对比一下react路由和RN路由的本质区别和使用方法。 路由(routing)是指分组从源到目的地时,决定端到端路径的网络范围的进程React路由简介使用React构建的单页面应用,要想实现页面间的跳转,首先想到的就是使用路由。在React中,常用的有两个包可以实现这个需求,那就是react-router和react-router-dom。本文主要针对react-router-dom进行说明 在根组件上配置路由,引用react-router-dom结构{ HashRouter as Router, Route ,Link ,Redirect ,Switch },HashRouter组件是路由的根组件,定义属性和方法传递给子组件。Router组件进行路由,指定每个路由跳转到相应的组件。Link组件指定跳转链接。Redirect组件路由重定向,不管什么情况下,都会跳转当前指定的路径,和switch组件联合起来一起调用,当路径匹配到路由,不在往下匹配 两类路由HashRouter:利用监听hash变化(有一个事件hashchange)实现路由切换,它是路由容器,渲染子组件,并向下层子组件传递(Context上下文传递)loaction,history等路由信息 BrowserHistory:利用H5Api实现路由切换,是路由容器,渲染子组件, 并向子组件传递loaction,history等路由信息路由配置 路由实现原理HashRouter只是一个容器,本身并没有DOM结构它渲染的就是它的子组件,并向下层传递location组件挂载完成之后根据hash改变pathname的值,如果没有hash值就默认展示根组件需要跳转路由页面时我们使用link或者push去赋值hash的pathname 如this.props.history.push({ pathname: preview, param: { pic, index } });当hash值发生变化的时候会通过hashchange捕获变化,并给pathname重新赋值拿到上下文中传过来的location,然后取出pathname。再对它的子组件进行遍历,如果子组件的path属性和当前上下文中传过来的pathname属性相匹配就进行渲染,若不匹配就返回null。总结React路由是实质就是,根据遍历识别路由的pathname,来切换router路由容器中component组件的加载渲染。每次更改pathname就都是组件的重新渲染流程,页面也都会呈现出刷新的效果。 RN路由简介RN把导航和路由都集中到了react-navigation库里面组件使用堆栈式的页面导航来实现各个页面跳转构造函数:StackNavigator(RouteConfigs, StackNavigatorConfig)RouteConfigs:页面路由配置StackNavigatorConfig:路由参数配置路由配置 参数详解navigationOptions:配置StackNavigator的一些属性。 title:标题,如果设置了这个导航栏和标签栏的title就会变成一样的,不推荐使用 header:可以设置一些导航的属性,如果隐藏顶部导航栏只要将这个属性设置为null headerTitle:设置导航栏标题,推荐 headerBackTitle:设置跳转页面左侧返回箭头后面的文字,默认是上一个页面的标题。可以自定义,也可以设置为null headerTruncatedBackTitle:设置当上个页面标题不符合返回箭头后的文字时,默认改成"返回" headerRight:设置导航条右侧。可以是按钮或者其他视图控件 headerLeft:设置导航条左侧。可以是按钮或者其他视图控件 headerStyle:设置导航条的样式。背景色,宽高等 headerTitleStyle:设置导航栏文字样式 headerBackTitleStyle:设置导航栏‘返回’文字样式 headerTintColor:设置导航栏颜色 headerPressColorAndroid:安卓独有的设置颜色纹理,需要安卓版本大于5.0 gesturesEnabled:是否支持滑动返回手势,iOS默认支持,安卓默认关闭 screen:对应界面名称,需要填入import之后的页面 mode:定义跳转风格 card:使用iOS和安卓默认的风格 modal:iOS独有的使屏幕从底部画出。类似iOS的present效果 headerMode:返回上级页面时动画效果 float:iOS默认的效果 screen:滑动过程中,整个页面都会返回 none:无动画 cardStyle:自定义设置跳转效果 transitionConfig: 自定义设置滑动返回的配置 onTransitionStart:当转换动画即将开始时被调用的功能 onTransitionEnd:当转换动画完成,将被调用的功能 path:路由中设置的路径的覆盖映射配置 initialRouteName:设置默认的页面组件,必须是上面已注册的页面组件 initialRouteParams:初始路由参数路由首页react: 在react中初始化时没有指定hash值,route会匹配路由表里面的根组件”/” RN: RN 需要在StackNavigatorConfig里面指定首页 RN路由使用 在入口路由列表注册完成之后 在导航器中的每一个页面,都有 navigation 属性 通过提供的navigate方法来提供跳转 ...

June 1, 2020 · 1 min · jiezi

20200531react01

一:react概述二:DOM和虚拟DOM介绍三:虚拟DOM的本质和目的四:diff算法介绍五:webpack4.x的基本使用【创建webpack项目】1 新建一个文件夹 【01.webpack-base】2 使用命令【num init -y】,快速创建一个webpack项目【快速初始化项目】,【运行后在文件夹下产生一个package.json的文件】。3 新建一个【src】目录,存放源代码。4 新建一个【dist】目录,存放产品打包后的文件。5 新建首页【index.html】6 新建js入口文件【index.js】7 使用cnpm安装webpack【cnpm i webpack -D】8 全局运行【npm i cnpm -g】9 安装【cnpm-cli】,【cnpm i webpack-cli -D】10 在【webpack.config.js】文件中配置运行环境 //向外暴露一个打包对象module.exports = { mode:'development'//development production}11 约定的打包的入口文件为【index.js】文件,【约定大于配置的规则】12 使用【webpack】打包,打包后在【dist】目录下生成一个【main.js】的文件。 六:webpack-dev-server的基本使用1 问题:修改代码后,【mian.js】还是上次打包的文件,不起作用,每次都要重新打包,比较麻烦2 实时打包编译工具:【webpack-dev-server】3 安装:【cnpm i webpack-dev-server -D】4 在【package.json中配置】 "dev":"webpack-dev-server" 5 执行:【npm run dev】,即可完成修改代码后的自动编译6 生成的【mian.js】是放在内存中的根目录下,引用内存中的【main.js】7 配置编译后自动打开浏览器 "dev":"webpack-dev-server --open --port --host" 七:配置html-webpack-plugin插件1 问题:编译后没有自动跳转到首页2 解决:配置编译后自动跳转到首页,即配置首页到内存中3 安装【html-webpack-plugin】插件,【cnpm i html-webpack-plugin -D】4 在【webpack.config.js】中进行配置【html-webpack-plugin】插件 //配置插件 const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') //配置在内存中自动生成index.js的插件 //创建一个插件的实例对象 const htmlPlugin = new HtmlWebpackPlugin({ template:path.join(__dirname,'./src/index.html'), filename:'index.html' })//向外暴露一个打包对象module.exports = { mode:'development', plugins:[ htmlPlugin ]}总结:完成了打包后的【mian.js】文件进内存,【index.html】进内存,并且打包好的【mian.js】自动注入到【index.html】中,使用包管理工具的基本环境设置完成。 ...

June 1, 2020 · 2 min · jiezi

React配置多页应用打包失败报错Cannot-read-property-filter-of-undefined解决方案

起因最近再做一个react的多页应用,在修改过 webpack 的 entry 及 HtmlWebpackPlugin 等配置后,进行打包测试,运行 npm run build 后控制台报错: Creating an optimized production build...Failed to compile.Cannot read property 'filter' of undefined分析因为控制台并没有明确提示错误位置,所以开始在修改的 webpack.config.js 文件中用到 filter方法的地方排查。开始以为是 entry 配置有问题,但经过测试发现问题不在 entry 。 之后对比了不同版本的 create-react-app 生成的 webpack.config.js文件中所用到 filter方法的地方,发现新的配置文件中 ManifestPlugin 的配置项发生了变化。 // 这是旧版的 ManifestPlugin 配置new ManifestPlugin({ fileName: 'asset-manifest.json', publicPath: paths.publicUrlOrPath,}), // 这是新版的 ManifestPlugin 配置new ManifestPlugin({ fileName: 'asset-manifest.json', publicPath: paths.publicUrlOrPath, generate: (seed, files, entrypoints) => { const manifestFiles = files.reduce((manifest, file) => { manifest[file.name] = file.path; return manifest; }, seed); const entrypointFiles = entrypoints.main.filter( fileName => !fileName.endsWith('.map') ); console.log('!!', entrypointFiles) return { files: manifestFiles, entrypoints: entrypointFiles, }; },}),可以看到配置项中多了 generate 这一属性,其中就有用到 filter方法。其中 entrypoints.main 调用了 filter方法,经过输出得知 entrypoints.main 的值是与 entry 配置对应的。 ...

May 30, 2020 · 1 min · jiezi

由React构造函数中bind引起的this指向理解React组件的方法为什么要用bind绑定this

React文档源码 class Toggle extends React.Component { constructor(props) { super(props); this.state = {isToggleOn: true}; // 为了在回调中使用 `this`,这个绑定是必不可少的 this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(state => ({ isToggleOn: !state.isToggleOn })); } render() { return ( <button onClick={this.handleClick}> {this.state.isToggleOn ? 'ON' : 'OFF'} </button> ); }}ReactDOM.render( <Toggle />, document.getElementById('root')); 为什么要用bind重新绑定?首先一点,React这是使用的ES6的 class ,它只是一种语法糖(只要它能实现的,ES5也能实现)。 而使用 class 创建的对象,在没有通过 new 关键字去实例化的之前,除 constructor() 外,它的内部方法this是无绑定状态的。 也就是说上面的代码,handleClick 方法如果不做绑定,那么这个方法的 this 会指向 undefined class Toggle extends React.Component { constructor(props) { super(props); this.state = {isToggleOn: true}; // 为了在回调中使用 `this`,这个绑定是必不可少的 // this.handleClick = this.handleClick.bind(this); } handleClick() { console.log(this) // 输出是 undefined } render() { return ( <button onClick={this.handleClick}> {this.state.isToggleOn ? 'ON' : 'OFF'} </button> ); }}ReactDOM.render( <Toggle />, document.getElementById('root')); ...

May 29, 2020 · 3 min · jiezi

2React入门state和ref

setSate 我们先看几个关于setState的demo,用于黑箱推测原理: 1) "打印0"——说明setState相当于发布订阅中的发布,只是把多个状态变更需求统一放入队列updateQueue; "打印2"——说明存在一个专门用于开启/关闭**批量**更新模式的控制器batchUpdate。 基本流程:开启控制器 —— 入队列(updateQueue和callbackQueue) —— 合并updateQueue中state的变化 —— 根据变化来更改state —— 执行callbackQueue —— 关闭控制器; 整个流程是同步的。 class Mine extends React.Component { constructor(props){ super(props); this.state = {number: 0}; } add = () => { this.setState({number: this.state.number+1}); this.setState({number: this.state.number+2}); console.log(this.state.number);//打印0 setTimeout(() => { console.log(this.state.number);//打印2 }) } render(){ return (<button onClick={this.add}></button>) }}2) 理解上面流程了,就能理解为什么定时器中的setState看起来并不是延迟执行。 因为定时器到期前,整个流程已经跑完了,控制器处于关闭状态,所以就立即更新state了; 即在setState源码中,若控制器开启,则需要入队列;若控制器关闭,则不入队列,立即执行;this.setState({number: this.state.number+1});this.setState({number: this.state.number+2});this.setState({number: this.state.number+3});setTimeout(()=>{ this.setState({number: this.state.number+4}); console.log(this.state);//打印7}, 1000)总之,setState有延迟/批量更新,是为了节省渲染DOM的开销,同时也保留了立即更新的功能 ref的几种用法 1) ref=字符串 不推荐,难以维护 ...

May 28, 2020 · 1 min · jiezi

React-hook-源码学习

useStateuseReduceruseEffect

May 28, 2020 · 1 min · jiezi

一个使用-render-props-而不是hooks的场景

自定义列功能 /* * @File: 自定义表格列 * @Author: lintao.wang * @Date: 2020-01-03 15:08:32 * @Last Modified by: lintao.wang * @Last Modified time: 2020-01-03 17:16:30 * @Usage * <CustomColumn columns={props.columns}> {(columns: ColumnProps<any>[]) => ( <Table className={`${styles.mTaskTable} ${className}`} expandRowByClick expandedRowKeys={expandedRowKeys} onExpandedRowsChange={onExpandedRowsChange} {...restProps} columns={columns} /> )} </CustomColumn>* */import React, { useState, useEffect } from 'react';import { Popover, Button, Checkbox } from 'antd';import { ColumnProps } from 'antd/lib/table';import _ from 'lodash';import styles from './index.less';const CustomColumn = (props: { columns: ColumnProps<any>[]; defaultColumns?: Array<ColumnProps<any> | string>; children: (columns) => React.ReactNode;}) => { const { columns = [] } = props; const [column, setColumn] = useState(columns); const onChange = value => { const { columns = [] } = props; const column = columns.filter( item => value.includes(item.dataIndex) || value.includes(item.title), ); setColumn(column); }; let { defaultColumns = [] } = props; useEffect(() => { defaultColumns = defaultColumns?.length ? defaultColumns : columns; const defaultValue = defaultColumns .map(item => (typeof item === 'string' ? item : item?.dataIndex || _.toString(item?.title))) .filter(item => column.some(cell => cell.dataIndex === item || cell.title === item)); onChange(defaultValue); }, []); const renderContent = () => ( <Checkbox.Group options={columns?.map((item: ColumnProps<any>, index) => ({ label: item.title || '', value: item.dataIndex || _.toString(item.title) || index, }))} value={column.map(item => item.dataIndex || _.toString(item.title))} onChange={onChange} /> ); return ( <div className={styles.cCustomColumn}> <Popover content={renderContent()} title="请选择要展示的列名" trigger="click" overlayClassName={styles.mCustomPopover} > <Button icon="setting">自定义列</Button> </Popover> {props.children(column)} </div> );};export default CustomColumn;

May 28, 2020 · 2 min · jiezi

React-hook-使用规则

React hook 使用规则只在最顶层使用 Hook(不要在循环,条件或嵌套函数中调用 Hook)只在 React 函数中调用 Hook(不要在普通的 JavaScript 函数中调用 Hook)理解 hook 使用规则使用 hook 开发可以通过使用react-hooks 插件强制执行这些规则,实时提示。

May 27, 2020 · 1 min · jiezi

初识-React-Hook

为什么用react-hook在组件之间复用状态逻辑很难可能你会使用render props或者高阶组件,但一方面,组件结构需要变动,使用render props和高阶组件也会使组件变得很难理解,另一方面会形成嵌套地狱,在用devtools排查问题时,很不方便。 复杂组件变得难以理解在此之前,我们可能要关注各种生命周期(虽然也写过一篇react生命周期的文章),componentDidMount,componentDidUpdate等等。举自己曾经在实际项目中的两个例子: componentDidMount里面发了n个请求,componentWillRecieveProps里面做了判断后,又发请求。componentDidMount添加了addEventListener,componentWillUnmount去removeEventListener。如果1和2再混在同一个组件里面想想,如果不是自己写的代码,去理解比较困难,其次,如果突然你去维护这样的代码,可能一不小心就把分散在其他地方的业务逻辑忘掉了,导致出现bug,就要背锅了。 难以理解的 class不止一次面试中问别人,以及被问过,react组件中constructor内绑定事件,箭头函数,bind绑定事件,都是怎么回事,就巴拉巴拉开始说,constructor只执行一次,箭头函数this是指向上下文的,bind每次都会重新生成函数什么什么的。再者,可能还要区分什么可以写成无状态组件,负责UI,什么组件要用Component,什么组件要用PureComponent。 异步执行,性能更好与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的 应用看起来响应更快。大多数情况下,effect 不需要同步地执 行。 使用多个 Effect 实现关注点分离使用 Hook解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题。 Hook是什么?就是以 use 开头的一系列函数,可以让我们在函数当中使用state其他的react特性。不得不感慨前端更新太快,做前端好难,不过还是要不断学习进步,知晓设计的初衷,知晓怎么用,再知晓其原理,干,就对了。 常用的官方hook APIuseState待补充 useEffect待补充 useEffectLayout待补充 useRef待补充 useCallback待补充 useMemo待补充 结语有人就说了,官方的 hook 那么少,写代码还要自己封装很多自定义的 hook 。不得不说,世界上大佬那么多,为什么人家那么聪明,还比我们努力。想了解更多 hook ,请看下篇:

May 27, 2020 · 1 min · jiezi

初识-React-hook-一

什么是 react hook ?它们不是共享状态的方法,而是共享有状态逻辑的方法举个简单例子如下自定义 useWindowSize ,可以像调用一个函数一样,调用一个hook,每个组件的内部状态相互独立 import useWindowSize from './useWindowSize';const Demo = () => { const {width, height} = useWindowSize(); return ( <div> <div>width: {width}</div> <div>height: {height}</div> </div> );};import { useEffect, useState } from "react";const useWindowSize = () => { const [state, setState] = useState<{ width: number; height: number }>({ width: window.innerWidth, height: window.innerHeight }); useEffect(() => { const handler = () => { setState({ width: window.innerWidth, height: window.innerHeight }); }; window.addEventListener('resize', handler); return () => { window.removeEventListener('resize', handler); }; }, []); return state;};export default useWindowSize;

May 27, 2020 · 1 min · jiezi

react0项目创建

创建项目quick-generator-web使用官方推荐的npx create-react-app QuickGeneratorWeb创建$ npx create-react-app QuickGeneratorWebCould not create a project called "QuickGeneratorWeb" because of npm naming restrictions: * name can no longer contain capital letters报错:npm不再支持大写命名,改用小写重新创建$ npx create-react-app quick-generator-web...A template was not provided. This is likely because you're using an outdated version of create-react-app.Please note that global installs of create-react-app are no longer supported.报错: 生成的模板项目缺失文件,原因是create-react-app工具需要非全局安装且是最新的,卸载create-react-app$ npm uninstall -g create-react-appremoved 63 packages in 2.251s$ rm -rf quick-generator-web/重新创建项目$ npx create-react-app quick-generator-webnpx: 98 安装成功,用时 77.74 秒Creating a new React app in D:\git\web\quick-generator-web.Installing packages. This might take a couple of minutes.Installing react, react-dom, and react-scripts with cra-template...yarn add v1.19.1[1/4] Resolving packages...[2/4] Fetching packages...info fsevents@1.2.12: The platform "win32" is incompatible with this module.info "fsevents@1.2.12" is an optional dependency and failed compatibility check. Excluding it from installation.info fsevents@2.1.2: The platform "win32" is incompatible with this module.info "fsevents@2.1.2" is an optional dependency and failed compatibility check. Excluding it from installation.[3/4] Linking dependencies...warning "react-scripts > @typescript-eslint/eslint-plugin > tsutils@3.17.1" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta".[4/4] Building fresh packages...success Saved lockfile.success Saved 13 new dependencies.info Direct dependencies├─ cra-template@1.0.3├─ react-dom@16.13.1├─ react-scripts@3.4.1└─ react@16.13.1info All dependencies├─ @babel/plugin-transform-flow-strip-types@7.9.0├─ @babel/plugin-transform-runtime@7.9.0├─ @babel/plugin-transform-typescript@7.9.6├─ @babel/preset-typescript@7.9.0├─ babel-preset-react-app@9.1.2├─ cra-template@1.0.3├─ eslint-config-react-app@5.2.1├─ react-dev-utils@10.2.1├─ react-dom@16.13.1├─ react-error-overlay@6.0.7├─ react-scripts@3.4.1├─ react@16.13.1└─ scheduler@0.19.1Done in 166.18s.Initialized a git repository.Installing template dependencies using yarnpkg...yarn add v1.19.1[1/4] Resolving packages...[2/4] Fetching packages...info fsevents@2.1.2: The platform "win32" is incompatible with this module.info "fsevents@2.1.2" is an optional dependency and failed compatibility check. Excluding it from installation.info fsevents@1.2.12: The platform "win32" is incompatible with this module.info "fsevents@1.2.12" is an optional dependency and failed compatibility check. Excluding it from installation.[3/4] Linking dependencies...warning "react-scripts > @typescript-eslint/eslint-plugin > tsutils@3.17.1" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta".warning " > @testing-library/user-event@7.2.1" has unmet peer dependency "@testing-library/dom@>=5".[4/4] Building fresh packages...success Saved lockfile.success Saved 20 new dependencies.info Direct dependencies├─ @testing-library/jest-dom@4.2.4├─ @testing-library/react@9.5.0├─ @testing-library/user-event@7.2.1├─ react-dom@16.13.1└─ react@16.13.1info All dependencies├─ @babel/runtime-corejs3@7.9.6├─ @sheerun/mutationobserver-shim@0.3.3├─ @testing-library/dom@6.16.0├─ @testing-library/jest-dom@4.2.4├─ @testing-library/react@9.5.0├─ @testing-library/user-event@7.2.1├─ @types/prop-types@15.7.3├─ @types/react-dom@16.9.8├─ @types/react@16.9.35├─ @types/testing-library__dom@7.0.2├─ @types/testing-library__react@9.1.3├─ css.escape@1.5.1├─ csstype@2.6.10├─ dom-accessibility-api@0.3.0├─ min-indent@1.0.0├─ react-dom@16.13.1├─ react@16.13.1├─ redent@3.0.0├─ strip-indent@3.0.0└─ wait-for-expect@3.0.2Done in 39.52s.Removing template package using yarnpkg...yarn remove v1.19.1[1/2] Removing module cra-template...[2/2] Regenerating lockfile and installing missing dependencies...info fsevents@2.1.2: The platform "win32" is incompatible with this module.info "fsevents@2.1.2" is an optional dependency and failed compatibility check. Excluding it from installation.info fsevents@1.2.12: The platform "win32" is incompatible with this module.info "fsevents@1.2.12" is an optional dependency and failed compatibility check. Excluding it from installation.warning " > @testing-library/user-event@7.2.1" has unmet peer dependency "@testing-library/dom@>=5".warning "react-scripts > @typescript-eslint/eslint-plugin > tsutils@3.17.1" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta".success Uninstalled packages.Done in 11.00s.Created git commit.Success! Created quick-generator-web at D:\git\web\quick-generator-webInside that directory, you can run several commands: yarn start Starts the development server. yarn build Bundles the app into static files for production. yarn test Starts the test runner. yarn eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back!We suggest that you begin by typing: cd quick-generator-web yarn startHappy hacking!创建成功,启动yarn start$ yarn start访问页面 http://localhost:3000/参考文档react中文网啥是npx生成模板项目文件不全解决

May 25, 2020 · 3 min · jiezi