React 原理
react做了什么?1Virtual Dom模型 2生命周期管理 3setState机制 4diff算法 5React patch、事件系统 6react的 Virtual Dom模型
virtual dom 实际上是对实际Dom的一个抽象,是一个js对象。react所有的表层操作实际上是在操作virtual dom。
经过diff算法会计算出virtual dom的差异,然后将这些差异进行实际的dom操作更新页面。
生命周期管理
挂载阶段:getDefaultProps getInitialState componentWillMount render componentDidMount
更新阶段:componentWillReceiveProps shouldComponentUpdate componentWillUpdate render componentDidUpdate
卸载阶段:componentDidUnMount
setState机制
理想情况:
setState是“异步”的,调用setState只会提交一次state修改到队列中,不会直接修改this.state。
等到满足一定条件时,react会合并队列中的所有修改,触发一次update流程,更新this.state。因此setState机制减少了update流程的触发次数,从而提高了性能。
由于setState会触发update过程,因此在update过程中必经的生命周期中调用setState会存在循环调用的风险。
另外用于监听state更新完成,可以使用setState方法的第二个参数,回调函数。在这个回调中读取this.state就是已经批量更新后的结果。
特殊情况:
在实际开发中,setState的表现有时会不同于理想情况。主要是以下两种。
在mount流程中调用setState。这种情况下,不会进入update流程,队列在mount时合并修改并render。
在setTimeout/Promise回调中调用setState。在种情况下,setState将不会进行队列的批更新,而是直接触发一次update流程。
这是由于setState的两种更新机制导致的,只有在批量更新模式中,才会是“异步”的。
diff算法
diff算法用于计算出两个virtual dom的差异,是react中开销最大的地方。
传统diff算法通过循环递归对比差异,算法复杂度为O(n3)。react diff算法制定了三条策略,将算法复杂度从 O(n3)降低到O(n)。
1.WebUI中DOM节点跨节点的操作特别少,可以忽略不计。
2.拥有相同类的组件会拥有相似的DOM结构。拥有不同类的组件会生成不同的DOM结构。
3.同一层级的子节点,可以根据唯一的ID来区分。
针对这三个策略,react diff实施的具体策略是:
diff对树进行分层比较,只对比两棵树同级别的节点。跨层级移动节点,将会导致节点删除,重新插入,无法复用。
diff对组件进行类比较,类相同的递归diff子节点,不同的直接销毁重建。diff对同一层级的子节点进行处理时,会根据key进行简要的复用。两棵树中存在相同key的节点时,只会移动节点。
另外,在对比同一层级的子节点时:diff算法会以新树的第一个子节点作为起点遍历新树,寻找旧树中与之相同的节点。如果节点存在,则移动位置。如果不存在,则新建一个节点。
在这过程中,维护了一个字段lastIndex,这个字段表示已遍历的所有新树子节点在旧树中最大的index。
在移动操作时,只有旧index小于lastIndex的才会移动。这个顺序优化方案实际上是基于一个假设,大部分的列表操作应该是保证列表基本有序的。
可以推倒倒序的情况下,子节点列表diff的算法复杂度为O(n2)
二、性能优化方案
由于react中性能主要耗费在于update阶段的diff算法,因此性能优化也主要针对diff算法。
1.减少diff算法触发次数 减少diff算法触发次数实际上就是减少update流程的次数。
正常进入update流程有三种方式:
1.setState
setState机制在正常运行时,由于批更新策略,已经降低了update过程的触发次数。
因此,setState优化主要在于非批更新阶段中(timeout/Promise回调),减少setState的触发次数。常见的业务场景即处理接口回调时,无论数据处理多么复杂,保证最后只调用一次setState。
2.父组件render
父组件的render必然会触发子组件进入update阶段(无论props是否更新)。此时最常用的优化方案即为shouldComponentUpdate方法。
最常见的方式为进行this.props和this.state的浅比较来判断组件是否需要更新。或者直接使用PureComponent,原理一致。
需要注意的是,父组件的render函数如果写的不规范,将会导致上述的策略失效。
forceUpdate 其中forceUpdate方法调用后将会直接进入componentWillUpdate阶段,无法拦截,因此在实际项目中应该弃用。
其他优化策略
shouldComponentUpdate 使用shouldComponentUpdate钩子,根据具体的业务状态,减少不必要的props变化导致的渲染。如一个不用于渲染的props导致的update。另外, 也要尽量避免在shouldComponentUpdate 中做一些比较复杂的操作, 比如超大数据的pick操作等。
合理设计state,不需要渲染的state,尽量使用实例成员变量。不需要渲染的props,合理使用context机制,或公共模块(比如一个单例服务)变量来替换。
2.正确使用diff算法
不使用跨层级移动节点的操作。对于条件渲染多个节点时,尽量采用隐藏等方式切换节点,而不是替换节点。尽量避免将后面的子节点移动到前面的操作,当节点数量较多时,会产生一定的性能问题。
redux 原理
引入redux的时候,会调用createStore 函数 有两个参数
最常用的是recucers 函数,enhancer :强化器,改进器
如果存在enhancer 把它作为参数在传进createStore 里面 把它变强;高阶函数eg:applyMiddleware中间件,可以吧crateStore变得更强
currentState :当前状态 用户存在这里的数据
currentListeners : 当前监听器
getState() 把store内部的属性全部返回出去 暴露出去
subscribe():用户期望 状态变更以后要执行的那些回调函数都放到 currentListeners数组中,将来在某个时刻统一去执行currentListeners的所有东西
dispatch(){currentState = reducer(currentState, action) }:
来执行真正的更新操作;执行以下reducer(更新状态的一个函数);根据action的指令来确定做什么样的更新;来返回全新的状态,覆盖之前的老状态,而不是更新,然后通知所有的currentListeners去做更新
dispatch一个动作,返回了一个 这样的接口。创建store以后可以通过这三个接口去调用做事情
enhancer 做了什么事情?applyMiddleware 可以传递若干中间件 有顺序,顺序是怎么一回事呢
中间件最终return 的还是一个函数
接收一个createStore 传进去的reducer 还要进行加工 创建store 还想做其他额外的事情就是中间件,想要在action 之前去做, 给中间件提供了它的api : getState dispatch,除了可以做它自己的事情以外,还可以把状态拿出来让中间件可以获取当前状态,也可以派发动作,接着做下一步的事情,;然后做 了一个map操作,作用是希望 拿出中间件里的每个函数,执行一个中间件并且 呢把刚才暴露的接口api传进去,这样中间件里面就可以调用这些api了;把返回的数组称为中间件链,因为它产生了一条链,有先后顺序关系;使用compse (高阶)函数:作用简单理解就是 可以把一个数组按照顺序就像洋葱一样包裹,进行复核组合;最终生成一个函数,也就是由一个函数组成的数组变成一个函数,同时把store.dispatch方法作为参数传进去。最终返回的函数会强化一下store.dispatch,进行处理最终返回;
总之就是reducer 函数处理之前先去执行中间件的函数
compose 就是一个聚合的作用;如果有多个函数进行聚合操作 reduce()
react-redux原理
核心就是connect 函数 传进来两个函数的映射
connect 函数 本身返回一个函数,这个函数接收一个组件然后返回一个新组件;封装的这个组件是会被强化的,强化方式是怎样的?想办法吧store中的值拿出来,通过上下文的方式,隔代的把store拿出来,通过context 拿到状态,然后可以订阅更新,讲来如果有状态发生变化的时候,可以执行更新函数,然后让组件重新渲染;更新的时候可以把用户传递的这种映射函数,怎么能把store中的状态拿出来,然后映射到组件的属性中去,就是安装mapStateProps规则进行映射的,从store中拿出来映射,返回一个对象,这个对象就是我希望传递给要包装的组件它的对象;dispatchProps 也是一个对象,希望把那些actioncreater 创建的action 加上dispatch函数,一起组合成一个新函数,对象的属性的值是个函数,可以派发action,让组件变得更干净,然后做一个setState处理,就会更新函数,真正要包装的组件上就可以使用state props
provider就是做上下文的数据提供
redux thunk
action dispatch 的是对象,如果dispatch的是函数 则thunk在处理的时候 会特别处理,当发现action是函数的时候,把action执行一下,这时候这个函数就执行了,写异步的时候接受到的两个函数是 dispatch 和getState函数,在里面可以通过dispatch派发动作,还可以通过gestate拿到一些想要的状态,如果就是一个普通的对象,就会进去洋葱的下一层
Redux saga 和 redux thunk 区别
thunk 不遵循react本身 中间件可能是一个异步函数而不是纯函数
saga使用副作用 异步数据获取 浏览器缓存获取 易于管理、执行、测试和失败处理,基于generater,异步更强,解决更复杂的处理方案
从saga里面导出来三个方法:call 用来调用异步函数 put 异步函数得出结果去做状态更新的时候调用put takEvery 负责全局监听action 截住后去执行这些异步事情
使用时注册的时候需要run 一下
umi
纯前端框架 开箱即用 内置react 、react-router
类 next.js且功能完备的路由约定,同时支持配置的路由方式
完善的插件体系,覆盖从源码到构建产物的每个生命周期
高性能,通过插件支持pwa,以路由为单元的code splitting
支持静态页面导出,适配各种环境,比如中台业务,无线业务,egg,支付宝钱包,等
开发快速启动
一键兼容到 ie9
完善的typescript支持 包括d.ts 定义 和 umi test
与dva 数据了深入融合 支持duck directory,model的自动加载,code splitting 等等
dva
redux saga太复杂了,每次都要重新配置,繁琐
原理:路由组件 通过dispatch 派发动作action ,去更新 还会有一些异步操作dva里面叫effect 副作用跟后台服务器去交互,得到的数据后去更新reducer,同步直接调用,异步先去effect 在回来调用reducer;subscription:特别的用户事件,比如键盘事件,监听,浏览器的地址变化之类的特殊事件,进行订阅,可以做一些特殊的事情派发dispatch 去做更新;当更新后 会把state状态通过 connect 函数去做更新
Hook
vuex 原理 状态管理单项数据流
用户通过触发dispach action通过commit 触发 mutations 方法 改变state render模板渲染 生成数据流
action 如何触发mutations的一个时机
getters计算数学 和computed 一样的用法做数据加工的
computed需要数据继续加工计算
mapActions是一个数组 代替action 把组件中的事件和action里的事件做映射
modules文件夹存放不同状态
vue 原理?
NewVue
$mount 初始化
compile编译 : 1.parse(使用正则解析template中的vue指令v-xxx变量等等形成AST语法树);2. optimize(标记一些静态节点,用作后面的性能优化,在diff的时候直接略过);3. gererate (把第一步生产的AST转化为渲染函数render function)
render function之后:1.virtual dom tree 虚拟树比较 进行不定;2.依赖收集 进行更新
数据响应式?
双向绑定原理defineProperty 属性把data里 每一个属性都定义了的get set函数,有机会去监听这些
属性,当这些属性发生变化的时候,可以通知那些需要更新的地方进行更新;
vue 编译过程?
先说什么是编译,为什么要编译 首先vue模板的语句 html 根本就不识别,
通过编译的过程可以进行依赖收集,依赖收集以后,就data中的数据模型和视图之间产生了绑定关系,依赖关系,以后如果模型发生变化的时候,就可以通知这些依赖的地方,让他们进行更新,这就是执行编译的目的,我们把这些界面全部编译以后进行更新操作,就做到了模型驱动视图的变化,这就是编译过程
双向绑定的原理
通常使用v-modle 指令放在input这样的输入元素上,为什么放v-model ?编译的时候可以解析出v-model,在做操作的时候有两件事情,第一件事情 把当前 v-model所属的元素上面加了事件监听,v-model指定的回调函数作 为input事件监听的回调函数去监听,如果input发生变化的时候,就可以把最新的值设置到vue的实例上,因为vue的实例已经实现了数据的响应化,响应化的set 函数会触发,界面中所有模型的依赖的更新,会通知所有依赖去做更新和刷新操作,所以界面中跟这个部分相关的地方就更新了
webpack
总结:webpack 是⼀一个模块打包⼯工具,可以识别出引⼊入模块的语法 ,早起的webpack只是个js模块的 打包⼯工具,现在可以是css,png,vue的模块打包⼯工具
webpack 配置⽂文件
当我们使⽤用npx webpack index.js时,表示的是使⽤用webpack处理理打包,名为index.js的⼊入⼝口模块。默 认放在当前⽬目录下的dist⽬目录,打包后的模块名称是main.js,当然我们也可以修改
webpack有默认的配置⽂文件,叫webpack.config.js,我们可以对这个⽂文件进行修改,进行个性化配
默认的配置⽂文件:webpack.config.js
npx webpack //执⾏行行命令后,webpack会找到默认的配置⽂文件,并使⽤用执⾏
不使⽤用默认的配置⽂文件: webpackconfig.js
npx webpack --config webpackconfig.js //指定webpack使⽤用webpackconfig.js⽂文件来作为 配置⽂件并执行行
修改package.json scripts字段:有过vue react开发经验的同学 习惯使⽤用npm run来启动,我们也 可以修改
"scripts":{ "bundle":"webpack"//这个地方不要添加npx ,因为npm run执行的命令,会优先使用项⽬工程里 的包,效果和npx非常类似 }
npm run bundle
Webpack 的核⼼心概念
entry:指定打包⼊入⼝口⽂文件
output: 打包后的⽂文件位置
loader
webpack是模块打包工具,⽽而模块不仅是js,还可以是css,图片或者其他格式
但是webpack默认只知道如何处理js模块,那么其他格式的模块处理,和处理⽅式就需要loader了
当webpack处理到不认识的模块时,需要在webpack中的module处进行配置,当检测到是什么格式的 模块,使用什么loader来处理。
Plugins :plugin 可以在webpack运行到某个阶段的时候,帮你做一些事情,类似于生命周期的概念;eg:htmlwebpackplugin会在打包结束后,⾃自动⽣生成⼀一个html⽂文件,并把打包⽣生成的js模块引⼊入到该html 中。
sourceMap: 源代码与打包后的代码的映射关系在dev模式中,默认开启,关闭的话 可以在配置文件里
WebpackDevServer :提升开发效率的利器每次改完代码都需要重新打包一次,打开浏览器,刷新一次,很麻烦,我们可以安装使用webpackdevserver来改善这块的体验启动服务后,会发现dist目录没有了,这是因为devServer把打包后的模块不会放在dist目录下,而是放 到内存中,从而提升速度
跨域:联调期间,前后端分离,直接获取数据会跨域,上线后我们使⽤用nginx转发,开发期间,webpack就可 以搞定 这件事修改webpack.config.js 设置服务器代理 proxy: { "/api": { target: "http://localhost:9092" } }
Hot Module Replacement (HMR:热模块替换)
Babel处理理ES6 :
npm i babel-loader @babel/core @babel/preset-env -D
//babel-loader是webpack 与 babel的通信桥梁梁,不会做把es6转成es5的工作,这部分工作需要用 到@babel/preset-env来做
//@babel/preset-env⾥里里包含了了es6转es5的转换规则
通过上⾯面的⼏几步 还不不够,Promise等⼀一些还有转换过来,这时候需要借助@babel/polyfill,把es的新特 性都装进来,来弥补低版本浏览器器中缺失的特性
@babel/polyfill
会发现打包的体积⼤了很多,这是因为polyfill默认会把所有特性注⼊入进来,假如我想我⽤用到的es6+,才 会注⼊入,没⽤用到的不注⼊入,从而减少打包的体积,可不可以呢?useBuiltIns:"usage" //按需注入
当我们开发的是组件库,⼯具库这些场景的时候,polyfill就不不适合了了,因为polyfill是注⼊入到全局变量量, window下的,会污染全局环境,所以推荐闭包⽅方式:@babel/plugin-transform-runtime
配置React打包环境 :npm install react react-dom --save npm install --save-dev @babel/preset-react
treeShaking: webpack2.x开始支持 “”摇树“” 只支持es module 方式;没有用的东西(不需要引用的模块)摇掉,从而缩小打包的体积
code spliting