React是一个简单的javascript UI库,用于构建高效、快速的用户界面。
它是一个轻量级库,因此很受欢迎。它遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效。
它使用虚拟DOM来有效地操作DOM。
它遵循从高阶组件到低阶组件的单向数据流。
React
特性有很多,如:
JSX语法
单向数据绑定
虚拟DOM
声明式编程
Component
通过上面的初步了解,可以感受到React
存在的优势:
高效灵活
声明式的设计,简单使用
组件式开发,提高代码复用率
单向响应的数据流会比双向绑定的更安全,速度更快
跟Vue
一致,React
通过引入Virtual DOM
的概念,极大地避免无效的Dom
操作,使我们的页面的构建效率提到了极大的提升
而diff
算法就是更高效地通过对比新旧Virtual DOM
来找出真正的Dom
变化之处
原理
react
中diff
算法主要遵循三个层级的策略:
tree层级
conponent 层级
element 层级
tree层级
DOM
节点跨层级的操作不做优化,只会对相同层级的节点进行比较
只有删除、创建操作,没有移动操作
conponent层级
如果是同一个类的组件,则会继续往下diff
运算,如果不是一个类的组件,那么直接删除这个组件下的所有子节点,创建新的
element层级
对于比较同一层级的节点们,每个节点在对应的层级用唯一的key
作为标识
提供了 3 种节点操作,分别为 INSERT_MARKUP
(插入)、MOVE_EXISTING
(移动)和 REMOVE_NODE
(删除)
生命周期(Life Cycle)
的概念应用很广泛,特别是在经济、环境、技术、社会等诸多领域经常出现,其基本涵义可以通俗地理解为“从摇篮到坟墓”(Cradle-to-Grave)
的整个过程
React
整个组件生命周期包括从创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程
这里主要讲述react16.4
之后的生命周期,可以分成三个阶段:
创建阶段
更新阶段
卸载阶段
创建阶段
constructor
getDerivedStateFromProps
render
componentDidMount
constructor
实例过程中自动调用的方法,在方法内部通过super
关键字获取来自父组件的props
在该方法中,通常的操作为初始化state
状态或者在this
上挂载方法
getDerivedStateFromProps
该方法是新增的生命周期方法,是一个静态的方法,因此不能访问到组件的实例
执行时机:组件创建和更新阶段,不论是props
变化还是state
变化,也会调用
在每次render
方法前调用,第一个参数为即将更新的props
,第二个参数为上一个状态的state
,可以比较props
和 state
来加一些限制条件,防止无用的state更新
该方法需要返回一个新的对象作为新的state
或者返回null
表示state
状态不需要更新
render
类组件必须实现的方法,用于渲染DOM
结构,可以访问组件state
与prop
属性
注意: 不要在 render
里面 setState
, 否则会触发死循环导致内存崩溃
componentDidMount
组件挂载到真实DOM
节点后执行,其在render
方法之后执行
此方法多用于执行一些数据获取,事件监听等操作
更新阶段
getDerivedStateFromProps
shouldComponentUpdate
render
getSnapshotBeforeUpdate
componentDidUpdate
shouldComponentUpdate
用于告知组件本身基于当前的props
和state
是否需要重新渲染组件,默认情况返回true
执行时机:到新的props或者state时都会调用,通过返回true或者false告知组件更新与否
一般情况,不建议在该周期方法中进行深层比较,会影响效率
同时也不能调用setState
,否则会导致无限循环调用更新
getSnapshotBeforeUpdate
该周期函数在render
后执行,执行之时DOM
元素还没有被更新
该方法返回的一个Snapshot
值,作为componentDidUpdate
第三个参数传入
componentDidUpdate
执行时机:组件更新结束后触发
在该方法中,可以根据前后的props
和state
的变化做相应的操作,如获取数据,修改DOM
样式等
卸载阶段
componentWillUnmount
此方法用于组件卸载前,清理一些注册是监听事件,或者取消订阅的网络请求等
一旦一个组件实例被卸载,其不会被再次挂载,而只可能是被重新创建
实际上它只是一层对真实DOM
的抽象,以JavaScript
对象 (VNode
节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上
创建虚拟DOM
就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM
对象的节点与真实DOM
的属性一一照应
虚拟DOM的实现原理
React虚拟DOM的实现原理,通过JS模拟网页文档节点,生成JS对象树(虚拟DOM),然后再进一步生成真实的DOM树,再绘制到屏幕。如果后面有内容发生改变,React会重新生成一棵全新的虚拟DOM树,再与前面的虚拟DOM树进行比对diff,把差异的部分打包成patch,再应用到真实DOM,然后渲染到屏幕浏览器。
Hook
是 React 16.8 的新增特性。它可以让你在不编写 class
的情况下使用 state
以及其他的 React
特性
至于为什么引入hook
难以重用和共享组件中的与状态相关的逻辑
逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的 local state 时,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面
类组件中的this增加学习成本,类组件在基于现有工具的优化上存在些许问题
由于业务变动,函数组件不得不改为类组件等等
Hooks
让我们的函数组件拥有了类组件的特性,例如组件内的状态、生命周期
通过对上面的初步认识,可以看到hooks
能够更容易解决状态相关的重用的问题:
每调用useHook一次都会生成一份独立的状态
通过自定义hook能够更好的封装我们的功能
编写hooks
为函数式编程,每个功能都包裹在函数中,整体风格更清爽,更优雅
hooks`的出现,使函数组件的功能得到了扩充,拥有了类组件相似的功能,在我们日常使用中,使用`hooks`能够解决大多数问题,并且还拥有代码复用机制,因此优先考虑`hooks
父子组件通信
父组件要把数据传递给子组件,就通过props
传过去。子组件可以通过this.props
方法来获得传递过来的数据,这种是最基本,很常见的,几乎每个写 react 的人都会用到吧。
然而 react 的数据流向是单向的,如果子组件要传递数据给父组件就要一些技巧,不过这种情况也很常见。
就是首先父组件传递一个方法名给子组件,这个方法必须先在父组件定义好,比如叫父组件叫ElementGroup
,子组件叫Element
,这时候可以在渲染子组件的时候,传递一个方法,叫deleteElement
。
兄弟间
兄弟间的组件,我们有一个共同的特点,就是父级组件,所以它们的数据传递主要是靠父级组件作为中间桥梁。
受控组件,简单来讲,就是受我们控制的组件,组件的状态全程响应外部数据
我们在输入框输入内容的时候,会发现输入的内容并无法显示出来,也就是input
标签是一个可读的状态
如果想要解除被控制,可以为input
标签设置onChange
事件,输入的时候触发事件函数,在函数内部实现state
的更新,从而导致input
框的内容页发现改变
受控组件我们一般需要初始状态和一个状态更新事件函数
非受控组件
非受控组件,简单来讲,就是不受我们控制的组件
一般情况是在初始化的时候接受外部数据,然后自己在内部存储其自身状态
大部分时候推荐使用受控组件来实现表单,因为在受控组件中,表单数据由React
组件负责处理
如果选择非受控组件的话,控制能力较弱,表单数据就由DOM
本身处理,但更加方便快捷,代码量少
针对两者的区别
作用:连接React组件与Redux Store
在使用介绍connect
之前, 先简单介绍一下什么是高阶组件
高阶组件就是一个函数,传给它一个组件,它返回一个新的组件。
connect
有四个参数, 但是后两个参数用到的很少
connect 的第一个参数是 mapStateToProps
这个函数允许我们将 store 中的数据作为 props 绑定到组件上,实现主要原理, 就是将需要绑定的props
作为一个函数传过来, 在connect
中传给mapStateToProps
一个真实的store
的数据
connect 的第二个参数是 mapDispatchToProps
由于更改数据必须要触发action
, 因此在这里的主要功能是将 action
作为props
绑定到 组件上, 主要是将action
和props
一起传到组件里.
Jsx是语法糖,实质是js函数,需要babel来解析,核心函数是React.createElement(tag,{attrbuties},children),参数tag是标签名可以是html标签和组件名,attrbuties参数是标签的属性,children参数是tag的子元素。用来创建一个vnode,最后渲染到页面上
中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的
类似于插件,可以在不影响原本功能、并且不改动原本代码的基础上,对其功能进行增强。在Redux中,中间件主要用于增强dispatch函数。
Redux
中,中间件就是放在就是在dispatch
过程,在分发action
进行拦截处理,
常用的中间件
有很多优秀的redux
中间件,如:
redux-thunk:用于异步操作
redux-logger:用于日志记录
上述的中间件都需要通过applyMiddlewares
进行注册,作用是将所有的中间件组成一个数组,依次执行
然后作为第二个参数传入到createStore
中
实现原理
中间件本身是一个函数,该函数接收一个store参数,表示创建的仓库,该仓库并非一个完整的仓库对象,仅包含getState,dispatch。该函数运行的时间,是在仓库创建之后运行。
由于创建仓库后需要自动运行设置的中间件函数,因此,需要在创建仓库时,告诉仓库有哪些中间件
需要调用applyMiddleware函数,将函数的返回结果作为createStore的第二或第三个参数。
中间件函数必须返回一个dispatch创建函数
这三个规范都是为Js模块化加载而生的,使模块能够按需加载,使系统同庞杂的代码得到组织和管理。模块化的管理代码使多人开发得到了更好的合作
CommonJS
常用于:服务器端
,node
,webpack
特点:同步/运行时加载
,磁盘读取速度快
语法:
// 1. 导出:通过module.exports或exports来暴露模块 module.exports = { attr1, attr2 } exports.attr = xx
注意 不可以exports = xxx
,这样写会无效,因为更改了exports的地址 而 exports 是 module.exports 的引用指向的是同一个内存,模块最后导出的是module.exports
// 2. 引用:require('x') const xx = require('xx') // 整体重命名 const { attr } = require('xx') // 解构某一个导出
常用于:不常用,CommonJs的浏览器端实现
特点:
异步加载
:因为面向浏览器端,为了不影响渲染肯定是异步加载
依赖前置
:所有的依赖必须写在最初的依赖数组中,速度快,但是会浪费资源,预先加载了所有依赖不管你是否用到
语法:
// 1. 导出:通过define来定义模块 // 如果该模块还依赖其他模块,则将模块的路径填入第一个参数的数组中 define(['x'], function(x){ function foo(){ return x.fn() + 1 } return { foo: foo }; }); // 2. 引用 require(['a'], function (a){ a.foo() });
常用于:不常用,根据CommonJs和AMD实现,优化了加载方式
特点:
异步加载
按需加载/依赖就近
:用到了再引用依赖,方便了开发,缺点是速度和性能较差
语法:
// 1. 导出:通过define来定义模块 // 如果该模块还依赖其他模块,在用到的地方引用即可 define(function(){ function foo(){ var x = require('x') return x.fn() + 1 } return { foo: foo }; }); // 2. 引用 var x = require('a'); a.foo();
版本号简介
软件版本号有四部分组成:
第一部分为主版本号,变化了表示有了一个不兼容上个版本的大更改
。
第二部分为次版本号,变化了表示增加了新功能,并且可以向后兼容
。
第三部分为修订版本号,变化了表示有bug修复,并且可以向后兼容
。
第四部分为日期版本号加希腊字母版本号,希腊字母版本号共有五种,分别为base、alpha、beta 、RC 、 release
react
通过将组件编写的JSX
映射到屏幕,以及组件中的状态发生了变化之后 React
会将这些「变化」更新到屏幕上
在react
中,节点大致可以分成四个类别:
原生标签节点
文本节点
函数组件
类组件
createElement
会根据传入的节点信息进行一个判断:
如果是原生标签节点, type 是字符串,如div、span
如果是文本节点, type就没有,这里是 TEXT
如果是函数组件,type 是函数名
如果是类组件,type 是类名
渲染流程:
使用React.createElement或JSX编写React组件,实际上所有的 JSX 代码最后都会转换成React.createElement(...) ,Babel帮助我们完成了这个转换的过程。
createElement函数对key和ref等特殊的props进行处理,并获取defaultProps对默认props进行赋值,并且对传入的孩子节点进行处理,最终构造成一个虚拟DOM对象
ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM
Redux 是 JavaScript 状态容器,提供可预测化的状态管理
redux 是一个状态管理工具,通常与 redux 一起使用,但是 redux 可以在任何 js 代码中使用,并不是 react 的专属工具
Redux Toolkit 提供了基于 redux 的封装,简化了 redux 创建流程及样板代码量,让我们能更加关注状态管理,同时 Redux Toolkit 附带了一些有用的工具库,例如Immer
,Redux-Thunk
和Reselect
等,并且无需在手动引入 redux-devtools-extension
理解react的render函数,要从这三点来认识。原理、执行时机、总结。
在类组件和函数组件中,render函数的形式是不同的。
在类组件中render函数指的就是render
方法;而在函数组件中,指的就是整个函数组件。
render
的执行时机主要分成了两部分:
类组件调用 setState 修改状态:
函数组件通过useState hook
修改状态:
ender函数里面可以编写JSX,转化成createElement这种形式,用于生成虚拟DOM,最终转化成真实DOM
在React 中,类组件只要执行了 setState 方法,就一定会触发 render 函数执行,函数组件使用useState更改状态不一定导致重新render
组件的props 改变了,不一定触发 render 函数的执行,但是如果 props 的值来自于父组件或者祖先组件的 state
在这种情况下,父组件或者祖先组件的 state 发生了改变,就会导致子组件的重新渲染
所以,一旦执行了setState就会执行render方法,useState 会判断当前值有无发生改变确定是否执行render方法,一旦父组件发生渲染,子组件也会渲染
React
凭借virtual DOM
和diff
算法拥有高效的性能,但是某些情况下,性能明显可以进一步提高
常见性能优化常见的手段有如下:
避免使用内联函数
使用 React Fragments 避免额外标记
使用 Immutable
懒加载组件
事件绑定方式
服务端渲染
防抖当事件被触发N秒之后再执行回调,如果在N秒内被触发,则重新计时。
节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
Koa是一个精简的node框架,被认为是第二代Node框架,其最大的特点就是独特的中间件流程控制,是一个典型的洋葱模型
,它的核心工作包括下面两个方面:
将node原生的req和res封装成为一个context对象。
基于async/await的中间件洋葱模型机制。
什么是洋葱模型
Koa的洋葱模型是以next()函数为分割点,先由外到内执行Request的逻辑,然后再由内到外执行Response的逻辑,这里的request的逻辑,我们可以理解为是next之前的内容,response的逻辑是next函数之后的内容,也可以说每一个中间件都有两次处理时机。洋葱模型的核心原理主要是借助compose方法。
为什么需要洋葱模型
因为很多时候,在一个app里面有很多中间件,有些中间件需要依赖其他中间件的结果
,用葱模型可以保证执行顺序,如果没有洋葱模型,执行顺序可能出乎我们的预期。
随着前端的项目逐渐扩大,必然会带来的一个问题就是性能
尤其在大型复杂的项目中,前端业务可能因为一个小小的数据依赖,导致整个页面卡顿甚至奔溃
一般项目在完成后,会通过webpack
进行打包,利用webpack
对前端项目性能优化是一个十分重要的环节
通过webpack
优化前端的手段有:
JS代码压缩
CSS代码压缩
Html文件代码压缩
文件大小压缩
图片压缩
Tree Shaking,Tree Shaking
是一个术语,在计算机中表示消除死代码
代码分离
内联 chunk
关于webpack
对前端性能的优化,可以通过文件体积大小入手,其次还可通过分包的形式、减少http请求次数等方式,实现对前端性能的优化
什么是WebSocket
WebSocket是一种基于TCP的全双工通信协议,在应用层。
为什么需要WebSocket
1、传统上的HTTP协议它是无状态的,服务器不能够识别是哪个客户端发送的请求,不能够保存状态。 2、WebSocket弥补了这一问题,在客户端向服务端发送请求之后,服务器处理请求并返回到客户端,使用WebSocket可以使得服务器主动向浏览器推送消息
WebSocket协议的原理 与服务器进行三次握手,建立TCP连接 向服务器发送HTTP请求,请求中包含WebSocket的版本信息:包括upgrade、connection等等。 服务器处理请求并返回客户端,此时可以进行WebSocket请求了 服务端也可以主动向客户端推送消息了。
WebSocket的优缺点
优点:建立WebSocket连接之后,客户端与服务端交流更方便
客户端只需要向服务端发送一次请求,服务端主动向客户端发送消息
缺点:在服务端的状态不会频繁变化的时候,就不需要使用WebSocket连接了,浪费性能