这一篇文章是博主最近在学习react的知识 会把自己学到的一些原理性的知识分享出来 如果感兴趣 不妨点个关注 大家一起学习
getDerivedStateFromProps
getDerivedStateFromProps():会在调用 render 方法之前调用,即在渲染 DOM 元素之前会调用,并且在初始挂载及后续更新时都会被调用。
state 的值在任何时候都取决于 props。
getDerivedStateFromProps 的存在只有一个目的:让组件在 props 变化时更新 state。
该方法返回一个对象用于更新 state,如果返回 null 则不更新任何内容。
getDerivedStateFromProps 容易编写反模式代码,使受控组件与非受控组件区分模糊。
componentWillMount
在 React中已被标记弃用,不推荐使用,主要原因是新的异步渲染架构会导致它被多次调用。 所以网络请求及事件绑定代码应移至 componentDidMount 中。
componentWillReceiveProps
同样被标记弃用,被 getDerivedStateFromProps 所取代,主要原因是性能问题。
shouldComponentUpdate
shouldComponentUpdate() 方法会返回一个布尔值,指定 React 是否应该继续渲染,默认值是 true, 即 state 每次发生变化组件都会重新渲染。
shouldComponentUpdate() 的返回值用于判断 React 组件的输出是否受当前 state 或 props 更改的影响,当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
通过返回true 或者 false 来确定是否需要触发新的渲染。主要用于性能优化。 componentWillUpdate
componentWillUpdate
同样是由于新的异步渲染机制,而被标记废弃,不推荐使用,原先的逻辑可结合getSnapshotBeforeUpdate 与componentDidUpdate 改造使用。
使用生命周期注意事项
如果在 componentWillUnmount函数中忘记解除事件绑定,取消定时器等清理操作,容易引发 bug
如果没有添加错误边界处理,当渲染发生异常时,用户将会看到一个无法操作的白屏,所以一定要添加
componentWillUnmount()在卸载和销毁组件之前立即调用。在此方法中执行任何必要的清理,例如使计时器无效,取消网络请求或清除在其中创建的任何订阅componentDidMount()。不应该调用setState(),componentWillUnmount()因为组件永远不会被重新呈现。卸载组件实例后,将永远不会再次安装它。
众所周知 在我们开发的过程中 渲染真实dom的开销是很大的,有时候我们修改了某个数据 直接渲染到真实的dom上会引起整个dom树的重绘和重排。我们希望更新我们修改的那一小块的dom,而不是整个dom,diff算法就帮我们实现了这个需求
diff算法的本质就是:找出两个对象之间的差异,目的是尽可能的做到节点复用。
react的diff算法
react中diff算法主要遵循三个层级的策略:
tree层级
DOM节点跨层级的操作不做优化,只会对相同层级的节点进行比较 conponent 层级
conponent 层级
如果是同一个类的组件,则会继续往下diff运算,如果不是一个类的组件,那么直接删除这个组件下的所有子节点,创建新的
element 层级
对于比较同一层级的节点们,每个节点在对应的层级用唯一的key作为标识
提供了 3 种节点操作,分别为 INSERT_MARKUP(插入)、MOVE_EXISTING (移动)和 REMOVE_NODE (删除)
Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。【永远只比较同层节点,不会跨层级比较节点。】
拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
对于同一层级的一组子节点,它们可以通过唯一 key 进行区分。
当调用 setState 时,React会做的第一件事情是将传递给 setState 的对象合并到组件的当前状态
这将启动一个称为和解(reconciliation)的过程。和解(reconciliation)的最终目标是以最有效的方式,根据这个新的状态来更新UI。 为此,React将构建一个新的 React 元素树(您可以将其视为 UI 的对象表示)
一旦有了这个树,为了弄清 UI 如何响应新的状态而改变,React 会将这个新树与上一个元素树相比较( diff )
通过这样做, React 将会知道发生的确切变化,并且通过了解发生什么变化,只需在绝对必要的情况下进行更新即可最小化 UI 的占用空间
原理
将应用的状态统一放到 state 中,由 store 来管理 state 。
reducer 的作用是 返回一个新的 state 去更新 store 中对用的 state。
按 redux 的原则,U层每一次状态的改变都应通过 action 去触发,ation 传入对应的 reducer 中,reducer 返回一个新的 state更
新 store 中存放的 state ,这样就完成了一次状态的更新4.subscribe 是为 store 订阅监听函数,这些订阅后的监听函数是在每一次 dipatch 发起后依次执行
可以添加中间件对提交的 dispatch 进行重写
核心API
createstore 创建仓库,接受 reducer 作为参数
bindActionCreator 绑定 store.dispatch 和 action 的关系
combineReducers 合并多个 reducers
applyMiddleware 洋葱模型的中间件,于 dispatch 和 action 之间,重写 dispatch
compose 整合多个中间件
原理
React并不是将click事件绑在该div的真实DOM上,而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装并交由真正的处理函数运行。
合成事件总结:
合成事件的监听器是统一注册在document上的,且仅有冒泡阶段。所以原生事件的监听器响应总是比合成事件的监听器早
阻止原生事件的冒泡后,会阻止合成事件的监听器执行
props
的方式,向子组件进行通讯。props
+回调的方式,父组件向子组件传递props
进行通讯,此props
为作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到⽗组件的作⽤域中。Context
设计⽬的是为了共享那些对于⼀个context
提供了组件之间通讯的一种方式,可以共享数据,其他数据都能读取对应的数据Context
通信再适合不过。event
模块进⾏通信。Redux
或者Mobx
等全局状态管理⼯具进⾏通信,这种⼯具会维护⼀个全局状态中⼼Store
,并根据不同的事件产⽣新的状态。目的是为了防止 XSS 攻击
。因为 Synbol
无法被序列化,所以 React
可以通过有没有 $$typeof
属性来断出当前的 element
对象是从数据库来的还是自己生成的。如果没有 $$typeof
这个属性,react
会拒绝处理该元素。
connect
是一个高阶函数,它真正连接 Redux
和 React
,包在我们的容器组件的外一层,接收上面 Provider
提供的 store
里面的 state
和 dispatch
,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件。
首先传入mapStateToProps
、mapDispatchToProps
,然后返回一个生产Component
的函数(wrapWithConnect
),然后再将真正的Component
作为参数传入wrapWithConnect
,这样就生产出一个经过包裹的Connect
组件,该组件具有:
(1)通过props.store
获取祖先Component
的store
(2)props
包括stateProps
、dispatchProps
、parentProps
,合并在一起得到nextState
,作为props
传给真正的Component
(3)componentDidMount
时,添加事件this.store.subscribe(this.handleChange)
,实现页面交互
(4)shouldComponentUpdate
时判断是否有避免进行渲染,提升页面性能,并得到nextState
(5)componentWillUnmount
时移除注册的事件this.handleChange
首先connect之所以会成功,是因为Provider组件:
JavaScript
引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行时,另一个线程只能挂起等待;如果 JavaScript
线程长时间地占用了主线程,那么渲染层面的更新就不得不长时间地等待,界面长时间不更新,会导致页面响应度变差,用户可能会感觉到卡顿
React Fiber
是对 React
做出的一个重大改变与优化,是对 React
核心算法的一次重新实现
requestIdleCallback api
,浏览器空闲的时候执行dom diff
树变成了链表,一个dom
对应两个fiber
(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行中间件是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的
Redux
中,中间件就是放在就是在dispatch
过程,在分发action
进行拦截处理
本质上一个函数,对store.dispatch
方法进行了改造,在发出 Action
和执行 Reducer
这两步之间,添加了其他功能
redux-thunk
:用于异步操作
redux-logger
:用于日志记录
所有中间件被放进了一个数组chain
,然后嵌套执行,最后执行store.dispatch
。可以看到,中间件内部(middlewareAPI
)可以拿到getState
和dispatch
这两个方法内部会将dispatch
进行一个判断,然后执行对应操作
1.使用纯组件
2.使用React.memo进行组件记忆(React.memo是一个高阶组件),对于相同的输入 不重复执行
3.如果是类组件,使用shouldComponentUpdate(重新渲染组件之前触发的其中的一个生命周期事件)生命周期事件可以利用此事件来决定何时需要重新渲染组件
4.路由懒加载
5.使用React Fragments 避免额外标记;
6.不要使用内联函数定义(如果我们使用内联函数 则每次调用"render"函数时都会创建一个新的函数实例);
7.避免在willxxx系列的生命周期中进行异步请求 操作dom等
8. 如果是使用类组件 事件函数constructor
中绑定bind改变this指向;
9. 避免使用内联样式属性;
10.不要再render方法中导出数据;
11.不要再render方法中导出数据
12.列表渲染的时候加key;
13.在函数组件中使用useCallback和useMemo来进行组件优化 依赖没有变化的话 不重复执行
14.类组件中使用immutable对象
任务在主线程不断进栈出栈的一个循环过程。任务会在将要执行时进入主线程,在执行完毕后会退出主线程。
JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,
但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环
在JavaScript中,所有的任务都可以分为
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等
异步任务还可以细分为微任务与宏任务
微任务一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
1、JSONP跨域
2、跨域资源共享(CORS)
3、nginx代理跨域
4、nodejs中间件代理跨域
5、document.domain+ iframe跨域
6、location.hash + iframe跨域
7、window.name + iframe跨域
8、postMessage跨域
9、WebSocket协议跨域
Array.length:
返回或设置一个数组中的元素个数Array.from() :
对伪数组或可迭代对象(包括arguments
,Array
,Map
,Set
,String
…)转换成数组对象Array.isArray():
用于确定传递的值是否是一个 Array
concat():
方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。every(callback):
方法测试数组的所有元素是否都通过了指定函数的测试filter():
创建一个新数组, 其包含通过所提供函数实现的测试的所有元素find():
返回数组中满足提供的测试函数的第一个元素的值forEach():
方法对数组的每个元素执行一次提供的函数includes():
用来判断一个数组是否包含一个指定的值,如果是,酌情返回 true
或 false
indexOf():
返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1join():
将数组(或一个类数组对象)的所有元素连接到一个字符串中lastIndexOf():
返回指定元素(也即有效的 JavaScript
值或变量)在数组中的最后一个的索引,如果 不存在则返回 -1。从数组的后面向前查找map():
创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果pop():
从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度push():
将一个或多个元素添加到数组的末尾reduce():
累加器和数组中的每个元素(从左到右)应用一个函数shift():
从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度slice():
返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象some():
测试数组中的某些元素是否通过由提供的函数实现的测试。sort():
当的位置对数组的元素进行排序,并返回数组。splice():
通过删除现有元素和/或添加新元素来更改一个数组的内容toString():
返回一个字符串,表示指定的数组及其元素unshift():
将一个或多个元素添加到数组的开头,并返回新数组的长度toLocaleString():
返回一个字符串表示数组中的元素。数组中的元素将使用各自的 toLocaleString
方法转成字符串,这些字符串将使用一个特定语言环境的字符串(例如一个逗号 “,”)隔开在类组件中render
函数指的就是render
方法;而在函数组件中,指的就是整个函数组件
render
函数中的jsx
语句会被编译成我们熟悉的js代码,在render
过程中,react
将新调用的render
函数返回的树与旧版本的树进行比较,这一步是决定如何更新 DOM 的必要步骤,然后进行 diff
比较,更新dom树
类组件调用 setState
修改状态
函数组件通过useState hook
修改状态
一旦执行了setState
就会执行render
方法,useState
会判断当前值有无发生改变确定是否执行render
方法,一旦父组件发生渲染,子组件也会渲染
mixin
是一种类,在vue
中就是js
文件,主要的作用是作为功能模块引用。因为在项目中,可能不同组件会有相同的功能,比如控制元素的显示和隐藏,如果他们的变量和规则也完全相同的话,就可以把这个功能单独提取出来,放在mixin.js
中,再引入,就可以实现一样的功能了。引入的方法也分为全局混入和局部混入,局部混入就是在每个组件中引入,全局混入就是在main.js中通过Vue.mixin()
引入。
for...in
循环:只能获得对象的键名,不能获得键值
for…in
循环有几个缺点
①数组的键名是数字,但是for…in
循环是以字符串作为键名“0”、“1”、“2”等等。
②for…in
循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
③某些情况下,for…in
循环会以任意顺序遍历键名。
for…in
循环主要是为遍历对象而设计的,不适用于遍历数组。
for...of
循环:允许遍历获得键值
for…of
循环
①有着同for…in
一样的简洁语法,但是没有for…in
那些缺点。
②不同于forEach
方法,它可以与break
、continue
和return
配合使用。
③提供了遍历所有数据结构的统一操作接口
1. typeof
判断
typeof
返回的类型都是字符串形式
2. Constructor
实例constructor
属性指向构造函数本身
constructor
判断方法跟instanceof
相似,但是constructor
检测Object
与instanceof
不一样,constructor
还可以处理基本数据类型的检测,不仅仅是对象类型
3. Instanceof
instanceof
可以判类型是否是实例的构造函数
instanceof
后面一定要是对象类型,并且大小写不能错,该方法适合一些条件选择或分支。
4. Object.prototype.toString.call()
判断类型的原型对象是否在某个对象的原型链上
5. 通过object
原型上的方法判断
比如array.isArray()
来判断是不是一个数组
6. ===(严格运算符)
通常出现在我们的条件判断中,用来判断数据类型的话就会非常的有局限性,比如判断一个变量是否为空,变量是否为数据等
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
该方法接受三个参数
obj
:要定义属性的对象,prop
:要定义或修改的属性的名称或 Symbol
,descriptor
:要定义或修改的属性描述符descriptor
所表示的属性描述符有两种形式:数据描述符和存取描述符。getter
函数和 setter
函数所描述的属性。configurable
: 是否可以删除目标属性或是否可以再次修改属性的特性(writable
, configurable
, enumerable
)。设置为true
可以被删除或可以重新设置特性;设置为false
,不能被可以被删除或不可以重新设置特性。默认为false
。enumerable
: 当且仅当该属性的 enumerable
键值为 true
时,该属性才会出现在对象的枚举属性中。默认为 false
。WebSocket
是HTML5
下一种新的协议(websocket
协议本质上是一个基于tcp
的协议)
它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
websocket
约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp
的连接,从而方便它们之间的通信
在websocket
出现之前,web
交互一般是基于http协议
的短连接或者长连接
websocket
是一种全新的协议,不属于http无状态协议,协议名为"ws"
useEffect相当于 componentDidMount(组件挂载),componentDidUpdate(组件更新) 和 componentWillUnmount(组件将要销毁) 这三个生命周期函数的组合。
useEffect有两个参数,第一个参数是一个回调函数,第二个参数是一个数组,这个数组中的元素都是依赖,每当依赖发生改变,就会触发第一个函数的执行。
1.第二个参数存放变量,当数组存放变量发生改变时,第一个参数,逻辑处理函数将会被执行
2.第二个参数如果不传,会导致逻辑处理函数无线循环调用
3.第二个参数如果传递为一个空数组,那么逻辑处理函数就只能执行一次,相当于componentDidMount(组件挂载)
4.如果第二个参数不是空数组,那么在数组中的依赖变化的时候就会触发逻辑函数的调用,就像componentDidMount和componentDidUpdate组合的生命周期函数一样
5.useEffect在组件被销毁去除之前也会调用逻辑执行函数,会消除副作用,类似componentWillUnmount生命周期函数
两者的区别如下:
虚拟 DOM 不会进行排版与重绘操作,而真实 DOM 会频繁重排与重绘
虚拟 DOM 的总损耗是“虚拟 DOM 增删改+真实 DOM 差异增删改+排版与重绘”,真实 DOM 的总损耗是“真实 DOM 完全增删改+排版与重绘”
易用
缺点:
效率低,解析速度慢,内存占用量过高
性能差:频繁操作真实 DOM,易于导致重绘与回流
简单方便:如果使用手动操作真实 DOM 来完成页面,繁琐又容易出错,在大规模应用下维护起来也很困难
性能方面:使用 Virtual DOM,能够有效避免真实 DOM 数频繁更新,减少多次引起重绘与回流,提高性能
跨平台:React 借助虚拟 DOM,带来了跨平台的能力,一套代码多端运行
缺点:
在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化
首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,速度比正常稍慢
componentWillUnmount (): 在此处完成组件的卸载和数据的销毁。
在React中,setState和replaceState都是用于更新组件状态的方法,它们的区别在于更新状态的方式和效果。
setState的作用是将新的状态合并到原有的状态中,仅更新需要改变的状态属性,同时保留原有的状态。setState方法接受一个对象或函数作为参数,对象中包含需要更新的状态属性和对应的值,函数的返回值也是一个状态对象。setState方法会在更新状态后重新渲染组件。
replaceState的作用是用新的状态替换原有的状态,新的状态会完全覆盖原有的状态。replaceState方法接受一个对象作为参数,对象中包含所有需要更新的状态属性和对应的值。replaceState方法会在更新状态后重新渲染组件。
因此,setState和replaceState的区别可以总结为:
setState是将新状态合并到原有状态中,而replaceState是用新状态替换原有状态。
setState会保留原有状态,只更新需要改变的状态属性,而replaceState会完全覆盖原有状态。
setState接受一个对象或函数作为参数,而replaceState只接受一个对象作为参数。
在React中,建议使用setState方法来更新组件状态。因为它不会完全替换原有状态,可以避免一些状态丢失的问题。同时,使用函数作为参数可以避免状态更新时的竞态问题。replaceState已经被标记为过时的方法,不建议使用。
React 中的 onClick 事件绑定是一种用于处理用户交互的常见方法。它的工作原理如下:
首先,在 React 组件中,开发人员定义一个 onClick 事件处理函数,该函数将在用户单击元素时被调用。
然后,使用 JSX 语法将该函数与 DOM 元素绑定。例如,可以在一个按钮上添加 onClick 属性并将其设置为处理函数的名称。
当用户单击该按钮时,浏览器会触发一个 click 事件。
React 将该事件传递给 onClick 处理函数,并调用该函数。
处理函数可以执行一些操作,例如更新组件的状态或调用其他函数。
总的来说,onClick 事件绑定的工作原理是通过将事件处理函数绑定到 DOM 元素上来实现的。当用户与该元素交互时,浏览器会触发事件并将其传递给 React,最终调用处理函数并执行相关操作。
什么是垂直外边距合并
外边距合并指的是,当两个垂直外边距相遇时,它们将形成一个外边距。
合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者。
实际工作中,垂直外边距合并问题常见于第一个子元素的margin-top会顶开父元素与父元素相邻元素的间距,而且只在标准浏览器下(FirfFox、Chrome、Opera、Sarfi)产生问题,IE下反而表现良好。
合并后的几种情况
相邻块元素垂直外边距的合并
当上下相邻的两个块元素相遇时,如果上面的元素有下外边距margin-bottom,下面的元素有上外边距margin-top,则他们之间的垂直间距不是margin-bottom与margin-top之和,而是两者中的较大者。
这种现象被称为相邻块元素垂直外边距的合并(也称外边距塌陷)。
解决方案:
尽量只给一个盒子添加margin值。
嵌套块元素垂直外边距的合并
对于两个嵌套关系的块元素,如果父元素没有上内边距及边框,则父元素的上外边距会与子元素的上外边距发生合并,合并后的外边距为两者中的较大者,即使父元素的上外边距为0,也会发生合并。
解决方法:
父盒子可以加个边框
用overflow(加了这句话在浏览器中可以看到也是自动加了1px的边框)。
可以为父元素定义上内边距。
什么是强缓存
服务器通过设置http中hdader的Expires和cache-control字段告诉浏览器换存的有效期。这种方法会有一个固定时间,所带来的问题是如果服务器数据进行了更新,但是还没有到强缓存的过期时间,则数据无法更新
3.1Expires
Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。
Expires的值是GMT格式的绝对时间,在设置的时间前浏览器会直接使用本地缓存。
3.2 cache-control
cache-control有12个值,其中的max-age值定义缓存的有效期,单位是秒,例如:cache-control:max-age=700,它表示缓存有效期为700秒,以消息的生成日期为基准,也就是header中的Date字段。
cache-control与Expires的区别在于cache-control的值是相对时间,而Expires是绝对时间,如果我们人为的修改了本地的时间,那么此时本地的时间与服务器上的时间不一致,就会导致缓存不能正确的被使用;而如果用相对时间,不管怎么改变本地的时间,缓存的有效期都不会改变。
什么是协商缓存
简单的说,协商缓存就是通过服务器来判断缓存是否可用。
4.1Last-Modified:
表示这个响应资源的最后修改时间,web服务器在响应请求时,告诉浏览器资源的最后修改时间。
If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示请求时间。web服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache。
4.1 Etag/If-None-Match
Etag/If-None-Match也要配合Cache-Control使用。
Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器觉得)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。
If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match (Etag的值)。web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200或304。
useEffect的依赖为引用类型的时候,可能会导致监听不出发,原因就是监听的统一个地址的时候,对象本身地址没变,所以监听的结果就是认为数据并没有改变从而不直径调用 解决方案
1.如果数据是对象的话,可以监听对象的里面的值,值是基本类型,如果值改变了,那么可以监听执行
2.在去修改对象和数据的时候,使用参拷贝或者浅拷贝,这样地址发生改变可以监听执行
3.可以转成字符串,通过JSON.stringify() ,监听字符串这样的,这样改变也会执行
react-redux react官方推出的redux绑定库,react-redux将所有组件分为两大类:UI组件和容器组件,其中所有容器组件包裹着UI组件,构成父子关系。容器组件负责和redux交互,里面使用redux API函数,UI组件负责页面渲染,不使用任何redux API。容器组件会给UI组件传递redux中保存对的状态和操作状态的方法
@reduxjs/toolkit Redux 官方强烈推荐,开箱即用的一个高效的 Redux 开发工具集。它旨在成为标准的 Redux 逻辑开发模式,使用 Redux Toolkit 都可以优化你的代码,使其更可维护
react.createPortal 是用来制作弹窗组件,它 在Modal 组件位置进行 fixed 定位,可以任意的挂载到某个dom元素上,使用后的管理更方便,但是注意需要预留html的挂载元素
createPortal应用场景:body外层遮罩 弹出框遮罩
connect()
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);
上面代码中,TodoList是 UI 组件,VisibleTodoList就是由 React-Redux 通过connect方法自动生成的容器组件。
但是,因为没有定义业务逻辑,上面这个容器组件毫无意义,只是 UI 组件的一个单纯的包装层。为了定义业务逻辑,需要给出下面两方面的信息。
输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数
输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去。
因此,connect方法的完整 API 如下。
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
上面代码中,connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
Provider组件
connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。
一种解决方法是将state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。
React-Redux 提供Provider组件,可以让容器组件拿到state。
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
上面代码中,Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。
它的原理是React组件的context属性,请看源码。
class Provider extends Component {
getChildContext() {
return {
store: this.props.store
};
}
render() {
return this.props.children;
}
}
Provider.childContextTypes = {
store: React.PropTypes.object
}
上面代码中,store放在了上下文对象context上面。然后,子组件就可以从context拿到store,代码大致如下。
class VisibleTodoList extends Component {
componentDidMount() {
const { store } = this.context;
this.unsubscribe = store.subscribe(() =>
this.forceUpdate()
);
}
render() {
const props = this.props;
const { store } = this.context;
const state = store.getState();
// ...
}
}
VisibleTodoList.contextTypes = {
store: React.PropTypes.object
}
React-Redux自动生成的容器组件的代码,就类似上面这样,从而拿到store。
常见的loader如下:
style-loader: 将css添加到DOM的内联样式标签style里
css-loader :允许将css文件通过require的方式引入,并返回css代码
less-loader: 处理less
sass-loader: 处理sass
postcss-loader: 用postcss来处理CSS
autoprefixer-loader: 处理CSS3属性前缀,已被弃用,建议直接使用postcss
file-loader: 分发文件到output目录并返回相对路径
url-loader: 和file-loader类似,但是当文件小于设定的limit时可以返回一个Data Url
html-minify-loader: 压缩HTML
babel-loader :用babel来转换ES6文件到ES
1、压缩代码
删除多余代码,注释,简化代码的写法等等方式。可以利用webpack的uglifyJsPlugin和ParallelUglifyPlugin来压缩js文件,利用cssnano来压缩css资源。
2、利用CDN加速:
在构建过程中,将引用的静态资源修改为CDN上对应的路径。我们想引用一个库,但是又不想让webpack打包,并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用,那就可以通过配置externals。
configureWebpack: {
externals: {
"vue": "Vue",
"vue-router": "VueRouter",
"axios": "axios",
"moment": "moment",
"element-ui": "ELEMENT",
}
}
3、Tree shaking:
将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现。
4、Code Splitting:
将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存。例如vue中的异步组件就是按需加载。
5、提取公共第三⽅库:
提取公共第三⽅库:来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码。
在JavaScript中,如果一个对象没有被正确地销毁或释放,它将一直占用内存。这种情况通常发生在使用全局变量或闭包时,因为这些变量或闭包会一直存在于内存中,直到程序结束。
循环引用是指两个或多个对象之间相互引用,导致它们无法被垃圾回收器自动清除。这种情况通常发生在对象之间的相互引用,例如在JavaScript中的事件处理程序中。
如果DOM元素没有被正确地从页面中移除,它将一直存在于内存中。这种情况通常发生在使用jQuery或其他DOM操作库时,因为这些库可能会缓存DOM元素并延迟它们的删除。
如果定时器没有被正确地清除,它将一直存在于内存中,导致内存泄漏。这种情况通常发生在使用setTimeout()和setInterval()函数时,因为这些函数会在后台运行并持续触发回调函数。
如果全局变量没有被正确地声明或初始化,它们将一直存在于内存中,导致内存泄漏。这种情况通常发生在使用全局变量时,因为这些变量会一直存在于内存中,直到程序结束。
React,用于构建用户界面的 JavaScript 库,只提供了 UI 层面的解决方案
遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效
使用虚拟 DOM 来有效地操作 DOM,遵循从高阶组件到低阶组件的单向数据流
帮助我们将界面成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,构成整体页面
react 类组件使用一个名为 render() 的方法或者函数组件return,接收输入的数据并返回需要展示的内容
React 特性有很多,如:
JSX 语法
单向数据绑定
虚拟 DOM
声明式编程
Component
React 存在的优势:
高效灵活
声明式的设计,简单使用
组件式开发,提高代码复用率
单向响应的数据流会比双向绑定的更安全,速度更快
挂载阶段
constructor:再react组件挂在之前会调用这个函数
componentWillmount:在调用render方法之前调用 并且在初始挂载及后续更新都会调用
componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用
更新阶段
componentWillReceiveProps: 在接受父组件改变后的props需要重新渲染组件时用到的比较多,外部组件传递频繁的时候会导致效率比较低
一个组件的显示形式可以由数据状态和外部参数决定 而数据状态就是state当需要修改里面的值的状态需要通过调用setState来改变 从而达到更新组件内部数据的作用
setState第一个参数可以是一个对象,或者一个函数 而第二个参数是一个回调函数 用于可以实时的获取到更新之后的数据
在使用setState更新数据的时候 setState的更新类型分为 同步更新 异步更新
在组件生命周期 或者react合成事件中 setState是异步
在setTimeout或者原生dom事件中,setState是同步
对同一个值进行多次setState setState的批量更新策略会对其进行覆盖 取最后一次的执行结果
React事件机制
React基于浏览器事件机制实现了一套自己的事件机制,包括:事件注册、事件合成、事件冒泡、事件触发等。
受控组件:受控制强度较高的组件 例如 我们使用input框的时候 我们如果给input绑定一个value那么我们会发现当前的input无法修改的了 所以我们还需要给input加一个onChange事件 当我们修改input里面的值的时候 去修改input绑定的值 应用场景 : 表单数据提交 一次性验证 强制输入格式 动态输入 一次性取值
非受控组件:不受控制的组件 例如 我们使用ref的时候 绑定给input一个ref 然后我们使用ref.current就可以来获取到我们想要的属性或者参数 应用场景: 一次性取值 提交时验证
1.div宽高法
我们定义一个div 然后设置border的颜色然后
<style>
div{
width: 0;
height: 0;
border: 100px solid;
border-color: red blue gray yellowgreen;
}
</style>
2.使用line-gradient
<style>
.second{
background: linear-gradient(45deg, deeppink, deeppink 50%, transparent 50%, transparent 100%);
}
</style>
3.使用 clip-path
<style>
div{
width: 100px;
height: 100px;
background: gold;
clip-path: polygon(0 0, 0 100%, 100% 100%);
}
</style>
react中的jsx语法会通过babel转化为 js代码,以React.createElement函数形式存在,createElement函数返回一个ReactElement函数,ReactElement函数返回一个的虚拟节点,虚拟节点中嵌套虚拟节点,就形成了虚拟DOM,最后通过ReactDOM.render方法转化为真实DOM。babel在转化jsx过程中,会判断首字母的大小写当首字母为小写的时候,会被认为是原生DOM标签, 那么createElement中的第一个参数就是一个字符串,表示标签的名称当首字母为大写的时候,会被认为是组件,那么createElement中的第一个参数就是组件的名称,
防抖:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。如果n秒内高频事件再次被触发,则重新计算时间。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>防抖title>
head>
<body>
<button id="debounce">点我防抖!button>
<script>
window.onload = function() {
// 1、获取这个按钮,并绑定事件
var myDebounce = document.getElementById("debounce");
myDebounce.addEventListener("click", debounce(sayDebounce));
}
// 2、防抖功能函数,接受传参
function debounce(fn) {
// 4、创建一个标记用来存放定时器的返回值
let timeout = null;
return function() {
// 5、每次当用户点击/输入的时候,把前一个定时器清除
clearTimeout(timeout);
// 6、然后创建一个新的 setTimeout,
// 这样就能保证点击按钮后的 interval 间隔内
// 如果用户还点击了的话,就不会执行 fn 函数
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, 1000);
};
}
// 3、需要进行防抖的事件处理
function sayDebounce() {
// ... 有些需要防抖的工作,在这里执行
console.log("防抖成功!");
}
script>
body>
html>
节流:指定时间间隔内只会执行一次任务。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>节流title>
head>
<body>
<button id="throttle">点我节流!button>
<script>
window.onload = function() {
// 1、获取按钮,绑定点击事件
var myThrottle = document.getElementById("throttle");
myThrottle.addEventListener("click", throttle(sayThrottle));
}
// 2、节流函数体
function throttle(fn) {
// 4、通过闭包保存一个标记
let canRun = true;
return function() {
// 5、在函数开头判断标志是否为 true,不为 true 则中断函数
if(!canRun) {
return;
}
// 6、将 canRun 设置为 false,防止执行之前再被执行
canRun = false;
// 7、定时器
setTimeout( () => {
fn.apply(this, arguments);
// 8、执行完事件(比如调用完接口)之后,重新将这个标志设置为 true
canRun = true;
}, 1000);
};
}
// 3、需要节流的事件
function sayThrottle() {
console.log("节流成功!");
}
script>
body>
html>
代码分割方法一:将原来的单入口文件改为多入口文件
将不同的文件例如js代码文件分为入口文件和测试文件,这个时候打包出来的代码就会根据不同的文件单独打包成属于他们自己的文件
使用splitChunks分离代码
当我们在文件当中引入了node_modules的包时,在打包之后可能打包工具会自动将node_modules当中的包打包进文件当中,这个时候使用以下代码:可以将引入的这些包单独打包,从而减小打包的体积
总的来说,功能就是:
1. 可以将node_modules中代码单独打包一个chunk最终输出
2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
通过js代码,让某个文件被单独打包成一个chunk
import动态导入语法:能将某个文件单独打包
Virtual DOM是一种编程概念。通俗点理解,虚拟DOM是一棵虚拟的JavaScript对象树,画重点,”虚拟的“、”JS对象“,指的是它把真实的网页文档节点,虚拟成一个个的js对象,并以树型结构,保存在内存中。
原理
React虚拟DOM的实现原理,通过JS模拟网页文档节点,生成JS对象树(虚拟DOM),然后再进一步生成真实的DOM树,再绘制到屏幕。如果后面有内容发生改变,React会重新生成一棵全新的虚拟DOM树,再与前面的虚拟DOM树进行比对diff,把差异的部分打包成patch,再应用到真实DOM,然后渲染到屏幕浏览器。